From a92afab956aa95672f4da53e0221218fe64bb7da Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 13 Dec 2025 20:37:06 +0100 Subject: [PATCH] Prepare for first irl test --- .../activity/includes/activity_info.html | 2 - apps/food/forms.py | 37 +++++++++++++++++++ .../migrations/0007_supplement_available.py | 18 +++++++++ apps/food/models.py | 26 +++++++++++-- apps/food/static/food/js/order.js | 3 ++ apps/food/tables.py | 2 +- apps/food/templates/food/dish_detail.html | 2 +- apps/food/templates/food/dish_form.html | 2 + apps/food/templates/food/dish_list.html | 2 + apps/food/templates/food/food_detail.html | 2 - apps/food/templates/food/food_list.html | 2 - apps/food/templates/food/order_list.html | 1 + apps/food/tests/test_food.py | 14 +++---- apps/food/urls.py | 30 +++++++-------- apps/food/views.py | 6 ++- 15 files changed, 113 insertions(+), 36 deletions(-) create mode 100644 apps/food/migrations/0007_supplement_available.py diff --git a/apps/activity/templates/activity/includes/activity_info.html b/apps/activity/templates/activity/includes/activity_info.html index 5a8887e2..ee403ba0 100644 --- a/apps/activity/templates/activity/includes/activity_info.html +++ b/apps/activity/templates/activity/includes/activity_info.html @@ -65,11 +65,9 @@ SPDX-License-Identifier: GPL-3.0-or-later {% if activity.open and activity.activity_type.manage_entries and ".change__open"|has_perm:activity %} {% trans "Entry page" %} {% endif %} - {% if false %} {% if activity.activity_type.name == "Perm bouffe" %} {% trans "Dish page" %} {% endif %} - {% endif %} {% if request.path_info == activity_detail_url %} {% if activity.valid and ".change__open"|has_perm:activity %} diff --git a/apps/food/forms.py b/apps/food/forms.py index 2b09699a..add5eaa7 100644 --- a/apps/food/forms.py +++ b/apps/food/forms.py @@ -13,6 +13,7 @@ from member.models import Club from note_kfet.inputs import Autocomplete, AmountInput from note_kfet.middlewares import get_current_request from permission.backends import PermissionBackend +from activity.models import Activity from .models import Food, BasicFood, TransformedFood, QRCode, Dish, Supplement, Order, Recipe @@ -201,6 +202,17 @@ class DishForm(forms.ModelForm): """ Form to create a dish """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # TODO find a better way to get pk (be not url scheme dependant) + pk = get_current_request().path.split('/')[3] + club = Activity.objects.get(pk=pk).organizer + qs = self.fields['main'].queryset.filter( + owner=club, + end_of_life='' + ).filter(PermissionBackend.filter_queryset(get_current_request(), Food, "change")) + self.fields['main'].queryset = qs + class Meta: model = Dish fields = ('main', 'price', 'available') @@ -213,6 +225,17 @@ class SupplementForm(forms.ModelForm): """ Form to create a dish """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # TODO find a better way to get pk (be not url scheme dependant) + pk = get_current_request().path.split('/')[3] + club = Activity.objects.get(pk=pk).organizer + qs = self.fields['food'].queryset.filter( + owner=club, + end_of_life='' + ).filter(PermissionBackend.filter_queryset(get_current_request(), Food, "change")) + self.fields['food'].queryset = qs + class Meta: model = Supplement fields = '__all__' @@ -249,6 +272,20 @@ class OrderForm(forms.ModelForm): """ Form to order food """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # TODO find a better way to get pk (be not url scheme dependant) + pk = get_current_request().path.split('/')[3] + qs = self.fields['supplements'].queryset.filter( + available=True + ).filter(PermissionBackend.filter_queryset(get_current_request(), Supplement, "view")) + self.fields['supplements'].queryset = qs + qs = self.fields['dish'].queryset.filter( + activity__pk=pk, + available=True + ).filter(PermissionBackend.filter_queryset(get_current_request(), Dish, "view")) + self.fields['dish'].queryset = qs + class Meta: model = Order exclude = ("activity", "number", "ordered_at", "served", "served_at") diff --git a/apps/food/migrations/0007_supplement_available.py b/apps/food/migrations/0007_supplement_available.py new file mode 100644 index 00000000..9e5f2f2e --- /dev/null +++ b/apps/food/migrations/0007_supplement_available.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.9 on 2025-12-13 23:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('food', '0006_recipe'), + ] + + operations = [ + migrations.AddField( + model_name='supplement', + name='available', + field=models.BooleanField(default=True, verbose_name='available'), + ), + ] diff --git a/apps/food/models.py b/apps/food/models.py index cd66c960..5584f36c 100644 --- a/apps/food/models.py +++ b/apps/food/models.py @@ -388,6 +388,11 @@ class Supplement(models.Model): verbose_name=_('price') ) + available = models.BooleanField( + default=True, + verbose_name=_('available'), + ) + class Meta: verbose_name = _('Supplement') verbose_name_plural = _('Supplements') @@ -476,7 +481,7 @@ class Order(models.Model): dish=str(self.dish), user=str(self.user)) - def save(self, *args, **kwargs): + def save(self, supplements=None, *args, **kwargs): if self.activity != self.dish.activity: raise ValidationError(_('Activities must be the same.')) created = self.pk is None @@ -487,12 +492,19 @@ class Order(models.Model): else: self.number = last_order.number + 1 super().save(*args, **kwargs) - + amount = self.dish.price + if supplements: + self.supplements.set(supplements) + super().save(*args, **kwargs) + for supplement in supplements: + if supplement.dish != self.dish: + raise ValidationError(_('(You cannot select these supplements for this dish.')) + amount += supplement.price transaction = FoodTransaction( order=self, source=self.user.note, destination=self.activity.organizer.note, - amount=self.amount, + amount=amount, quantity=1, reason=str(self.dish), ) @@ -504,6 +516,12 @@ class Order(models.Model): self.transaction.save() super().save(*args, **kwargs) + def delete(self, *args, **kwargs): + if self.transaction.valid: + raise ValidationError(_('You cannot delete this order because the transaction is valid')) + else: + super().delete(*args, **kwargs) + class FoodTransaction(Transaction): """ @@ -511,7 +529,7 @@ class FoodTransaction(Transaction): """ order = models.OneToOneField( Order, - on_delete=models.PROTECT, + on_delete=models.CASCADE, related_name='transaction', verbose_name=_('order') ) diff --git a/apps/food/static/food/js/order.js b/apps/food/static/food/js/order.js index 0e2043cc..a1192a8e 100644 --- a/apps/food/static/food/js/order.js +++ b/apps/food/static/food/js/order.js @@ -32,10 +32,13 @@ function serve_button(button_id, table_id, current_state) { }) }) .done(function () { + console.log('done'); if (current_state) { + console.log('true'); $('table').load(location.pathname + ' table') } else { + console.log('false'); $('#' + table_id).load(location.pathname + ' #' + table_id + ' > *'); } }) diff --git a/apps/food/tables.py b/apps/food/tables.py index 758ac0a3..f4e2690a 100644 --- a/apps/food/tables.py +++ b/apps/food/tables.py @@ -47,7 +47,7 @@ class DishTable(tables.Table): supplements = tables.Column(empty_values=(), verbose_name=_('Available supplements'), orderable=False) def render_supplements(self, record): - return ", ".join(str(q.food) for q in record.supplements.all()) + return ", ".join(str(q.food) for q in record.supplements.filter(available=True)) def render_price(self, value): return pretty_money(value) diff --git a/apps/food/templates/food/dish_detail.html b/apps/food/templates/food/dish_detail.html index 136672cf..216391a8 100644 --- a/apps/food/templates/food/dish_detail.html +++ b/apps/food/templates/food/dish_detail.html @@ -22,7 +22,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
  • {% trans "Available" %} : {{ dish.available|yesno }}
  • {% trans "Possible supplements" %} : {% for supp in supplements %} - {{ supp.food.name }} ({{ supp.price|pretty_money }}){% if not forloop.last %},{% endif %} + {% if not supp.food.available %}{% endif %}{{ supp.food.name }} ({{ supp.price|pretty_money }}) {% if not supp.food.available %}{% endif %}{% if not forloop.last %},{% endif %} {% endfor %}
  • diff --git a/apps/food/templates/food/dish_form.html b/apps/food/templates/food/dish_form.html index f7bc6c90..b9e7af75 100644 --- a/apps/food/templates/food/dish_form.html +++ b/apps/food/templates/food/dish_form.html @@ -26,6 +26,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {{ form.food.label }}* {{ form.price.label }}* + {{ form.available.label }}* @@ -33,6 +34,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {{ form.food }} {{ form.price }} + {{ form.available }} {# These fields are hidden but handled by the formset to link the id and the invoice id #} {{ form.dish }} {{ form.id }} diff --git a/apps/food/templates/food/dish_list.html b/apps/food/templates/food/dish_list.html index 62acfb9b..97e039db 100644 --- a/apps/food/templates/food/dish_list.html +++ b/apps/food/templates/food/dish_list.html @@ -17,6 +17,8 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "New dish" %} {% endif %} {% trans "Activity page" %} + {% trans "Order food" %} + {% trans "Order list" %} {% trans "Return to the food list" %} diff --git a/apps/food/templates/food/food_detail.html b/apps/food/templates/food/food_detail.html index c0bc9555..024dee1e 100644 --- a/apps/food/templates/food/food_detail.html +++ b/apps/food/templates/food/food_detail.html @@ -47,11 +47,9 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "Manage ingredients" %} - {% if false %} {% trans "Use a recipe" %} - {% endif %} {% endif %} {% trans "Return to the food list" %} diff --git a/apps/food/templates/food/food_list.html b/apps/food/templates/food/food_list.html index d126f607..54c8fb1c 100644 --- a/apps/food/templates/food/food_list.html +++ b/apps/food/templates/food/food_list.html @@ -70,7 +70,6 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "New meal" %} {% endif %} - {% if false %} {% if can_view_recipes %} {% trans "View recipes" %} @@ -86,7 +85,6 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "View" %} {{ activity.name }} {% endfor %} - {% endif %} {% if served.data %} diff --git a/apps/food/templates/food/order_list.html b/apps/food/templates/food/order_list.html index 352f78c9..46cb7384 100644 --- a/apps/food/templates/food/order_list.html +++ b/apps/food/templates/food/order_list.html @@ -21,6 +21,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {% render_table table %} {% endif %} + {% if forloop.last %}{% trans "Return to dish list" %} {% endif %} {% endfor %} {% endblock %} diff --git a/apps/food/tests/test_food.py b/apps/food/tests/test_food.py index 38629a43..6a068a36 100644 --- a/apps/food/tests/test_food.py +++ b/apps/food/tests/test_food.py @@ -11,7 +11,7 @@ from member.models import Club from ..api.views import AllergenViewSet, BasicFoodViewSet, TransformedFoodViewSet, QRCodeViewSet, \ DishViewSet, SupplementViewSet, OrderViewSet, FoodTransactionViewSet -from ..models import Allergen, BasicFood, TransformedFood, QRCode, Dish, Supplement, Order # TODO FoodTransaction +from ..models import Allergen, BasicFood, TransformedFood, QRCode, Dish, Supplement, Order, FoodTransaction class TestFood(TestCase): @@ -120,7 +120,7 @@ class TestFood(TestCase): self.assertEqual(response.status_code, 200) -'''class TestFoodOrder(TestCase): +class TestFoodOrder(TestCase): """ Test Food Order """ @@ -198,7 +198,7 @@ class TestFood(TestCase): ) self.supplement = Supplement.objects.create( - dish=self.dish, + dish=self.second_dish, food=self.basicfood, price=100, ) @@ -208,8 +208,6 @@ class TestFood(TestCase): activity=self.activity, dish=self.dish, ) - self.order.supplements.add(self.supplement) - self.order.save() def test_dish_list(self): """ @@ -300,8 +298,10 @@ class TestFood(TestCase): dish=self.second_dish.pk, supplements=self.supplement.pk )) - self.assertRedirects(response, reverse("food:food_list")) + self.assertRedirects(response, reverse("food:dish_list", kwargs={"activity_pk": self.activity.pk})) self.assertTrue(Order.objects.filter(user=self.user, dish=self.second_dish, activity=self.activity).exists()) + order = Order.objects.get(user=self.user, dish=self.second_dish, activity=self.activity, supplements=self.supplement) + self.assertTrue(FoodTransaction.objects.filter(order=order, amount=order.amount, valid=False, source=order.user.note).exists()) def test_order_list(self): """ @@ -337,7 +337,7 @@ class TestFood(TestCase): self.assertEqual(response.status_code, 200) self.assertTrue(Order.objects.filter(dish=self.dish, user=self.user, served=False).exists()) - self.assertTrue(FoodTransaction.objects.filter(order=self.order, valid=False).exists())''' + self.assertTrue(FoodTransaction.objects.filter(order=self.order, valid=False).exists()) class TestFoodAPI(TestAPI): diff --git a/apps/food/urls.py b/apps/food/urls.py index 299548f6..00bdc3e3 100644 --- a/apps/food/urls.py +++ b/apps/food/urls.py @@ -20,19 +20,19 @@ urlpatterns = [ path('add/ingredient//', views.AddIngredientView.as_view(), name='add_ingredient'), path('redirect/', views.QRCodeRedirectView.as_view(), name='redirect_view'), # TODO not always store activity_pk in url - # path('activity//dishes/add/', views.DishCreateView.as_view(), name='dish_create'), - # path('activity//dishes/', views.DishListView.as_view(), name='dish_list'), - # path('activity//dishes//', views.DishDetailView.as_view(), name='dish_detail'), - # path('activity//dishes//update/', views.DishUpdateView.as_view(), name='dish_update'), - # path('activity//dishes//delete/', views.DishDeleteView.as_view(), name='dish_delete'), - # 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//kitchen/', views.KitchenView.as_view(), name='kitchen'), - # path('recipe/add/', views.RecipeCreateView.as_view(), name='recipe_create'), - # path('recipe/', views.RecipeListView.as_view(), name='recipe_list'), - # path('recipe//', views.RecipeDetailView.as_view(), name='recipe_detail'), - # path('recipe//update/', views.RecipeUpdateView.as_view(), name='recipe_update'), - # path('update/ingredients//recipe/', views.UseRecipeView.as_view(), name='recipe_use'), - # path('ajax/get_ingredients/', views.get_ingredients_for_recipe, name='get_ingredients'), + path('activity//dishes/add/', views.DishCreateView.as_view(), name='dish_create'), + path('activity//dishes/', views.DishListView.as_view(), name='dish_list'), + path('activity//dishes//', views.DishDetailView.as_view(), name='dish_detail'), + path('activity//dishes//update/', views.DishUpdateView.as_view(), name='dish_update'), + path('activity//dishes//delete/', views.DishDeleteView.as_view(), name='dish_delete'), + 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//kitchen/', views.KitchenView.as_view(), name='kitchen'), + path('recipe/add/', views.RecipeCreateView.as_view(), name='recipe_create'), + path('recipe/', views.RecipeListView.as_view(), name='recipe_list'), + path('recipe//', views.RecipeDetailView.as_view(), name='recipe_detail'), + path('recipe//update/', views.RecipeUpdateView.as_view(), name='recipe_update'), + path('update/ingredients//recipe/', views.UseRecipeView.as_view(), name='recipe_use'), + path('ajax/get_ingredients/', views.get_ingredients_for_recipe, name='get_ingredients'), ] diff --git a/apps/food/views.py b/apps/food/views.py index c2dfa17c..7394fa0e 100644 --- a/apps/food/views.py +++ b/apps/food/views.py @@ -777,11 +777,13 @@ class OrderCreateView(ProtectQuerysetMixin, ProtectedCreateView): activity = Activity.objects.get(pk=self.kwargs["activity_pk"]) form.instance.activity = activity + supplements = Supplement.objects.filter(pk__in=form.data.getlist('supplements')).all() - return super().form_valid(form) + form.instance.save(supplements=supplements) + return HttpResponseRedirect(self.get_success_url()) def get_success_url(self): - return reverse_lazy('food:food_list') + return reverse_lazy('food:dish_list', kwargs={"activity_pk": self.kwargs["activity_pk"]}) class OrderListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):