mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-21 18:08:21 +02:00
Rewrite food apps, new feature some changes to model
This commit is contained in:
402
apps/food/views.py
Normal file
402
apps/food/views.py
Normal file
@ -0,0 +1,402 @@
|
||||
# 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 django_tables2.views import MultiTableMixin
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.views.generic import DetailView, UpdateView
|
||||
from django.views.generic.list import ListView
|
||||
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 permission.backends import PermissionBackend
|
||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView, LoginRequiredMixin
|
||||
|
||||
from .models import Food, BasicFood, TransformedFood, QRCode
|
||||
from .forms import AddIngredientForms, BasicFoodForms, TransformedFoodForms, BasicFoodUpdateForms, TransformedFoodUpdateForms, QRCodeForms
|
||||
from .tables import FoodTable
|
||||
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}))
|
||||
else:
|
||||
qs = qs.none()
|
||||
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()).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)
|
||||
# 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')
|
||||
return context
|
||||
|
||||
|
||||
class QRCodeCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
"""
|
||||
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):
|
||||
return BasicFood(
|
||||
name="",
|
||||
owner_id=1,
|
||||
expiry_date=timezone.now(),
|
||||
is_ready=True,
|
||||
arrival_date=timezone.now(),
|
||||
date_type='DLC',
|
||||
)
|
||||
|
||||
@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)
|
||||
print(context['form'].fields)
|
||||
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):
|
||||
return TransformedFood(
|
||||
name="",
|
||||
owner_id=1,
|
||||
expiry_date=timezone.now(),
|
||||
is_ready=True,
|
||||
)
|
||||
|
||||
@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})
|
||||
|
||||
|
||||
class AddIngredientView(ProtectQuerysetMixin, 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()
|
||||
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()]
|
||||
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):
|
||||
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):
|
||||
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()
|
||||
return context
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'transformedfood')
|
||||
return super().get(*args, **kwargs)
|
Reference in New Issue
Block a user