Add product category for color hue

This commit is contained in:
Gabriel Augendre 2023-04-02 19:33:24 +02:00
parent 24a46d398d
commit 5998a280ba
8 changed files with 236 additions and 20 deletions

View file

@ -3,7 +3,14 @@ from django.contrib.admin import register
from django.utils.translation import gettext_lazy as _
from solo.admin import SingletonModelAdmin
from purchase.models import Basket, BasketItem, Cache, PaymentMethod, Product
from purchase.models import (
Basket,
BasketItem,
Cache,
PaymentMethod,
Product,
ProductCategory,
)
from purchase.templatetags.purchase import currency
@ -13,15 +20,16 @@ class ProductAdmin(admin.ModelAdmin):
"name",
"display_order",
"initials",
"category",
"unit_price",
"sold",
"turnover",
]
list_editable = ["display_order", "initials"]
list_editable = ["display_order"]
search_fields = ["name"]
def get_queryset(self, request):
return super().get_queryset(request).with_sold().with_turnover()
return super().get_queryset(request).with_category().with_sold().with_turnover()
@admin.display(description=_("unit price"))
def unit_price(self, instance: Product):
@ -49,6 +57,12 @@ class PaymentMethodAdmin(admin.ModelAdmin):
return currency(instance.turnover)
@register(ProductCategory)
class ProductCategoryAdmin(admin.ModelAdmin):
list_display = ["name", "color_hue"]
search_fields = ["name"]
class BasketItemInline(admin.TabularInline):
model = BasketItem
fields = ["product", "quantity", "price"]

View file

