1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-11-14 10:41:27 +01:00

Compare commits

...

5 Commits

Author SHA1 Message Date
Ehouarn
6cffe94bae 'Add all identical food' on ManageIngredients 2025-10-31 23:49:14 +01:00
Ehouarn
78372807f8 Autocomplete food on ManageIngredients now show owners 2025-10-31 23:26:17 +01:00
Ehouarn
b9bf01f2e3 Quark's tests now run, but they're still weak 2025-10-31 19:20:05 +01:00
Ehouarn
624f94823c Tests 2025-10-31 17:48:24 +01:00
Ehouarn
30a598c0b7 Fix dish form and add kitchen view 2025-10-31 17:47:11 +01:00
17 changed files with 543 additions and 135 deletions

View File

@@ -21,9 +21,13 @@ class FoodSerializer(serializers.ModelSerializer):
REST API Serializer for Food.
The djangorestframework plugin will analyse the model `Food` and parse all fields in the API.
"""
# This fields is used for autocompleting food in ManageIngredientsView
# TODO Find a better way to do it
owner_name = serializers.CharField(source='owner.name', read_only=True)
class Meta:
model = Food
fields = '__all__'
fields = ['name', 'owner', 'allergens', 'expiry_date', 'end_of_life', 'is_ready', 'order', 'owner_name']
class BasicFoodSerializer(serializers.ModelSerializer):

View File

@@ -3,7 +3,6 @@
from api.viewsets import ReadProtectedModelViewSet
from django_filters.rest_framework import DjangoFilterBackend
from django.utils import timezone
from rest_framework.filters import SearchFilter
from .serializers import AllergenSerializer, FoodSerializer, BasicFoodSerializer, TransformedFoodSerializer, QRCodeSerializer, \
@@ -114,12 +113,6 @@ class OrderViewSet(ReadProtectedModelViewSet):
filterset_fields = ['user', 'activity', 'dish', 'supplements', 'number', ]
search_fields = ['$user', '$activity', '$dish', '$supplements', '$number', ]
def perform_update(self, serializer):
instance = serializer.save()
if instance.served and not instance.served_at:
instance.served_at = timezone.now()
instance.save()
class FoodTransactionViewSet(ReadProtectedModelViewSet):
"""

View File

@@ -167,7 +167,7 @@ class ManageIngredientsForm(forms.Form):
model=Food,
resetable=True,
attrs={"api_url": "/api/food/food",
"class": "autocomplete"},
"class": "autocomplete manageingredients-autocomplete"},
)
name.label = _('Name')
@@ -181,6 +181,11 @@ class ManageIngredientsForm(forms.Form):
)
qrcode.label = _('QR code number')
add_all_same_name = forms.BooleanField(
required=False,
label=_("Add all identical food")
)
ManageIngredientsFormSet = forms.formset_factory(
ManageIngredientsForm,
@@ -219,7 +224,7 @@ SupplementFormSet = forms.inlineformset_factory(
Dish,
Supplement,
form=SupplementForm,
extra=0,
extra=1,
)

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.6 on 2025-10-31 17:46
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('food', '0003_dish_order_foodtransaction_supplement_and_more'),
]
operations = [
migrations.AlterField(
model_name='foodtransaction',
name='order',
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='transaction', to='food.order', verbose_name='order'),
),
]

View File

