From a201a66e2b0054f3374324eb4b32fbc3d38f83c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Cant=C3=B9?= Date: Wed, 19 Jun 2024 10:51:24 +0200 Subject: [PATCH] allow to pass context down into form collections --- src/genlab_bestilling/forms.py | 130 +++++++++--------- src/genlab_bestilling/formset_utils.py | 13 ++ .../analysisorder_detail.html | 33 ++++- .../genlab_bestilling/sample_form.html | 10 ++ src/genlab_bestilling/urls.py | 6 + src/genlab_bestilling/views.py | 60 ++++++-- 6 files changed, 175 insertions(+), 77 deletions(-) create mode 100644 src/genlab_bestilling/formset_utils.py create mode 100644 src/genlab_bestilling/templates/genlab_bestilling/sample_form.html diff --git a/src/genlab_bestilling/forms.py b/src/genlab_bestilling/forms.py index cb711e7..a4c93d5 100644 --- a/src/genlab_bestilling/forms.py +++ b/src/genlab_bestilling/forms.py @@ -1,11 +1,9 @@ from django import forms -from django.db.utils import IntegrityError -from django.forms.models import BaseModelForm, construct_instance -from formset.collection import FormCollection from formset.renderers.tailwind import FormRenderer from formset.utils import FormMixin -from formset.widgets import DualSortableSelector, Selectize +from formset.widgets import DateInput, DualSortableSelector, Selectize +from .formset_utils import ContextFormCollection from .models import ( AnalysisOrder, EquimentOrderQuantity, @@ -78,6 +76,17 @@ class Meta: class EquipmentOrderQuantityForm(forms.ModelForm): id = forms.IntegerField(required=False, widget=forms.widgets.HiddenInput) + def reinit(self, context): + self.order_id = context["order_id"] + + def save(self, commit=True): + obj = super().save(commit=False) + obj.order_id = self.order_id + if commit: + obj.save() + self.save_m2m() + return obj + class Meta: model = EquimentOrderQuantity fields = ("id", "equipment", "quantity") @@ -86,15 +95,11 @@ class Meta: } -class EquipmentQuantityCollection(FormCollection): +class EquipmentQuantityCollection(ContextFormCollection): min_siblings = 1 add_label = "Add equipment" equipments = EquipmentOrderQuantityForm() - def __init__(self, *args, order_id, **kwargs): - super().__init__(*args, **kwargs) - self.order_id = order_id - def retrieve_instance(self, data): if data := data.get("equipments"): try: @@ -105,53 +110,12 @@ def retrieve_instance(self, data): quantity=data.get("quantity"), ) - def construct_instance(self, instance=None): - """ - Override method from: - https://github.com/jrief/django-formset/blob/releases/1.4/formset/collection.py#L447 - """ - assert ( # noqa: S101 - self.is_valid() - ), f"Can not construct instance with invalid collection {self.__class__} object" - if self.has_many: - for valid_holders in self.valid_holders: - # first, handle holders which are forms - for _name, holder in valid_holders.items(): - if not isinstance(holder, BaseModelForm): - continue - if holder.marked_for_removal: - holder.instance.delete() - continue - construct_instance(holder, holder.instance) - if getattr(self, "related_field", None): - setattr(holder.instance, self.related_field, instance) - - # NOTE: only added this line to inject the order id - holder.instance.order_id = self.order_id - - try: - holder.save() - except (IntegrityError, ValueError) as error: - # some errors are caught only after attempting to save - holder._update_errors(error) - - # next, handle holders which are sub-collections - for _name, holder in valid_holders.items(): - if callable(getattr(holder, "construct_instance", None)): - holder.construct_instance(holder.instance) - else: - for name, holder in self.valid_holders.items(): - if callable(getattr(holder, "construct_instance", None)): - holder.construct_instance(instance) - elif isinstance(holder, BaseModelForm): - opts = holder._meta - holder.cleaned_data = self.cleaned_data[name] - holder.instance = instance - construct_instance(holder, instance, opts.fields, opts.exclude) - try: - holder.save() - except IntegrityError as error: - holder._update_errors(error) + def update_holder_instances(self, name, holder): + if name == "equipments": + holder.reinit(self.context) + + def update_instance_before_save(self, holder, context): + holder.instance.order_id = context["order_id"] class AnalysisOrderForm(FormMixin, forms.ModelForm): @@ -200,6 +164,24 @@ class Meta: class SampleForm(forms.ModelForm): id = forms.IntegerField(required=False, widget=forms.widgets.HiddenInput) + def reinit(self, context): + self.project = context["project"] + self.order_id = context["order_id"] + self.fields["type"].queryset = self.project.sample_types.all() + self.fields["species"].queryset = self.project.species.all() + self.fields["markers"].queryset = Marker.objects.filter( + species__projects__id=self.project.id + ) + + def save(self, commit=True): + obj = super().save(commit=False) + obj.order_id = self.order_id + obj.area = self.project.area + if commit: + obj.save() + self.save_m2m() + return obj + class Meta: model = Sample fields = ( @@ -211,18 +193,42 @@ class Meta: "date", "notes", "pop_id", - "area", "location", "volume", ) + widgets = { + "species": Selectize( + search_lookup="name_icontains", + ), + "location": Selectize(search_lookup="name_icontains"), + "type": Selectize(search_lookup="name_icontains"), + "markers": DualSortableSelector(search_lookup="name_icontains"), + "date": DateInput(), + } + -class SampleCollection(FormCollection): +class SamplesCollection(ContextFormCollection): min_siblings = 1 add_label = "Add sample" - sample = SampleForm() - legend = "Samples" + samples = SampleForm() + def update_holder_instances(self, name, holder): + if name == "samples": + holder.reinit(self.context) -class SamplesCollection(FormCollection): - samples = SampleCollection() + def retrieve_instance(self, data): + if data := data.get("samples"): + try: + return Sample.objects.get(id=data.get("id") or -1) + except (AttributeError, Sample.DoesNotExist, ValueError): + return Sample( + guid=data.get("guid"), + type_id=data.get("type"), + species_id=data.get("species"), + date=data.get("date"), + notes=data.get("notes"), + pop_id=data.get("pop_id"), + location_id=data.get("location"), + volume=data.get("volume"), + ) diff --git a/src/genlab_bestilling/formset_utils.py b/src/genlab_bestilling/formset_utils.py new file mode 100644 index 0000000..fc17b0c --- /dev/null +++ b/src/genlab_bestilling/formset_utils.py @@ -0,0 +1,13 @@ +from formset.collection import FormCollection + + +class ContextFormCollection(FormCollection): + def __init__(self, *args, context=None, **kwargs): + super().__init__(*args, **kwargs) + self.context = context or {} + + for name, holder in self.declared_holders.items(): + self.update_holder_instances(name, holder) + + def update_holder_instances(self, name, holder): + pass diff --git a/src/genlab_bestilling/templates/genlab_bestilling/analysisorder_detail.html b/src/genlab_bestilling/templates/genlab_bestilling/analysisorder_detail.html index 327fff1..7b9dfdd 100644 --- a/src/genlab_bestilling/templates/genlab_bestilling/analysisorder_detail.html +++ b/src/genlab_bestilling/templates/genlab_bestilling/analysisorder_detail.html @@ -4,7 +4,15 @@ {% block content %} - + {% fragment as table_header %} + {% #table-cell header=True %}GUID{% /table-cell %} + {% #table-cell header=True %}Type{% /table-cell %} + {% #table-cell header=True %}Species{% /table-cell %} + {% #table-cell header=True %}Markers{% /table-cell %} + {% #table-cell header=True %}Location{% /table-cell %} + {% #table-cell header=True %}Date{% /table-cell %} + {% #table-cell header=True %}Volume{% /table-cell %} + {% endfragment %}

Order #{{ object.id }} - {{ object.name }}

back @@ -13,9 +21,28 @@

Order #{{ object.id }} - {{ object.name }}

{% object-detail object=object %} -
Samples
+
Samples
+ + {% #table headers=table_header %} + {% for oq in object.samples.all %} + + {% #table-cell %}{{ oq.guid }}{% /table-cell %} + {% #table-cell %}{{ oq.type }}{% /table-cell %} + {% #table-cell %}{{ oq.species }}{% /table-cell %} + {% #table-cell %}{{ oq.markers.all|join:', ' }}{% /table-cell %} + {% #table-cell %}{{ oq.location }}{% /table-cell %} + {% #table-cell %}{{ oq.date }}{% /table-cell %} + {% #table-cell %}{{ oq.volume }}{% /table-cell %} + + {% empty %} + + No Samples found + + {% endfor %} + {% /table %} + -
+
Edit
diff --git a/src/genlab_bestilling/templates/genlab_bestilling/sample_form.html b/src/genlab_bestilling/templates/genlab_bestilling/sample_form.html new file mode 100644 index 0000000..8eda500 --- /dev/null +++ b/src/genlab_bestilling/templates/genlab_bestilling/sample_form.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} +{% load core %} + +{% block content %} +

Edit {{ view.model|verbose_name }}

+
+ back +
+ {% formset endpoint=request.path csrf_token=csrf_token form_collection=form_collection %} +{% endblock %} diff --git a/src/genlab_bestilling/urls.py b/src/genlab_bestilling/urls.py index c9db531..db1ad59 100644 --- a/src/genlab_bestilling/urls.py +++ b/src/genlab_bestilling/urls.py @@ -13,6 +13,7 @@ ProjectListView, ProjectOrderListView, ProjectUpdateView, + SamplesUpdateView, ) appname = "genlab_bestilling" @@ -77,4 +78,9 @@ AnalysisOrderEditView.as_view(), name="project-analysis-update", ), + path( + "projects//orders/analysis//samples/", + SamplesUpdateView.as_view(), + name="project-analysis-samples", + ), ] diff --git a/src/genlab_bestilling/views.py b/src/genlab_bestilling/views.py index 353dbdd..61e1277 100644 --- a/src/genlab_bestilling/views.py +++ b/src/genlab_bestilling/views.py @@ -18,8 +18,16 @@ EquipmentOrderForm, EquipmentQuantityCollection, ProjectForm, + SamplesCollection, +) +from .models import ( + AnalysisOrder, + EquimentOrderQuantity, + EquipmentOrder, + Order, + Project, + Sample, ) -from .models import AnalysisOrder, EquimentOrderQuantity, EquipmentOrder, Order, Project from .tables import OrderTable, ProjectTable @@ -170,24 +178,18 @@ def get_success_url(self): ) -class SamplesView(LoginRequiredMixin, DetailView): - model = AnalysisOrder - template_name = "genlab_bestilling/samples.html" - - def get_queryset(self) -> QuerySet[Any]: - self.project = Project.objects.get(id=self.kwargs["project_id"]) - return super().get_queryset().filter(project_id=self.project.id) - - class EquipmentOrderQuantityUpdateView(ProjectNestedMixin, BulkEditCollectionView): collection_class = EquipmentQuantityCollection template_name = "genlab_bestilling/equipmentorderquantity_form.html" model = EquimentOrderQuantity project_id_accessor = "order__project_id" + def get_queryset(self) -> QuerySet[Any]: + return super().get_queryset().filter(order_id=self.kwargs["pk"]) + def get_collection_kwargs(self): kwargs = super().get_collection_kwargs() - kwargs["order_id"] = self.kwargs["pk"] + kwargs["context"] = {"order_id": self.kwargs["pk"]} return kwargs def get_success_url(self): @@ -199,5 +201,39 @@ def get_success_url(self): def get_initial(self): collection_class = self.get_collection_class() queryset = self.get_queryset() - initial = collection_class(order_id=self.kwargs["pk"]).models_to_list(queryset) + initial = collection_class( + context={"order_id": self.kwargs["pk"]} + ).models_to_list(queryset) + return initial + + +class SamplesUpdateView(ProjectNestedMixin, BulkEditCollectionView): + collection_class = SamplesCollection + template_name = "genlab_bestilling/sample_form.html" + model = Sample + project_id_accessor = "order__project_id" + + def get_queryset(self) -> QuerySet[Any]: + return super().get_queryset().filter(order_id=self.kwargs["pk"]) + + def get_collection_kwargs(self): + kwargs = super().get_collection_kwargs() + kwargs["context"] = { + "order_id": self.kwargs["pk"], + "project": self.project, + } + return kwargs + + def get_success_url(self): + return reverse( + "project-analysis-detail", + kwargs={"project_id": self.project.id, "pk": self.kwargs["pk"]}, + ) + + def get_initial(self): + collection_class = self.get_collection_class() + queryset = self.get_queryset() + initial = collection_class( + context={"order_id": self.kwargs["pk"], "project": self.project} + ).models_to_list(queryset) return initial