diff --git a/src/genlab_bestilling/forms.py b/src/genlab_bestilling/forms.py index 472c69f..cb711e7 100644 --- a/src/genlab_bestilling/forms.py +++ b/src/genlab_bestilling/forms.py @@ -1,4 +1,6 @@ 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 @@ -87,19 +89,70 @@ class Meta: class EquipmentQuantityCollection(FormCollection): min_siblings = 1 add_label = "Add equipment" - equipment = EquipmentOrderQuantityForm() - related_field = "order" - legend = "Equipments required" + 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("equipment"): + if data := data.get("equipments"): try: - return self.instance.equipments.get(id=data.get("id") or -1) + return EquimentOrderQuantity.objects.get(id=data.get("id") or -1) except (AttributeError, EquimentOrderQuantity.DoesNotExist, ValueError): return EquimentOrderQuantity( - quantity=data.get("quantity"), order=self.instance + equipment_id=data.get("equipment"), + 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) + class AnalysisOrderForm(FormMixin, forms.ModelForm): default_renderer = FormRenderer(field_css_classes="mb-3") diff --git a/src/genlab_bestilling/templates/genlab_bestilling/equipmentorder_detail.html b/src/genlab_bestilling/templates/genlab_bestilling/equipmentorder_detail.html index 2c8f916..e1563c3 100644 --- a/src/genlab_bestilling/templates/genlab_bestilling/equipmentorder_detail.html +++ b/src/genlab_bestilling/templates/genlab_bestilling/equipmentorder_detail.html @@ -18,7 +18,7 @@

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

{% object-detail object=object %} -
Equipment
+
Requested Equipment
{% #table headers=table_header %} {% for oq in object.equipments.all %} @@ -26,11 +26,16 @@
Equipment
{% #table-cell %}{{ oq.unit }}{% /table-cell %} {% #table-cell %}{{ oq.quantity }}{% /table-cell %} + {% empty %} + + No Equipment requested + {% endfor %} {% /table %}
Edit + Edit requested equipment
{% endblock %} diff --git a/src/genlab_bestilling/templates/genlab_bestilling/equipmentorderquantity_form.html b/src/genlab_bestilling/templates/genlab_bestilling/equipmentorderquantity_form.html new file mode 100644 index 0000000..ae4d2d5 --- /dev/null +++ b/src/genlab_bestilling/templates/genlab_bestilling/equipmentorderquantity_form.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} +{% load core %} + +{% block content %} +

{% if object.id %}{{ object }}{% else %}Create {{ view.model|verbose_name }}{% endif %}

+
+ 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 ee665fa..c9db531 100644 --- a/src/genlab_bestilling/urls.py +++ b/src/genlab_bestilling/urls.py @@ -7,6 +7,7 @@ EquipmentOrderCreateView, EquipmentOrderDetailView, EquipmentOrderEditView, + EquipmentOrderQuantityUpdateView, ProjectCreateView, ProjectDetailView, ProjectListView, @@ -56,6 +57,11 @@ EquipmentOrderEditView.as_view(), name="project-equipment-update", ), + path( + "projects//orders/equipment//quantity/", + EquipmentOrderQuantityUpdateView.as_view(), + name="project-equipment-quantity-update", + ), path( "projects//orders/analysis/create/", AnalysisOrderCreateView.as_view(), diff --git a/src/genlab_bestilling/views.py b/src/genlab_bestilling/views.py index 6142107..353dbdd 100644 --- a/src/genlab_bestilling/views.py +++ b/src/genlab_bestilling/views.py @@ -7,6 +7,7 @@ from django.views.generic import CreateView, DetailView, UpdateView from django_tables2.views import SingleTableView from formset.views import ( + BulkEditCollectionView, FormViewMixin, IncompleteSelectResponseMixin, ) @@ -15,9 +16,10 @@ from .forms import ( AnalysisOrderForm, EquipmentOrderForm, + EquipmentQuantityCollection, ProjectForm, ) -from .models import AnalysisOrder, EquipmentOrder, Order, Project +from .models import AnalysisOrder, EquimentOrderQuantity, EquipmentOrder, Order, Project from .tables import OrderTable, ProjectTable @@ -71,6 +73,8 @@ class ProjectCreateView(FormsetCreateView): class ProjectNestedMixin(LoginRequiredMixin): + project_id_accessor = "project_id" + def get_project(self): return Project.objects.get(id=self.kwargs["project_id"]) @@ -83,7 +87,8 @@ def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self) -> QuerySet[Any]: - return super().get_queryset().filter(project_id=self.project.id) + kwargs = {self.project_id_accessor: self.project.id} + return super().get_queryset().filter(**kwargs) def get_context_data(self, **kwargs: Any) -> dict[str, Any]: ctx = super().get_context_data(**kwargs) @@ -118,7 +123,7 @@ class EquipmentOrderEditView( def get_success_url(self): return reverse( - "project-analysis-detail", + "project-order-detail", kwargs={"project_id": self.project.id, "pk": self.object.id}, ) @@ -132,7 +137,7 @@ class EquipmentOrderCreateView( def get_success_url(self): return reverse( - "project-analysis-detail", + "project-order-detail", kwargs={"project_id": self.project.id, "pk": self.object.id}, ) @@ -172,3 +177,27 @@ class SamplesView(LoginRequiredMixin, DetailView): 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_collection_kwargs(self): + kwargs = super().get_collection_kwargs() + kwargs["order_id"] = self.kwargs["pk"] + return kwargs + + def get_success_url(self): + return reverse( + "project-equipment-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(order_id=self.kwargs["pk"]).models_to_list(queryset) + return initial