@@ -256,7 +256,7 @@ class TransformedFood(Food):
self.allergens.set(self.allergens.union(child.allergens.all()))
if not (child.polymorphic_ctype.model == 'basicfood' and child.date_type == 'DDM'):
self.expiry_date = min(self.expiry_date, child.expiry_date)
return super().save(force_insert, force_update, using, update_fields)
return super().save(force_insert=False, force_update=force_update, using=using, update_fields=update_fields)
class Meta:
verbose_name = _('Transformed food')
@@ -445,36 +445,29 @@ class Order(models.Model):
self.number = 1
else:
self.number = last_order.number + 1
super().save(*args, **kwargs)
elif self.served:
if FoodTransaction.objects.filter(order=self).exists():
transaction = FoodTransaction.objects.get(order=self)
transaction.valid = True
transaction.save()
else:
transaction = FoodTransaction(
source=self.user.note,
destination=self.activity.organizer.note,
amount=self.amount,
quantity=1,
valid=True,
order=self,
)
transaction.save()
transaction = FoodTransaction(
order=self,
source=self.user.note,
destination=self.activity.organizer.note,
amount=self.amount,
quantity=1,
)
transaction.save()
else:
if FoodTransaction.objects.filter(order=self).exists():
transaction = FoodTransaction.objects.get(order=self)
transaction.valid = False
transaction.save()
return super().save(*args, **kwargs)
old_object = Order.objects.get(pk=self.pk)
if not old_object.served and self.served:
self.served_at = timezone.now()
self.transaction.save()
super().save(*args, **kwargs)
class FoodTransaction(Transaction):
"""
Special type of :model:`note.Transaction` associated to a :model:`food.Order`.
"""
order = models.ForeignKey(
order = models.OneToOneField(
Order,
on_delete=models.PROTECT,
related_name='transaction',
@@ -484,3 +477,7 @@ class FoodTransaction(Transaction):
class Meta:
verbose_name = _("food transaction")
verbose_name_plural = _("food transactions")
def save(self, *args, **kwargs):
self.valid = self.order.served
super().save(*args, **kwargs)

View File

@@ -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 + '/',

View File

@@ -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',

View File

@@ -31,6 +31,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% trans "Update" %}
</a>
{% endif %}
<a class="btn btn-sm btn-primary" href="{% url "food:dish_list" activity_pk=dish.activity.pk %}">
{% trans "Return to dish list" %}
</a>
{% if delete %}
<a class="btn btn-sm btn-danger" href="{% url "food:dish_delete" activity_pk=dish.activity.pk pk=dish.pk %}">
{% trans "Delete" %}

View File

@@ -16,7 +16,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% crispy form %}
</div>
<h3 class="card-header text-center">
{% trans "Ajouter des suppléments (optionnel)" %}
{% trans "Add supplements (optional)" %}
</h3>
{{ formset.management_form }}
<table class="table table-condensed table-striped">

View File

@@ -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 %}
<!-- Colonne de plats -->
<div style="display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: 2rem;">
{% for food, quantity in orders.items %}
<div class="card bg-white mb-3" style="flex: 1 1 calc(33.333% - 1rem); border: 1px solid #ccc; padding: 1rem; border-radius: 0.5rem; box-sizing: border-box;">
<h3 class="card-header text-center">
<strong>{{ food }}</strong><br>
</h3>
<h1 class="card-body text-center">
{{ quantity }}</h1>
</div>
{% endfor %}
</div>
<!-- Colonne de la table -->
<div class="card bg-white mb-3">
<h3 class="card-header text-center">
{% trans "Special orders" %}
</h3>
{% if table.data %}
{% render_table table %}
{% else %}
<div class="card-body">
<div class="alert alert-warning">
{% trans "There are no special orders." %}
</div>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -22,6 +22,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<th>{{ form.name.label }}</th>
<th>{{ form.qrcode.label }}</th>
<th>{{ form.fully_used.label }}</th>
<th>{{ form.add_all_same_name.label }}</th>
</tr>
</thead>
<tbody id="form_body">
@@ -34,6 +35,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<td>{{ form.name }}</td>
<td>{{ form.qrcode }}</td>
<td>{{ form.fully_used }}</td>
<td>{{ form.add_all_same_name }}</td>
</tr>
{% endfor %}
</tbody>

View File

View File

