From 30a598c0b7ebcfac7e55595cc57e9a3db18360c7 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 31 Oct 2025 17:47:11 +0100 Subject: [PATCH] Fix dish form and add kitchen view --- apps/food/forms.py | 2 +- apps/food/static/food/js/order.js | 1 - apps/food/tables.py | 6 +- apps/food/templates/food/dish_detail.html | 3 + apps/food/templates/food/dish_form.html | 2 +- apps/food/templates/food/kitchen.html | 41 +++++++++++ apps/food/urls.py | 2 +- apps/food/views.py | 86 +++++++++++++++++++---- 8 files changed, 120 insertions(+), 23 deletions(-) create mode 100644 apps/food/templates/food/kitchen.html diff --git a/apps/food/forms.py b/apps/food/forms.py index dbfd2da3..72bef4c4 100644 --- a/apps/food/forms.py +++ b/apps/food/forms.py @@ -219,7 +219,7 @@ SupplementFormSet = forms.inlineformset_factory( Dish, Supplement, form=SupplementForm, - extra=0, + extra=1, ) diff --git a/apps/food/static/food/js/order.js b/apps/food/static/food/js/order.js index b7df89bf..0e2043cc 100644 --- a/apps/food/static/food/js/order.js +++ b/apps/food/static/food/js/order.js @@ -21,7 +21,6 @@ function delete_button (button_id, table_id) { * @param table_id: Id of the table to reload */ function serve_button(button_id, table_id, current_state) { - console.log("update") const new_state = !current_state; $.ajax({ url: '/api/food/order/' + button_id + '/', diff --git a/apps/food/tables.py b/apps/food/tables.py index 5deb8ca0..964797f1 100644 --- a/apps/food/tables.py +++ b/apps/food/tables.py @@ -106,14 +106,10 @@ class OrderTable(tables.Table): get_current_request(), "food.change_order_saved", record) else '')}}, verbose_name=_("Serve"), ) - request = tables.Column( - orderable=False - ) - class Meta: model = Order template_name = 'django_tables2/bootstrap4.html' - fields = ('ordered_at', 'user', 'dish', 'supplements', 'request', 'serve', 'delete') + fields = ('number', 'ordered_at', 'user', 'dish', 'supplements', 'request', 'serve', 'delete') order_by = ('ordered_at', ) row_attrs = { 'class': 'table-row', diff --git a/apps/food/templates/food/dish_detail.html b/apps/food/templates/food/dish_detail.html index 6ac5339f..136672cf 100644 --- a/apps/food/templates/food/dish_detail.html +++ b/apps/food/templates/food/dish_detail.html @@ -31,6 +31,9 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "Update" %} {% endif %} + + {% trans "Return to dish list" %} + {% if delete %} {% trans "Delete" %} diff --git a/apps/food/templates/food/dish_form.html b/apps/food/templates/food/dish_form.html index 5480847f..f7bc6c90 100644 --- a/apps/food/templates/food/dish_form.html +++ b/apps/food/templates/food/dish_form.html @@ -16,7 +16,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {% crispy form %}

- {% trans "Ajouter des suppléments (optionnel)" %} + {% trans "Add supplements (optional)" %}

{{ formset.management_form }} diff --git a/apps/food/templates/food/kitchen.html b/apps/food/templates/food/kitchen.html new file mode 100644 index 00000000..01674e58 --- /dev/null +++ b/apps/food/templates/food/kitchen.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load render_table from django_tables2 %} +{% load i18n %} + +{% block content %} + + +
+ {% for food, quantity in orders.items %} +
+ +

+ {{ food }}
+

+

+ {{ quantity }}

+
+ {% endfor %} +
+ + +
+

+ {% trans "Special orders" %} +

