Skip to content

Commit

Permalink
allow to pass context down into form collections
Browse files Browse the repository at this point in the history
  • Loading branch information
nicokant committed Jun 19, 2024
1 parent 00498fc commit a201a66
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 77 deletions.
130 changes: 68 additions & 62 deletions src/genlab_bestilling/forms.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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")
Expand All @@ -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:
Expand All @@ -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):
Expand Down Expand Up @@ -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 = (
Expand All @@ -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"),
)
13 changes: 13 additions & 0 deletions src/genlab_bestilling/formset_utils.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
<h3 class="text-4xl mb-5">Order #{{ object.id }} - {{ object.name }}</h3>
<div class="flex gap-5 mb-5">
<a class="btn bg-primary" href="{% url 'project-order-list' project_id=project.id %}"><i class="fas fa-arrow-left"></i> back</a>
Expand All @@ -13,9 +21,28 @@ <h3 class="text-4xl mb-5">Order #{{ object.id }} - {{ object.name }}</h3>
{% object-detail object=object %}


<h5 class="text-2xl mb-5">Samples</h5>
<h5 class="text-2xl my-5">Samples</h5>

{% #table headers=table_header %}
{% for oq in object.samples.all %}
<tr>
{% #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 %}
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center font-bold">No Samples found</td>
</tr>
{% endfor %}
{% /table %}


<div class="flex gap-5">
<div class="flex gap-5 my-5">
<a class="btn bg-primary" href="{% url 'project-analysis-update' project_id=object.project_id pk=object.id %}">Edit</a>
<button class="btn bg-secondary">Confirm Order</button>
</div>
Expand Down
10 changes: 10 additions & 0 deletions src/genlab_bestilling/templates/genlab_bestilling/sample_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% extends "base.html" %}
{% load core %}

{% block content %}
<h3 class="text-4xl mb-5">Edit {{ view.model|verbose_name }}</h3>
<div class="flex gap-5 mb-5">
<a class="btn bg-primary" href="{% url 'project-analysis-detail' project_id=project.id pk=view.kwargs.pk %}"><i class="fas fa-arrow-left"></i> back</a>
</div>
{% formset endpoint=request.path csrf_token=csrf_token form_collection=form_collection %}
{% endblock %}
6 changes: 6 additions & 0 deletions src/genlab_bestilling/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
ProjectListView,
ProjectOrderListView,
ProjectUpdateView,
SamplesUpdateView,
)

appname = "genlab_bestilling"
Expand Down Expand Up @@ -77,4 +78,9 @@
AnalysisOrderEditView.as_view(),
name="project-analysis-update",
),
path(
"projects/<int:project_id>/orders/analysis/<int:pk>/samples/",
SamplesUpdateView.as_view(),
name="project-analysis-samples",
),
]
60 changes: 48 additions & 12 deletions src/genlab_bestilling/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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):
Expand All @@ -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

0 comments on commit a201a66

Please sign in to comment.