From 5f7a9fc6004ffa7da1ac3fb66e17de14a93fa9ec Mon Sep 17 00:00:00 2001 From: imperosol Date: Thu, 5 Mar 2026 19:07:42 +0100 Subject: [PATCH] Price formset in the Product edit page --- core/api.py | 2 +- counter/forms.py | 50 ++++++++--- counter/templates/counter/product_form.jinja | 92 +++++++++++++++++++- 3 files changed, 126 insertions(+), 18 deletions(-) diff --git a/core/api.py b/core/api.py index 2f2c0fb1..08aefa6f 100644 --- a/core/api.py +++ b/core/api.py @@ -123,7 +123,7 @@ class GroupController(ControllerBase): ) @paginate(PageNumberPaginationExtra, page_size=50) def search_group(self, search: Annotated[str, MinLen(1)]): - return Group.objects.filter(name__icontains=search).values() + return Group.objects.filter(name__icontains=search).order_by("name").values() DepthValue = Annotated[int, Ge(0), Le(10)] diff --git a/counter/forms.py b/counter/forms.py index a0ba39e5..86862e10 100644 --- a/counter/forms.py +++ b/counter/forms.py @@ -1,6 +1,7 @@ import json import math import uuid +from collections import defaultdict from datetime import date, datetime, timezone from dateutil.relativedelta import relativedelta @@ -35,6 +36,7 @@ from counter.models import ( Customer, Eticket, InvoiceCall, + Price, Product, ProductFormula, Refilling, @@ -292,7 +294,21 @@ ScheduledProductActionFormSet = forms.modelformset_factory( can_delete=True, can_delete_extra=False, extra=0, +) + + +ProductPriceFormSet = forms.inlineformset_factory( + parent_model=Product, + model=Price, + fields=["amount", "label", "groups", "is_always_shown"], + widgets={ + "groups": AutoCompleteSelectMultipleGroup, + "is_always_shown": forms.CheckboxInput(attrs={"class": "switch"}), + }, + absolute_max=None, + can_delete_extra=False, min_num=1, + extra=0, ) @@ -307,10 +323,7 @@ class ProductForm(forms.ModelForm): "description", "product_type", "code", - "buying_groups", "purchase_price", - "selling_price", - "special_selling_price", "icon", "club", "limit_age", @@ -325,8 +338,8 @@ class ProductForm(forms.ModelForm): } widgets = { "product_type": AutoCompleteSelect, - "buying_groups": AutoCompleteSelectMultipleGroup, "club": AutoCompleteSelectClub, + "tray": forms.CheckboxInput(attrs={"class": "switch"}), } counters = forms.ModelMultipleChoiceField( @@ -336,14 +349,18 @@ class ProductForm(forms.ModelForm): queryset=Counter.objects.all(), ) - def __init__(self, *args, instance=None, **kwargs): - super().__init__(*args, instance=instance, **kwargs) + def __init__(self, *args, prefix: str | None = None, instance=None, **kwargs): + super().__init__(*args, prefix=prefix, instance=instance, **kwargs) + self.fields["name"].widget.attrs["autofocus"] = "autofocus" if self.instance.id: self.fields["counters"].initial = self.instance.counters.all() if hasattr(self.instance, "formula"): self.formula_init(self.instance.formula) + self.price_formset = ProductPriceFormSet( + *args, instance=self.instance, prefix="price", **kwargs + ) self.action_formset = ScheduledProductActionFormSet( - *args, product=self.instance, **kwargs + *args, product=self.instance, prefix="action", **kwargs ) def formula_init(self, formula: ProductFormula): @@ -366,20 +383,25 @@ class ProductForm(forms.ModelForm): self.fields[key].validators.append(MaxValueValidator(price)) def is_valid(self): - return super().is_valid() and self.action_formset.is_valid() + return ( + super().is_valid() + and self.price_formset.is_valid() + and self.action_formset.is_valid() + ) def save(self, *args, **kwargs) -> Product: product = super().save(*args, **kwargs) product.counters.set(self.cleaned_data["counters"]) + # if it's a creation, the product given in the formset + # wasn't a persisted instance. + # So if we tried to persist the related objects in the current state, + # they would be linked to no product, thus be completely useless + # To make it work, we have to replace + # the initial product with a persisted one for form in self.action_formset: - # if it's a creation, the product given in the formset - # wasn't a persisted instance. - # So if we tried to persist the scheduled actions in the current state, - # they would be linked to no product, thus be completely useless - # To make it work, we have to replace - # the initial product with a persisted one form.set_product(product) self.action_formset.save() + self.price_formset.save() return product diff --git a/counter/templates/counter/product_form.jinja b/counter/templates/counter/product_form.jinja index ffd751c0..190670ad 100644 --- a/counter/templates/counter/product_form.jinja +++ b/counter/templates/counter/product_form.jinja @@ -39,6 +39,44 @@ {% endmacro %} +{% macro price_form(form) %} +
+ {{ form.non_field_errors() }} +
+
{{ form.amount.as_field_group() }}
+
+ {{ form.label.errors }} + + {{ form.label }} + {{ form.label.help_text }} +
+
{{ form.groups.as_field_group() }}
+
+
+
+ {{ form.is_always_shown.as_field_group() }} +
+
+ {%- if form.DELETE -%} +
+ {{ form.DELETE.as_field_group() }} +
+ {%- else -%} +
+ + {%- endif -%} + {%- for field in form.hidden_fields() -%} + {{ field }} + {%- endfor -%} +
+
+{% endmacro %} + {% block content %} {% if object %}

{% trans name=object %}Edit product {{ name }}{% endtrans %}

@@ -49,7 +87,54 @@ {% endif %}
{% csrf_token %} - {{ form.as_p() }} + {{ form.non_field_errors() }} +
+
{{ form.name.as_field_group() }}
+
{{ form.code.as_field_group() }}
+
+
+
{{ form.description.as_field_group() }}
+
+
+
{{ form.club.as_field_group() }}
+
{{ form.product_type.as_field_group() }}
+
+
{{ form.icon.as_field_group() }}
+
{{ form.purchase_price.as_field_group() }}
+
+
{{ form.limit_age.as_field_group() }}
+
+
+
+ {{ form.tray }} +
+ {{ form.tray.label_tag() }} + {{ form.tray.help_text }} +
+
+
+
{{ form.counters.as_field_group() }}
+ +

{% trans %}Prices{% endtrans %}

+ +
+ {{ form.price_formset.management_form }} +
+ {%- for form in form.price_formset.forms -%} +
+ {{ price_form(form) }} +
+ {%- endfor -%} +
+ + +

@@ -64,7 +149,7 @@

-
+
{{ form.action_formset.management_form }}
{%- for f in form.action_formset.forms -%} @@ -78,6 +163,7 @@ {% trans %}Add action{% endtrans %}
+
{{ form.archived.as_field_group() }}

-{% endblock %} \ No newline at end of file +{% endblock %}