@@ -6,9 +6,12 @@ from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from activity.models import Activity, ActivityType
from member.models import Club
from ..api.views import AllergenViewSet, BasicFoodViewSet, TransformedFoodViewSet, QRCodeViewSet
from ..models import Allergen, BasicFood, TransformedFood, QRCode
from ..api.views import AllergenViewSet, BasicFoodViewSet, TransformedFoodViewSet, QRCodeViewSet, \
DishViewSet, SupplementViewSet, OrderViewSet, FoodTransactionViewSet
from ..models import Allergen, BasicFood, TransformedFood, QRCode, Dish, Supplement, Order, FoodTransaction
class TestFood(TestCase):
@@ -53,73 +56,293 @@ class TestFood(TestCase):
food_container=self.basicfood,
)
def test_food_list(self):
"""
Display food list
"""
response = self.client.get(reverse('food:food_list'))
self.assertEqual(response.status_code, 200)
def test_food_list(self):
"""
Display food list
"""
response = self.client.get(reverse('food:food_list'))
self.assertEqual(response.status_code, 200)
def test_qrcode_create(self):
"""
Display QRCode creation
"""
response = self.client.get(reverse('food:qrcode_create'))
self.assertEqual(response.status_code, 200)
def test_qrcode_create(self):
"""
Display QRCode creation
"""
response = self.client.get(reverse('food:qrcode_create', kwargs={"slug": 2}))
self.assertEqual(response.status_code, 200)
def test_basicfood_create(self):
"""
Display BasicFood creation
"""
response = self.client.get(reverse('food:basicfood_create'))
self.assertEqual(response.status_code, 200)
def test_basicfood_create(self):
"""
Display BasicFood creation
"""
response = self.client.get(reverse('food:basicfood_create', kwargs={"slug": 2}))
self.assertEqual(response.status_code, 200)
def test_transformedfood_create(self):
"""
Display TransformedFood creation
"""
response = self.client.get(reverse('food:transformedfood_create'))
self.assertEqual(response.status_code, 200)
def test_transformedfood_create(self):
"""
Display TransformedFood creation
"""
response = self.client.get(reverse('food:transformedfood_create'))
self.assertEqual(response.status_code, 200)
def test_food_create(self):
"""
Display Food update
"""
response = self.client.get(reverse('food:food_update'))
self.assertEqual(response.status_code, 200)
def test_food_update(self):
"""
Display Food update
"""
response = self.client.get(reverse('food:food_update', args=(self.basicfood.pk,)))
self.assertEqual(response.status_code, 200)
def test_food_view(self):
"""
Display Food detail
"""
response = self.client.get(reverse('food:food_view'))
self.assertEqual(response.status_code, 302)
def test_food_view(self):
"""
Display Food detail
"""
response = self.client.get(reverse('food:food_view', args=(self.basicfood.pk,)))
self.assertEqual(response.status_code, 302)
def test_basicfood_view(self):
"""
Display BasicFood detail
"""
response = self.client.get(reverse('food:basicfood_view'))
self.assertEqual(response.status_code, 200)
def test_basicfood_view(self):
"""
Display BasicFood detail
"""
response = self.client.get(reverse('food:basicfood_view', args=(self.basicfood.pk,)))
self.assertEqual(response.status_code, 200)
def test_transformedfood_view(self):
"""
Display TransformedFood detail
"""
response = self.client.get(reverse('food:transformedfood_view'))
self.assertEqual(response.status_code, 200)
def test_transformedfood_view(self):
"""
Display TransformedFood detail
"""
response = self.client.get(reverse('food:transformedfood_view', args=(self.transformedfood.pk,)))
self.assertEqual(response.status_code, 200)
def test_add_ingredient(self):
"""
Display add ingredient view
"""
response = self.client.get(reverse('food:add_ingredient'))
self.assertEqual(response.status_code, 200)
def test_add_ingredient(self):
"""
Display add ingredient view
"""
response = self.client.get(reverse('food:add_ingredient', args=(self.transformedfood.pk,)))
self.assertEqual(response.status_code, 200)
class TestFoodOrder(TestCase):
"""
Test Food Order
"""
fixtures = ('initial',)
def setUp(self):
self.user = User.objects.create_superuser(
username='admintoto',
password='toto1234',
email='toto@example.com'
)
self.client.force_login(self.user)
sess = self.client.session
sess['permission_mask'] = 42
sess.save()
self.basicfood = BasicFood.objects.create(
id=1,
name='basicfood',
owner=Club.objects.get(name="BDE"),
expiry_date=timezone.now(),
is_ready=True,
date_type='DLC',
)
self.transformedfood = TransformedFood.objects.create(
id=2,
name='transformedfood',
owner=Club.objects.get(name="BDE"),
expiry_date=timezone.now(),
is_ready=True,
)
self.second_transformedfood = TransformedFood.objects.create(
id=3,
name='second transformedfood',
owner=Club.objects.get(name="BDE"),
expiry_date=timezone.now(),
is_ready=True,
)
self.third_transformedfood = TransformedFood.objects.create(
id=4,
name='third transformedfood',
owner=Club.objects.get(name="BDE"),
expiry_date=timezone.now(),
is_ready=True,
)
self.activity = Activity.objects.create(
activity_type=ActivityType.objects.get(name="Perm bouffe"),
organizer=Club.objects.get(name="BDE"),
creater=self.user,
attendees_club_id=1,
date_start=timezone.now(),
date_end=timezone.now(),
name="Test activity",
open=True,
valid=True,
)
self.dish = Dish.objects.create(
main=self.transformedfood,
price=500,
activity=self.activity,
available=True,
)
self.second_dish = Dish.objects.create(
main=self.second_transformedfood,
price=1000,
activity=self.activity,
available=True,
)
self.supplement = Supplement.objects.create(
dish=self.dish,
food=self.basicfood,
price=100,
)
self.order = Order.objects.create(
user=self.user,
activity=self.activity,
dish=self.dish,
)
self.order.supplements.add(self.supplement)
self.order.save()
def test_dish_list(self):
"""
Try to display dish list
"""
response = self.client.get(reverse("food:dish_list", kwargs={"activity_pk": self.activity.pk}))
self.assertEqual(response.status_code, 200)
def test_dish_create(self):
"""
Try to create a dish
"""
response = self.client.get(reverse("food:dish_create", kwargs={"activity_pk": self.activity.pk}))
self.assertEqual(response.status_code, 200)
response = self.client.post(reverse("food:dish_create", kwargs={"activity_pk": self.activity.pk}), data={
"main": self.third_transformedfood.pk,
"price": 4,
"activity": self.activity.pk,
"supplements-0-food": self.basicfood.pk,
"supplements-0-price": 0.5,
"supplements-TOTAL_FORMS": 1,
"supplements-INITIAL_FORMS": 0,
"supplements-MIN_NUM_FORMS": 0,
"supplements-MAX_NUM_FORMS": 1000,
})
self.assertRedirects(response, reverse("food:dish_list", kwargs={"activity_pk": self.activity.pk}), 302, 200)
self.assertTrue(Dish.objects.filter(main=self.third_transformedfood).exists())
self.assertTrue(Supplement.objects.filter(food=self.basicfood, price=50).exists())
def test_dish_update(self):
"""
Try to update a dish
"""
response = self.client.get(reverse("food:dish_update", kwargs={"activity_pk": self.activity.pk, "pk": self.dish.pk}))
self.assertEqual(response.status_code, 200)
response = self.client.post(reverse("food:dish_update", kwargs={"activity_pk": self.activity.pk, "pk": self.dish.pk}), data={
"price": 6,
"supplements-0-food": self.basicfood.pk,
"supplements-0-price": 1,
"supplements-1-food": self.basicfood.pk,
"supplements-1-price": 0.25,
"supplements-TOTAL_FORMS": 2,
"supplements-INITIAL_FORMS": 0,
"supplements-MIN_NUM_FORMS": 0,
"supplements-MAX_NUM_FORMS": 1000,
})
self.assertRedirects(response, reverse("food:dish_detail", kwargs={"activity_pk": self.activity.pk, "pk": self.dish.pk}), 302, 200)
self.dish.refresh_from_db()
self.assertTrue(Dish.objects.filter(main=self.transformedfood, price=600).exists())
self.assertTrue(Supplement.objects.filter(dish=self.dish, food=self.basicfood, price=25).exists())
def test_dish_detail(self):
"""
Try to display dish details
"""
response = self.client.get(reverse("food:dish_detail", kwargs={"activity_pk": self.activity.pk, "pk": self.dish.pk}))
self.assertEqual(response.status_code, 200)
def test_dish_delete(self):
"""
Try to delete a dish
"""
response = self.client.get(reverse("food:dish_delete", kwargs={"activity_pk": self.activity.pk, "pk": self.dish.pk}))
self.assertEqual(response.status_code, 200)
# Cannot delete already ordered Dish
response = self.client.delete(reverse("food:dish_delete", kwargs={"activity_pk": self.activity.pk, "pk": self.dish.pk}))
self.assertEqual(response.status_code, 403)
self.assertTrue(Dish.objects.filter(pk=self.dish.pk).exists())
# Can delete a Dish with no order
response = self.client.delete(reverse("food:dish_delete", kwargs={"activity_pk": self.activity.pk, "pk": self.second_dish.pk}))
self.assertRedirects(response, reverse("food:dish_list", kwargs={"activity_pk": self.activity.pk}))
self.assertFalse(Dish.objects.filter(pk=self.second_dish.pk).exists())
def test_order_food(self):
"""
Try to make an order
"""
response = self.client.get(reverse("food:order_create", kwargs={"activity_pk": self.activity.pk}))
self.assertEqual(response.status_code, 200)
response = self.client.post(reverse("food:order_create", kwargs={"activity_pk": self.activity.pk}), data=dict(
user=self.user.pk,
activity=self.activity.pk,
dish=self.second_dish.pk,
supplements=self.supplement.pk
))
self.assertRedirects(response, reverse("food:food_list"))
self.assertTrue(Order.objects.filter(user=self.user, dish=self.second_dish, activity=self.activity).exists())
def test_order_list(self):
"""
Try to display order list
"""
response = self.client.get(reverse("food:order_list", kwargs={"activity_pk": self.activity.pk}))
self.assertEqual(response.status_code, 200)
def test_served_order_list(self):
"""
Try to display served order list
"""
response = self.client.get(reverse("food:served_order_list", kwargs={"activity_pk": self.activity.pk}))
self.assertEqual(response.status_code, 200)
def test_serve_order(self):
"""
Try to serve an order, then to unserve it
"""
response = self.client.patch("/api/food/order/" + str(self.order.pk) + "/", data=dict(
served=True
), content_type="application/json")
self.assertEqual(response.status_code, 200)
self.order.refresh_from_db()
self.assertTrue(Order.objects.filter(dish=self.dish, user=self.user, served=True).exists())
self.assertIsNotNone(self.order.served_at)
self.assertTrue(FoodTransaction.objects.filter(order=self.order, valid=True).exists())
response = self.client.patch("/api/food/order/" + str(self.order.pk) + "/", data=dict(
served=False
), content_type="application/json")
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())
class TestFoodAPI(TestAPI):
def setUp(self) -> None:
super().setUP()
super().setUp()
self.allergen = Allergen.objects.create(
name='name',
@@ -145,26 +368,84 @@ class TestFoodAPI(TestAPI):
food_container=self.basicfood,
)
def test_allergen_api(self):
"""
Load Allergen API page and test all filters and permissions
"""
self.check_viewset(AllergenViewSet, '/api/food/allergen/')
self.activity = Activity.objects.create(
activity_type=ActivityType.objects.get(name="Perm bouffe"),
organizer=Club.objects.get(name="BDE"),
creater=self.user,
attendees_club_id=1,
date_start=timezone.now(),
date_end=timezone.now(),
name="Test activity",
open=True,
valid=True,
)
def test_basicfood_api(self):
"""
Load BasicFood API page and test all filters and permissions
"""
self.check_viewset(BasicFoodViewSet, '/api/food/basicfood/')
self.dish = Dish.objects.create(
main=self.transformedfood,
price=500,
activity=self.activity,
available=True,
)
self.supplement = Supplement.objects.create(
dish=self.dish,
food=self.basicfood,
price=100,
)
self.order = Order.objects.create(
user=self.user,
activity=self.activity,
dish=self.dish,
)
self.order.supplements.add(self.supplement)
self.order.save()
def test_allergen_api(self):
"""
Load Allergen API page and test all filters and permissions
"""
self.check_viewset(AllergenViewSet, '/api/food/allergen/')
def test_basicfood_api(self):
"""
Load BasicFood API page and test all filters and permissions
"""
self.check_viewset(BasicFoodViewSet, '/api/food/basicfood/')
# TODO Repair and detabulate this test
def test_transformedfood_api(self):
"""
Load TransformedFood API page and test all filters and permissions
"""
self.check_viewset(TransformedFoodViewSet, '/api/food/transformedfood/')
def test_qrcode_api(self):
"""
Load QRCode API page and test all filters and permissions
"""
self.check_viewset(QRCodeViewSet, '/api/food/qrcode/')
def test_qrcode_api(self):
"""
Load QRCode API page and test all filters and permissions
"""
self.check_viewset(QRCodeViewSet, '/api/food/qrcode/')
def test_dish_api(self):
"""
Load Dish API page and test all filters and permissions
"""
self.check_viewset(DishViewSet, '/api/food/dish/')
def test_supplement_api(self):
"""
Load Supplement API page and test all filters and permissions
"""
self.check_viewset(SupplementViewSet, '/api/food/supplement/')
def test_order_api(self):
"""
Load Order API page and test all filters and permissions
"""
self.check_viewset(OrderViewSet, '/api/food/order/')
def test_foodtransaction_api(self):
"""
Load FoodTransaction API page and test all filters and permissions
"""
self.check_viewset(FoodTransactionViewSet, '/api/food/foodtransaction/')

