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 %}
{% 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 %}
+
+ {% 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