diff --git a/src/genlab_bestilling/api/serializers.py b/src/genlab_bestilling/api/serializers.py index e47cfb43..a5e4e924 100644 --- a/src/genlab_bestilling/api/serializers.py +++ b/src/genlab_bestilling/api/serializers.py @@ -102,9 +102,10 @@ class SampleCSVSerializer(serializers.ModelSerializer): analysis_orders = serializers.SerializerMethodField() project = serializers.SerializerMethodField() isolation_method = serializers.SerializerMethodField() - marked = serializers.SerializerMethodField() - plucked = serializers.SerializerMethodField() - isolated = serializers.SerializerMethodField() + is_marked = serializers.SerializerMethodField() + is_plucked = serializers.SerializerMethodField() + is_isolated = serializers.SerializerMethodField() + internal_note = serializers.SerializerMethodField() class Meta: model = Sample @@ -123,9 +124,10 @@ class Meta: "analysis_orders", "project", "isolation_method", - "marked", - "plucked", - "isolated", + "is_marked", + "is_plucked", + "is_isolated", + "internal_note", ] def get_fish_id(self, obj: Sample) -> str: @@ -148,15 +150,20 @@ def get_isolation_method(self, obj: Sample) -> str: def _flag(self, value: bool) -> str: return "x" if value else "" - def get_marked(self, obj: Sample) -> str: + def get_is_marked(self, obj: Sample) -> str: return self._flag(obj.is_marked) - def get_plucked(self, obj: Sample) -> str: + def get_is_plucked(self, obj: Sample) -> str: return self._flag(obj.is_plucked) - def get_isolated(self, obj: Sample) -> str: + def get_is_isolated(self, obj: Sample) -> str: return self._flag(obj.is_isolated) + def get_internal_note(self, obj: Sample) -> str: + if obj.internal_note: + return obj.internal_note + return "" + class SampleUpdateSerializer(serializers.ModelSerializer): has_error = serializers.SerializerMethodField() diff --git a/src/genlab_bestilling/api/views.py b/src/genlab_bestilling/api/views.py index 9ea27c65..bd8b3b30 100644 --- a/src/genlab_bestilling/api/views.py +++ b/src/genlab_bestilling/api/views.py @@ -1,3 +1,4 @@ +import re import uuid from django.db import transaction @@ -92,30 +93,64 @@ class SampleViewset(ModelViewSet): "length": "Length", "weight": "Weight", "classification": "Classification", - "year": "Date", - "notes": "Remarks", - "project": "Projectnumber", + "year": "Year", + "notes": "Notes", + "project": "Project Number", "isolation_method": "Isolation Method", "qiagen_number": "Qiagen#", "is_marked": "Marked", "is_plucked": "Plucked", "is_isolated": "Isolated", "station": "Station", - "placement_in_fridge": "Placement in fridge", - "delivered_to_lab": "Delivered to lab", + "placement_in_fridge": "Placement in Fridge", + "delivered_to_lab": "Delivered to Lab", + "fluidigm#": "Fluidigm#", + "laksov_species": "LaksOV + Species", + "species_date": "Species Date", + "pcr": "PCR", + "fluidigm": "Fluidigm", + "output": "Output", + "analysis_note": "Analysis Note", + "rerun": "Rerun", + "rerun_qiagen": "Rerun Qiagen", + "river": "River", + "str_date": "STR Date", + "rerun_date": "Rerun Date", + "watercourse_number": "Watercourse Number", + "under_locality": "Sub-locality", + "county": "County", + "snp_assay_1": "SNP Assay 1", + "snp_assay_2": "SNP Assay 2", + "str_mixab": "STR Mix AB", + "str_mixc": "STR Mix C", + "str_mixy": "STR Mix Y", + "internal_note": "Internal Note", + "bb#": "BB#", + "output_bb": "Output", + "bba2#": "BBA2#", + "output_bba2": "Output", + "mixab": "Mix AB", + "ab_rep#": "AB Rep#", + "mixc": "Mix C", + "c_rep#": "C Rep#", + "re_extracted_date": "Re-extracted Date", + "re_extracted_qiagen#": "Re-extracted Qiagen#", + "weight_g_for_diet_analysis": "Weight (g) for Diet Analysis", + "position": "Position", } # NOTE: This can be modified to include more fields based on species or area. CSV_FIELDS_BY_AREA: dict[str, list[str]] = { "Akvatisk": [ + "name", "genlab_id", "fish_id", "guid", "order", "analysis_orders", "location.name", + "watercourse_number", "pop_id", - "name", "species.name", "gender", "length", @@ -127,32 +162,52 @@ class SampleViewset(ModelViewSet): "type.name", "isolation_method", "qiagen_number", + "fluidigm#", + "laksov_species", + "species_date", "is_marked", "is_plucked", "is_isolated", + "pcr", + "fluidigm", + "output", + "analysis_note", + "rerun", + "rerun_date", ], "Elvemusling": [ + "name", "genlab_id", "fish_id", "guid", + "river", "location.name", + "under_locality", + "watercourse_number", + "county", "year", - "name", "station", "type.name", "length", "notes", "isolation_method", "qiagen_number", + "position", "placement_in_fridge", "is_marked", "is_plucked", "is_isolated", + "pcr", + "str_date", + "output", + "analysis_note", + "rerun", + "rerun_date", ], "Terrestrisk": [ + "name", "genlab_id", "guid", - "name", "type.name", "species.name", "location.name", @@ -160,13 +215,29 @@ class SampleViewset(ModelViewSet): "order", "analysis_orders", "notes", + "snp_assay_1", + "snp_assay_2", + "str_mixab", + "str_mixc", + "str_mixy", "is_marked", "is_plucked", "is_isolated", "isolation_method", "qiagen_number", + "internal_note", + "bb#", + "output_bb", + "bba2#", + "output_bba2", + "mixab", + "ab_rep#", + "mixc", + "c_rep#", + "re_extracted_date", + "re_extracted_qiagen#", + "weight_g_for_diet_analysis", ], - # Same as "Terrestrisk" for now, can be modified later if needed. "default": [ "genlab_id", "guid", @@ -174,7 +245,6 @@ class SampleViewset(ModelViewSet): "type.name", "species.name", "location.name", - "delivered_to_lab", "order", "analysis_orders", "notes", @@ -239,19 +309,40 @@ def get_nested(self, obj: dict, dotted: str) -> str | None: return obj def build_csv_data( - self, serialized_data: list[dict], fields: list[str] + self, serialized_data: list[dict], fields: list[str], area_name: str ) -> list[dict[str, str]]: - return [ - { - self.CSV_FIELD_LABELS[f]: ( - ", ".join(v) - if isinstance(v := self.get_nested(item, f), list) - else v or "" - ) - for f in fields - } - for item in serialized_data - ] + rows = [] + + for item in serialized_data: + row = {} + + for f in fields: + if area_name in {"Akvatisk", "Elvemusling"} and f == "location.name": + full_location = self.get_nested(item, "location.name") + + if ( + full_location + and re.search(r"\d+", full_location) + and " " in full_location + ): + watercourse, location = full_location.split(" ", 1) + else: + watercourse, location = "", full_location or "" + + row[self.CSV_FIELD_LABELS["watercourse_number"]] = watercourse + row[self.CSV_FIELD_LABELS["location.name"]] = location + + # We skip "watercourse_number" because it is handled above. + elif f != "watercourse_number": + value = self.get_nested(item, f) + if isinstance(value, list): + row[self.CSV_FIELD_LABELS[f]] = ", ".join(value) + else: + row[self.CSV_FIELD_LABELS[f]] = value or "" + + rows.append(row) + + return rows @action( methods=["GET"], @@ -269,7 +360,7 @@ def csv(self, request: Request) -> HttpResponse: ) fields, headers = self.get_csv_fields_and_labels(area_name, queryset) - data = self.build_csv_data(serializer.data, fields) + data = self.build_csv_data(serializer.data, fields, area_name) csv_data = CSVRenderer().render( data, diff --git a/src/genlab_bestilling/migrations/0029_alter_order_contact_email_alter_order_contact_person.py b/src/genlab_bestilling/migrations/0029_alter_order_contact_email_alter_order_contact_person.py new file mode 100644 index 00000000..c0969e59 --- /dev/null +++ b/src/genlab_bestilling/migrations/0029_alter_order_contact_email_alter_order_contact_person.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.3 on 2025-07-18 07:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("genlab_bestilling", "0028_sample_is_isolated_sample_is_marked_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="order", + name="contact_email", + field=models.EmailField( + help_text="Email to contact with questions about this order", + max_length=254, + null=True, + ), + ), + migrations.AlterField( + model_name="order", + name="contact_person", + field=models.CharField( + help_text="Person to contact with questions about this order", null=True + ), + ), + ] diff --git a/src/genlab_bestilling/models.py b/src/genlab_bestilling/models.py index c23890a6..6a8bfd51 100644 --- a/src/genlab_bestilling/models.py +++ b/src/genlab_bestilling/models.py @@ -282,12 +282,12 @@ class OrderPriority: ) contact_person = models.CharField( null=True, - blank=True, + blank=False, help_text="Person to contact with questions about this order", ) contact_email = models.EmailField( null=True, - blank=True, + blank=False, help_text="Email to contact with questions about this order", ) responsible_staff = models.ManyToManyField( @@ -310,6 +310,8 @@ class OrderPriority: def confirm_order(self) -> None: self.status = Order.OrderStatus.DELIVERED self.confirmed_at = timezone.now() + if self.is_urgent: + self.is_seen = True self.save() def clone(self) -> None: @@ -325,6 +327,10 @@ def to_draft(self) -> None: self.confirmed_at = None self.save() + def to_processing(self) -> None: + self.status = Order.OrderStatus.PROCESSING + self.save() + def toggle_seen(self) -> None: self.is_seen = not self.is_seen self.save() diff --git a/src/genlab_bestilling/templates/genlab_bestilling/base_filter.html b/src/genlab_bestilling/templates/genlab_bestilling/base_filter.html index e27e6f3b..7b7be329 100644 --- a/src/genlab_bestilling/templates/genlab_bestilling/base_filter.html +++ b/src/genlab_bestilling/templates/genlab_bestilling/base_filter.html @@ -10,6 +10,7 @@