+ {% if table.data %} + {% render_table table %} + {% else %} +
+
+ {% trans "There are no special orders." %} +
+
+ {% endif %} +
+ +{% endblock %} \ No newline at end of file diff --git a/apps/food/urls.py b/apps/food/urls.py index 4c4ffe2d..92a21e9f 100644 --- a/apps/food/urls.py +++ b/apps/food/urls.py @@ -28,5 +28,5 @@ urlpatterns = [ path('activity//order/', views.OrderCreateView.as_view(), name='order_create'), path('activity//orders/', views.OrderListView.as_view(), name='order_list'), path('activity//orders/served', views.ServedOrderListView.as_view(), name='served_order_list'), - path('activity/orders//delete/', views.OrderDeleteView.as_view(), name='order_delete'), + path('activity//kitchen/', views.KitchenView.as_view(), name='kitchen'), ] diff --git a/apps/food/views.py b/apps/food/views.py index 2ffbbd4b..7e6bde15 100644 --- a/apps/food/views.py +++ b/apps/food/views.py @@ -8,7 +8,7 @@ from crispy_forms.helper import FormHelper from django_tables2.views import SingleTableView, MultiTableMixin from django.core.exceptions import PermissionDenied from django.db import transaction -from django.db.models import Q +from django.db.models import Q, Count from django.http import HttpResponseRedirect, Http404 from django.views.generic import DetailView, UpdateView, CreateView from django.views.generic.list import ListView @@ -22,7 +22,7 @@ from activity.models import Activity from permission.backends import PermissionBackend from permission.views import ProtectQuerysetMixin, ProtectedCreateView, LoginRequiredMixin -from .models import Food, BasicFood, TransformedFood, QRCode, Order, Dish +from .models import Food, BasicFood, TransformedFood, QRCode, Order, Dish, Supplement from .forms import QRCodeForms, BasicFoodForms, TransformedFoodForms, \ ManageIngredientsForm, ManageIngredientsFormSet, AddIngredientForms, \ BasicFoodUpdateForms, TransformedFoodUpdateForms, \ @@ -591,7 +591,8 @@ class DishCreateView(ProtectQuerysetMixin, ProtectedCreateView): formset = SupplementFormSet(self.request.POST, instance=form.instance) if formset.is_valid(): for f in formset: - if f.is_valid(): + # We don't save the product if the price is not entered, ie. if the line is empty + if f.is_valid() and f.instance.price: f.save() f.instance.save() else: @@ -656,12 +657,54 @@ class DishUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): form_class = DishForm extra_context = {"title": _("Update a dish")} - def get_form(self, **kwargs): - form = super().get_form(**kwargs) + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + form = context['form'] + form.helper = FormHelper() + # Remove form tag on the generation of the form in the template (already present on the template) + form.helper.form_tag = False + # The formset handles the set of the supplements + form_set = SupplementFormSet(instance=form.instance) + context['formset'] = form_set + context['helper'] = SupplementFormSetHelper() + + return context + + def get_form(self, form_class=None): + form = super().get_form(form_class) if 'main' in form.fields: del form.fields["main"] return form + @transaction.atomic + def form_valid(self, form): + activity = Activity.objects.get(pk=self.kwargs["activity_pk"]) + + form.instance.activity = activity + + ret = super().form_valid(form) + + # For each supplement, we save it + formset = SupplementFormSet(self.request.POST, instance=form.instance) + saved = [] + if formset.is_valid(): + for f in formset: + # We don't save the product if the price is not entered, ie. if the line is empty + if f.is_valid() and f.instance.price: + f.save() + f.instance.save() + saved.append(f.instance.pk) + else: + f.instance = None + # Remove old supplements that weren't given in the form + Supplement.objects.filter(~Q(pk__in=saved), dish=form.instance).delete() + + return ret + + def get_success_url(self): + return reverse_lazy('food:dish_detail', kwargs={"activity_pk": self.kwargs["activity_pk"], "pk": self.kwargs["pk"]}) + class DishDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView): """ @@ -787,17 +830,32 @@ class ServedOrderListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableV return context -class OrderDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView): +class KitchenView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): """ - Delete an order + The view to display useful information for the kitchen """ model = Order - extra_context = {"title": _('Delete dish')} + table_class = OrderTable + template_name = 'food/kitchen.html' + extra_context = {'title': _('Kitchen')} - def delete(self, request, *args, **kwargs): - if self.get_object().served: - raise PermissionDenied(_("This order cannot be deleted because it has already been served")) - return super().delete(request, *args, **kwargs) + def get_queryset(self): + return super().get_queryset().filter(~Q(supplements__isnull=True, request=''), activity__pk=self.kwargs["activity_pk"]) - def get_success_url(self): - return reverse_lazy('food:order_list', kwargs={"activity_pk": self.kwargs["activity_pk"]}) + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + orders_count = Order.objects.values('dish__main__name').annotate(quantity=Count('id')) + + context["orders"] = {o['dish__main__name']: o['quantity'] for o in orders_count} + + return context + + def get_table(self, **kwargs): + table = super().get_table(**kwargs) + + hide = ["ordered_at", "serve", "delete"] + for field in hide: + table.columns.hide(field) + + return table