fournee/fournée/core/admin.py

326 lines
11 KiB
Python

from datetime import date, timedelta
from decimal import Decimal
from django.contrib import admin, messages
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.safestring import mark_safe
import nested_admin
from . import models
class MyAdminSite(admin.AdminSite):
site_header = "Fournée 2.0"
index_title = (
"Une interface de gestion offerte par Django (et un peu de sueur)"
)
admin_site = MyAdminSite(name="admin")
class PainAdmin(admin.ModelAdmin):
list_display = ["nom", "pâte", "poids", "moulé"]
list_editable = ["pâte", "poids", "moulé"]
list_select_related = ["pâte"]
actions = None
save_as = True
admin_site.register(models.Pain, PainAdmin)
class IngrédientInline(admin.TabularInline):
model = models.Ingrédient
sortable_field_name = "ordre"
extra = 1
class RecetteAdmin(admin.ModelAdmin):
inlines = [
IngrédientInline,
]
actions = None
save_as = True
admin_site.register(models.Recette, RecetteAdmin)
class MyDateFieldListFilter(admin.filters.DateFieldListFilter):
def __init__(self, field, request, params, model, model_admin, field_path):
super().__init__(field, request, params, model, model_admin, field_path)
self.links = (
self.links[0:1]
+ (
(
"Demain",
{
self.lookup_kwarg_since: str(
date.today() + timedelta(days=1)
),
self.lookup_kwarg_until: str(
date.today() + timedelta(days=2)
),
},
),
)
+ self.links[1:]
)
class RéservationAdmin(admin.ModelAdmin):
model = models.Réservation
actions = None
list_display = ["pour", "date", "pain", "quantité", "fournée"]
list_display_links = ["fournée"]
list_filter = [
("commande__fournée__date", MyDateFieldListFilter),
"commande__pour",
"commande__fournée__pour",
"pain",
]
list_editable = ["pain", "quantité"]
list_select_related = ["commande", "commande__fournée"]
@admin.display(ordering="commande__pour")
def pour(self, obj):
return obj.commande.pour
@admin.display(ordering="commande__fournée__pour")
def fournée(self, obj):
return mark_safe(
'<a href="{}">{}</a>'.format(
reverse(
"admin:core_fournée_change", args=(obj.commande.fournée.pk,)
),
obj.commande.fournée.pour,
)
)
@admin.display(ordering="commande__fournée__date")
def date(self, obj):
return obj.commande.fournée.date
admin_site.register(models.Réservation, RéservationAdmin)
class RéservationInline(nested_admin.NestedTabularInline):
model = models.Réservation
extra = 1
classes = ["collapse"]
class CommandeInline(nested_admin.NestedStackedInline):
model = models.Commande
inlines = [
RéservationInline,
]
extra = 1
classes = ["collapse"]
class CouléeInline(
nested_admin.SortableHiddenMixin, nested_admin.NestedTabularInline
):
model = models.Coulée
extra = 0
classes = ["collapse"]
sortable_field_name = "ordre"
class FournéeAdmin(nested_admin.NestedModelAdmin):
change_form_template = "admin/fournée_change_form.html"
inlines = [
CommandeInline,
CouléeInline,
]
actions = None
extra = 1
list_filter = [("date", MyDateFieldListFilter), "commande__fournée__pour"]
date_hierarchy = "date"
save_as = True
save_on_top = True
def save_related(self, request, form, formsets, change):
"""Création des coulées manquantes"""
super().save_related(request, form, formsets, change)
if change and "_continueandcreatecoulees" in request.POST:
obj = form.instance
"""Pour chaque pâte on regarde si les coulées sont complètes.
Si elles ne le sont pas, on ajoute ce qui manque."""
for pâte_id, pâte_nom in (
obj.commande_set.values_list(
"réservation__pain__pâte", "réservation__pain__pâte__nom"
)
.order_by("réservation__pain__pâte")
.distinct()
):
somme_commandée = obj.masse_commandée(pâte_id)
somme_coulée = sum(
obj.coulée_set.filter(pâte_id=pâte_id).values_list(
"quantité", flat=True
)
)
pâte_manquante = somme_commandée - somme_coulée
if pâte_manquante > 0:
obj.coulée_set.create(
pâte_id=pâte_id, quantité=pâte_manquante
)
self.message_user(
request,
f"Création d'une coulée de {pâte_manquante.normalize()} kg pour la pâte {pâte_nom}.",
)
elif pâte_manquante < 0:
self.message_user(
request,
f"Attention la pâte {pâte_nom} est prévue en excès ({-pâte_manquante.normalize()} kg en trop)",
messages.WARNING,
)
def save_model(self, request, obj, form, change):
"""Décale d'une semaine en cas de save_as si c'est utile"""
if not change:
date = form.cleaned_data.get("date", None)
if obj._meta.model.objects.filter(date=date).exists():
prochaine_date = date + timedelta(weeks=1)
if not obj._meta.model.objects.filter(
date=prochaine_date
).exists():
obj.date = date + timedelta(weeks=1)
super().save_model(request, obj, form, change)
def response_post_save_change(self, request, obj):
"""Hack la réponse pour forcer un _continue sans hacker tout
le fichier nested_admin/nested.py"""
if "_continueandcreatecoulees" in request.POST:
return redirect("admin:core_fournée_change", obj.pk)
else:
return super().response_change(request, obj)
def change_view(self, request, object_id, form_url="", extra_context=None):
extra_context = extra_context or {}
obj = self.model.objects.get(pk=object_id)
extra_context["commandes"] = obj.commande_set.all()
coulées = obj.coulée_set.all()
ingrédients = sorted(
set(
coulées.values_list(
"pâte__ingrédient__nom", "pâte__ingrédient__ordre"
)
),
key=lambda x: x[1],
)
extra_context["ingrédients"] = ingrédients
""" on créé un tableau coulée / ingrédient """
extra_context["coulées"] = []
for c in obj.coulée_set.all():
tmp = [c]
for i, _ in ingrédients:
if c.pâte.ingrédient_set.filter(nom=i).exists():
tmp.append(
(c.quantité / c.pâte.poids_de_base * sum(
c.pâte.ingrédient_set.filter(nom=i).values_list("quantité", flat=True)
)).quantize(Decimal('1.000')).normalize()
)
else:
tmp.append("")
masse_coulée = sum(
obj.coulée_set.filter(pâte=c.pâte).values_list(
"quantité", flat=True
)
)
masse_commandée = obj.masse_commandée(c.pâte_id)
if masse_coulée == masse_commandée:
problems = ""
elif masse_coulée > masse_commandée:
problems = f"{(masse_coulée - masse_commandée).normalize()} kg coulés en trop !!"
elif masse_coulée < masse_commandée:
problems = f"{(masse_commandée - masse_coulée).normalize()} kg coulés en moins !!"
extra_context["coulées"].append([problems, tmp])
pains = set(obj.commande_set.exclude(réservation__isnull=True).values_list("pains__nom", flat=True))
totaux = []
for i, _ in ingrédients:
total = sum([
qté * qté_unit / unit
for qté, qté_unit, unit in
obj.coulée_set.filter(pâte__ingrédient__nom=i)
.values_list('quantité', 'pâte__ingrédient__quantité', 'pâte__poids_de_base')
]).quantize(Decimal('1.000')).normalize()
totaux.append(total)
extra_context["totaux_coulées"] = totaux
""" liste des pains, vérifiant s'ils sont coulés """
extra_context["pains"] = [
(not obj.coulée_set.filter(pâte__pain__nom=p).exists(), p)
for p in pains
]
""" on créé un tableau pains / commande """
extra_context["divisions"] = []
for c in obj.commande_set.all():
tmp = [c.pour]
for p in pains:
if c.réservation_set.filter(pain__nom=p).exists():
tmp.append(
sum(
c.réservation_set.filter(pain__nom=p).values_list(
"quantité", flat=True
)
).normalize()
)
else:
tmp.append("")
extra_context["divisions"].append(tmp)
totaux = []
for p in pains:
total = sum(
obj.commande_set.filter(pains__nom=p).values_list(
"réservation__quantité", flat=True
)
).normalize()
totaux.append(total)
extra_context["totaux_divisions"] = totaux
return super().change_view(
request,
object_id,
form_url,
extra_context=extra_context,
)
admin_site.register(models.Fournée, FournéeAdmin)
class CouléeAdmin(admin.ModelAdmin):
model = models.Coulée
actions = None
list_display = ["pâte", "quantité", "date", "fournée_pour"]
list_display_links = ["fournée_pour"]
list_filter = [("fournée__date", MyDateFieldListFilter), "pâte"]
list_editable = ["pâte", "quantité"]
list_select_related = ["fournée"]
@admin.display(ordering="fournée__pour")
def fournée_pour(self, obj):
return mark_safe(
'<a href="{}">{}</a>'.format(
reverse("admin:core_fournée_change", args=(obj.fournée.pk,)),
obj.fournée.pour,
)
)
@admin.display(ordering="fournée__date")
def date(self, obj):
return obj.fournée.date
admin_site.register(models.Coulée, CouléeAdmin)