from datetime import date, timedelta 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 from .utils import clean 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 DocumentInline(admin.TabularInline): model = models.Document extra = 1 class FournitureAdmin(admin.ModelAdmin): list_display = ["nom", "ordre", "couleur", "conditionnement", "emballage"] list_editable = ["ordre", "couleur", "conditionnement", "emballage"] inlines = [ DocumentInline, ] actions = None save_as = True admin_site.register(models.Fourniture, FournitureAdmin) 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( '{}'.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.NestedTabularInline): model = models.Commande inlines = [ RéservationInline, ] extra = 1 classes = ["collapse"] template = "nesting/admin/inlines/réservations_tabular.html" 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, pâte_ordre in ( obj.commande_set.values_list( "réservation__pain__pâte", "réservation__pain__pâte__nom", "réservation__pain__pâte__ordre" ) .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, ordre=pâte_ordre ) self.message_user( request, f"Création d'une coulée de " f"{clean(pâte_manquante)} kg " f"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 " f"en excès ({-clean(pâte_manquante)} 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() """ on créé les tableaux coulée / ingrédient """ extra_context["coulées"] = [] for c in obj.coulée_set.all(): quantités = [[None, c]] ingrédients = c.pâte.ingrédient_set.all() for i in ingrédients: quantités.append([ i, clean(c.quantité / c.pâte.poids_de_base * sum( c.pâte.ingrédient_set.filter(pk=i.pk) .values_list("quantité", flat=True) )) ]) 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"{clean(masse_coulée - masse_commandée)} " "kg coulés en trop !!" ) elif masse_coulée < masse_commandée: problems = ( f"{clean(masse_commandée - masse_coulée)} " "kg coulés en moins !!" ) extra_context["coulées"].append([problems, ingrédients, quantités]) """ on créé un tableau total coulée / ingrédient """ ingrédients = [ models.Fourniture.objects.get(nom=nom) for nom in set( coulées.values_list( "pâte__ingrédient__fourniture__nom", flat=True ) ) ] ingrédients.sort(key=lambda x: x.ordre) extra_context["ingrédients"] = ingrédients pains = models.Pain.objects.filter( pk__in=obj.commande_set.exclude(réservation__isnull=True) .values_list("pains", flat=True) ).values_list("nom", flat=True) totaux = [ [None, sum(obj.coulée_set.values_list('quantité', flat=True))] ] for i in ingrédients: total = clean(sum([ qté * qté_unit / unit for qté, qté_unit, unit in obj.coulée_set.filter( pâte__ingrédient__fourniture__nom=i.nom ) .values_list( 'quantité', 'pâte__ingrédient__quantité', 'pâte__poids_de_base', ) ])) totaux.append([i, 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( clean(sum( c.réservation_set.filter(pain__nom=p).values_list( "quantité", flat=True ) )) ) else: tmp.append("") extra_context["divisions"].append(tmp) totaux = [sum(obj.coulée_set.values_list('quantité', flat=True))] for p in pains: total = clean(sum( obj.commande_set.filter(pains__nom=p).values_list( "réservation__quantité", flat=True ) )) 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( '{}'.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)