mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-04-23 23:22:38 +00:00
403 lines
15 KiB
Python
403 lines
15 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 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)
|