{% block page-title %}{% endblock page-title %}

{{ filter.form | crispy }} + Clear Filters
diff --git a/src/staff/tables.py b/src/staff/tables.py index ae6a3502..892887d0 100644 --- a/src/staff/tables.py +++ b/src/staff/tables.py @@ -1,9 +1,10 @@ import re from collections.abc import Sequence -from datetime import datetime from typing import Any import django_tables2 as tables +from django.db import models +from django.db.models.query import QuerySet from django.utils.safestring import mark_safe from genlab_bestilling.models import ( @@ -18,6 +19,46 @@ from nina.models import Project +class StatusMixinTable(tables.Table): + status = tables.Column( + orderable=True, + verbose_name="Status", + ) + + def order_status( + self, queryset: QuerySet[Order], is_descending: bool + ) -> tuple[QuerySet[Order], bool]: + prefix = "-" if is_descending else "" + sorted_by_status = queryset.annotate( + status_order=models.Case( + models.When(status=Order.OrderStatus.DELIVERED, then=0), + models.When(status=Order.OrderStatus.DRAFT, then=1), + models.When(status=Order.OrderStatus.PROCESSING, then=2), + models.When(status=Order.OrderStatus.COMPLETED, then=3), + ) + ).order_by(f"{prefix}status_order") + + return (sorted_by_status, True) + + def render_status(self, value: Order.OrderStatus, record: Order) -> str: + status_colors = { + "Processing": "bg-yellow-100 text-yellow-800", + "Completed": "bg-green-100 text-green-800", + "Delivered": "bg-red-100 text-red-800", + } + status_text = { + "Processing": "Processing", + "Completed": "Completed", + "Delivered": "Not started", + "Draft": "Draft", + } + color_class = status_colors.get(value, "bg-gray-100 text-gray-800") + status_text = status_text.get(value, "Unknown") + return mark_safe( # noqa: S308 + f'{status_text}' # noqa: E501 + ) + + class ProjectTable(tables.Table): number = tables.Column( linkify=("staff:projects-detail", {"pk": tables.A("number")}), @@ -31,67 +72,54 @@ class Meta: fields = ("number", "name", "active", "verified_at") -class OrderTable(tables.Table): +class OrderTable(StatusMixinTable): id = tables.Column( linkify=True, orderable=False, empty_values=(), + verbose_name="Order ID", ) - is_urgent = tables.Column( + def render_id(self, record: Any) -> str: + return str(record) + + priority = tables.TemplateColumn( orderable=True, - visible=True, - verbose_name="", + verbose_name="Priority", + template_name="staff/components/priority_column.html", ) - status = tables.Column( - verbose_name="Status", - orderable=False, + area = tables.Column( + accessor="genrequest__area__name", + verbose_name="Area", + orderable=True, ) - is_seen = tables.Column( + description = tables.Column( + accessor="genrequest__name", + verbose_name="Description", + orderable=True, + ) + + responsible_staff = tables.ManyToManyColumn( + accessor="responsible_staff", + verbose_name="Assigned staff", orderable=False, - visible=True, - verbose_name="", + transform=lambda x: x.first_name + " " + x.last_name, ) class Meta: fields = [ - "name", + "priority", + "id", "status", - "genrequest", - "genrequest__name", - "genrequest__project", - "genrequest__area", - "genrequest__samples_owner", - "created_at", - "last_modified_at", - "is_urgent", - "is_seen", + "area", + "description", + "total_samples", + "responsible_staff", ] - sequence = ("is_seen", "is_urgent", "status", "id", "name") empty_text = "No Orders" - order_by = ("-is_urgent", "last_modified_at", "created_at") - - def render_id(self, record: Any) -> str: - return str(record) - - def render_is_urgent(self, value: bool) -> str: - html_exclaimation_mark = ( - "" - ) - if value: - return mark_safe(html_exclaimation_mark) # noqa: S308 - else: - return "" - - def render_is_seen(self, value: bool) -> str: - if not value: - return mark_safe( - '' - ) - return "" + order_by = ("-is_urgent",) class AnalysisOrderTable(OrderTable): @@ -101,9 +129,35 @@ class AnalysisOrderTable(OrderTable): empty_values=(), ) + markers = tables.ManyToManyColumn( + accessor="markers", + verbose_name="Markers", + orderable=False, + transform=lambda x: x.name, + ) + + expected_delivery_date = tables.DateColumn( + accessor="expected_delivery_date", + verbose_name="Delivery date", + format="d/m/Y", + orderable=True, + empty_values=(), + ) + class Meta(OrderTable.Meta): model = AnalysisOrder - fields = OrderTable.Meta.fields + ["return_samples"] + fields = OrderTable.Meta.fields + ["markers", "expected_delivery_date"] + sequence = ( + "priority", + "id", + "status", + "area", + "description", + "total_samples", + "markers", + "responsible_staff", + "expected_delivery_date", + ) class ExtractionOrderTable(OrderTable): @@ -113,26 +167,37 @@ class ExtractionOrderTable(OrderTable): empty_values=(), ) - sample_count = tables.Column( - accessor="sample_count", - verbose_name="Sample Count", - orderable=False, + def render_total_samples(self, record: ExtractionOrder) -> str: + return record.total_samples or "0" + + def render_total_samples_isolated(self, record: ExtractionOrder) -> str: + return record.total_samples_isolated or "0" + + confirmed_at = tables.DateColumn( + accessor="confirmed_at", + verbose_name="Confirmed at", + format="d/m/Y", + orderable=True, + empty_values=(), ) class Meta(OrderTable.Meta): model = ExtractionOrder fields = OrderTable.Meta.fields + [ - "species", - "sample_types", - "internal_status", - "needs_guid", - "return_samples", - "pre_isolated", + "total_samples_isolated", + "confirmed_at", ] - sequence = OrderTable.Meta.sequence + ("sample_count",) - - def render_sample_count(self, record: Any) -> str: - return record.sample_count or "0" + sequence = ( + "priority", + "id", + "status", + "area", + "description", + "total_samples", + "total_samples_isolated", + "responsible_staff", + "confirmed_at", + ) class EquipmentOrderTable(OrderTable): @@ -295,7 +360,7 @@ class Meta: "internal_note", "isolation_method", ] - order_by = () + order_by = ("genlab_id",) def render_checked(self, record: Any) -> str: return mark_safe( # noqa: S308 @@ -349,30 +414,6 @@ class Meta: empty_text = "No Plates" -class StatusMixinTable(tables.Table): - status = tables.Column( - orderable=False, - verbose_name="Status", - ) - - def render_status(self, value: Order.OrderStatus, record: Order) -> str: - status_colors = { - "Processing": "bg-yellow-100 text-yellow-800", - "Completed": "bg-green-100 text-green-800", - "Delivered": "bg-red-100 text-red-800", - } - status_text = { - "Processing": "Processing", - "Completed": "Completed", - "Delivered": "Not started", - } - color_class = status_colors.get(value, "bg-gray-100 text-gray-800") - status_text = status_text.get(value, "Unknown") - return mark_safe( # noqa: S308 - f'{status_text}' # noqa: E501 - ) - - class StatusMixinTableSamples(tables.Table): sample_status = tables.Column( verbose_name="Sample Status", empty_values=(), orderable=True @@ -511,7 +552,6 @@ class UrgentOrderTable(StaffIDMixinTable, StatusMixinTable): priority = tables.TemplateColumn( orderable=False, verbose_name="Priority", - accessor="priority", template_name="staff/components/priority_column.html", ) @@ -521,16 +561,13 @@ class UrgentOrderTable(StaffIDMixinTable, StatusMixinTable): orderable=False, ) - delivery_date = tables.Column( + delivery_date = tables.DateColumn( verbose_name="Delivery date", orderable=False, + format="d/m/Y", + empty_values=(), ) - def render_delivery_date(self, value: datetime | None) -> str: - if value: - return value.strftime("%d/%m/%Y") - return "-" - class Meta: model = Order fields = ["priority", "id", "description", "delivery_date", "status"] @@ -552,16 +589,13 @@ class NewUnseenOrderTable(StaffIDMixinTable): orderable=False, ) - delivery_date = tables.Column( + delivery_date = tables.DateColumn( verbose_name="Delivery date", orderable=False, + format="d/m/Y", + empty_values=(), ) - def render_delivery_date(self, value: datetime | None) -> str: - if value: - return value.strftime("%d/%m/%Y") - return "-" - samples = tables.Column( accessor="sample_count", verbose_name="Samples", @@ -588,7 +622,6 @@ class NewSeenOrderTable(StaffIDMixinTable): priority = tables.TemplateColumn( orderable=False, verbose_name="Priority", - accessor="priority", template_name="staff/components/priority_column.html", ) @@ -598,16 +631,13 @@ class NewSeenOrderTable(StaffIDMixinTable): orderable=False, ) - delivery_date = tables.Column( + delivery_date = tables.DateColumn( verbose_name="Delivery date", orderable=False, + format="d/m/Y", + empty_values=(), ) - def render_delivery_date(self, value: datetime | None) -> str: - if value: - return value.strftime("%d/%m/%Y") - return "-" - samples = tables.Column( accessor="sample_count", verbose_name="Samples", @@ -641,7 +671,6 @@ class AssignedOrderTable(StatusMixinTable, StaffIDMixinTable): priority = tables.TemplateColumn( orderable=False, verbose_name="Priority", - accessor="priority", template_name="staff/components/priority_column.html", ) @@ -673,7 +702,6 @@ class DraftOrderTable(StaffIDMixinTable): priority = tables.TemplateColumn( orderable=False, verbose_name="Priority", - accessor="priority", template_name="staff/components/priority_column.html", ) @@ -683,16 +711,13 @@ class DraftOrderTable(StaffIDMixinTable): orderable=False, ) - delivery_date = tables.Column( + delivery_date = tables.DateColumn( verbose_name="Delivery date", orderable=False, + format="d/m/Y", + empty_values=(), ) - def render_delivery_date(self, value: datetime | None) -> str: - if value: - return value.strftime("%d/%m/%Y") - return "-" - samples = tables.Column( accessor="sample_count", verbose_name="Samples", diff --git a/src/staff/templates/staff/analysisorder_filter.html b/src/staff/templates/staff/analysisorder_filter.html index c388eb48..4db95d15 100644 --- a/src/staff/templates/staff/analysisorder_filter.html +++ b/src/staff/templates/staff/analysisorder_filter.html @@ -11,6 +11,7 @@
{{ filter.form | crispy }} + Clear Filters
diff --git a/src/staff/templates/staff/components/priority_column.html b/src/staff/templates/staff/components/priority_column.html index ce76db60..d46c30d0 100644 --- a/src/staff/templates/staff/components/priority_column.html +++ b/src/staff/templates/staff/components/priority_column.html @@ -1,13 +1,15 @@ -{% if value == 3 %} +{% if record.is_urgent %} {% else %}
{% csrf_token %} + + {% comment %}Outlined flag icon does not work using the tag, so also using the SVG for the filled flag icon for consistency. If it can be fixed in the future it should.{% endcomment %} - + Clear Filters
diff --git a/src/staff/templates/staff/extractionorder_filter.html b/src/staff/templates/staff/extractionorder_filter.html index c85fc272..d6849d29 100644 --- a/src/staff/templates/staff/extractionorder_filter.html +++ b/src/staff/templates/staff/extractionorder_filter.html @@ -11,6 +11,7 @@
{{ filter.form | crispy }} + Clear Filters
diff --git a/src/staff/templates/staff/extractionplate_filter.html b/src/staff/templates/staff/extractionplate_filter.html index 4463ae68..c8ed54ab 100644 --- a/src/staff/templates/staff/extractionplate_filter.html +++ b/src/staff/templates/staff/extractionplate_filter.html @@ -16,6 +16,7 @@ {{ filter.form | crispy }} + Clear Filters {% render_table table %} diff --git a/src/staff/templates/staff/project_filter.html b/src/staff/templates/staff/project_filter.html index eb62d2fe..afc3319e 100644 --- a/src/staff/templates/staff/project_filter.html +++ b/src/staff/templates/staff/project_filter.html @@ -11,6 +11,7 @@
{{ filter.form | crispy }} + Clear Filters
diff --git a/src/staff/templates/staff/sample_filter.html b/src/staff/templates/staff/sample_filter.html index fffea62b..f0ec49e3 100644 --- a/src/staff/templates/staff/sample_filter.html +++ b/src/staff/templates/staff/sample_filter.html @@ -29,6 +29,7 @@
{{ filter.form | crispy }} + Clear Filters
diff --git a/src/staff/templates/staff/sample_lab.html b/src/staff/templates/staff/sample_lab.html index fececc23..24c2abc5 100644 --- a/src/staff/templates/staff/sample_lab.html +++ b/src/staff/templates/staff/sample_lab.html @@ -13,6 +13,7 @@

{% block page-title %}{% if order %}{{ order }} - Samp
{{ filter.form | crispy }} + Clear Filters
{% endcomment %} diff --git a/src/staff/templates/staff/samplemarkeranalysis_filter.html b/src/staff/templates/staff/samplemarkeranalysis_filter.html index a4478564..749af0e8 100644 --- a/src/staff/templates/staff/samplemarkeranalysis_filter.html +++ b/src/staff/templates/staff/samplemarkeranalysis_filter.html @@ -17,6 +17,7 @@
{{ filter.form | crispy }} + Clear Filters
diff --git a/src/staff/views.py b/src/staff/views.py index 9fd4274d..4e741c7a 100644 --- a/src/staff/views.py +++ b/src/staff/views.py @@ -110,6 +110,7 @@ def get_queryset(self) -> models.QuerySet[AnalysisOrder]: "genrequest__project", "genrequest__area", ) + .annotate(total_samples=Count("samples")) ) @@ -130,7 +131,12 @@ def get_queryset(self) -> models.QuerySet[ExtractionOrder]: "genrequest__area", ) .prefetch_related("species", "sample_types") - .annotate(sample_count=Count("samples")) + .annotate( + total_samples=Count("samples"), + total_samples_isolated=models.Count( + "samples", filter=models.Q(samples__is_isolated=True) + ), + ) ) @@ -430,6 +436,10 @@ def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: self.update_isolation_methods(samples, isolation_method, request) return HttpResponseRedirect(self.get_success_url()) + def statuses_with_lower_or_equal_priority(self, status_name: str) -> list[str]: + index = self.VALID_STATUSES.index(status_name) + return self.VALID_STATUSES[: index + 1] + def assign_status_to_samples( self, samples: models.QuerySet, @@ -440,23 +450,24 @@ def assign_status_to_samples( messages.error(request, f"Status '{status_name}' is not valid.") return - update_fields = [] + statuses_to_turn_on = self.statuses_with_lower_or_equal_priority(status_name) + field_name = f"is_{status_name}" - if status_name == self.MARKED: - update_fields.append("is_marked") - samples.update(is_marked=True) + samples_to_turn_off_ids = list( + samples.filter(**{field_name: True}).values_list("id", flat=True) + ) + samples_to_turn_on_ids = list( + samples.filter(**{field_name: False}).values_list("id", flat=True) + ) - elif status_name == self.PLUCKED: - update_fields.extend(["is_marked", "is_plucked"]) - samples.update(is_marked=True, is_plucked=True) + Sample.objects.filter(id__in=samples_to_turn_off_ids).update( + **{field_name: False} + ) - elif status_name == self.ISOLATED: - update_fields.extend(["is_marked", "is_plucked", "is_isolated"]) - samples.update(is_marked=True, is_plucked=True, is_isolated=True) + update_dict = {f"is_{status}": True for status in statuses_to_turn_on} + Sample.objects.filter(id__in=samples_to_turn_on_ids).update(**update_dict) - messages.success( - request, f"{samples.count()} samples updated with status '{status_name}'." - ) + messages.success(request, "Samples updated successfully") # Checks if all samples in the order are isolated # If they are, it updates the order status to completed @@ -467,6 +478,12 @@ def check_all_isolated(self, samples: models.QuerySet) -> None: self.request, "All samples are isolated. The order status is updated to completed.", ) + elif self.get_order().status == Order.OrderStatus.COMPLETED: + self.get_order().to_processing() + messages.success( + self.request, + "Not all samples are isolated. The order status is updated to processing.", # noqa: E501 + ) def update_isolation_methods( self, samples: models.QuerySet, isolation_method: str, request: HttpRequest @@ -787,13 +804,20 @@ def form_invalid(self, form: Form) -> HttpResponse: class OrderPrioritizedAdminView(StaffMixin, ActionView): + def get_success_url(self) -> str: + next_url = self.request.POST.get("next") + if next_url and url_has_allowed_host_and_scheme( + next_url, + allowed_hosts={self.request.get_host()}, + require_https=self.request.is_secure(), + ): + return next_url + + return reverse("staff:dashboard") + def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: pk = kwargs.get("pk") order = Order.objects.get(pk=pk) order.toggle_prioritized() - return HttpResponseRedirect( - reverse( - "staff:dashboard", - ) - ) + return redirect(self.get_success_url())