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 %}
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 @@
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 %}
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 @@
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 @@
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 @@
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
{% 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 @@
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())