From 329f6de26c42f051d28c10de76460ab0c6e4919a Mon Sep 17 00:00:00 2001 From: aastabk Date: Thu, 24 Jul 2025 10:44:48 +0200 Subject: [PATCH 01/11] Added results contact info for analysis --- src/genlab_bestilling/forms.py | 58 ++++++++++++++++++- src/genlab_bestilling/models.py | 23 +++++++- .../analysisorder_detail.html | 23 +++++++- src/genlab_bestilling/views.py | 5 ++ src/staff/tables.py | 4 +- src/staff/templatetags/order_tags.py | 29 +++++++++- src/templates/components/order-detail.html | 2 +- 7 files changed, 135 insertions(+), 9 deletions(-) diff --git a/src/genlab_bestilling/forms.py b/src/genlab_bestilling/forms.py index dcb382c7..05b2367c 100644 --- a/src/genlab_bestilling/forms.py +++ b/src/genlab_bestilling/forms.py @@ -14,6 +14,7 @@ from .libs.formset import ContextFormCollection from .models import ( AnalysisOrder, + AnalysisOrderResultsCommunication, EquimentOrderQuantity, EquipmentOrder, ExtractionOrder, @@ -122,6 +123,12 @@ def __init__(self, *args, genrequest: Genrequest, **kwargs): # self.fields["species"].queryset = genrequest.species.all() self.fields["sample_types"].queryset = genrequest.sample_types.all() # type: ignore[attr-defined] + self.fields[ + "contact_person" + ].help_text = "Person to contact with questions about this order" + self.fields[ + "contact_email" + ].help_text = "Email to contact with questions about this order" def save(self, commit: bool = True) -> EquipmentOrder: obj = super().save(commit=False) @@ -258,9 +265,8 @@ def __init__(self, *args, genrequest: Genrequest, **kwargs): self.fields["species"].queryset = genrequest.species.all() # type: ignore[attr-defined] self.fields["sample_types"].queryset = genrequest.sample_types.all() # type: ignore[attr-defined] - # self.fields["markers"].queryset = Marker.objects.filter( - # species__genrequests__id=genrequest.id - # ).distinct() + self.fields["contact_person"].label = "Responsible genetic researcher" + self.fields["contact_email"].label = "Responsible genetic researcher email" def save(self, commit: bool = True) -> ExtractionOrder: obj = super().save(commit=False) @@ -329,10 +335,14 @@ def __init__(self, *args, genrequest: Genrequest, **kwargs): " with the sample selection by pressing Submit" ) + self.fields["contact_person"].label = "Responsible genetic researcher" + self.fields["contact_email"].label = "Responsible genetic researcher email" + def save(self, commit: bool = True) -> Model: if not commit: msg = "This form is always committed" raise NotImplementedError(msg) + with transaction.atomic(): obj = super().save(commit=False) obj.genrequest = self.genrequest @@ -342,9 +352,39 @@ def save(self, commit: bool = True) -> Model: if obj.from_order and not obj.name and obj.from_order.name: obj.name = obj.from_order.name + " - Analysis" + obj.save() self.save_m2m() obj.populate_from_order() + + # Save AnalysisOrderResultsCommunication objects + # Delete old entries first (in case of resubmission) + obj.results_contacts.all().delete() + + names = [ + n.strip() + for n in self.cleaned_data.get("contact_person_results", "").split(",") + if n.strip() + ] + emails = [ + e.strip() + for e in self.cleaned_data.get("contact_email_results", "").split(",") + if e.strip() + ] + + if names or emails: + if len(names) != len(emails): + raise ValidationError( + "The number of names must match the number of emails." # noqa: EM101 + ) + + for name, email in zip(names, emails, strict=False): + AnalysisOrderResultsCommunication.objects.create( + analysis_order=obj, + contact_person_results=name, + contact_email_results=email, + ) + return obj def clean(self) -> None: @@ -354,6 +394,18 @@ def clean(self) -> None: msg = "An extraction order must be selected" raise ValidationError(msg) + contact_person_results = forms.CharField( + label="Contact person(s) for results", + help_text="Comma-separated list of names to contact with results", + required=False, + ) + + contact_email_results = forms.CharField( + label="Contact email(s) for results", + help_text="Comma-separated list of emails to contact with results (must match order of names)", # noqa: E501 + required=False, + ) + field_order = [ "name", "use_all_samples", diff --git a/src/genlab_bestilling/models.py b/src/genlab_bestilling/models.py index 783e3607..c83f9f12 100644 --- a/src/genlab_bestilling/models.py +++ b/src/genlab_bestilling/models.py @@ -284,7 +284,7 @@ class OrderPriority: contact_person = models.CharField( null=True, blank=False, - help_text="Person to contact with questions about this order", + help_text="Responsible for genetic bioinformatics analysis", ) contact_email = models.EmailField( null=True, @@ -532,6 +532,27 @@ def order_selected_checked( ) +class AnalysisOrderResultsCommunication(models.Model): + analysis_order = models.ForeignKey( + f"{an}.AnalysisOrder", + on_delete=models.CASCADE, + related_name="results_contacts", + ) + contact_person_results = models.CharField( + null=True, + blank=False, + help_text="Person to contact for analysis resuls", + ) + contact_email_results = models.EmailField( + null=True, + blank=False, + help_text="Email to send analysis results", + ) + + def __str__(self): + return f"{str(self.analysis_order)} {str(self.contact_person_results)} {str(self.contact_email_results)}" # noqa: E501 + + class AnalysisOrder(Order): samples = models.ManyToManyField( f"{an}.Sample", blank=True, through="SampleMarkerAnalysis" diff --git a/src/genlab_bestilling/templates/genlab_bestilling/analysisorder_detail.html b/src/genlab_bestilling/templates/genlab_bestilling/analysisorder_detail.html index 982a072d..9e264540 100644 --- a/src/genlab_bestilling/templates/genlab_bestilling/analysisorder_detail.html +++ b/src/genlab_bestilling/templates/genlab_bestilling/analysisorder_detail.html @@ -26,6 +26,27 @@

Order {{ object }}

{% object-detail object=object %} + {% if results_contacts %} +
Contacts for Analysis Results
+
+ +
+ {% else %} +
Contacts for Analysis Results
+
+

No contacts provided for analysis results.

+
+ {% endif %} +
Samples to analyze
@@ -48,7 +69,7 @@
Samples to analyze
Delete {% endif %} {% elif object.status == object.OrderStatus.DELIVERED %} - Samples + Samples {% endif %}
{% endblock %} diff --git a/src/genlab_bestilling/views.py b/src/genlab_bestilling/views.py index 19b6874b..399f1961 100644 --- a/src/genlab_bestilling/views.py +++ b/src/genlab_bestilling/views.py @@ -467,6 +467,11 @@ def gen_crumbs(self) -> list[tuple]: (str(self.object), ""), ] + def get_context_data(self, **kwargs) -> dict[str, Any]: + context = super().get_context_data(**kwargs) + context["results_contacts"] = self.object.results_contacts.all() + return context + def get_queryset(self) -> QuerySet: return ( super() diff --git a/src/staff/tables.py b/src/staff/tables.py index 318d7910..aac10d18 100644 --- a/src/staff/tables.py +++ b/src/staff/tables.py @@ -704,13 +704,13 @@ class DraftOrderTable(StaffIDMixinTable): contact_person = tables.Column( accessor="contact_person", - verbose_name="Contact Person", + verbose_name="Responsible genetic researcher", orderable=False, ) contact_email = tables.Column( accessor="contact_email", - verbose_name="Contact Email", + verbose_name="Responsible genetic researcher email", orderable=False, ) diff --git a/src/staff/templatetags/order_tags.py b/src/staff/templatetags/order_tags.py index d09fff73..c3e95d47 100644 --- a/src/staff/templatetags/order_tags.py +++ b/src/staff/templatetags/order_tags.py @@ -2,9 +2,15 @@ from django import template from django.db import models +from django.utils.html import format_html_join from django.utils.safestring import mark_safe -from genlab_bestilling.models import Area, Order +from genlab_bestilling.models import ( + AnalysisOrder, + AnalysisOrderResultsCommunication, + Area, + Order, +) from ..tables import ( AssignedOrderTable, @@ -339,11 +345,32 @@ def analysis_order_samples_detail_table(order: Order, extraction_orders: dict) - @register.inclusion_tag("../templates/components/order-detail.html") def contact_detail_table(order: Order) -> dict: + # Default values + result_contacts_html = "—" + + # Only fetch contacts if it's an AnalysisOrder instance + if isinstance(order, AnalysisOrder): + result_contacts = ( + AnalysisOrderResultsCommunication.objects.filter(analysis_order=order) + .values_list("contact_person_results", "contact_email_results") + .distinct() + ) + if result_contacts: + result_contacts_html = mark_safe( # noqa: S308 + format_html_join( + "", + '
{} — {}
', # noqa: E501 + [(name, email, email) for name, email in result_contacts], + ) + ) + fields = { "Samples owner of genetic project": order.genrequest.samples_owner, "Contact person": order.contact_person, "Contact Email": order.contact_email, + "Contacts for analysis results": result_contacts_html, } + return { "fields": fields, "header": "Contact", diff --git a/src/templates/components/order-detail.html b/src/templates/components/order-detail.html index 8b883ed3..e153a72e 100644 --- a/src/templates/components/order-detail.html +++ b/src/templates/components/order-detail.html @@ -14,7 +14,7 @@ {% for f, v in fields.items %} {% #table-cell %}{{ f }}{% /table-cell %} - {% #table-cell %}{{ v }}{% /table-cell %} + {% #table-cell %}{{ v|safe }}{% /table-cell %} {% endfor %} {% /table %} From ef570ec88ddf9f5eab10e35986c7cb2349223e8c Mon Sep 17 00:00:00 2001 From: aastabk Date: Thu, 24 Jul 2025 10:45:21 +0200 Subject: [PATCH 02/11] Added migrations --- ...033_alter_order_contact_person_and_more.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/genlab_bestilling/migrations/0033_alter_order_contact_person_and_more.py diff --git a/src/genlab_bestilling/migrations/0033_alter_order_contact_person_and_more.py b/src/genlab_bestilling/migrations/0033_alter_order_contact_person_and_more.py new file mode 100644 index 00000000..d2df21f8 --- /dev/null +++ b/src/genlab_bestilling/migrations/0033_alter_order_contact_person_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.3 on 2025-07-24 08:45 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('genlab_bestilling', '0032_alter_isolationmethod_remove_species_add_type_20250722_1226'), + ] + + operations = [ + migrations.AlterField( + model_name='order', + name='contact_person', + field=models.CharField(help_text='Responsible for genetic bioinformatics analysis', null=True), + ), + migrations.CreateModel( + name='AnalysisOrderResultsCommunication', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('contact_person_results', models.CharField(help_text='Person to contact for analysis resuls', null=True)), + ('contact_email_results', models.EmailField(help_text='Email to send analysis results', max_length=254, null=True)), + ('analysis_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results_contacts', to='genlab_bestilling.analysisorder')), + ], + ), + ] From ba626d0e8fb4bcf818c87e0764e5dd9c5632c01c Mon Sep 17 00:00:00 2001 From: aastabk Date: Thu, 24 Jul 2025 10:51:12 +0200 Subject: [PATCH 03/11] Added migrations --- ...033_alter_order_contact_person_and_more.py | 51 +++++++++++++++---- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/src/genlab_bestilling/migrations/0033_alter_order_contact_person_and_more.py b/src/genlab_bestilling/migrations/0033_alter_order_contact_person_and_more.py index d2df21f8..051aa388 100644 --- a/src/genlab_bestilling/migrations/0033_alter_order_contact_person_and_more.py +++ b/src/genlab_bestilling/migrations/0033_alter_order_contact_person_and_more.py @@ -5,24 +5,55 @@ class Migration(migrations.Migration): - dependencies = [ - ('genlab_bestilling', '0032_alter_isolationmethod_remove_species_add_type_20250722_1226'), + ( + "genlab_bestilling", + "0032_alter_isolationmethod_remove_species_add_type_20250722_1226", + ), ] operations = [ migrations.AlterField( - model_name='order', - name='contact_person', - field=models.CharField(help_text='Responsible for genetic bioinformatics analysis', null=True), + model_name="order", + name="contact_person", + field=models.CharField( + help_text="Responsible for genetic bioinformatics analysis", null=True + ), ), migrations.CreateModel( - name='AnalysisOrderResultsCommunication', + name="AnalysisOrderResultsCommunication", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('contact_person_results', models.CharField(help_text='Person to contact for analysis resuls', null=True)), - ('contact_email_results', models.EmailField(help_text='Email to send analysis results', max_length=254, null=True)), - ('analysis_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results_contacts', to='genlab_bestilling.analysisorder')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "contact_person_results", + models.CharField( + help_text="Person to contact for analysis resuls", null=True + ), + ), + ( + "contact_email_results", + models.EmailField( + help_text="Email to send analysis results", + max_length=254, + null=True, + ), + ), + ( + "analysis_order", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="results_contacts", + to="genlab_bestilling.analysisorder", + ), + ), ], ), ] From 24db6b4515bfdbaf5f3a8f553a6125891ccdf2be Mon Sep 17 00:00:00 2001 From: aastabk Date: Thu, 24 Jul 2025 14:10:50 +0200 Subject: [PATCH 04/11] Safe fields for name and email for analysis results --- src/genlab_bestilling/forms.py | 34 +++++++++++++++++++--- src/genlab_bestilling/views.py | 12 ++++++++ src/staff/templatetags/order_tags.py | 15 +++++----- src/templates/components/order-detail.html | 2 +- 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/genlab_bestilling/forms.py b/src/genlab_bestilling/forms.py index 05b2367c..ce26413f 100644 --- a/src/genlab_bestilling/forms.py +++ b/src/genlab_bestilling/forms.py @@ -3,8 +3,11 @@ from django import forms from django.contrib.auth.models import User from django.core.exceptions import ValidationError +from django.core.exceptions import ValidationError as CoreValidationError +from django.core.validators import validate_email from django.db import transaction from django.db.models import Model +from django.utils.html import strip_tags from formset.renderers.tailwind import FormRenderer from formset.utils import FormMixin from formset.widgets import DualSortableSelector, Selectize, TextInput @@ -338,6 +341,29 @@ def __init__(self, *args, genrequest: Genrequest, **kwargs): self.fields["contact_person"].label = "Responsible genetic researcher" self.fields["contact_email"].label = "Responsible genetic researcher email" + def clean_contact_email_results(self) -> str: + emails_raw = self.cleaned_data.get("contact_email_results", "") + emails = [e.strip() for e in emails_raw.split(",") if e.strip()] + + try: + for email in emails: + validate_email(email) + except CoreValidationError: + msg = f"Invalid email: {email}" + raise forms.ValidationError(msg) from CoreValidationError(msg) + return ", ".join(emails) + + def clean_contact_person_results(self) -> str: + names_raw = self.cleaned_data.get("contact_person_results", "") + names = [n.strip() for n in names_raw.split(",") if n.strip()] + + for name in names: + # Optionally allow hyphens and apostrophes in names + if not all(c.isalpha() or c.isspace() or c in "-'" for c in name): + msg = f"Invalid name: {name}" + raise forms.ValidationError(msg) from CoreValidationError(msg) + return ", ".join(names) + def save(self, commit: bool = True) -> Model: if not commit: msg = "This form is always committed" @@ -362,13 +388,13 @@ def save(self, commit: bool = True) -> Model: obj.results_contacts.all().delete() names = [ - n.strip() - for n in self.cleaned_data.get("contact_person_results", "").split(",") + strip_tags(n.strip()) + for n in self.cleaned_data["contact_person_results"].split(",") if n.strip() ] emails = [ - e.strip() - for e in self.cleaned_data.get("contact_email_results", "").split(",") + strip_tags(e.strip()) + for e in self.cleaned_data["contact_email_results"].split(",") if e.strip() ] diff --git a/src/genlab_bestilling/views.py b/src/genlab_bestilling/views.py index 399f1961..d89ec12c 100644 --- a/src/genlab_bestilling/views.py +++ b/src/genlab_bestilling/views.py @@ -831,6 +831,9 @@ def gen_crumbs(self) -> list[tuple]: ] def get_success_url(self) -> str: + # Clear any leftover error messages before redirect + list(messages.get_messages(self.request)) + obj: AnalysisOrder = self.object # type: ignore[assignment] # Possibly None if obj.from_order: return reverse( @@ -848,6 +851,15 @@ def get_success_url(self) -> str: }, ) + # Override form_invalid to show errors in the form + # instead of just redirecting to the success URL. + def form_invalid(self, form: Form) -> HttpResponse: + for field, errors in form.errors.items(): + label = form.fields.get(field).label if field in form.fields else field + for error in errors: + messages.error(self.request, f"{label}: {error}") + return super().form_invalid(form) + class ExtractionOrderCreateView( GenrequestNestedMixin, diff --git a/src/staff/templatetags/order_tags.py b/src/staff/templatetags/order_tags.py index c3e95d47..ec760629 100644 --- a/src/staff/templatetags/order_tags.py +++ b/src/staff/templatetags/order_tags.py @@ -2,7 +2,7 @@ from django import template from django.db import models -from django.utils.html import format_html_join +from django.utils.html import escape, format_html_join from django.utils.safestring import mark_safe from genlab_bestilling.models import ( @@ -356,12 +356,13 @@ def contact_detail_table(order: Order) -> dict: .distinct() ) if result_contacts: - result_contacts_html = mark_safe( # noqa: S308 - format_html_join( - "", - '
{} — {}
', # noqa: E501 - [(name, email, email) for name, email in result_contacts], - ) + result_contacts_html = format_html_join( + "", # no separator between divs + '
{} — {}
', # noqa: E501 + [ + (escape(name), escape(email), escape(email)) + for name, email in result_contacts + ], ) fields = { diff --git a/src/templates/components/order-detail.html b/src/templates/components/order-detail.html index e153a72e..8b883ed3 100644 --- a/src/templates/components/order-detail.html +++ b/src/templates/components/order-detail.html @@ -14,7 +14,7 @@ {% for f, v in fields.items %} {% #table-cell %}{{ f }}{% /table-cell %} - {% #table-cell %}{{ v|safe }}{% /table-cell %} + {% #table-cell %}{{ v }}{% /table-cell %} {% endfor %} {% /table %} From 896d32f52b3d91ceb7a460bb5d10927eaae2c99c Mon Sep 17 00:00:00 2001 From: aastabk Date: Thu, 24 Jul 2025 14:18:52 +0200 Subject: [PATCH 05/11] Mypy fix --- src/genlab_bestilling/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/genlab_bestilling/views.py b/src/genlab_bestilling/views.py index d89ec12c..0fbe3164 100644 --- a/src/genlab_bestilling/views.py +++ b/src/genlab_bestilling/views.py @@ -855,7 +855,8 @@ def get_success_url(self) -> str: # instead of just redirecting to the success URL. def form_invalid(self, form: Form) -> HttpResponse: for field, errors in form.errors.items(): - label = form.fields.get(field).label if field in form.fields else field + field_obj = form.fields.get(field) + label = field_obj.label if field_obj is not None else field for error in errors: messages.error(self.request, f"{label}: {error}") return super().form_invalid(form) From 0524b02eea70ae65aa81a818272acd75feb9078a Mon Sep 17 00:00:00 2001 From: aastabk Date: Thu, 24 Jul 2025 15:04:46 +0200 Subject: [PATCH 06/11] Safer (?) fix --- src/genlab_bestilling/forms.py | 13 ++++++------- src/staff/templatetags/order_tags.py | 20 +++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/genlab_bestilling/forms.py b/src/genlab_bestilling/forms.py index ce26413f..6df72fa3 100644 --- a/src/genlab_bestilling/forms.py +++ b/src/genlab_bestilling/forms.py @@ -3,7 +3,7 @@ from django import forms from django.contrib.auth.models import User from django.core.exceptions import ValidationError -from django.core.exceptions import ValidationError as CoreValidationError +from django.core.exceptions import ValidationError as DjangoValidationError from django.core.validators import validate_email from django.db import transaction from django.db.models import Model @@ -348,9 +348,9 @@ def clean_contact_email_results(self) -> str: try: for email in emails: validate_email(email) - except CoreValidationError: + except DjangoValidationError: msg = f"Invalid email: {email}" - raise forms.ValidationError(msg) from CoreValidationError(msg) + raise forms.ValidationError(msg) from DjangoValidationError(msg) return ", ".join(emails) def clean_contact_person_results(self) -> str: @@ -361,7 +361,7 @@ def clean_contact_person_results(self) -> str: # Optionally allow hyphens and apostrophes in names if not all(c.isalpha() or c.isspace() or c in "-'" for c in name): msg = f"Invalid name: {name}" - raise forms.ValidationError(msg) from CoreValidationError(msg) + raise forms.ValidationError(msg) from DjangoValidationError(msg) return ", ".join(names) def save(self, commit: bool = True) -> Model: @@ -400,9 +400,8 @@ def save(self, commit: bool = True) -> Model: if names or emails: if len(names) != len(emails): - raise ValidationError( - "The number of names must match the number of emails." # noqa: EM101 - ) + msg = "The number of names must match the number of emails." + raise ValidationError(msg) for name, email in zip(names, emails, strict=False): AnalysisOrderResultsCommunication.objects.create( diff --git a/src/staff/templatetags/order_tags.py b/src/staff/templatetags/order_tags.py index ec760629..c42dbe5e 100644 --- a/src/staff/templatetags/order_tags.py +++ b/src/staff/templatetags/order_tags.py @@ -2,7 +2,7 @@ from django import template from django.db import models -from django.utils.html import escape, format_html_join +from django.utils.html import format_html from django.utils.safestring import mark_safe from genlab_bestilling.models import ( @@ -356,14 +356,16 @@ def contact_detail_table(order: Order) -> dict: .distinct() ) if result_contacts: - result_contacts_html = format_html_join( - "", # no separator between divs - '
{} — {}
', # noqa: E501 - [ - (escape(name), escape(email), escape(email)) - for name, email in result_contacts - ], - ) + contact_rows = [] + for name, email in result_contacts: + html = format_html( + '
{} — {}
', # noqa: E501 + name, + email, + email, + ) + contact_rows.append(html) + result_contacts_html = mark_safe("".join(contact_rows)) # noqa: S308 fields = { "Samples owner of genetic project": order.genrequest.samples_owner, From 1e5ebbc1892cb18bb78bb04c4a73473ef26a1037 Mon Sep 17 00:00:00 2001 From: aastabk Date: Thu, 24 Jul 2025 15:08:23 +0200 Subject: [PATCH 07/11] Fixed comments --- src/genlab_bestilling/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/genlab_bestilling/views.py b/src/genlab_bestilling/views.py index 0fbe3164..a4da7cdd 100644 --- a/src/genlab_bestilling/views.py +++ b/src/genlab_bestilling/views.py @@ -852,7 +852,6 @@ def get_success_url(self) -> str: ) # Override form_invalid to show errors in the form - # instead of just redirecting to the success URL. def form_invalid(self, form: Form) -> HttpResponse: for field, errors in form.errors.items(): field_obj = form.fields.get(field) From 1aa897b729cf6fe98107633f2e22c30f75f2d355 Mon Sep 17 00:00:00 2001 From: aastabk Date: Thu, 24 Jul 2025 15:49:05 +0200 Subject: [PATCH 08/11] Removed mark_safe() --- src/staff/templatetags/order_tags.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/staff/templatetags/order_tags.py b/src/staff/templatetags/order_tags.py index c42dbe5e..f1af8f01 100644 --- a/src/staff/templatetags/order_tags.py +++ b/src/staff/templatetags/order_tags.py @@ -2,7 +2,7 @@ from django import template from django.db import models -from django.utils.html import format_html +from django.utils.html import format_html_join from django.utils.safestring import mark_safe from genlab_bestilling.models import ( @@ -356,16 +356,11 @@ def contact_detail_table(order: Order) -> dict: .distinct() ) if result_contacts: - contact_rows = [] - for name, email in result_contacts: - html = format_html( - '
{} — {}
', # noqa: E501 - name, - email, - email, - ) - contact_rows.append(html) - result_contacts_html = mark_safe("".join(contact_rows)) # noqa: S308 + result_contacts_html = format_html_join( + "\n", + '
{} — {}
', # noqa: E501 + [(name, email, email) for name, email in result_contacts], + ) fields = { "Samples owner of genetic project": order.genrequest.samples_owner, From 49ad97a5648b204a2d789c148e50d09b5c4ac21a Mon Sep 17 00:00:00 2001 From: aastabk Date: Fri, 25 Jul 2025 08:04:45 +0200 Subject: [PATCH 09/11] Removed duplicate import --- src/genlab_bestilling/forms.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/genlab_bestilling/forms.py b/src/genlab_bestilling/forms.py index 6df72fa3..256b8176 100644 --- a/src/genlab_bestilling/forms.py +++ b/src/genlab_bestilling/forms.py @@ -3,7 +3,6 @@ from django import forms from django.contrib.auth.models import User from django.core.exceptions import ValidationError -from django.core.exceptions import ValidationError as DjangoValidationError from django.core.validators import validate_email from django.db import transaction from django.db.models import Model @@ -348,9 +347,9 @@ def clean_contact_email_results(self) -> str: try: for email in emails: validate_email(email) - except DjangoValidationError: + except ValidationError: msg = f"Invalid email: {email}" - raise forms.ValidationError(msg) from DjangoValidationError(msg) + raise forms.ValidationError(msg) from ValidationError(msg) return ", ".join(emails) def clean_contact_person_results(self) -> str: @@ -361,7 +360,7 @@ def clean_contact_person_results(self) -> str: # Optionally allow hyphens and apostrophes in names if not all(c.isalpha() or c.isspace() or c in "-'" for c in name): msg = f"Invalid name: {name}" - raise forms.ValidationError(msg) from DjangoValidationError(msg) + raise forms.ValidationError(msg) from ValidationError(msg) return ", ".join(names) def save(self, commit: bool = True) -> Model: From 3620be81edc235a943581b2165a64162b2504c4c Mon Sep 17 00:00:00 2001 From: aastabk Date: Fri, 25 Jul 2025 11:14:48 +0200 Subject: [PATCH 10/11] Changed names of columns (analysis results contact person) --- src/genlab_bestilling/views.py | 13 ++++--------- src/staff/templatetags/order_tags.py | 6 +++--- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/genlab_bestilling/views.py b/src/genlab_bestilling/views.py index a4da7cdd..e7c98fc5 100644 --- a/src/genlab_bestilling/views.py +++ b/src/genlab_bestilling/views.py @@ -469,6 +469,10 @@ def gen_crumbs(self) -> list[tuple]: def get_context_data(self, **kwargs) -> dict[str, Any]: context = super().get_context_data(**kwargs) + all_samples_have_no_genlab_id = not self.object.samples.exclude( + genlab_id__isnull=True + ).exists() + context["all_samples_have_no_genlab_id"] = all_samples_have_no_genlab_id context["results_contacts"] = self.object.results_contacts.all() return context @@ -480,15 +484,6 @@ def get_queryset(self) -> QuerySet: .prefetch_related("sample_markers", "markers") ) - def get_context_data(self, **kwargs: Any) -> dict[str, Any]: - context = super().get_context_data(**kwargs) - order = self.object - all_samples_have_no_genlab_id = not order.samples.exclude( - genlab_id__isnull=True - ).exists() - context["all_samples_have_no_genlab_id"] = all_samples_have_no_genlab_id - return context - class ExtractionOrderDetailView(GenrequestNestedMixin, DetailView): model = ExtractionOrder diff --git a/src/staff/templatetags/order_tags.py b/src/staff/templatetags/order_tags.py index f1af8f01..640d5655 100644 --- a/src/staff/templatetags/order_tags.py +++ b/src/staff/templatetags/order_tags.py @@ -364,9 +364,9 @@ def contact_detail_table(order: Order) -> dict: fields = { "Samples owner of genetic project": order.genrequest.samples_owner, - "Contact person": order.contact_person, - "Contact Email": order.contact_email, - "Contacts for analysis results": result_contacts_html, + "Responsible genetic researcher": order.contact_person, + "Responsible genetic researcher email": order.contact_email, + "Contact name and email for analysis results": result_contacts_html, } return { From 4264a97f246483a5587a5c7a4aa5734992e9f8f2 Mon Sep 17 00:00:00 2001 From: aastabk Date: Fri, 25 Jul 2025 13:23:35 +0200 Subject: [PATCH 11/11] Make field mandatory --- src/genlab_bestilling/forms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/genlab_bestilling/forms.py b/src/genlab_bestilling/forms.py index 256b8176..d28798dd 100644 --- a/src/genlab_bestilling/forms.py +++ b/src/genlab_bestilling/forms.py @@ -397,7 +397,7 @@ def save(self, commit: bool = True) -> Model: if e.strip() ] - if names or emails: + if names and emails: if len(names) != len(emails): msg = "The number of names must match the number of emails." raise ValidationError(msg) @@ -421,13 +421,13 @@ def clean(self) -> None: contact_person_results = forms.CharField( label="Contact person(s) for results", help_text="Comma-separated list of names to contact with results", - required=False, + required=True, ) contact_email_results = forms.CharField( label="Contact email(s) for results", help_text="Comma-separated list of emails to contact with results (must match order of names)", # noqa: E501 - required=False, + required=True, ) field_order = [