1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-11-07 15:29:51 +01:00

Add field 'traces' for model Food

This commit is contained in:
Ehouarn
2025-11-02 18:43:33 +01:00
parent 4f016fed38
commit 48b1ef9ec8
5 changed files with 68 additions and 8 deletions

View File

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

View 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'),
),
]

View File

@@ -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)

View File

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

View File

@@ -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(),