@ -5,6 +5,7 @@
"created_at": "2022-04-26T20:30:22.959Z",
"updated_at": "2023-04-02T13:47:16.252Z",
"name": "Clou",
"category": 1,
"unit_price_cents": 140,
"initials": "C",
"display_order": 1
@ -16,6 +17,7 @@
"created_at": "2022-04-26T20:30:22.959Z",
"updated_at": "2023-04-02T14:16:24.038Z",
"name": "Villard'Ain",
"category": 1,
"unit_price_cents": 310,
"initials": "V",
"display_order": 3
@ -27,6 +29,7 @@
"created_at": "2022-04-26T20:30:22.959Z",
"updated_at": "2023-04-02T14:16:24.046Z",
"name": "Herbier",
"category": 1,
"unit_price_cents": 360,
"initials": "Hb",
"display_order": 9
@ -36,8 +39,9 @@
"model": "purchase.product",
"fields": {
"created_at": "2022-04-26T20:30:22.959Z",
"updated_at": "2023-04-02T14:16:24.053Z",
"updated_at": "2023-04-02T17:27:16.954Z",
"name": "Blanc vache",
"category": 3,
"unit_price_cents": 360,
"initials": "BV",
"display_order": 15
@ -49,6 +53,7 @@
"created_at": "2022-04-28T16:08:08.298Z",
"updated_at": "2023-04-02T14:16:24.048Z",
"name": "Ap\u00e9rich\u00e8vres",
"category": 1,
"unit_price_cents": 360,
"initials": "Ac",
"display_order": 7
@ -60,6 +65,7 @@
"created_at": "2022-04-28T16:08:16.542Z",
"updated_at": "2023-04-02T13:47:52.614Z",
"name": "Clougert",
"category": 1,
"unit_price_cents": 470,
"initials": "CM",
"display_order": 6
@ -71,6 +77,7 @@
"created_at": "2022-04-28T16:08:20.471Z",
"updated_at": "2023-04-02T14:16:24.050Z",
"name": "Brique",
"category": 1,
"unit_price_cents": 310,
"initials": "Bq",
"display_order": 4
@ -82,6 +89,7 @@
"created_at": "2022-04-28T16:08:20.471Z",
"updated_at": "2023-04-02T14:16:24.051Z",
"name": "Tomme de ch\u00e8vre",
"category": 1,
"unit_price_cents": 0,
"initials": "T",
"display_order": 50
@ -91,8 +99,9 @@
"model": "purchase.product",
"fields": {
"created_at": "2023-04-02T12:47:15.522Z",
"updated_at": "2023-04-02T14:16:24.070Z",
"updated_at": "2023-04-02T17:27:16.961Z",
"name": "Cr\u00eapes chocolat",
"category": 4,
"unit_price_cents": 200,
"initials": "CrC",
"display_order": 18
@ -104,6 +113,7 @@
"created_at": "2023-04-02T13:02:38.149Z",
"updated_at": "2023-04-02T14:16:24.043Z",
"name": "Caprinoux",
"category": 1,
"unit_price_cents": 100,
"initials": "Cap",
"display_order": 5
@ -115,6 +125,7 @@
"created_at": "2023-04-02T13:31:13.712Z",
"updated_at": "2023-04-02T14:16:24.055Z",
"name": "Clou affin\u00e9",
"category": 1,
"unit_price_cents": 160,
"initials": "CA",
"display_order": 2
@ -126,6 +137,7 @@
"created_at": "2023-04-02T13:32:12.289Z",
"updated_at": "2023-04-02T14:16:24.057Z",
"name": "Ap\u00e9rich\u00e8vres frais",
"category": 1,
"unit_price_cents": 250,
"initials": "AcF",
"display_order": 8
@ -137,6 +149,7 @@
"created_at": "2023-04-02T13:32:29.365Z",
"updated_at": "2023-04-02T14:16:24.059Z",
"name": "Blanc ch\u00e8vre",
"category": 1,
"unit_price_cents": 480,
"initials": "BC",
"display_order": 10
@ -146,8 +159,9 @@
"model": "purchase.product",
"fields": {
"created_at": "2023-04-02T13:32:41.308Z",
"updated_at": "2023-04-02T14:16:24.060Z",
"updated_at": "2023-04-02T17:27:16.948Z",
"name": "Gros de vache",
"category": 3,
"unit_price_cents": 200,
"initials": "GV",
"display_order": 13
@ -157,8 +171,9 @@
"model": "purchase.product",
"fields": {
"created_at": "2023-04-02T13:32:48.404Z",
"updated_at": "2023-04-02T14:16:24.062Z",
"updated_at": "2023-04-02T17:27:16.952Z",
"name": "P'tit clou",
"category": 3,
"unit_price_cents": 60,
"initials": "PC",
"display_order": 14
@ -168,8 +183,9 @@
"model": "purchase.product",
"fields": {
"created_at": "2023-04-02T13:33:12.184Z",
"updated_at": "2023-04-02T13:47:52.633Z",
"updated_at": "2023-04-02T17:27:16.957Z",
"name": "Ap\u00e9rivache",
"category": 3,
"unit_price_cents": 300,
"initials": "AV",
"display_order": 16
@ -181,6 +197,7 @@
"created_at": "2023-04-02T13:33:24.357Z",
"updated_at": "2023-04-02T14:16:24.064Z",
"name": "Fromage fort",
"category": 1,
"unit_price_cents": 360,
"initials": "FF",
"display_order": 11
@ -192,6 +209,7 @@
"created_at": "2023-04-02T13:33:49.793Z",
"updated_at": "2023-04-02T14:16:24.066Z",
"name": "Lait de ch\u00e8vre",
"category": 1,
"unit_price_cents": 200,
"initials": "L",
"display_order": 12
@ -201,8 +219,9 @@
"model": "purchase.product",
"fields": {
"created_at": "2023-04-02T13:33:54.405Z",
"updated_at": "2023-04-02T14:16:24.068Z",
"updated_at": "2023-04-02T17:27:16.959Z",
"name": "Verre",
"category": 4,
"unit_price_cents": 100,
"initials": "Ve",
"display_order": 17
@ -212,8 +231,9 @@
"model": "purchase.product",
"fields": {
"created_at": "2023-04-02T13:34:34.802Z",
"updated_at": "2023-04-02T14:16:24.072Z",
"updated_at": "2023-04-02T17:27:16.963Z",
"name": "Cr\u00eape sucre",
"category": 4,
"unit_price_cents": 150,
"initials": "CrS",
"display_order": 19
@ -223,8 +243,9 @@
"model": "purchase.product",
"fields": {
"created_at": "2023-04-02T13:34:44.535Z",
"updated_at": "2023-04-02T14:16:24.073Z",
"updated_at": "2023-04-02T17:27:16.966Z",
"name": "Cr\u00eape nature",
"category": 4,
"unit_price_cents": 140,
"initials": "CrN",
"display_order": 20
@ -234,11 +255,42 @@
"model": "purchase.product",
"fields": {
"created_at": "2023-04-02T14:02:56.130Z",
"updated_at": "2023-04-02T14:16:24.075Z",
"updated_at": "2023-04-02T17:27:16.968Z",
"name": "Facture",
"category": 4,
"unit_price_cents": 0,
"initials": "Fa",
"display_order": 51
}
},
{
"model": "purchase.productcategory",
"pk": 1,
"fields": {
"created_at": "2023-04-02T17:24:02.168Z",
"updated_at": "2023-04-02T17:30:07.195Z",
"name": "Ch\u00e8vre",
"color_hue": 145
}
},
{
"model": "purchase.productcategory",
"pk": 3,
"fields": {
"created_at": "2023-04-02T17:25:41.912Z",
"updated_at": "2023-04-02T17:30:04.093Z",
"name": "Vache",
"color_hue": 276
}
},
{
"model": "purchase.productcategory",
"pk": 4,
"fields": {
"created_at": "2023-04-02T17:25:57.992Z",
"updated_at": "2023-04-02T17:29:59.720Z",
"name": "Autres",
"color_hue": 38
}
}
]

View file

@ -40,7 +40,7 @@ class BasketForm(forms.ModelForm):
for item in basket.items.all():
products[item.product] = item.quantity
fields = []
for product in Product.objects.with_fixed_price():
for product in Product.objects.with_category().with_fixed_price():
field_name = f"{PRICED_PREFIX}{product.id}"
self.fields.update(
{

View file

@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-02 18:32+0200\n"
"POT-Creation-Date: 2023-04-02 19:31+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -56,6 +56,26 @@ msgstr ""
msgid "payment methods"
msgstr ""
#: purchase/models.py
msgid "color hue"
msgstr ""
#: purchase/models.py
msgid "Color hue in degrees (0-360)"
msgstr ""
#: purchase/models.py
msgid "product category"
msgstr ""
#: purchase/models.py
msgid "product categories"
msgstr ""
#: purchase/models.py
msgid "category"
msgstr ""
#: purchase/models.py
msgid "unit price (cents)"
msgstr ""

View file

@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-02 18:32+0200\n"
"POT-Creation-Date: 2023-04-02 19:31+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -55,6 +55,27 @@ msgstr "moyen de paiement"
msgid "payment methods"
msgstr "moyens de paiement"
#: purchase/models.py
msgid "color hue"
msgstr "teinte de couleur"
#: purchase/models.py
msgid "Color hue in degrees (0-360)"
msgstr "Teinte de couleur en degrés (0-360)"
#: purchase/models.py
#, fuzzy
msgid "product category"
msgstr "catégorie de produits"
#: purchase/models.py
msgid "product categories"
msgstr "catégories de produits"
#: purchase/models.py
msgid "category"
msgstr "catégorie"
#: purchase/models.py
msgid "unit price (cents)"
msgstr "prix unitaire (centimes)"

View file

@ -0,0 +1,62 @@
# Generated by Django 4.1.7 on 2023-04-02 17:21
import django.core.validators
from django.db import migrations, models
def create_default_categories(apps, schema_editor):
ProductCategory = apps.get_model("purchase", "ProductCategory") # noqa: N806
ProductCategory.objects.using(schema_editor.connection.alias).create(
name="default",
color_hue=0,
)
class Migration(migrations.Migration):
dependencies = [
("purchase", "0016_alter_paymentmethod_options"),
]
operations = [
migrations.CreateModel(
name="ProductCategory",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="created at"),
),
(
"updated_at",
models.DateTimeField(auto_now=True, verbose_name="updated at"),
),
(
"name",
models.CharField(max_length=250, unique=True, verbose_name="name"),
),
(
"color_hue",
models.PositiveIntegerField(
help_text="Color hue in degrees (0-360)",
validators=[django.core.validators.MaxValueValidator(360)],
verbose_name="color hue",
),
),
],
options={
"abstract": False,
},
),
migrations.RunPython(
create_default_categories,
reverse_code=migrations.RunPython.noop,
),
]

View file

@ -0,0 +1,24 @@
# Generated by Django 4.1.7 on 2023-04-02 17:23
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("purchase", "0017_productcategory"),
]
operations = [
migrations.AddField(
model_name="product",
name="category",
field=models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.PROTECT,
to="purchase.productcategory",
verbose_name="category",
),
preserve_default=False,
),
]

View file

@ -1,8 +1,8 @@
from __future__ import annotations
import hashlib
import uuid
from django.core.validators import MaxValueValidator
from django.db import models
from django.db.models import Avg, Count, F, Sum
from django.db.models.functions import Coalesce
@ -65,6 +65,22 @@ def default_product_display_order():
return last.display_order + 1
class ProductCategory(Model):
name = models.CharField(max_length=250, unique=True, verbose_name=_("name"))
color_hue = models.PositiveIntegerField(
validators=[MaxValueValidator(360)],
verbose_name=_("color hue"),
help_text=_("Color hue in degrees (0-360)"),
)
class Meta:
verbose_name = _("product category")
verbose_name_plural = _("product categories")
def __str__(self):
return self.name
class ProductQuerySet(models.QuerySet):
def with_turnover(self):
return self.annotate(
@ -83,6 +99,9 @@ class ProductQuerySet(models.QuerySet):
def with_no_fixed_price(self):
return self.filter(unit_price_cents=0)
def with_category(self):
return self.select_related("category")
class ProductManager(models.Manager):
def get_by_natural_key(self, name):
@ -91,6 +110,13 @@ class ProductManager(models.Manager):
class Product(Model):
name = models.CharField(max_length=250, unique=True, verbose_name=_("name"))
category = models.ForeignKey(
ProductCategory,
on_delete=models.PROTECT,
verbose_name=_("category"),
null=False,
blank=False,
)
unit_price_cents = models.PositiveIntegerField(
verbose_name=_("unit price (cents)"),
help_text=_(
@ -123,10 +149,7 @@ class Product(Model):
@property
def color_hue(self):
return int(
hashlib.sha256(bytes(self.name, encoding="utf-8")).hexdigest()[:2],
base=16,
)
return self.category.color_hue
@property
def has_fixed_price(self) -> bool: