mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-31 15:50:03 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			804 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			804 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
 | |
| # SPDX-License-Identifier: GPL-3.0-or-later
 | |
| 
 | |
| from datetime import timedelta
 | |
| 
 | |
| from api.viewsets import is_regex
 | |
| 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.http import HttpResponseRedirect, Http404
 | |
| from django.views.generic import DetailView, UpdateView, CreateView
 | |
| from django.views.generic.list import ListView
 | |
| from django.views.generic.base import RedirectView
 | |
| from django.views.generic.edit import DeleteView
 | |
| from django.urls import reverse_lazy
 | |
| from django.utils import timezone
 | |
| from django.utils.translation import gettext_lazy as _
 | |
| from member.models import Club, Membership
 | |
| 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 .forms import QRCodeForms, BasicFoodForms, TransformedFoodForms, \
 | |
|     ManageIngredientsForm, ManageIngredientsFormSet, AddIngredientForms, \
 | |
|     BasicFoodUpdateForms, TransformedFoodUpdateForms, \
 | |
|     DishForm, SupplementFormSet, SupplementFormSetHelper, OrderForm
 | |
| from .tables import FoodTable, DishTable, OrderTable
 | |
| from .utils import pretty_duration
 | |
| 
 | |
| 
 | |
| class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
 | |
|     """
 | |
|     Display Food
 | |
|     """
 | |
|     model = Food
 | |
|     tables = [FoodTable, FoodTable, FoodTable, ]
 | |
|     extra_context = {"title": _('Food')}
 | |
|     template_name = 'food/food_list.html'
 | |
| 
 | |
|     def get_queryset(self, **kwargs):
 | |
|         return super().get_queryset(**kwargs).distinct()
 | |
| 
 | |
|     def get_tables(self):
 | |
|         bureau_role_pk = 4
 | |
|         clubs = Club.objects.filter(membership__in=Membership.objects.filter(
 | |
|             user=self.request.user, roles=bureau_role_pk).filter(
 | |
|                 date_end__gte=timezone.now()))
 | |
| 
 | |
|         tables = [FoodTable] * (clubs.count() + 3)
 | |
|         self.tables = tables
 | |
|         tables = super().get_tables()
 | |
|         tables[0].prefix = 'search-'
 | |
|         tables[1].prefix = 'open-'
 | |
|         tables[2].prefix = 'served-'
 | |
|         for i in range(clubs.count()):
 | |
|             tables[i + 3].prefix = clubs[i].name
 | |
|         return tables
 | |
| 
 | |
|     def get_tables_data(self):
 | |
|         # table search
 | |
|         qs = self.get_queryset().order_by('name')
 | |
|         if "search" in self.request.GET and self.request.GET['search']:
 | |
|             pattern = self.request.GET['search']
 | |
| 
 | |
|             # check regex
 | |
|             valid_regex = is_regex(pattern)
 | |
|             suffix = '__iregex' if valid_regex else '__istartswith'
 | |
|             prefix = '^' if valid_regex else ''
 | |
|             qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern})
 | |
|                            | Q(**{f'owner__name{suffix}': prefix + pattern})
 | |
|                            | Q(**{f'owner__note__alias__name{suffix}': prefix + pattern}))
 | |
|         else:
 | |
|             qs = qs.none()
 | |
|         if "stock" not in self.request.GET or not self.request.GET["stock"] == '1':
 | |
|             qs = qs.filter(end_of_life='')
 | |
| 
 | |
|         search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view'))
 | |
|         # table open
 | |
|         open_table = self.get_queryset().order_by('expiry_date').filter(
 | |
|             Q(polymorphic_ctype__model='transformedfood')
 | |
|             | Q(polymorphic_ctype__model='basicfood', basicfood__date_type='DLC')).filter(
 | |
|                 expiry_date__lt=timezone.now(), end_of_life='').filter(
 | |
|                     PermissionBackend.filter_queryset(self.request, Food, 'view'))
 | |
|         # table served
 | |
|         served_table = self.get_queryset().order_by('-pk').filter(
 | |
|             end_of_life='', is_ready=True).exclude(
 | |
|                 Q(polymorphic_ctype__model='basicfood',
 | |
|                   basicfood__date_type='DLC',
 | |
|                   expiry_date__lte=timezone.now(),)
 | |
|                 | Q(polymorphic_ctype__model='transformedfood',
 | |
|                     expiry_date__lte=timezone.now(),
 | |
|                     ))
 | |
|         # tables club
 | |
|         bureau_role_pk = 4
 | |
|         clubs = Club.objects.filter(membership__in=Membership.objects.filter(
 | |
|             user=self.request.user, roles=bureau_role_pk).filter(
 | |
|                 date_end__gte=timezone.now()))
 | |
|         club_table = []
 | |
|         for club in clubs:
 | |
|             club_table.append(self.get_queryset().order_by('expiry_date').filter(
 | |
|                 owner=club, end_of_life='').filter(
 | |
|                     PermissionBackend.filter_queryset(self.request, Food, 'view')
 | |
|             ))
 | |
| 
 | |
|         return [search_table, open_table, served_table] + club_table
 | |
| 
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
| 
 | |
|         tables = context['tables']
 | |
|         # for extends base_search.html we need to name 'search_table' in 'table'
 | |
|         for name, table in zip(['table', 'open', 'served'], tables):
 | |
|             context[name] = table
 | |
|         context['club_tables'] = tables[3:]
 | |
| 
 | |
|         context['can_add_meal'] = PermissionBackend.check_perm(self.request, 'food.transformedfood_add')
 | |
| 
 | |
|         context["open_activities"] = Activity.objects.filter(activity_type__name="Perm bouffe", open=True)
 | |
| 
 | |
|         return context
 | |
| 
 | |
| 
 | |
| class QRCodeCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
 | |
|     """
 | |
|     A view to add qrcode
 | |
|     """
 | |
|     model = QRCode
 | |
|     template_name = 'food/qrcode.html'
 | |
|     form_class = QRCodeForms
 | |
|     extra_context = {"title": _("Add a new QRCode")}
 | |
| 
 | |
|     def get(self, *args, **kwargs):
 | |
|         qrcode = kwargs["slug"]
 | |
|         if self.model.objects.filter(qr_code_number=qrcode).count() > 0:
 | |
|             pk = self.model.objects.get(qr_code_number=qrcode).food_container.pk
 | |
|             return HttpResponseRedirect(reverse_lazy("food:food_view", kwargs={"pk": pk}))
 | |
|         else:
 | |
|             return super().get(*args, **kwargs)
 | |
| 
 | |
|     @transaction.atomic
 | |
|     def form_valid(self, form):
 | |
|         qrcode_food_form = QRCodeForms(data=self.request.POST)
 | |
|         if not qrcode_food_form.is_valid():
 | |
|             return self.form_invalid(form)
 | |
| 
 | |
|         qrcode = form.save(commit=False)
 | |
|         qrcode.qr_code_number = self.kwargs['slug']
 | |
|         qrcode._force_save = True
 | |
|         qrcode.save()
 | |
|         qrcode.refresh_from_db()
 | |
|         return super().form_valid(form)
 | |
| 
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
|         context['slug'] = self.kwargs['slug']
 | |
| 
 | |
|         # get last 10 BasicFood objects with distincts 'name' ordered by '-pk'
 | |
|         # we can't use .distinct and .order_by with differents columns hence the generator
 | |
|         context['last_items'] = [food for food in BasicFood.get_lastests_objects(10, 'name', '-pk')]
 | |
|         return context
 | |
| 
 | |
|     def get_success_url(self, **kwargs):
 | |
|         self.object.refresh_from_db()
 | |
|         return reverse_lazy('food:food_view', kwargs={'pk': self.object.food_container.pk})
 | |
| 
 | |
|     def get_sample_object(self):
 | |
|         return QRCode(
 | |
|             qr_code_number=self.kwargs['slug'],
 | |
|             food_container_id=1,
 | |
|         )
 | |
| 
 | |
| 
 | |
| class BasicFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
 | |
|     """
 | |
|     A view to add basicfood
 | |
|     """
 | |
|     model = BasicFood
 | |
|     form_class = BasicFoodForms
 | |
|     extra_context = {"title": _("Add an aliment")}
 | |
|     template_name = "food/food_update.html"
 | |
| 
 | |
|     def get_sample_object(self):
 | |
|         # We choose a club which may work or BDE else
 | |
|         food = BasicFood(
 | |
|             name="",
 | |
|             owner_id=1,
 | |
|             expiry_date=timezone.now(),
 | |
|             is_ready=True,
 | |
|             arrival_date=timezone.now(),
 | |
|             date_type='DLC',
 | |
|         )
 | |
| 
 | |
|         for membership in self.request.user.memberships.all():
 | |
|             club_id = membership.club.id
 | |
|             food.owner_id = club_id
 | |
|             if PermissionBackend.check_perm(self.request, "food.add_basicfood", food):
 | |
|                 return food
 | |
| 
 | |
|         return food
 | |
| 
 | |
|     @transaction.atomic
 | |
|     def form_valid(self, form):
 | |
|         if QRCode.objects.filter(qr_code_number=self.kwargs['slug']).count() > 0:
 | |
|             return HttpResponseRedirect(reverse_lazy('food:qrcode_create', kwargs={'slug': self.kwargs['slug']}))
 | |
|         food_form = BasicFoodForms(data=self.request.POST)
 | |
|         if not food_form.is_valid():
 | |
|             return self.form_invalid(form)
 | |
| 
 | |
|         food = form.save(commit=False)
 | |
|         food.is_ready = False
 | |
|         food.save()
 | |
|         food.refresh_from_db()
 | |
| 
 | |
|         qrcode = QRCode()
 | |
|         qrcode.qr_code_number = self.kwargs['slug']
 | |
|         qrcode.food_container = food
 | |
|         qrcode.save()
 | |
| 
 | |
|         return super().form_valid(form)
 | |
| 
 | |
|     def get_success_url(self, **kwargs):
 | |
|         self.object.refresh_from_db()
 | |
|         return reverse_lazy('food:basicfood_view', kwargs={"pk": self.object.pk})
 | |
| 
 | |
|     def get_context_data(self, *args, **kwargs):
 | |
|         context = super().get_context_data(*args, **kwargs)
 | |
| 
 | |
|         copy = self.request.GET.get('copy', None)
 | |
|         if copy is not None:
 | |
|             food = BasicFood.objects.get(pk=copy)
 | |
| 
 | |
|             for field in context['form'].fields:
 | |
|                 if field == 'allergens':
 | |
|                     context['form'].fields[field].initial = getattr(food, field).all()
 | |
|                 else:
 | |
|                     context['form'].fields[field].initial = getattr(food, field)
 | |
| 
 | |
|         return context
 | |
| 
 | |
| 
 | |
| class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
 | |
|     """
 | |
|     A view to add transformedfood
 | |
|     """
 | |
|     model = TransformedFood
 | |
|     form_class = TransformedFoodForms
 | |
|     extra_context = {"title": _("Add a meal")}
 | |
|     template_name = "food/food_update.html"
 | |
| 
 | |
|     def get_sample_object(self):
 | |
|         # We choose a club which may work or BDE else
 | |
|         food = TransformedFood(
 | |
|             name="",
 | |
|             owner_id=1,
 | |
|             expiry_date=timezone.now(),
 | |
|             is_ready=True,
 | |
|         )
 | |
| 
 | |
|         for membership in self.request.user.memberships.all():
 | |
|             club_id = membership.club.id
 | |
|             food.owner_id = club_id
 | |
|             if PermissionBackend.check_perm(self.request, "food.add_transformedfood", food):
 | |
|                 return food
 | |
| 
 | |
|         return food
 | |
| 
 | |
|     @transaction.atomic
 | |
|     def form_valid(self, form):
 | |
|         form.instance.expiry_date = timezone.now() + timedelta(days=3)
 | |
|         form.instance.is_ready = False
 | |
|         return super().form_valid(form)
 | |
| 
 | |
|     def get_success_url(self, **kwargs):
 | |
|         self.object.refresh_from_db()
 | |
|         return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk})
 | |
| 
 | |
| 
 | |
| MAX_FORMS = 100
 | |
| 
 | |
| 
 | |
| class ManageIngredientsView(LoginRequiredMixin, UpdateView):
 | |
|     """
 | |
|     A view to manage ingredient for a transformed food
 | |
|     """
 | |
|     model = TransformedFood
 | |
|     fields = ['ingredients']
 | |
|     extra_context = {"title": _("Manage ingredients of:")}
 | |
|     template_name = 'food/manage_ingredients.html'
 | |
| 
 | |
|     @transaction.atomic
 | |
|     def form_valid(self, form):
 | |
|         old_ingredients = list(self.object.ingredients.all()).copy()
 | |
|         old_allergens = list(self.object.allergens.all()).copy()
 | |
|         self.object.ingredients.clear()
 | |
|         for i in range(self.object.ingredients.all().count() + 1 + MAX_FORMS):
 | |
|             prefix = 'form-' + str(i) + '-'
 | |
|             if form.data[prefix + 'qrcode'] not in ['0', '']:
 | |
|                 ingredient = QRCode.objects.get(pk=form.data[prefix + 'qrcode']).food_container
 | |
|                 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()
 | |
| 
 | |
|             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()
 | |
|         # We recalculate new expiry date and allergens
 | |
|         self.object.expiry_date = self.object.creation_date + self.object.shelf_life
 | |
|         self.object.allergens.clear()
 | |
| 
 | |
|         for ingredient in self.object.ingredients.iterator():
 | |
|             if not (ingredient.polymorphic_ctype.model == 'basicfood' and ingredient.date_type == 'DDM'):
 | |
|                 self.object.expiry_date = min(self.object.expiry_date, ingredient.expiry_date)
 | |
|             self.object.allergens.set(self.object.allergens.union(ingredient.allergens.all()))
 | |
| 
 | |
|         self.object.save(old_ingredients=old_ingredients, old_allergens=old_allergens)
 | |
|         return HttpResponseRedirect(self.get_success_url())
 | |
| 
 | |
|     def get_context_data(self, *args, **kwargs):
 | |
|         context = super().get_context_data(*args, **kwargs)
 | |
|         context['title'] += ' ' + self.object.name
 | |
|         formset = ManageIngredientsFormSet()
 | |
|         ingredients = self.object.ingredients.all()
 | |
|         formset.extra += ingredients.count() + MAX_FORMS
 | |
|         context['form'] = ManageIngredientsForm()
 | |
|         context['ingredients_count'] = ingredients.count()
 | |
|         display = [True] * (1 + ingredients.count()) + [False] * (formset.extra - ingredients.count() - 1)
 | |
|         context['formset'] = zip(display, formset)
 | |
|         context['ingredients'] = []
 | |
|         for ingredient in ingredients:
 | |
|             qr = QRCode.objects.filter(food_container=ingredient)
 | |
| 
 | |
|             context['ingredients'].append({
 | |
|                 'food_pk': ingredient.pk,
 | |
|                 'food_name': ingredient.name,
 | |
|                 'qr_pk': '' if qr.count() == 0 else qr[0].pk,
 | |
|                 'qr_number': '' if qr.count() == 0 else qr[0].qr_code_number,
 | |
|                 'fully_used': 'true' if ingredient.end_of_life else '',
 | |
|             })
 | |
|         return context
 | |
| 
 | |
|     def get_success_url(self, **kwargs):
 | |
|         return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk})
 | |
| 
 | |
| 
 | |
| class AddIngredientView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | |
|     """
 | |
|     A view to add ingredient to a meal
 | |
|     """
 | |
|     model = Food
 | |
|     extra_context = {"title": _("Add the ingredient:")}
 | |
|     form_class = AddIngredientForms
 | |
|     template_name = 'food/food_update.html'
 | |
| 
 | |
|     def get_context_data(self, *args, **kwargs):
 | |
|         context = super().get_context_data(*args, **kwargs)
 | |
|         context['title'] += ' ' + self.object.name
 | |
|         return context
 | |
| 
 | |
|     @transaction.atomic
 | |
|     def form_valid(self, form):
 | |
|         meals = TransformedFood.objects.filter(pk__in=form.data.getlist('ingredients')).all()
 | |
|         if not meals:
 | |
|             return HttpResponseRedirect(reverse_lazy('food:food_view', kwargs={"pk": self.object.pk}))
 | |
|         for meal in meals:
 | |
|             old_ingredients = list(meal.ingredients.all()).copy()
 | |
|             old_allergens = list(meal.allergens.all()).copy()
 | |
|             meal.ingredients.add(self.object.pk)
 | |
|             # update allergen and expiry date if necessary
 | |
|             if not (self.object.polymorphic_ctype.model == 'basicfood'
 | |
|                     and self.object.date_type == 'DDM'):
 | |
|                 meal.expiry_date = min(meal.expiry_date, self.object.expiry_date)
 | |
|             meal.allergens.set(meal.allergens.union(self.object.allergens.all()))
 | |
|             meal.save(old_ingredients=old_ingredients, old_allergens=old_allergens)
 | |
|             if 'fully_used' in form.data:
 | |
|                 if not self.object.end_of_life:
 | |
|                     self.object.end_of_life = _(f'Food fully used in : {meal.name}')
 | |
|                 else:
 | |
|                     self.object.end_of_life += ', ' + meal.name
 | |
|         if 'fully_used' in form.data:
 | |
|             self.object.is_ready = False
 | |
|         self.object.save()
 | |
|         # We redirect only the first parent
 | |
|         parent_pk = meals[0].pk
 | |
|         return HttpResponseRedirect(self.get_success_url(parent_pk=parent_pk))
 | |
| 
 | |
|     def get_success_url(self, **kwargs):
 | |
|         return reverse_lazy('food:transformedfood_view', kwargs={"pk": kwargs['parent_pk']})
 | |
| 
 | |
| 
 | |
| class FoodUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | |
|     """
 | |
|     A view to update Food
 | |
|     """
 | |
|     model = Food
 | |
|     extra_context = {"title": _("Update an aliment")}
 | |
|     template_name = 'food/food_update.html'
 | |
| 
 | |
|     @transaction.atomic
 | |
|     def form_valid(self, form):
 | |
|         form.instance.creater = self.request.user
 | |
|         food = Food.objects.get(pk=self.kwargs['pk'])
 | |
|         old_allergens = list(food.allergens.all()).copy()
 | |
| 
 | |
|         if food.polymorphic_ctype.model == 'transformedfood':
 | |
|             old_ingredients = food.ingredients.all()
 | |
|             form.instance.shelf_life = timedelta(
 | |
|                 seconds=int(form.data['shelf_life']) * 60 * 60)
 | |
| 
 | |
|         food_form = self.get_form_class()(data=self.request.POST)
 | |
|         if not food_form.is_valid():
 | |
|             return self.form_invalid(form)
 | |
|         ans = super().form_valid(form)
 | |
|         if food.polymorphic_ctype.model == 'transformedfood':
 | |
|             form.instance.save(old_ingredients=old_ingredients)
 | |
|         else:
 | |
|             form.instance.save(old_allergens=old_allergens)
 | |
|         return ans
 | |
| 
 | |
|     def get_form_class(self, **kwargs):
 | |
|         food = Food.objects.get(pk=self.kwargs['pk'])
 | |
|         if food.polymorphic_ctype.model == 'basicfood':
 | |
|             return BasicFoodUpdateForms
 | |
|         else:
 | |
|             return TransformedFoodUpdateForms
 | |
| 
 | |
|     def get_form(self, **kwargs):
 | |
|         form = super().get_form(**kwargs)
 | |
|         if 'shelf_life' in form.initial:
 | |
|             hours = form.initial['shelf_life'].days * 24 + form.initial['shelf_life'].seconds // 3600
 | |
|             form.initial['shelf_life'] = hours
 | |
|         return form
 | |
| 
 | |
|     def get_success_url(self, **kwargs):
 | |
|         self.object.refresh_from_db()
 | |
|         return reverse_lazy('food:food_view', kwargs={"pk": self.object.pk})
 | |
| 
 | |
| 
 | |
| class FoodDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
 | |
|     """
 | |
|     A view to see a food
 | |
|     """
 | |
|     model = Food
 | |
|     extra_context = {"title": _('Details of:')}
 | |
|     context_object_name = "food"
 | |
|     template_name = "food/food_detail.html"
 | |
| 
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
|         fields = ["name", "owner", "expiry_date", "allergens", "is_ready", "end_of_life", "order"]
 | |
| 
 | |
|         fields = dict([(field, getattr(self.object, field)) for field in fields])
 | |
|         if fields["is_ready"]:
 | |
|             fields["is_ready"] = _("Yes")
 | |
|         else:
 | |
|             fields["is_ready"] = _("No")
 | |
|         fields["allergens"] = ", ".join(
 | |
|             allergen.name for allergen in fields["allergens"].all())
 | |
| 
 | |
|         context["fields"] = [(
 | |
|             Food._meta.get_field(field).verbose_name.capitalize(),
 | |
|             value) for field, value in fields.items()]
 | |
|         if self.object.QR_code.exists():
 | |
|             context["QR_code"] = self.object.QR_code.first()
 | |
|         context["meals"] = self.object.transformed_ingredient_inv.all()
 | |
|         context["update"] = PermissionBackend.check_perm(self.request, "food.change_food")
 | |
|         context["add_ingredient"] = (self.object.end_of_life == '' and PermissionBackend.check_perm(self.request, "food.change_transformedfood"))
 | |
|         return context
 | |
| 
 | |
|     def get(self, *args, **kwargs):
 | |
|         if Food.objects.filter(pk=kwargs['pk']).count() != 1:
 | |
|             return Http404
 | |
|         model = Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model
 | |
|         if 'stop_redirect' in kwargs and kwargs['stop_redirect']:
 | |
|             return super().get(*args, **kwargs)
 | |
|         kwargs = {'pk': kwargs['pk']}
 | |
|         if model == 'basicfood':
 | |
|             return HttpResponseRedirect(reverse_lazy("food:basicfood_view", kwargs=kwargs))
 | |
|         return HttpResponseRedirect(reverse_lazy("food:transformedfood_view", kwargs=kwargs))
 | |
| 
 | |
| 
 | |
| class BasicFoodDetailView(FoodDetailView):
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
|         fields = ['arrival_date', 'date_type']
 | |
|         for field in fields:
 | |
|             context["fields"].append((
 | |
|                 BasicFood._meta.get_field(field).verbose_name.capitalize(),
 | |
|                 getattr(self.object, field)
 | |
|             ))
 | |
|         return context
 | |
| 
 | |
|     def get(self, *args, **kwargs):
 | |
|         if Food.objects.filter(pk=kwargs['pk']).count() == 1:
 | |
|             kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'basicfood')
 | |
|         return super().get(*args, **kwargs)
 | |
| 
 | |
| 
 | |
| class TransformedFoodDetailView(FoodDetailView):
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
|         context["fields"].append((
 | |
|             TransformedFood._meta.get_field("creation_date").verbose_name.capitalize(),
 | |
|             self.object.creation_date
 | |
|         ))
 | |
|         context["fields"].append((
 | |
|             TransformedFood._meta.get_field("shelf_life").verbose_name.capitalize(),
 | |
|             pretty_duration(self.object.shelf_life)
 | |
|         ))
 | |
|         context["foods"] = self.object.ingredients.all()
 | |
|         context["manage_ingredients"] = True
 | |
|         return context
 | |
| 
 | |
|     def get(self, *args, **kwargs):
 | |
|         if Food.objects.filter(pk=kwargs['pk']).count() == 1:
 | |
|             kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'transformedfood')
 | |
|         return super().get(*args, **kwargs)
 | |
| 
 | |
| 
 | |
| class QRCodeRedirectView(RedirectView):
 | |
|     """
 | |
|     Redirects to the QR code creation page from Food List
 | |
|     """
 | |
|     def get_redirect_url(self, *args, **kwargs):
 | |
|         slug = self.request.GET.get('slug')
 | |
|         if slug:
 | |
|             return reverse_lazy('food:qrcode_create', kwargs={'slug': slug})
 | |
|         return reverse_lazy('food:list')
 | |
| 
 | |
| 
 | |
| class DishCreateView(ProtectQuerysetMixin, ProtectedCreateView):
 | |
|     """
 | |
|     Create a dish
 | |
|     """
 | |
|     model = Dish
 | |
|     form_class = DishForm
 | |
|     extra_context = {"title": _('Create dish')}
 | |
| 
 | |
|     def get_sample_object(self):
 | |
|         activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
 | |
|         sample_food = TransformedFood(
 | |
|             name="Sample food",
 | |
|             owner=activity.organizer,
 | |
|             expiry_date=timezone.now() + timedelta(days=7),
 | |
|             is_ready=True,
 | |
|         )
 | |
|         sample_dish = Dish(
 | |
|             main=sample_food,
 | |
|             price=100,
 | |
|             activity=activity,
 | |
|         )
 | |
|         return sample_dish
 | |
| 
 | |
|     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 "available" in form.fields:
 | |
|             del form.fields["available"]
 | |
|         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)
 | |
|         if formset.is_valid():
 | |
|             for f in formset:
 | |
|                 if f.is_valid():
 | |
|                     f.save()
 | |
|                     f.instance.save()
 | |
|                 else:
 | |
|                     f.instance = None
 | |
| 
 | |
|         return ret
 | |
| 
 | |
|     def get_success_url(self):
 | |
|         return reverse_lazy('food:dish_list', kwargs={"activity_pk": self.kwargs["activity_pk"]})
 | |
| 
 | |
| 
 | |
| class DishListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
 | |
|     """
 | |
|     List dishes for this activity
 | |
|     """
 | |
|     model = Dish
 | |
|     table_class = DishTable
 | |
|     extra_context = {"title": _('Dishes served during')}
 | |
|     template_name = 'food/dish_list.html'
 | |
| 
 | |
|     def get_queryset(self):
 | |
|         return super().get_queryset().filter(activity__pk=self.kwargs["activity_pk"])
 | |
| 
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
| 
 | |
|         activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
 | |
|         context["activity"] = activity
 | |
| 
 | |
|         context["can_add_dish"] = PermissionBackend.check_perm(self.request, 'food.dish_add')
 | |
| 
 | |
|         return context
 | |
| 
 | |
| 
 | |
| class DishDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
 | |
|     """
 | |
|     View a dish for this activity
 | |
|     """
 | |
|     model = Dish
 | |
|     extra_context = {"title": _('Details of:')}
 | |
|     context_oject_name = "dish"
 | |
| 
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
| 
 | |
|         context["food"] = self.object.main
 | |
| 
 | |
|         context["supplements"] = self.object.supplements.all()
 | |
| 
 | |
|         context["update"] = PermissionBackend.check_perm(self.request, "food.change_dish")
 | |
| 
 | |
|         context["delete"] = not Order.objects.filter(dish=self.get_object()).exists() and PermissionBackend.check_perm(self.request, "food.delete_dish")
 | |
| 
 | |
|         return context
 | |
| 
 | |
| 
 | |
| class DishUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | |
|     """
 | |
|     A view to update a dish
 | |
|     """
 | |
|     model = Dish
 | |
|     form_class = DishForm
 | |
|     extra_context = {"title": _("Update a dish")}
 | |
| 
 | |
|     def get_form(self, **kwargs):
 | |
|         form = super().get_form(**kwargs)
 | |
|         if 'main' in form.fields:
 | |
|             del form.fields["main"]
 | |
|         return form
 | |
| 
 | |
| 
 | |
| class DishDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView):
 | |
|     """
 | |
|     Delete a dish with no order yet
 | |
|     """
 | |
|     model = Dish
 | |
|     extra_context = {"title": _('Delete dish')}
 | |
| 
 | |
|     def delete(self, request, *args, **kwargs):
 | |
|         if Order.objects.filter(dish=self.get_object()).exists():
 | |
|             raise PermissionDenied(_("This dish cannot be deleted because it has already been ordered"))
 | |
|         return super().delete(request, *args, **kwargs)
 | |
| 
 | |
|     def get_success_url(self):
 | |
|         return reverse_lazy('food:dish_list', kwargs={"activity_pk": self.kwargs["activity_pk"]})
 | |
| 
 | |
| 
 | |
| class OrderCreateView(ProtectQuerysetMixin, ProtectedCreateView):
 | |
|     """
 | |
|     Order a meal
 | |
|     """
 | |
|     model = Order
 | |
|     form_class = OrderForm
 | |
|     extra_context = {"title": _('Order food')}
 | |
| 
 | |
|     def get_sample_object(self):
 | |
|         activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
 | |
|         sample_order = Order(
 | |
|             user=self.request.user,
 | |
|             activity=activity,
 | |
|             dish=Dish.objects.filter(activity=activity).last(),
 | |
|         )
 | |
|         return sample_order
 | |
| 
 | |
|     def get_form(self):
 | |
|         form = super().get_form()
 | |
| 
 | |
|         form.fields["user"].initial = self.request.user
 | |
|         form.fields["user"].disabled = True
 | |
| 
 | |
|         return form
 | |
| 
 | |
|     def form_valid(self, form):
 | |
|         activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
 | |
| 
 | |
|         form.instance.activity = activity
 | |
| 
 | |
|         return super().form_valid(form)
 | |
| 
 | |
|     def get_success_url(self):
 | |
|         return reverse_lazy('food:food_list')
 | |
| 
 | |
| 
 | |
| class OrderListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
 | |
|     """
 | |
|     List existing Families
 | |
|     """
 | |
|     model = Order
 | |
|     table_class = OrderTable
 | |
|     extra_context = {"title": _('Order list')}
 | |
|     paginate_by = 10
 | |
| 
 | |
|     def get_queryset(self, **kwargs):
 | |
|         activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
 | |
|         return Order.objects.filter(activity=activity)
 | |
| 
 | |
|     def get_tables(self):
 | |
|         activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
 | |
|         dishes = Dish.objects.filter(activity=activity)
 | |
| 
 | |
|         tables = [OrderTable] * dishes.count()
 | |
|         self.tables = tables
 | |
|         tables = super().get_tables()
 | |
|         for i in range(dishes.count()):
 | |
|             tables[i].prefix = dishes[i].main.name
 | |
|         return tables
 | |
| 
 | |
|     def get_tables_data(self):
 | |
|         activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
 | |
|         dishes = Dish.objects.filter(activity=activity)
 | |
| 
 | |
|         tables = []
 | |
| 
 | |
|         for dish in dishes:
 | |
|             tables.append(self.get_queryset().order_by('ordered_at').filter(
 | |
|                 dish=dish, served=False).filter(
 | |
|                     PermissionBackend.filter_queryset(self.request, Order, 'view')
 | |
|             ))
 | |
| 
 | |
|         return tables
 | |
| 
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
| 
 | |
|         context["activity"] = Activity.objects.get(pk=self.kwargs["activity_pk"])
 | |
| 
 | |
|         return context
 | |
| 
 | |
| 
 | |
| class ServedOrderListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
 | |
|     """
 | |
|     View served orders
 | |
|     """
 | |
|     model = Order
 | |
|     template_name = 'food/served_order_list.html'
 | |
|     table_class = OrderTable
 | |
| 
 | |
|     def get_queryset(self):
 | |
|         return super().get_queryset().filter(activity__pk=self.kwargs["activity_pk"], served=True).order_by('-served_at')
 | |
| 
 | |
|     def get_table(self, **kwargs):
 | |
|         table = super().get_table(**kwargs)
 | |
| 
 | |
|         table.columns.hide("delete")
 | |
| 
 | |
|         return table
 | |
| 
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
| 
 | |
|         context["activity"] = Activity.objects.get(pk=self.kwargs["activity_pk"])
 | |
| 
 | |
|         return context
 | |
| 
 | |
| 
 | |
| class OrderDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView):
 | |
|     """
 | |
|     Delete an order
 | |
|     """
 | |
|     model = Order
 | |
|     extra_context = {"title": _('Delete dish')}
 | |
| 
 | |
|     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_success_url(self):
 | |
|         return reverse_lazy('food:order_list', kwargs={"activity_pk": self.kwargs["activity_pk"]})
 |