mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-11-07 07:29:45 +01:00
Add field 'traces' for model Food
This commit is contained in:
@@ -55,7 +55,7 @@ class BasicFoodForms(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = BasicFood
|
model = BasicFood
|
||||||
fields = ('name', 'owner', 'date_type', 'expiry_date', 'allergens', 'order',)
|
fields = ('name', 'owner', 'date_type', 'expiry_date', 'allergens', 'traces', 'order',)
|
||||||
widgets = {
|
widgets = {
|
||||||
"owner": Autocomplete(
|
"owner": Autocomplete(
|
||||||
model=Club,
|
model=Club,
|
||||||
@@ -98,7 +98,7 @@ class BasicFoodUpdateForms(forms.ModelForm):
|
|||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = BasicFood
|
model = BasicFood
|
||||||
fields = ('name', 'owner', 'date_type', 'expiry_date', 'end_of_life', 'is_ready', 'order', 'allergens')
|
fields = ('name', 'owner', 'date_type', 'expiry_date', 'end_of_life', 'is_ready', 'order', 'allergens', 'traces')
|
||||||
widgets = {
|
widgets = {
|
||||||
"owner": Autocomplete(
|
"owner": Autocomplete(
|
||||||
model=Club,
|
model=Club,
|
||||||
|
|||||||
18
apps/food/migrations/0005_food_traces.py
Normal file
18
apps/food/migrations/0005_food_traces.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.6 on 2025-11-02 17:38
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('food', '0004_alter_foodtransaction_order'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='food',
|
||||||
|
name='traces',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='food_with_traces', to='food.allergen', verbose_name='traces'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -53,6 +53,13 @@ class Food(PolymorphicModel):
|
|||||||
verbose_name=_('allergens'),
|
verbose_name=_('allergens'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
traces = models.ManyToManyField(
|
||||||
|
Allergen,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_('traces'),
|
||||||
|
related_name='food_with_traces'
|
||||||
|
)
|
||||||
|
|
||||||
expiry_date = models.DateTimeField(
|
expiry_date = models.DateTimeField(
|
||||||
verbose_name=_('expiry date'),
|
verbose_name=_('expiry date'),
|
||||||
null=False,
|
null=False,
|
||||||
@@ -91,6 +98,19 @@ class Food(PolymorphicModel):
|
|||||||
if old_allergens != list(parent.allergens.all()):
|
if old_allergens != list(parent.allergens.all()):
|
||||||
parent.save(old_allergens=old_allergens)
|
parent.save(old_allergens=old_allergens)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def update_traces(self):
|
||||||
|
# update parents
|
||||||
|
for parent in self.transformed_ingredient_inv.iterator():
|
||||||
|
old_traces = list(parent.traces.all()).copy()
|
||||||
|
parent.traces.clear()
|
||||||
|
for child in parent.ingredients.iterator():
|
||||||
|
if child.pk != self.pk:
|
||||||
|
parent.traces.set(parent.traces.union(child.traces.all()))
|
||||||
|
parent.traces.set(parent.traces.union(self.traces.all()))
|
||||||
|
if old_traces != list(parent.traces.all()):
|
||||||
|
parent.save(old_traces=old_traces)
|
||||||
|
|
||||||
def update_expiry_date(self):
|
def update_expiry_date(self):
|
||||||
# update parents
|
# update parents
|
||||||
for parent in self.transformed_ingredient_inv.iterator():
|
for parent in self.transformed_ingredient_inv.iterator():
|
||||||
@@ -142,6 +162,10 @@ class BasicFood(Food):
|
|||||||
and list(self.allergens.all()) != kwargs['old_allergens']):
|
and list(self.allergens.all()) != kwargs['old_allergens']):
|
||||||
self.update_allergens()
|
self.update_allergens()
|
||||||
|
|
||||||
|
if ('old_traces' in kwargs
|
||||||
|
and list(self.traces.all()) != kwargs['old_traces']):
|
||||||
|
self.update_traces()
|
||||||
|
|
||||||
# Expiry date
|
# Expiry date
|
||||||
if ((self.expiry_date != old_food.expiry_date
|
if ((self.expiry_date != old_food.expiry_date
|
||||||
and self.date_type == 'DLC')
|
and self.date_type == 'DLC')
|
||||||
@@ -214,7 +238,7 @@ class TransformedFood(Food):
|
|||||||
created = self.pk is None
|
created = self.pk is None
|
||||||
if not created:
|
if not created:
|
||||||
# Check if important fields are updated
|
# Check if important fields are updated
|
||||||
update = {'allergens': False, 'expiry_date': False}
|
update = {'allergens': False, 'traces': False, 'expiry_date': False}
|
||||||
old_food = Food.objects.select_for_update().get(pk=self.pk)
|
old_food = Food.objects.select_for_update().get(pk=self.pk)
|
||||||
if not hasattr(self, "_force_save"):
|
if not hasattr(self, "_force_save"):
|
||||||
# Allergens
|
# Allergens
|
||||||
@@ -224,6 +248,10 @@ class TransformedFood(Food):
|
|||||||
and list(self.allergens.all()) != kwargs['old_allergens']):
|
and list(self.allergens.all()) != kwargs['old_allergens']):
|
||||||
update['allergens'] = True
|
update['allergens'] = True
|
||||||
|
|
||||||
|
if ('old_traces' in kwargs
|
||||||
|
and list(self.traces.all()) != kwargs['old_traces']):
|
||||||
|
update['traces'] = True
|
||||||
|
|
||||||
# Expiry date
|
# Expiry date
|
||||||
update['expiry_date'] = (self.shelf_life != old_food.shelf_life
|
update['expiry_date'] = (self.shelf_life != old_food.shelf_life
|
||||||
or self.creation_date != old_food.creation_date)
|
or self.creation_date != old_food.creation_date)
|
||||||
@@ -234,6 +262,7 @@ class TransformedFood(Food):
|
|||||||
if ('old_ingredients' in kwargs
|
if ('old_ingredients' in kwargs
|
||||||
and list(self.ingredients.all()) != list(kwargs['old_ingredients'])):
|
and list(self.ingredients.all()) != list(kwargs['old_ingredients'])):
|
||||||
update['allergens'] = True
|
update['allergens'] = True
|
||||||
|
update['traces'] = True
|
||||||
update['expiry_date'] = True
|
update['expiry_date'] = True
|
||||||
|
|
||||||
# it's preferable to keep a queryset but we allow list too
|
# it's preferable to keep a queryset but we allow list too
|
||||||
@@ -243,6 +272,8 @@ class TransformedFood(Food):
|
|||||||
self.check_cycle(self.ingredients.all().difference(kwargs['old_ingredients']), self, [])
|
self.check_cycle(self.ingredients.all().difference(kwargs['old_ingredients']), self, [])
|
||||||
if update['allergens']:
|
if update['allergens']:
|
||||||
self.update_allergens()
|
self.update_allergens()
|
||||||
|
if update['traces']:
|
||||||
|
self.update_traces()
|
||||||
if update['expiry_date']:
|
if update['expiry_date']:
|
||||||
self.update_expiry_date()
|
self.update_expiry_date()
|
||||||
|
|
||||||
@@ -254,6 +285,7 @@ class TransformedFood(Food):
|
|||||||
|
|
||||||
for child in self.ingredients.iterator():
|
for child in self.ingredients.iterator():
|
||||||
self.allergens.set(self.allergens.union(child.allergens.all()))
|
self.allergens.set(self.allergens.union(child.allergens.all()))
|
||||||
|
self.traces.set(self.traces.union(child.traces.all()))
|
||||||
if not (child.polymorphic_ctype.model == 'basicfood' and child.date_type == 'DDM'):
|
if not (child.polymorphic_ctype.model == 'basicfood' and child.date_type == 'DDM'):
|
||||||
self.expiry_date = min(self.expiry_date, child.expiry_date)
|
self.expiry_date = min(self.expiry_date, child.expiry_date)
|
||||||
return super().save(force_insert=False, force_update=force_update, using=using, update_fields=update_fields)
|
return super().save(force_insert=False, force_update=force_update, using=using, update_fields=update_fields)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class FoodTable(tables.Table):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Food
|
model = Food
|
||||||
template_name = 'django_tables2/bootstrap4.html'
|
template_name = 'django_tables2/bootstrap4.html'
|
||||||
fields = ('name', 'owner', 'qr_code_numbers', 'allergens', 'date', 'expiry_date')
|
fields = ('name', 'owner', 'qr_code_numbers', 'allergens', 'traces', 'date', 'expiry_date')
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': 'table-row',
|
'class': 'table-row',
|
||||||
'data-href': lambda record: 'detail/' + str(record.pk),
|
'data-href': lambda record: 'detail/' + str(record.pk),
|
||||||
|
|||||||
@@ -235,6 +235,8 @@ class BasicFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
for field in context['form'].fields:
|
for field in context['form'].fields:
|
||||||
if field == 'allergens':
|
if field == 'allergens':
|
||||||
context['form'].fields[field].initial = getattr(food, field).all()
|
context['form'].fields[field].initial = getattr(food, field).all()
|
||||||
|
elif field == 'traces':
|
||||||
|
context['form'].fields[field].initial = getattr(food, field).all()
|
||||||
else:
|
else:
|
||||||
context['form'].fields[field].initial = getattr(food, field)
|
context['form'].fields[field].initial = getattr(food, field)
|
||||||
|
|
||||||
@@ -294,6 +296,7 @@ class ManageIngredientsView(LoginRequiredMixin, UpdateView):
|
|||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
old_ingredients = list(self.object.ingredients.all()).copy()
|
old_ingredients = list(self.object.ingredients.all()).copy()
|
||||||
old_allergens = list(self.object.allergens.all()).copy()
|
old_allergens = list(self.object.allergens.all()).copy()
|
||||||
|
old_traces = list(self.object.traces.all()).copy()
|
||||||
self.object.ingredients.clear()
|
self.object.ingredients.clear()
|
||||||
for i in range(self.object.ingredients.all().count() + 1 + MAX_FORMS):
|
for i in range(self.object.ingredients.all().count() + 1 + MAX_FORMS):
|
||||||
prefix = 'form-' + str(i) + '-'
|
prefix = 'form-' + str(i) + '-'
|
||||||
@@ -320,13 +323,15 @@ class ManageIngredientsView(LoginRequiredMixin, UpdateView):
|
|||||||
# We recalculate new expiry date and allergens
|
# We recalculate new expiry date and allergens
|
||||||
self.object.expiry_date = self.object.creation_date + self.object.shelf_life
|
self.object.expiry_date = self.object.creation_date + self.object.shelf_life
|
||||||
self.object.allergens.clear()
|
self.object.allergens.clear()
|
||||||
|
self.object.traces.clear()
|
||||||
|
|
||||||
for ingredient in self.object.ingredients.iterator():
|
for ingredient in self.object.ingredients.iterator():
|
||||||
if not (ingredient.polymorphic_ctype.model == 'basicfood' and ingredient.date_type == 'DDM'):
|
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.expiry_date = min(self.object.expiry_date, ingredient.expiry_date)
|
||||||
self.object.allergens.set(self.object.allergens.union(ingredient.allergens.all()))
|
self.object.allergens.set(self.object.allergens.union(ingredient.allergens.all()))
|
||||||
|
self.object.tracese.set(self.object.traces.union(ingredient.traces.all()))
|
||||||
|
|
||||||
self.object.save(old_ingredients=old_ingredients, old_allergens=old_allergens)
|
self.object.save(old_ingredients=old_ingredients, old_allergens=old_allergens, old_traces=old_traces)
|
||||||
return HttpResponseRedirect(self.get_success_url())
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
def get_context_data(self, *args, **kwargs):
|
||||||
@@ -378,13 +383,15 @@ class AddIngredientView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
|||||||
for meal in meals:
|
for meal in meals:
|
||||||
old_ingredients = list(meal.ingredients.all()).copy()
|
old_ingredients = list(meal.ingredients.all()).copy()
|
||||||
old_allergens = list(meal.allergens.all()).copy()
|
old_allergens = list(meal.allergens.all()).copy()
|
||||||
|
old_traces = list(meal.traces.all()).copy()
|
||||||
meal.ingredients.add(self.object.pk)
|
meal.ingredients.add(self.object.pk)
|
||||||
# update allergen and expiry date if necessary
|
# update allergen and expiry date if necessary
|
||||||
if not (self.object.polymorphic_ctype.model == 'basicfood'
|
if not (self.object.polymorphic_ctype.model == 'basicfood'
|
||||||
and self.object.date_type == 'DDM'):
|
and self.object.date_type == 'DDM'):
|
||||||
meal.expiry_date = min(meal.expiry_date, self.object.expiry_date)
|
meal.expiry_date = min(meal.expiry_date, self.object.expiry_date)
|
||||||
meal.allergens.set(meal.allergens.union(self.object.allergens.all()))
|
meal.allergens.set(meal.allergens.union(self.object.allergens.all()))
|
||||||
meal.save(old_ingredients=old_ingredients, old_allergens=old_allergens)
|
meal.traces.set(meal.traces.union(self.object.traces.all()))
|
||||||
|
meal.save(old_ingredients=old_ingredients, old_allergens=old_allergens, old_traces=old_traces)
|
||||||
if 'fully_used' in form.data:
|
if 'fully_used' in form.data:
|
||||||
if not self.object.end_of_life:
|
if not self.object.end_of_life:
|
||||||
self.object.end_of_life = _(f'Food fully used in : {meal.name}')
|
self.object.end_of_life = _(f'Food fully used in : {meal.name}')
|
||||||
@@ -414,6 +421,7 @@ class FoodUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
|||||||
form.instance.creater = self.request.user
|
form.instance.creater = self.request.user
|
||||||
food = Food.objects.get(pk=self.kwargs['pk'])
|
food = Food.objects.get(pk=self.kwargs['pk'])
|
||||||
old_allergens = list(food.allergens.all()).copy()
|
old_allergens = list(food.allergens.all()).copy()
|
||||||
|
old_traces = list(food.traces.all()).copy()
|
||||||
|
|
||||||
if food.polymorphic_ctype.model == 'transformedfood':
|
if food.polymorphic_ctype.model == 'transformedfood':
|
||||||
old_ingredients = food.ingredients.all()
|
old_ingredients = food.ingredients.all()
|
||||||
@@ -427,7 +435,7 @@ class FoodUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
|||||||
if food.polymorphic_ctype.model == 'transformedfood':
|
if food.polymorphic_ctype.model == 'transformedfood':
|
||||||
form.instance.save(old_ingredients=old_ingredients)
|
form.instance.save(old_ingredients=old_ingredients)
|
||||||
else:
|
else:
|
||||||
form.instance.save(old_allergens=old_allergens)
|
form.instance.save(old_allergens=old_allergens, old_traces=old_traces)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def get_form_class(self, **kwargs):
|
def get_form_class(self, **kwargs):
|
||||||
@@ -460,7 +468,7 @@ class FoodDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
fields = ["name", "owner", "expiry_date", "allergens", "is_ready", "end_of_life", "order"]
|
fields = ["name", "owner", "expiry_date", "allergens", "traces", "is_ready", "end_of_life", "order"]
|
||||||
|
|
||||||
fields = dict([(field, getattr(self.object, field)) for field in fields])
|
fields = dict([(field, getattr(self.object, field)) for field in fields])
|
||||||
if fields["is_ready"]:
|
if fields["is_ready"]:
|
||||||
@@ -469,6 +477,8 @@ class FoodDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
fields["is_ready"] = _("No")
|
fields["is_ready"] = _("No")
|
||||||
fields["allergens"] = ", ".join(
|
fields["allergens"] = ", ".join(
|
||||||
allergen.name for allergen in fields["allergens"].all())
|
allergen.name for allergen in fields["allergens"].all())
|
||||||
|
fields["traces"] = ", ".join(
|
||||||
|
trace.name for trace in fields["traces"].all())
|
||||||
|
|
||||||
context["fields"] = [(
|
context["fields"] = [(
|
||||||
Food._meta.get_field(field).verbose_name.capitalize(),
|
Food._meta.get_field(field).verbose_name.capitalize(),
|
||||||
|
|||||||
Reference in New Issue
Block a user