From 483670e79863afa6f616d2a0643619c687a6d8fc Mon Sep 17 00:00:00 2001 From: imperosol Date: Sun, 15 Dec 2024 18:55:09 +0100 Subject: [PATCH] Make `ProductType` an `OrderedModel` --- counter/admin.py | 2 +- counter/api.py | 4 +- ...0028_alter_producttype_comment_and_more.py | 62 +++++++ counter/models.py | 17 +- counter/views/admin.py | 6 +- eboutic/models.py | 2 +- eboutic/templates/eboutic/eboutic_main.jinja | 2 +- locale/fr/LC_MESSAGES/django.po | 172 +++++++++--------- 8 files changed, 167 insertions(+), 100 deletions(-) create mode 100644 counter/migrations/0028_alter_producttype_comment_and_more.py diff --git a/counter/admin.py b/counter/admin.py index b3e6a91a..5dc795f2 100644 --- a/counter/admin.py +++ b/counter/admin.py @@ -129,7 +129,7 @@ class PermanencyAdmin(SearchModelAdmin): @admin.register(ProductType) class ProductTypeAdmin(admin.ModelAdmin): - list_display = ("name", "priority") + list_display = ("name", "order") @admin.register(CashRegisterSummary) diff --git a/counter/api.py b/counter/api.py index 7c181aa0..8c37d0ac 100644 --- a/counter/api.py +++ b/counter/api.py @@ -71,7 +71,7 @@ class ProductController(ControllerBase): def search_products(self, filters: Query[ProductFilterSchema]): return filters.filter( Product.objects.order_by( - F("product_type__priority").desc(nulls_last=True), + F("product_type__order").asc(nulls_last=True), "product_type", "name", ).values() @@ -95,7 +95,7 @@ class ProductController(ControllerBase): .prefetch_related("buying_groups") .select_related("product_type") .order_by( - F("product_type__priority").desc(nulls_last=True), + F("product_type__order").asc(nulls_last=True), "product_type", "name", ) diff --git a/counter/migrations/0028_alter_producttype_comment_and_more.py b/counter/migrations/0028_alter_producttype_comment_and_more.py new file mode 100644 index 00000000..f7fabb83 --- /dev/null +++ b/counter/migrations/0028_alter_producttype_comment_and_more.py @@ -0,0 +1,62 @@ +# Generated by Django 4.2.17 on 2024-12-15 17:53 + +from django.db import migrations, models +from django.db.migrations.state import StateApps + + +def move_priority_to_order(apps: StateApps, schema_editor): + """Migrate the previous homemade `priority` to `OrderedModel.order`. + + `priority` was a system were click managers set themselves the priority + of a ProductType. + The higher the priority, the higher it was to be displayed in the eboutic. + Multiple product types could share the same priority, in which + case they were ordered by alphabetic order. + + The new field is unique per object, and works in the other way : + the nearer from 0, the higher it should appear. + """ + ProductType = apps.get_model("counter", "ProductType") + product_types = list(ProductType.objects.order_by("-priority", "name")) + for order, product_type in enumerate(product_types): + product_type.order = order + ProductType.objects.bulk_update(product_types, ["order"]) + + +class Migration(migrations.Migration): + dependencies = [("counter", "0027_alter_refilling_payment_method")] + + operations = [ + migrations.AlterField( + model_name="producttype", + name="comment", + field=models.TextField( + default="", + help_text="A text that will be shown on the eboutic.", + verbose_name="comment", + ), + ), + migrations.AlterField( + model_name="producttype", + name="description", + field=models.TextField(default="", verbose_name="description"), + ), + migrations.AlterModelOptions( + name="producttype", + options={"ordering": ["order"], "verbose_name": "product type"}, + ), + migrations.AddField( + model_name="producttype", + name="order", + field=models.PositiveIntegerField( + db_index=True, default=0, editable=False, verbose_name="order" + ), + preserve_default=False, + ), + migrations.RunPython( + move_priority_to_order, + reverse_code=migrations.RunPython.noop, + elidable=True, + ), + migrations.RemoveField(model_name="producttype", name="priority"), + ] diff --git a/counter/models.py b/counter/models.py index 087baffc..b55207fb 100644 --- a/counter/models.py +++ b/counter/models.py @@ -35,6 +35,7 @@ from django.utils import timezone from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from django_countries.fields import CountryField +from ordered_model.models import OrderedModel from phonenumber_field.modelfields import PhoneNumberField from accounting.models import CurrencyField @@ -289,26 +290,26 @@ class AccountDump(models.Model): ) -class ProductType(models.Model): +class ProductType(OrderedModel): """A product type. Useful only for categorizing. """ name = models.CharField(_("name"), max_length=30) - description = models.TextField(_("description"), null=True, blank=True) - comment = models.TextField(_("comment"), null=True, blank=True) + description = models.TextField(_("description"), default="") + comment = models.TextField( + _("comment"), + default="", + help_text=_("A text that will be shown on the eboutic."), + ) icon = ResizedImageField( height=70, force_format="WEBP", upload_to="products", null=True, blank=True ) - # priority holds no real backend logic but helps to handle the order in which - # the items are to be shown to the user - priority = models.PositiveIntegerField(default=0) - class Meta: verbose_name = _("product type") - ordering = ["-priority", "name"] + ordering = ["order"] def __str__(self): return self.name diff --git a/counter/views/admin.py b/counter/views/admin.py index fbf466b3..c1f5c63b 100644 --- a/counter/views/admin.py +++ b/counter/views/admin.py @@ -109,7 +109,7 @@ class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView """A create view for the admins.""" model = ProductType - fields = ["name", "description", "comment", "icon", "priority"] + fields = ["name", "description", "comment", "icon"] template_name = "core/create.jinja" current_tab = "products" @@ -119,7 +119,7 @@ class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): model = ProductType template_name = "core/edit.jinja" - fields = ["name", "description", "comment", "icon", "priority"] + fields = ["name", "description", "comment", "icon"] pk_url_kwarg = "type_id" current_tab = "products" @@ -129,7 +129,7 @@ class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): queryset = Product.objects.values("id", "name", "code", "product_type__name") template_name = "counter/product_list.jinja" ordering = [ - F("product_type__priority").desc(nulls_last=True), + F("product_type__order").asc(nulls_last=True), "product_type", "name", ] diff --git a/eboutic/models.py b/eboutic/models.py index 7ec9deef..7f7282b1 100644 --- a/eboutic/models.py +++ b/eboutic/models.py @@ -36,7 +36,7 @@ def get_eboutic_products(user: User) -> list[Product]: .products.filter(product_type__isnull=False) .filter(archived=False) .filter(limit_age__lte=user.age) - .annotate(priority=F("product_type__priority")) + .annotate(order=F("product_type__order")) .annotate(category=F("product_type__name")) .annotate(category_comment=F("product_type__comment")) .prefetch_related("buying_groups") # <-- used in `Product.can_be_sold_to` diff --git a/eboutic/templates/eboutic/eboutic_main.jinja b/eboutic/templates/eboutic/eboutic_main.jinja index bf5d7556..b71eb434 100644 --- a/eboutic/templates/eboutic/eboutic_main.jinja +++ b/eboutic/templates/eboutic/eboutic_main.jinja @@ -88,7 +88,7 @@ {% endif %} - {% for priority_groups in products|groupby('priority')|reverse %} + {% for priority_groups in products|groupby('order') %} {% for category, items in priority_groups.list|groupby('category') %} {% if items|count > 0 %}
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 1f74ddaa..20ddf28a 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-17 10:53+0100\n" +"POT-Creation-Date: 2024-12-17 13:09+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -18,8 +18,8 @@ msgstr "" #: accounting/models.py:62 accounting/models.py:101 accounting/models.py:132 #: accounting/models.py:190 club/models.py:55 com/models.py:274 -#: com/models.py:293 counter/models.py:298 counter/models.py:329 -#: counter/models.py:480 forum/models.py:60 launderette/models.py:29 +#: com/models.py:293 counter/models.py:299 counter/models.py:330 +#: counter/models.py:481 forum/models.py:60 launderette/models.py:29 #: launderette/models.py:80 launderette/models.py:116 msgid "name" msgstr "nom" @@ -65,8 +65,8 @@ msgid "account number" msgstr "numéro de compte" #: accounting/models.py:107 accounting/models.py:136 club/models.py:345 -#: com/models.py:74 com/models.py:259 com/models.py:299 counter/models.py:358 -#: counter/models.py:482 trombi/models.py:209 +#: com/models.py:74 com/models.py:259 com/models.py:299 counter/models.py:359 +#: counter/models.py:483 trombi/models.py:209 msgid "club" msgstr "club" @@ -87,12 +87,12 @@ msgstr "Compte club" msgid "%(club_account)s on %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s" -#: accounting/models.py:188 club/models.py:351 counter/models.py:965 +#: accounting/models.py:188 club/models.py:351 counter/models.py:966 #: election/models.py:16 launderette/models.py:165 msgid "start date" msgstr "date de début" -#: accounting/models.py:189 club/models.py:352 counter/models.py:966 +#: accounting/models.py:189 club/models.py:352 counter/models.py:967 #: election/models.py:17 msgid "end date" msgstr "date de fin" @@ -105,8 +105,8 @@ msgstr "est fermé" msgid "club account" msgstr "compte club" -#: accounting/models.py:199 accounting/models.py:255 counter/models.py:92 -#: counter/models.py:683 +#: accounting/models.py:199 accounting/models.py:255 counter/models.py:93 +#: counter/models.py:684 msgid "amount" msgstr "montant" @@ -128,18 +128,18 @@ msgstr "classeur" #: accounting/models.py:256 core/models.py:956 core/models.py:1467 #: core/models.py:1512 core/models.py:1541 core/models.py:1565 -#: counter/models.py:693 counter/models.py:797 counter/models.py:1001 +#: counter/models.py:694 counter/models.py:798 counter/models.py:1002 #: eboutic/models.py:57 eboutic/models.py:193 forum/models.py:312 #: forum/models.py:413 msgid "date" msgstr "date" -#: accounting/models.py:257 counter/models.py:300 counter/models.py:1002 +#: accounting/models.py:257 counter/models.py:302 counter/models.py:1003 #: pedagogy/models.py:208 msgid "comment" msgstr "commentaire" -#: accounting/models.py:259 counter/models.py:695 counter/models.py:799 +#: accounting/models.py:259 counter/models.py:696 counter/models.py:800 #: subscription/models.py:56 msgid "payment method" msgstr "méthode de paiement" @@ -166,7 +166,7 @@ msgstr "type comptable" #: accounting/models.py:294 accounting/models.py:429 accounting/models.py:460 #: accounting/models.py:492 core/models.py:1540 core/models.py:1566 -#: counter/models.py:763 +#: counter/models.py:764 msgid "label" msgstr "étiquette" @@ -264,7 +264,7 @@ msgstr "" "Vous devez fournir soit un type comptable simplifié ou un type comptable " "standard" -#: accounting/models.py:421 counter/models.py:339 pedagogy/models.py:41 +#: accounting/models.py:421 counter/models.py:340 pedagogy/models.py:41 msgid "code" msgstr "code" @@ -1041,7 +1041,7 @@ msgstr "Vous ne pouvez pas faire de boucles dans les clubs" msgid "A club with that unix_name already exists" msgstr "Un club avec ce nom UNIX existe déjà." -#: club/models.py:337 counter/models.py:956 counter/models.py:992 +#: club/models.py:337 counter/models.py:957 counter/models.py:993 #: eboutic/models.py:53 eboutic/models.py:189 election/models.py:183 #: launderette/models.py:130 launderette/models.py:184 sas/models.py:273 #: trombi/models.py:205 @@ -1053,8 +1053,8 @@ msgstr "nom d'utilisateur" msgid "role" msgstr "rôle" -#: club/models.py:359 core/models.py:90 counter/models.py:299 -#: counter/models.py:330 election/models.py:13 election/models.py:115 +#: club/models.py:359 core/models.py:90 counter/models.py:300 +#: counter/models.py:331 election/models.py:13 election/models.py:115 #: election/models.py:188 forum/models.py:61 forum/models.py:245 msgid "description" msgstr "description" @@ -2501,7 +2501,7 @@ msgstr "Forum" msgid "Gallery" msgstr "Photos" -#: core/templates/core/base/navbar.jinja:22 counter/models.py:490 +#: core/templates/core/base/navbar.jinja:22 counter/models.py:491 #: counter/templates/counter/counter_list.jinja:11 #: eboutic/templates/eboutic/eboutic_main.jinja:4 #: eboutic/templates/eboutic/eboutic_main.jinja:22 @@ -3607,13 +3607,13 @@ msgstr "Chèque" msgid "Cash" msgstr "Espèces" -#: counter/apps.py:30 counter/models.py:801 sith/settings.py:415 +#: counter/apps.py:30 counter/models.py:802 sith/settings.py:415 #: sith/settings.py:420 msgid "Credit card" msgstr "Carte bancaire" -#: counter/apps.py:36 counter/models.py:506 counter/models.py:962 -#: counter/models.py:998 launderette/models.py:32 +#: counter/apps.py:36 counter/models.py:507 counter/models.py:963 +#: counter/models.py:999 launderette/models.py:32 msgid "counter" msgstr "comptoir" @@ -3637,180 +3637,184 @@ msgstr "Vidange de votre compte AE" msgid "Ecocup regularization" msgstr "Régularization des ecocups" -#: counter/models.py:91 +#: counter/models.py:92 msgid "account id" msgstr "numéro de compte" -#: counter/models.py:93 +#: counter/models.py:94 msgid "recorded product" msgstr "produits consignés" -#: counter/models.py:98 +#: counter/models.py:99 msgid "customer" msgstr "client" -#: counter/models.py:99 +#: counter/models.py:100 msgid "customers" msgstr "clients" -#: counter/models.py:111 counter/views/click.py:68 +#: counter/models.py:112 counter/views/click.py:68 msgid "Not enough money" msgstr "Solde insuffisant" -#: counter/models.py:197 +#: counter/models.py:198 msgid "First name" msgstr "Prénom" -#: counter/models.py:198 +#: counter/models.py:199 msgid "Last name" msgstr "Nom de famille" -#: counter/models.py:199 +#: counter/models.py:200 msgid "Address 1" msgstr "Adresse 1" -#: counter/models.py:200 +#: counter/models.py:201 msgid "Address 2" msgstr "Adresse 2" -#: counter/models.py:201 +#: counter/models.py:202 msgid "Zip code" msgstr "Code postal" -#: counter/models.py:202 +#: counter/models.py:203 msgid "City" msgstr "Ville" -#: counter/models.py:203 +#: counter/models.py:204 msgid "Country" msgstr "Pays" -#: counter/models.py:211 +#: counter/models.py:212 msgid "Phone number" msgstr "Numéro de téléphone" -#: counter/models.py:253 +#: counter/models.py:254 msgid "When the mail warning that the account was about to be dumped was sent." msgstr "Quand le mail d'avertissement de la vidange du compte a été envoyé." -#: counter/models.py:258 +#: counter/models.py:259 msgid "Set this to True if the warning mail received an error" msgstr "Mettre à True si le mail a reçu une erreur" -#: counter/models.py:265 +#: counter/models.py:266 msgid "The operation that emptied the account." msgstr "L'opération qui a vidé le compte." -#: counter/models.py:310 counter/models.py:334 +#: counter/models.py:304 +msgid "A text that will be shown on the eboutic." +msgstr "Un texte qui sera affiché sur l'eboutic." + +#: counter/models.py:311 counter/models.py:335 msgid "product type" msgstr "type du produit" -#: counter/models.py:341 +#: counter/models.py:342 msgid "purchase price" msgstr "prix d'achat" -#: counter/models.py:342 +#: counter/models.py:343 msgid "Initial cost of purchasing the product" msgstr "Coût initial d'achat du produit" -#: counter/models.py:344 +#: counter/models.py:345 msgid "selling price" msgstr "prix de vente" -#: counter/models.py:346 +#: counter/models.py:347 msgid "special selling price" msgstr "prix de vente spécial" -#: counter/models.py:347 +#: counter/models.py:348 msgid "Price for barmen during their permanence" msgstr "Prix pour les barmen durant leur permanence" -#: counter/models.py:355 +#: counter/models.py:356 msgid "icon" msgstr "icône" -#: counter/models.py:360 +#: counter/models.py:361 msgid "limit age" msgstr "âge limite" -#: counter/models.py:361 +#: counter/models.py:362 msgid "tray price" msgstr "prix plateau" -#: counter/models.py:363 +#: counter/models.py:364 msgid "buying groups" msgstr "groupe d'achat" -#: counter/models.py:365 election/models.py:50 +#: counter/models.py:366 election/models.py:50 msgid "archived" msgstr "archivé" -#: counter/models.py:368 counter/models.py:1096 +#: counter/models.py:369 counter/models.py:1097 msgid "product" msgstr "produit" -#: counter/models.py:485 +#: counter/models.py:486 msgid "products" msgstr "produits" -#: counter/models.py:488 +#: counter/models.py:489 msgid "counter type" msgstr "type de comptoir" -#: counter/models.py:490 +#: counter/models.py:491 msgid "Bar" msgstr "Bar" -#: counter/models.py:490 +#: counter/models.py:491 msgid "Office" msgstr "Bureau" -#: counter/models.py:493 +#: counter/models.py:494 msgid "sellers" msgstr "vendeurs" -#: counter/models.py:501 launderette/models.py:178 +#: counter/models.py:502 launderette/models.py:178 msgid "token" msgstr "jeton" -#: counter/models.py:701 +#: counter/models.py:702 msgid "bank" msgstr "banque" -#: counter/models.py:703 counter/models.py:804 +#: counter/models.py:704 counter/models.py:805 msgid "is validated" msgstr "est validé" -#: counter/models.py:708 +#: counter/models.py:709 msgid "refilling" msgstr "rechargement" -#: counter/models.py:781 eboutic/models.py:249 +#: counter/models.py:782 eboutic/models.py:249 msgid "unit price" msgstr "prix unitaire" -#: counter/models.py:782 counter/models.py:1076 eboutic/models.py:250 +#: counter/models.py:783 counter/models.py:1077 eboutic/models.py:250 msgid "quantity" msgstr "quantité" -#: counter/models.py:801 +#: counter/models.py:802 msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:809 +#: counter/models.py:810 msgid "selling" msgstr "vente" -#: counter/models.py:913 +#: counter/models.py:914 msgid "Unknown event" msgstr "Événement inconnu" -#: counter/models.py:914 +#: counter/models.py:915 #, python-format msgid "Eticket bought for the event %(event)s" msgstr "Eticket acheté pour l'événement %(event)s" -#: counter/models.py:916 counter/models.py:929 +#: counter/models.py:917 counter/models.py:930 #, python-format msgid "" "You bought an eticket for the event %(event)s.\n" @@ -3822,67 +3826,67 @@ msgstr "" "Vous pouvez également retrouver tous vos e-tickets sur votre page de compte " "%(url)s." -#: counter/models.py:967 +#: counter/models.py:968 msgid "last activity date" msgstr "dernière activité" -#: counter/models.py:970 +#: counter/models.py:971 msgid "permanency" msgstr "permanence" -#: counter/models.py:1003 +#: counter/models.py:1004 msgid "emptied" msgstr "coffre vidée" -#: counter/models.py:1006 +#: counter/models.py:1007 msgid "cash register summary" msgstr "relevé de caisse" -#: counter/models.py:1072 +#: counter/models.py:1073 msgid "cash summary" msgstr "relevé" -#: counter/models.py:1075 +#: counter/models.py:1076 msgid "value" msgstr "valeur" -#: counter/models.py:1078 +#: counter/models.py:1079 msgid "check" msgstr "chèque" -#: counter/models.py:1080 +#: counter/models.py:1081 msgid "True if this is a bank check, else False" msgstr "Vrai si c'est un chèque, sinon Faux." -#: counter/models.py:1084 +#: counter/models.py:1085 msgid "cash register summary item" msgstr "élément de relevé de caisse" -#: counter/models.py:1100 +#: counter/models.py:1101 msgid "banner" msgstr "bannière" -#: counter/models.py:1102 +#: counter/models.py:1103 msgid "event date" msgstr "date de l'événement" -#: counter/models.py:1104 +#: counter/models.py:1105 msgid "event title" msgstr "titre de l'événement" -#: counter/models.py:1106 +#: counter/models.py:1107 msgid "secret" msgstr "secret" -#: counter/models.py:1145 +#: counter/models.py:1146 msgid "uid" msgstr "uid" -#: counter/models.py:1150 counter/models.py:1155 +#: counter/models.py:1151 counter/models.py:1156 msgid "student card" msgstr "carte étudiante" -#: counter/models.py:1156 +#: counter/models.py:1157 msgid "student cards" msgstr "cartes étudiantes" @@ -4194,15 +4198,15 @@ msgid "There is no products in this website." msgstr "Il n'y a pas de produits dans ce site web." #: counter/templates/counter/producttype_list.jinja:4 -#: counter/templates/counter/producttype_list.jinja:10 +#: counter/templates/counter/producttype_list.jinja:26 msgid "Product type list" msgstr "Liste des types de produit" -#: counter/templates/counter/producttype_list.jinja:8 +#: counter/templates/counter/producttype_list.jinja:16 msgid "New product type" msgstr "Nouveau type de produit" -#: counter/templates/counter/producttype_list.jinja:17 +#: counter/templates/counter/producttype_list.jinja:42 msgid "There is no product types in this website." msgstr "Il n'y a pas de types de produit dans ce site web."