diff --git a/conf/first_install_tables.json b/conf/first_install_tables.json index dc38574f8..b8359979c 100644 --- a/conf/first_install_tables.json +++ b/conf/first_install_tables.json @@ -1512,84 +1512,100 @@ "model": "wetlab.runstates", "pk": 1, "fields": { - "run_state_name": "Pre-Recorded" + "run_state_name": "pre_recorded", + "state_display": "Pre Recorded", + "description": "Run name is defined but pending for input data.", + "show_in_stats": false } }, { "model": "wetlab.runstates", "pk": 2, "fields": { - "run_state_name": "Recorded" + "run_state_name": "recorded", + "state_display": "Recorded", + "description": "Run name is defined.", + "show_in_stats": true } }, { "model": "wetlab.runstates", "pk": 3, "fields": { - "run_state_name": "Sample Sent" + "run_state_name": "sample_sent", + "state_display": "Sample Sent", + "description": "Run has copied sample sheet on remote server.", + "show_in_stats": false } }, { "model": "wetlab.runstates", "pk": 4, "fields": { - "run_state_name": "Processing Run" + "run_state_name": "processing_run", + "state_display": "Processing Run", + "description": "Run is handled on the sequencer.", + "show_in_stats": false } }, { "model": "wetlab.runstates", "pk": 5, "fields": { - "run_state_name": "Processed Run" + "run_state_name": "processed_run", + "state_display": "Processed Run", + "description": "Run is already processed on sequencer.", + "show_in_stats": false } }, { "model": "wetlab.runstates", "pk": 6, "fields": { - "run_state_name": "Processing Bcl2fastq" + "run_state_name": "processing_bcl2fastq", + "state_display": "Processing Bcl2fastq", + "description": "Bcl2fastq is running", + "show_in_stats": false } }, { "model": "wetlab.runstates", "pk": 7, "fields": { - "run_state_name": "Processed Bcl2fastq" + "run_state_name": "processed_bcl2fastq", + "state_display": "Processed Bcl2fastq", + "description": "Bcl2fastq already completed", + "show_in_stats": true } }, { "model": "wetlab.runstates", "pk": 8, "fields": { - "run_state_name": "Completed" + "run_state_name": "completed", + "state_display": "Completed", + "description": "Run is completed and all processes are finished.", + "show_in_stats": false } }, { "model": "wetlab.runstates", "pk": 9, "fields": { - "run_state_name": "Cancelled" + "run_state_name": "cancelled", + "state_display": "Cancelled", + "description": "Run was manually cancelled while running on sequencer.", + "show_in_stats": true } }, { "model": "wetlab.runstates", "pk": 10, "fields": { - "run_state_name": "Error" - } -}, -{ - "model": "wetlab.runstates", - "pk": 11, - "fields": { - "run_state_name": "Processing Metrics" - } -}, -{ - "model": "wetlab.runstates", - "pk": 12, - "fields": { - "run_state_name": "Processing Demultiplexing" + "run_state_name": "error", + "state_display": "Error", + "description": "There was an issue while processing the run", + "show_in_stats": true } }, { @@ -1996,6 +2012,33 @@ "generated_at": "2021-01-28T22:59:03.042" } }, +{ + "model": "wetlab.configsetting", + "pk": 15, + "fields": { + "configuration_name": "ALLOW_REPEAT_SAMPLE_NAMES", + "configuration_value": "FALSE", + "generated_at": "2024-05-28T22:59:03.142" + } +}, +{ + "model": "wetlab.configsetting", + "pk": 16, + "fields": { + "configuration_name": "ALLOW_REPEAT_SAMPLE_NAMES_PER_RESEARCHER", + "configuration_value": "FALSE", + "generated_at": "2024-05-28T22:59:04.042" + } +}, +{ + "model": "wetlab.configsetting", + "pk": 17, + "fields": { + "configuration_name": "ALLOW_SAMPLE_NAMES_WITH_UNDERSCORE", + "configuration_value": "FALSE", + "generated_at": "2024-05-28T22:59:05.042" + } +}, { "model": "wetlab.collectionindexvalues", "pk": 2073, diff --git a/core/utils/graphics.py b/core/utils/graphics.py index 788644fb6..44dd03bae 100644 --- a/core/utils/graphics.py +++ b/core/utils/graphics.py @@ -1,25 +1,18 @@ -def preparation_3D_pie(heading, sub_title, theme, source_data): - """_summary_ +def preparation_3D_pie( + heading: str, sub_title: str, theme: str, source_data: dict +) -> dict: + """Join the parameters to create a dictionary with two keys: chart and data. + The chart key contains the heading, sub_title, theme, numberPrefix, showlegend, placevaluesInside, showpercentvalues, rotatevalues, showCanvasBg, showCanvasBase, canvasBaseDepth, canvasBgDepth, canvasBaseColor, canvasBgColor, exportEnabled. + The data key contains a list of dictionaries with the keys label and value. - Parameters - ---------- - heading : str - text to insert on top of graphic - sub_title : str - additional text to include in graphic - axis_x_description : str - description of x axis - axis_y_description : str - description - theme : str - name of the available themes to be used for the graphic - source_data : dict - Dictionary containing value for the graphic + Args: + heading (str): title of the chart + sub_title (str): additional information + theme (str): theme of the chart + source_data (dict): dictionary with the data to be displayed - Returns - ------- - _type_ - _description_ + Returns: + dict: dictionary with the keys chart and data """ data_source = {} data_source["chart"] = { @@ -58,15 +51,30 @@ def preparation_3D_pie(heading, sub_title, theme, source_data): def preparation_graphic_data( - heading, - sub_caption, - x_axis_name, - y_axis_name, - theme, - input_data, - label_key=None, - label_value=None, -): + heading: str, + sub_caption: str, + x_axis_name: str, + y_axis_name: str, + theme: str, + input_data: dict, + label_key: str = None, + label_value: str = None, +) -> dict: + """Join the parameters to create a dictionary with two keys: chart and data. + + Args: + heading (str): heading of the chart + sub_caption (str): sub_caption of the chart + x_axis_name (str): name of the x axis + y_axis_name (str): name of the y axis + theme (str): theme of the chart + input_data (dict): data to be displayed + label_key (str, optional): key name from the input dict. Defaults to None. + label_value (str, optional): value field in the innput dict. Defaults to None. + + Returns: + dict: dictionary with the keys chart and data + """ data_source = {} data_source["chart"] = { "caption": heading, diff --git a/core/utils/samples.py b/core/utils/samples.py index d96ab0752..4febefe2c 100644 --- a/core/utils/samples.py +++ b/core/utils/samples.py @@ -6,6 +6,7 @@ from django.contrib.auth.models import User from django.db.models import CharField, Count, F, Func, Prefetch, Value +from django.db.models.functions import Lower # Local imports import core.core_config @@ -193,20 +194,24 @@ def save_recorded_samples(samples_data, req_user, app_name): return samples_data -def validate_sample_data(sample_data, req_user, app_name): - """Sample data validation +def validate_sample_data( + sample_data: json, + req_user: str, + app_name: str, + repeat_allowed: bool = False, + allow_user_repeat: bool = False, +) -> tuple: + """sample data validation - Parameters - ---------- - sample_data - sample data formatted in json obtained from jspreadsheet - req_user - requesting user - app_name - application name (wetlab, drylab, core, etc.) + Args: + sample_data (json): sample data formatted in json obtained from jspreadsheet + req_user (str): requested user name + app_name (str): application name (wetlab, drylab, core, etc.) + repeat_allowed (bool, optional): allow or not that sample can be repeated. Defaults to False. + allow_user_repeat (bool, optional): if allowed to be repeated check if same request user is allowed have repeated sample names. Defaults to False. - Returns - ------- + Returns: + tuple: returns result as boolean and a dictionary with the following keys validation list with validation info for each sample with format: [{"Sample Name": "test 01", @@ -222,6 +227,27 @@ def validate_sample_data(sample_data, req_user, app_name): sample_name_list = [] line = 0 result = True + not_allowed_sample_names = {} + # collect the sample names already in the database in case that the user + # is not allowed to repeat + if not repeat_allowed: + if not allow_user_repeat: + if core.models.Samples.objects.filter( + sample_user__username__exact=req_user + ).exists(): + existing_sample_name_list = list( + core.models.Samples.objects.filter( + sample_user__username__iexact=req_user + ).values_list("sample_name", flat=True) + ) + else: + existing_sample_name_list = list( + core.models.Samples.objects.all().values_list( + Lower("sample_name", flat=True) + ) + ) + # convert to dict to speed up the search + not_allowed_sample_names = dict.fromkeys(existing_sample_name_list, 0) for sample in sample_data: line += 1 sample_dict = {} @@ -237,7 +263,10 @@ def validate_sample_data(sample_data, req_user, app_name): validation.append(sample_dict) continue - if sample["sample_name"] not in sample_name_list: + if repeat_allowed or ( + sample["sample_name"].lower() not in sample_name_list + and sample["sample_name"].lower() not in not_allowed_sample_names + ): sample_name_list.append(sample["sample_name"]) else: error_cause = core.core_config.ERROR_REPEATED_SAMPLE_NAME.copy() diff --git a/drylab/templates/drylab/stats_services_time.html b/drylab/templates/drylab/stats_services_time.html index 8b9de7ce8..006207969 100644 --- a/drylab/templates/drylab/stats_services_time.html +++ b/drylab/templates/drylab/stats_services_time.html @@ -1,6 +1,7 @@ {% extends 'core/base.html' %} {% load static %} {% block content %} + {% include "core/cdn_table_functionality.html" %} {% include "core/graphic_chart_functionality.html" %} {% include "drylab/menu.html" %}
@@ -21,122 +22,270 @@
{{ error_message }}
{% endif %} {% if services_stats_info %} -
-
-
-
-

Services per user

-
-
- {% if services_stats_info.graphic_requested_services_per_user %} -
- {{ services_stats_info.graphic_requested_services_per_user |safe }} - {% endif %} -
-
-
-
-
-
-

Services status

-
-
- {% if services_stats_info.graphic_status_requested_services %} -
- {{ services_stats_info.graphic_status_requested_services |safe }} - {% endif %} -
-
-
-
-
-
-
-
-

Services per classification area

-
-
- {% if services_stats_info.graphic_area_services %} -
- {{ services_stats_info.graphic_area_services |safe }} - {% endif %} -
+
+
-
-
-
-

Services per center

+ + -
-
-
-
-

Service statistics per center

-
-
-

{{ services_stats_info.period_time }}.

- {% if services_stats_info.graphic_center_services_per_time %} -
- {{ services_stats_info.graphic_center_services_per_time |safe }} - {% endif %} + -
-
-
-
-

Services Statistics per classification area

+
+
+
+
+

Requested Services per center

+
+
+ {% if services_stats_info.graphic_center_services %} +
+ + {{ services_stats_info.graphic_center_services |safe }} + {% endif %} +
+
+
+
+
+
+

Services per classification area

+
+
+ {% if services_stats_info.graphic_area_services %} +
+ {{ services_stats_info.graphic_area_services |safe }} + {% endif %} +
+
+
-
-

{{ services_stats_info.period_time }}.

- {% if services_stats_info.graphic_area_services_per_time %} -
- - {{ services_stats_info.graphic_area_services_per_time |safe }} - {% endif %} +
+
+
+
+

Service statistics per center

+
+
+

{{ services_stats_info.period_time }}.

+ {% if services_stats_info.graphic_center_services_per_time %} +
+ {{ services_stats_info.graphic_center_services_per_time |safe }} + {% endif %} +
+
+
+
+
+
+

Services Statistics per classification area

+
+
+

{{ services_stats_info.period_time }}.

+ {% if services_stats_info.graphic_area_services_per_time %} +
+ + {{ services_stats_info.graphic_area_services_per_time |safe }} + {% endif %} +
+
+
-
-
-
-
-
-
-

Level 2 available services

+ -
-
-
-
-
-

Level 3 Available Services

-
-
-

{{ services_stats_info.period_time }}.

- {% if services_stats_info.graphic_req_l3_services %} -
- {{ services_stats_info.graphic_req_l3_services |safe }} - {% endif %} +
@@ -189,4 +338,28 @@

Services statistics

{% endif %} + {% endblock %} diff --git a/drylab/views.py b/drylab/views.py index 1952dab78..e122e323b 100644 --- a/drylab/views.py +++ b/drylab/views.py @@ -8,13 +8,14 @@ from django.conf import settings from django.contrib.auth.decorators import login_required from django.core.files.storage import FileSystemStorage -from django.db.models import Prefetch +from django.db.models import Prefetch, Count from django.http import HttpResponse from django.shortcuts import redirect, render # Local imports import core.fusioncharts.fusioncharts import core.utils.common +import core.utils.graphics import django_utils.models import drylab.config import drylab.models @@ -1139,6 +1140,8 @@ def stats_by_services_request(request): if request.method == "POST" and request.POST["action"] == "service_statistics": start_date = request.POST["start_date"] end_date = request.POST["end_date"] + if start_date == "" and end_date == "": + return render(request, "drylab/stats_services_time.html") if start_date != "" and not drylab.utils.common.check_valid_date_format( start_date ): @@ -1168,12 +1171,10 @@ def stats_by_services_request(request): else: user_services[user] = 1 - period_of_time_selected = str( - " For the period between " + start_date + " and " + end_date - ) + period_of_time_selected = str(" From " + start_date + " to " + end_date) # creating the graphic for requested services data_source = drylab.utils.graphics.column_graphic_dict( - "Requested Services by:", + "Requested Services from users", period_of_time_selected, "User names", "Number of Services", @@ -1181,7 +1182,7 @@ def stats_by_services_request(request): user_services, ) graphic_requested_services = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", "ex1", "525", "350", "chart-1", "json", data_source + "column3d", "ex1", "900", "350", "chart-1", "json", data_source ) services_stats_info["graphic_requested_services_per_user"] = ( graphic_requested_services.render() @@ -1207,7 +1208,7 @@ def stats_by_services_request(request): ) graphic_status_requested_services = ( core.fusioncharts.fusioncharts.FusionCharts( - "pie3d", "ex2", "525", "350", "chart-2", "json", data_source + "pie3d", "ex2", "500", "400", "chart-2", "json", data_source ) ) services_stats_info["graphic_status_requested_services"] = ( @@ -1323,7 +1324,7 @@ def stats_by_services_request(request): if d_period not in user_services_period[center]: user_services_period[center][d_period] = 0 data_source = drylab.utils.graphics.column_graphic_per_time( - "Services requested by center ", + "Services requested by center and period of time", period_of_time_selected, "date", "number of services", @@ -1408,7 +1409,7 @@ def stats_by_services_request(request): "Requested Services:", "level 2 ", "", "", "fint", service_dict ) graphic_req_l2_services = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", "ex7", "800", "375", "chart-7", "json", data_source + "column3d", "ex7", "600", "375", "chart-7", "json", data_source ) services_stats_info["graphic_req_l2_services"] = ( graphic_req_l2_services.render() @@ -1431,12 +1432,109 @@ def stats_by_services_request(request): "Requested Services:", "level 3 ", "", "", "fint", service_dict ) graphic_req_l3_services = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", "ex8", "800", "375", "chart-8", "json", data_source + "column3d", "ex8", "900", "375", "chart-8", "json", data_source ) services_stats_info["graphic_req_l3_services"] = ( graphic_req_l3_services.render() ) + # Samples handled by requested services + + sample_in_services_objs = ( + drylab.models.RequestedSamplesInServices.objects.filter( + samples_in_service__in=services_found + ) + ) + ana_sample_in_runs = ( + sample_in_services_objs.exclude(run_name=None) + .values("run_name") + .annotate(sample_count=Count("sample_name")) + ) + g_data = core.utils.graphics.preparation_graphic_data( + "Analyzed Samples from Runs", + "", + "", + "", + "ocean", + ana_sample_in_runs, + "run_name", + "sample_count", + ) + graphic_req_samples_run = core.fusioncharts.fusioncharts.FusionCharts( + "column3d", "ex9", "600", "375", "chart-9", "json", g_data + ) + services_stats_info["graphic_samples_per_run"] = ( + graphic_req_samples_run.render() + ) + ana_sample_in_proj = ( + sample_in_services_objs.exclude(project_name=None) + .values("project_name") + .annotate(sample_count=Count("sample_name")) + ) + g_data = core.utils.graphics.preparation_graphic_data( + "Analyzed Samples per projects", + "", + "", + "", + "ocean", + ana_sample_in_proj, + "project_name", + "sample_count", + ) + graphic_req_samples_proj = core.fusioncharts.fusioncharts.FusionCharts( + "column3d", "ex10", "600", "375", "chart-10", "json", g_data + ) + services_stats_info["graphic_samples_per_project"] = ( + graphic_req_samples_proj.render() + ) + ana_sample_in_user = sample_in_services_objs.values( + "samples_in_service__service_user_id__username" + ).annotate(sample_count=Count("sample_name")) + g_data = core.utils.graphics.preparation_graphic_data( + "Analyzed Samples per user", + "", + "", + "", + "ocean", + ana_sample_in_user, + "samples_in_service__service_user_id__username", + "sample_count", + ) + + graphic_req_samples_user = core.fusioncharts.fusioncharts.FusionCharts( + "column3d", "ex11", "600", "375", "chart-11", "json", g_data + ) + services_stats_info["graphic_samples_per_user"] = ( + graphic_req_samples_user.render() + ) + analyzed_samples = {} + analyzed_samples["Sequenced samples"] = sample_in_services_objs.exclude( + only_recorded_sample=False + ).count() + analyzed_samples["Only recorded samples"] = sample_in_services_objs.exclude( + only_recorded_sample=True + ).count() + data_source = drylab.utils.graphics.graphic_3D_pie( + "Analyzed Samples", + period_of_time_selected, + "", + "", + "fint", + analyzed_samples, + ) + graphic_analyzed_samples = core.fusioncharts.fusioncharts.FusionCharts( + "pie3d", "ex12", "600", "400", "chart-12", "json", data_source + ) + services_stats_info["graphic_analyzed_samples"] = ( + graphic_analyzed_samples.render() + ) + services_stats_info["table_samples"] = sample_in_services_objs.values_list( + "sample_name", + "samples_in_service__service_request_number", + "samples_in_service__pk", + "samples_in_service__service_user_id__username", + "samples_in_service__service_state__state_display", + ) return render( request, "drylab/stats_services_time.html", diff --git a/install.sh b/install.sh index eec4802fd..dba79a70d 100644 --- a/install.sh +++ b/install.sh @@ -563,7 +563,21 @@ if [ $upgrade == true ]; then else echo "checking for database changes" if python manage.py makemigrations | grep -q "No changes"; then - echo "No migration is required" + # check for pending migrations + if ./manage.py showmigrations | grep '\[ \]'; then + echo "There are pending migrations" + read -p "Do you want to update database with the pending migrations? (Y/N) " -n 1 -r + echo # move to a new line + if [[ ! $REPLY =~ ^[Yy]$ ]] ; then + echo "Continue running script without running migrate command." + else + echo "Running migrate..." + python manage.py migrate + echo "Done migrate command." + fi + else + echo "No migration is required" + fi else read -p "Do you want to proceed with the migrate command? (Y/N) " -n 1 -r echo # (optional) move to a new line @@ -780,6 +794,11 @@ if [ $install == true ]; then if [ $LOG_TYPE == "symbolic_link" ]; then if [ -d $LOG_PATH ]; then + if [ ! -d $INSTALL_PATH/logs ]; then + echo "Deleting existing symbolin link" + rm $INSTALL_PATH/logs + fi + echo "Creating symbolic link to log folder" ln -s $LOG_PATH $INSTALL_PATH/logs chmod 775 $LOG_PATH else @@ -787,9 +806,13 @@ if [ $install == true ]; then exit 1 fi else - mkdir -p $INSTALL_PATH/logs - chown $user:$apache_group $INSTALL_PATH/logs - chmod 775 $INSTALL_PATH/logs + if [ ! -d $INSTALL_PATH/logs ]; then + mkdir -p $INSTALL_PATH/logs + chown $user:$apache_group $INSTALL_PATH/logs + chmod 775 $INSTALL_PATH/logs + else + echo "Log folder path: $INSTALL_PATH/logs already exist." + fi fi rsync -rlv README.md LICENSE test conf $REQUIRED_MODULES $INSTALL_PATH diff --git a/test/test_data.json b/test/test_data.json index 4f63aca3e..edc6466b3 100644 --- a/test/test_data.json +++ b/test/test_data.json @@ -9158,84 +9158,120 @@ "model": "wetlab.runstates", "pk": 1, "fields": { - "run_state_name": "Pre-Recorded" + "run_state_name": "pre_recorded", + "state_display": "Pre Recorded", + "description": "Run name is defined but pending for input data.", + "show_in_stats": false } }, { "model": "wetlab.runstates", "pk": 2, "fields": { - "run_state_name": "Recorded" + "run_state_name": "recorded", + "state_display": "Recorded", + "description": "Run name is defined.", + "show_in_stats": true } }, { "model": "wetlab.runstates", "pk": 3, "fields": { - "run_state_name": "Sample Sent" + "run_state_name": "sample_sent", + "state_display": "Sample Sent", + "description": "Run has copied sample sheet on remote server.", + "show_in_stats": false } }, { "model": "wetlab.runstates", "pk": 4, "fields": { - "run_state_name": "Processing Run" + "run_state_name": "processing_run", + "state_display": "Processing Run", + "description": "Run is handled on the sequencer.", + "show_in_stats": false } }, { "model": "wetlab.runstates", "pk": 5, "fields": { - "run_state_name": "Processed Run" + "run_state_name": "processed_run", + "state_display": "Processed Run", + "description": "Run is already processed on sequencer.", + "show_in_stats": false } }, { "model": "wetlab.runstates", "pk": 6, "fields": { - "run_state_name": "Processing Bcl2fastq" + "run_state_name": "processing_bcl2fastq", + "state_display": "Processing Bcl2fastq", + "description": "Bcl2fastq is running", + "show_in_stats": false } }, { "model": "wetlab.runstates", "pk": 7, "fields": { - "run_state_name": "Processed Bcl2fastq" + "run_state_name": "processed_bcl2fastq", + "state_display": "Processed Bcl2fastq", + "description": "Bcl2fastq already completed", + "show_in_stats": true } }, { "model": "wetlab.runstates", "pk": 8, "fields": { - "run_state_name": "Completed" + "run_state_name": "completed", + "state_display": "Completed", + "description": "Run is completed and all processes are finished.", + "show_in_stats": false } }, { "model": "wetlab.runstates", "pk": 9, "fields": { - "run_state_name": "Cancelled" + "run_state_name": "cancelled", + "state_display": "Cancelled", + "description": "Run was manually cancelled while running on sequencer.", + "show_in_stats": true } }, { "model": "wetlab.runstates", "pk": 10, "fields": { - "run_state_name": "Error" + "run_state_name": "error", + "state_display": "Error", + "description": "There was an issue while processing the run", + "show_in_stats": true } }, { "model": "wetlab.runstates", "pk": 11, "fields": { - "run_state_name": "Processing Metrics" + "run_state_name": "spare_1", + "state_display": "", + "description": "", + "show_in_stats": false } }, { "model": "wetlab.runstates", "pk": 12, "fields": { - "run_state_name": "Processing Demultiplexing" + "run_state_name": "spare_2", + "state_display": "", + "description": "", + "show_in_stats": false } }, { diff --git a/wetlab/admin.py b/wetlab/admin.py index f1477a3bb..647e5c54e 100644 --- a/wetlab/admin.py +++ b/wetlab/admin.py @@ -60,6 +60,12 @@ class AdditionaKitsLibraryPreparationAdmin(admin.ModelAdmin): list_display = ["kit_name", "protocol_id", "commercial_kit_id"] +class LibraryKitAdmin(admin.ModelAdmin): + list_display = [ + "library_name", + ] + + class AdditionalUserLotKitAdmin(admin.ModelAdmin): list_display = ["lib_prep_id", "additional_lot_kits", "user_lot_kit_id"] @@ -69,7 +75,7 @@ class RunErrorsAdmin(admin.ModelAdmin): class RunStatesAdmin(admin.ModelAdmin): - list_display = ("run_state_name",) + list_display = ["run_state_name", "state_display", "description"] class RunningParametersAdmin(admin.ModelAdmin): @@ -290,6 +296,7 @@ class RunConfigurationTestAdmin(admin.ModelAdmin): admin.site.register(wetlab.models.RunErrors, RunErrorsAdmin) admin.site.register(wetlab.models.RunStates, RunStatesAdmin) admin.site.register(wetlab.models.LibPrepareStates, StatesForLibraryPreparationAdmin) +admin.site.register(wetlab.models.LibraryKit, LibraryKitAdmin) admin.site.register(wetlab.models.PoolStates, StatesForPoolAdmin) admin.site.register(wetlab.models.SamplesInProject, SamplesInProjectAdmin) admin.site.register(wetlab.models.StatsRunSummary, StatsRunSummaryAdmin) diff --git a/wetlab/api/views.py b/wetlab/api/views.py index 5d3ff5952..198edd535 100644 --- a/wetlab/api/views.py +++ b/wetlab/api/views.py @@ -1,4 +1,5 @@ from django.http import QueryDict +from django.db.models.functions import Lower from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema from rest_framework import status @@ -17,6 +18,7 @@ import wetlab.api.utils.sample import wetlab.models import wetlab.config +import wetlab.utils.common sample_project_fields = openapi.Parameter( "project", @@ -181,11 +183,49 @@ def create_sample_data(request): data = data.dict() if "sample_name" not in data or "sample_project" not in data: return Response(status=status.HTTP_400_BAD_REQUEST) - if core.models.Samples.objects.filter( - sample_name__iexact=data["sample_name"] - ).exists(): + not_allowed_sample_names = [] + allowed_sample_repeat = ( + False + if wetlab.utils.common.get_configuration_from_database( + "ALLOW_REPEAT_SAMPLE_NAMES" + ) + == "FALSE" + else True + ) + if not allowed_sample_repeat: + if ( + wetlab.utils.common.get_configuration_from_database( + "ALLOW_REPEAT_USER_SAMPLE_NAMES" + ) + == "FALSE" + ): + not_allowed_sample_names = list( + core.models.Samples.objects.filter( + sample_user__username__iexact=request.user.username + ).values_list(Lower("sample_name", flat=True)) + ) + else: + not_allowed_sample_names = list( + core.models.Samples.objects.values_list( + Lower("sample_name", flat=True) + ) + ) + # get information it underscore is allowed in sample name + allow_underscore = ( + False + if wetlab.utils.common.get_configuration_from_database( + "ALLOW_UNDERSCORE_SAMPLE_NAMES" + ) + == "FALSE" + else True + ) + if data["sample_name"].lower() in not_allowed_sample_names: error = {"ERROR": "sample already defined"} return Response(error, status=status.HTTP_400_BAD_REQUEST) + if not allow_underscore: + if "_" in data["sample_name"]: + error = {"ERROR": "sample name cannot have underscore"} + return Response(error, status=status.HTTP_400_BAD_REQUEST) split_data = wetlab.api.utils.sample.split_sample_data(data) if not isinstance(split_data, dict): return Response(split_data, status=status.HTTP_400_BAD_REQUEST) diff --git a/wetlab/config.py b/wetlab/config.py index d3c66df32..cca19fc02 100644 --- a/wetlab/config.py +++ b/wetlab/config.py @@ -164,6 +164,7 @@ "[Settings]", "[I7]", ] +ERROR_NO_LIBRARY_KIT_DEFINED = ["Library Kits are not defined yet"] ############################################################## MIGRATION_DIRECTORY_FILES = "wetlab/BaseSpaceMigrationFiles/" @@ -567,20 +568,46 @@ HEADING_FOR_STATISTICS_RUNS_BASIC_DATA = ["Run Name", "Date sequencer start"] -HEADING_STATISTICS_FOR_RESEARCHER_SAMPLE = [ - "Samples", +HEADING_STATISTICS_FOR_SECUENCED_RESEARCHER_SAMPLE = [ + "Sample name", "Project name", "Run name", "Platform", ] +HEADING_STATISTICS_FOR_RECORDED_RESEARCHER_SAMPLE = [ + "sample name", + "unique sample ID", + "sample type", + "specimen type", + "sample state", + "project name", +] +HEADING_STATISTICS_FOR_RECORDED_LAB_SAMPLE = [ + "sample name", + "unique ID", + "sample type", + "specimen type", + "sample state", + "project name", + "user name", +] HEADING_STATISTICS_FOR_TIME_RUN = ["Run name", "Run state", "Sequencer", "Run date"] -HEADING_STATISTICS_FOR_TIME_SAMPLE = [ +HEADING_STATISTICS_FOR_TIME_SEQUENCED_SAMPLE = [ "Sample name", "Researcher", "Project name", "Run name", "Barcode", ] +HEADING_STATISTICS_FOR_TIME_DEFINED_SAMPLE = [ + "Sample name", + "Unique ID", + "State", + "Recorded date", + "Species", + "Sample type", + "lab code", +] HEADING_STATISTICS_FOR_SEQUENCER_RUNS = [ "Run name", "Run state", @@ -645,6 +672,10 @@ ERROR_SAMPLE_SHEET_BOTH_INSTRUMENT_AND_INDEX_NOT_INCLUDED = [ "Sample Sheet does not have Instrument type neither Index Adapters" ] +ERROR_SAMPLE_SHEET_HAS_INVALID_HEADING = [ + "Sample sheet does not have a valid sample heading" +] +ERROR_SAMPLE_SHEET_DOES_NOT_HAVE_PROJECTS = ["Sample sheet does not have Projects"] ERROR_SAMPLE_SHEET_USER_IS_NOT_DEFINED = ["User in sample sheet is not defined"] ERROR_SAMPLE_SHEET_DOES_NOT_HAVE_DESCRIPTION_FIELD = [ "Sample sheet does not have Description column " @@ -736,15 +767,15 @@ ] # ERROR TEXT FOR SEACHING ############################################# -ERROR_INVALID_FORMAT_FOR_DATES = "Invalid date format. Use the format (DD-MM-YYYY)" +ERROR_INVALID_FORMAT_FOR_DATES = ["Invalid date format. Use the format (DD-MM-YYYY)"] ERROR_MANY_USER_MATCHES_FOR_INPUT_CONDITIONS = [ "There are many user names that matches your request" ] -ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS = ( +ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS = [ "There is not any match for your input conditions" -) +] ERROR_NO_MATCHES_FOR_LIBRARY_STATISTICS = ( "There is not any Index Library Kit that mathes your input conditions" ) @@ -762,9 +793,9 @@ ] ERROR_NO_SAMPLES_SELECTED = "They were not selected any Sample on your request" -ERROR_NOT_RUNS_FOUND_IN_SELECTED_PERIOD = ( +ERROR_NOT_RUNS_FOUND_IN_SELECTED_PERIOD = [ "There are not runs for the selected period of time" -) +] ERROR_NOT_SAMPLES_FOR_USER_FOUND_BECAUSE_OF_CONFIGURATION_SETTINGS = [ "No results. This could because the DESCRIPTION_IN_SAMPLE_SHEET_MUST_HAVE_USERNAME setting is set fo FALSE" ] @@ -784,6 +815,7 @@ ERROR_WRONG_SAMBA_CONFIGURATION_SETTINGS = ( "Unsuccessful configuration settings for Samba connection" ) +ERROR_USER_NOT_DEFINED = ["User is not defined"] ERROR_WRONG_SAMBA_FOLDER_SETTINGS = ( "Unsuccessful configuration. Samba folder not reachable" ) diff --git a/wetlab/cron.py b/wetlab/cron.py index a7190a837..124411f7d 100644 --- a/wetlab/cron.py +++ b/wetlab/cron.py @@ -113,7 +113,7 @@ def delete_invalid_run(): days=int(wetlab.config.RETENTION_TIME) ) run_found_for_deleting = wetlab.models.RunProcess.objects.filter( - state__run_state_name="Pre-Recorded", generate_dat__lte=date_for_removing + state__run_state_name="pre_recorded", generate_dat__lte=date_for_removing ) for run_found in run_found_for_deleting: diff --git a/wetlab/models.py b/wetlab/models.py index 4bc090d4e..86f15290d 100644 --- a/wetlab/models.py +++ b/wetlab/models.py @@ -136,6 +136,9 @@ def __str__(self): class RunStates(models.Model): run_state_name = models.CharField(max_length=50) + state_display = models.CharField(max_length=80, null=True, blank=True) + description = models.CharField(max_length=255, null=True, blank=True) + show_in_stats = models.BooleanField(default=False) class Meta: db_table = "wetlab_run_states" @@ -149,7 +152,7 @@ def get_run_state_name(self): class RunProcessManager(models.Manager): def create_new_run_from_crontab(self, run_data): - run_state = RunStates.objects.get(run_state_name__exact="Recorded") + run_state = RunStates.objects.get(run_state_name__exact="recorded") new_run = self.create(state=run_state, run_name=run_data["experiment_name"]) return new_run diff --git a/wetlab/templates/wetlab/create_next_seq_run.html b/wetlab/templates/wetlab/create_next_seq_run.html index 03d73dd62..2d37c3915 100644 --- a/wetlab/templates/wetlab/create_next_seq_run.html +++ b/wetlab/templates/wetlab/create_next_seq_run.html @@ -1,218 +1,322 @@ {% extends "core/base.html" %} {% load static %} {% block content %} -{% include "wetlab/menu.html" %} -{% include 'registration/login_inline.html' %} - -
- {% if get_user_names %} -
-
-
-
-

Run creation progress

-
-

Fill the user name and the Used Library Kit for each project in the run {{ get_user_names.experiment_name }}

-

This is the second, and the last, FORM that user need to fill down to have the information about the run

-

Once the name of the user and the libray Kit used in the run, were filled and submited, the next page will show the successful run creation

+ {% include "wetlab/menu.html" %} + +
+
+ {% include 'registration/login_inline.html' %} + {% if error_message %} +
+
+
+
+

Unable to process your request

+
+
+ {% for message in error_message %}

{{ message }}

{% endfor %} +
-
-
    -
  1. Upload Sample Sheet
  2. Assign Library Kit
  3. Show Results
  4. -
+
+
+ {% endif %} + {% if get_user_names %} +
+
+
+
+
+

Run creation progress

+
+
+

+ Fill the user name and the Used Library Kit for each project in the run {{ get_user_names.experiment_name }} +

+

This is the second, and the last, FORM that user need to fill down to have the information about the run

+

+ Once the name of the user and the libray Kit used in the run, were filled and submited, the next page will show the successful run creation +

+
+
+
+
    +
  1. + Upload Sample Sheet +
  2. + +
  3. + Assign Library Kit +
  4. + +
  5. + Show Results +
  6. +
+
-
-
-
- -
-
-
-
-
Form to upload the Sample Sheet file. Run Parameters definition
-
-
- {% csrf_token %} - - -
- -
- -
- -
- -
- {% for key, values in get_user_names.projects_user %} -

Project name assigned to run :

-

{{ key }}

- -
- -
- -
- +
+ +
+
+
+
+
Form to upload the Sample Sheet file. Run Parameters definition
+
+ + {% csrf_token %} + + +
+
- +
- +
- +
- {% endfor %} -
- - - + {% for key, values in get_user_names.projects_user %} +

Project name assigned to run :

+

{{ key }}

+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ {% endfor %} +
+ + +
+
-
-
-
-
-
Notes for user validation
-
-

The following information have been collected from the Sample Sheet uploaded previously.

-

It contains the userid information. Correct in the user name field if this information need to be changed

-

Write down the Library kit used for each project

-

Submit button is disabled if any of the user names is not previoulsy defined in database

+
+
+
Notes for user validation
+
+

The following information have been collected from the Sample Sheet uploaded previously.

+

It contains the userid information. Correct in the user name field if this information need to be changed

+

Write down the Library kit used for each project

+

+ Submit button is disabled if any of the user names is not previoulsy defined in database +

+
-
-
-
-
-
- - + //Enable or disable button based on if user names are valid or not + $('input').filter('[id=users]').on('keyup',function() { + valid = checkInputs(); + }) + checkInputs() + - {% elif completed_form %} - {% for key, val in completed_form%} - {% if key == "runname" %} -
-
-
    -
  1. Upload Sample Sheet
  2. Assign Library Kit
  3. Show Results
  4. -
-
-
-
-
-
-
Run configuration have been sucessfully stored
-
-

All the required information is stored now on database.

-

For the run name : {{ val }}

-
+ {% elif completed_form %} + {% for key, val in completed_form %} + {% if key == "runname" %} +
+
+
    +
  1. + Upload Sample Sheet +
  2. + +
  3. + Assign Library Kit +
  4. + +
  5. + Show Results +
  6. +
-
- {% endif %} - {% endfor %} - {% else %} - -
-
-
    -
  1. Upload Sample Sheet
  2. Assign Library Kit
  3. Show Results
  4. -
-
-
-
-
-

This FORM will be used to generated the input file that BaseSpace requires to execute the run

-
-
-
-
-
-
-
Form to upload the Sample Sheet file
-
-
- {% csrf_token %} - -
- -
- +
+
+
+
+
Run configuration have been sucessfully stored
+
+
+

All the required information is stored now on database.

+

+ For the run name : {{ val }} +

-

Fields marked with * are mandatory

- - -
+
+
+ {% endif %} + {% endfor %} + {% else %} + +
+
+
    +
  1. + Upload Sample Sheet +
  2. + +
  3. + Assign Library Kit +
  4. + +
  5. + Show Results +
  6. +
-
-
-
Form to upload the Sample Sheet file
-
-

This Form is used to upload the Sample Sheet generated by Illumina Experience Manager tool.

-

Guide for Sample Sheet creation and download the IEM tool can be found at illumina Web page.

-

Click here for getting this information.

-
+
+
+

+ This FORM will be used to generated the input file that BaseSpace requires to execute the run +

+
-
-
- - - {% endif %} -
-{% endblock %} + +
+ {% endif %} +
+ {% endblock %} diff --git a/wetlab/templates/wetlab/handling_library_preparation.html b/wetlab/templates/wetlab/handling_library_preparation.html index 10c41749a..ae3aa488c 100644 --- a/wetlab/templates/wetlab/handling_library_preparation.html +++ b/wetlab/templates/wetlab/handling_library_preparation.html @@ -88,7 +88,7 @@

{{error_message}}

- +
diff --git a/wetlab/templates/wetlab/menu.html b/wetlab/templates/wetlab/menu.html index 9fb49ad32..f78d15aee 100644 --- a/wetlab/templates/wetlab/menu.html +++ b/wetlab/templates/wetlab/menu.html @@ -1,107 +1,233 @@ {% load static %} {% load user_groups %}
-