View File

@@ -28,5 +28,5 @@ urlpatterns = [
path('activity/<int:activity_pk>/order/', views.OrderCreateView.as_view(), name='order_create'),
path('activity/<int:activity_pk>/orders/', views.OrderListView.as_view(), name='order_list'),
path('activity/<int:activity_pk>/orders/served', views.ServedOrderListView.as_view(), name='served_order_list'),
path('activity/orders/<int:pk>/delete/', views.OrderDeleteView.as_view(), name='order_delete'),
path('activity/<int:activity_pk>/kitchen/', views.KitchenView.as_view(), name='kitchen'),
]

View File

@@ -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, \
@@ -307,11 +307,19 @@ class ManageIngredientsView(LoginRequiredMixin, UpdateView):
elif form.data[prefix + 'name'] != '':
ingredient = Food.objects.get(pk=form.data[prefix + 'name'])
self.object.ingredients.add(ingredient)
if (prefix + 'fully_used') in form.data and form.data[prefix + 'fully_used'] == 'on':
ingredient.end_of_life = _('Fully used in {meal}'.format(
meal=self.object.name))
ingredient.save()
if form.data.get(prefix + 'add_all_same_name') == 'on':
ingredients = Food.objects.filter(name=ingredient.name, owner=ingredient.owner, end_of_life='')
for ingredient in ingredients:
self.object.ingredients.add(ingredient)
if form.data.get(prefix + 'fully_used') == 'on':
ingredient.end_of_life = _('Fully used in {meal}'.format(meal=self.object.name))
ingredient.save()
else:
self.object.ingredients.add(ingredient)
if (prefix + 'fully_used') in form.data and form.data[prefix + 'fully_used'] == 'on':
ingredient.end_of_life = _('Fully used in {meal}'.format(
meal=self.object.name))
ingredient.save()
# We recalculate new expiry date and allergens
self.object.expiry_date = self.object.creation_date + self.object.shelf_life
self.object.allergens.clear()
@@ -591,7 +599,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 +665,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):
"""
@@ -726,7 +777,7 @@ class OrderListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, L
def get_queryset(self, **kwargs):
activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
return Order.objects.filter(activity=activity)
return Order.objects.filter(activity=activity).order_by('number')
def get_tables(self):
activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
@@ -787,17 +838,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

View File

@@ -74,7 +74,6 @@ class InvoiceCreateView(ProtectQuerysetMixin, ProtectedCreateView):
# For each product, we save it
formset = ProductFormSet(self.request.POST, instance=form.instance)
print(formset)
if formset.is_valid():
for f in formset:
# We don't save the product if the designation is not entered, ie. if the line is empty

View File

@@ -13,11 +13,14 @@ $(document).ready(function () {
target.addClass('is-invalid')
target.removeClass('is-valid')
const isManageIngredients = target.hasClass('manageingredients-autocomplete')
$.getJSON(api_url + (api_url.includes('?') ? '&' : '?') + 'format=json&search=^' + input + api_url_suffix, function (objects) {
let html = '<ul class="list-group list-group-flush" id="' + prefix + '_list">'
objects.results.forEach(function (obj) {
html += li(prefix + '_' + obj.id, obj[name_field])
const extra = isManageIngredients ? ` (${obj.owner_name})` : ''
html += li(`${prefix}_${obj.id}`, `${obj[name_field]}${extra}`)
})
html += '</ul>'