1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-02-23 16:41:23 +00:00

Compare commits

...

6 Commits

Author SHA1 Message Date
quark
a3073ba5a5 Un peu de nettoyage, rajout de commentaires 2024-05-25 22:34:59 +02:00
quark
9b9fa0bcfe few changes in models, delete default label 2024-05-25 16:47:24 +02:00
quark
b1d0cf92b1 création de forms fonctionnel (form + views + url + html), few changes in models.py 2024-05-25 15:27:26 +02:00
quark
0c3e712f8f création d'un form pour l'ajout d'aliments basiques 2024-05-24 21:49:23 +02:00
quark
708216a67f nom app 2024-05-24 21:47:30 +02:00
quark
c27a8fefe5 First forms 2024-05-23 23:53:33 +02:00
9 changed files with 311 additions and 34 deletions

View File

@ -1,7 +1,10 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib import admin from django.contrib import admin
from note_kfet.admin import admin_site from note_kfet.admin import admin_site
from .models import QR_code, Basic_food, Transformed_food, Allergen from .models import QR_code, BasicFood, TransformedFood, Allergen
@admin.register(QR_code, site = admin_site) @admin.register(QR_code, site = admin_site)
@ -10,13 +13,13 @@ class QR_codeAdmin(admin.ModelAdmin):
TEMPORARY TEMPORARY
""" """
@admin.register(Basic_food, site = admin_site) @admin.register(BasicFood, site = admin_site)
class Basic_foodAdmin(admin.ModelAdmin): class Basic_foodAdmin(admin.ModelAdmin):
""" """
TEMPORARY TEMPORARY
""" """
@admin.register(Transformed_food, site = admin_site) @admin.register(TransformedFood, site = admin_site)
class Transformed_foodAdmin(admin.ModelAdmin): class Transformed_foodAdmin(admin.ModelAdmin):
""" """
TEMPORARY TEMPORARY

View File

@ -1,5 +1,10 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig
class FoodkfetConfig(AppConfig): class FoodkfetConfig(AppConfig):
name = 'foodkfet' name = 'food'
verbose_name = _('food')

92
apps/food/forms.py Normal file
View File

@ -0,0 +1,92 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from random import shuffle
from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from member.models import Club
from note_kfet.inputs import Autocomplete, DatePickerInput, DateTimePickerInput
from note_kfet.middlewares import get_current_request
from permission.backends import PermissionBackend
from .models import QR_code, Allergen, BasicFood, TransformedFood
class BasicFoodForms(forms.ModelForm):
"""
Form for add non-transformed food
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({"autofocus": "autofocus"})
self.fields['name'].required = True
self.fields['owner'].required = True
self.fields['label'].help_text = _('The lot number must be contained in the picture')
# Some example
self.fields['name'].widget.attrs.update({"placeholder": _("pasta")})
clubs = list(Club.objects.filter(PermissionBackend.filter_queryset(get_current_request(), Club, "change")).all())
shuffle(clubs)
self.fields['owner'].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ", ..."
def clean_dlm_or_dlc(self):
is_dlc = self.cleaned_data["is_DLC"]
is_ddm = self.cleaned_data["is_DDM"]
if is_dlc and is_ddm:
self.add_error("is_ddm", _("the product cannot be a DLC and a DDM"))
return is_ddm
class Meta:
model = BasicFood
fields = ('name', 'owner', 'is_DLC', 'is_DDM', 'expiry_date', 'label')
widgets = {
"owner": Autocomplete(
model = Club,
attrs = {"api_url": "/api/members/club/"},
),
'expiry_date': DatePickerInput(),
}
class TransformedFoodForms(forms.ModelForm):
"""
Form for add transformed food
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({"autofocus": "autofocus"})
self.fields['name'].required = True
self.fields['owner'].required = True
self.fields['creation_date'].required = True
self.fields['creation_date'].initial = timezone.now
self.fields['is_active'].initial = True
# Some example
self.fields['name'].widget.attrs.update({"placeholder": _("lasagna")})
clubs = list(Club.objects.filter(PermissionBackend.filter_queryset(get_current_request(), Club, "change")).all())
shuffle(clubs)
self.fields['owner'].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ", ..."
class Meta:
model = TransformedFood
fields = ('name', 'creation_date', 'owner', 'is_active',)
widgets = {
"owner": Autocomplete(
model = Club,
attrs = {"api_url": "/api/members/club/"},
),
'creation_date': DateTimePickerInput(),
}
class AllergenForms(forms.ModelForm):
"""
Form for allergen
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class Meta:
model = Allergen
exclude = ['basic_food', 'transformed_food']

View File

@ -1,4 +1,4 @@
# Generated by Django 2.2.28 on 2024-05-21 12:05 # Generated by Django 2.2.28 on 2024-05-25 20:32
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -15,14 +15,15 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Basic_food', name='BasicFood',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='name')), ('name', models.CharField(max_length=255, verbose_name='name')),
('is_DLC', models.BooleanField(default=False, verbose_name='is DLC')), ('is_DLC', models.BooleanField(default=False, verbose_name='is DLC')),
('is_DDM', models.BooleanField(default=False, verbose_name='is DDM')), ('is_DDM', models.BooleanField(default=False, verbose_name='is DDM')),
('expiry_date', models.DateTimeField(blank=True, default=django.utils.timezone.now, verbose_name='expiry date')), ('arrival_date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='arrival date')),
('label', models.ImageField(default='pic/default.png', max_length=255, upload_to='label/', verbose_name='food label')), ('expiry_date', models.DateTimeField(blank=True, null=True, verbose_name='expiry date')),
('label', models.ImageField(max_length=255, upload_to='label/', verbose_name='food label')),
('was_eaten', models.BooleanField(default=False, verbose_name='was eaten')), ('was_eaten', models.BooleanField(default=False, verbose_name='was eaten')),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='owner')), ('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='owner')),
], ],
@ -32,7 +33,7 @@ class Migration(migrations.Migration):
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Transformed_food', name='TransformedFood',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='name')), ('name', models.CharField(max_length=255, verbose_name='name')),
@ -41,7 +42,7 @@ class Migration(migrations.Migration):
('is_active', models.BooleanField(default=True, verbose_name='is active')), ('is_active', models.BooleanField(default=True, verbose_name='is active')),
('was_eaten', models.BooleanField(default=False, verbose_name='was eaten')), ('was_eaten', models.BooleanField(default=False, verbose_name='was eaten')),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='owner')), ('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='owner')),
('transformed_ingredient', models.ManyToManyField(blank=True, related_name='transformed_ingredient_inv', to='food.Transformed_food', verbose_name='transformed ingredient')), ('transformed_ingredient', models.ManyToManyField(blank=True, related_name='transformed_ingredient_inv', to='food.TransformedFood', verbose_name='transformed ingredient')),
], ],
options={ options={
'verbose_name': 'Transformed food', 'verbose_name': 'Transformed food',
@ -53,8 +54,8 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('qr_code_number', models.PositiveIntegerField(verbose_name='QR-code number')), ('qr_code_number', models.PositiveIntegerField(verbose_name='QR-code number')),
('basic_food', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='QR_code', to='food.Basic_food', verbose_name='basic food')), ('basic_food', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='QR_code', to='food.BasicFood', verbose_name='basic food')),
('transformed_food_container', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='QR_code', to='food.Transformed_food', verbose_name='transformed food container')), ('transformed_food_container', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='QR_code', to='food.TransformedFood', verbose_name='transformed food container')),
], ],
options={ options={
'verbose_name': 'QR-code', 'verbose_name': 'QR-code',
@ -62,9 +63,9 @@ class Migration(migrations.Migration):
}, },
), ),
migrations.AddField( migrations.AddField(
model_name='basic_food', model_name='basicfood',
name='transformed_food', name='transformed_food',
field=models.ManyToManyField(blank=True, related_name='Basic_food', to='food.Transformed_food', verbose_name='transformed food'), field=models.ManyToManyField(blank=True, related_name='BasicFood', to='food.TransformedFood', verbose_name='transformed food'),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Allergen', name='Allergen',
@ -85,8 +86,8 @@ class Migration(migrations.Migration):
('groundnut', models.BooleanField(default=False, verbose_name='groundnut')), ('groundnut', models.BooleanField(default=False, verbose_name='groundnut')),
('sesame', models.BooleanField(default=False, verbose_name='sesame')), ('sesame', models.BooleanField(default=False, verbose_name='sesame')),
('alcohol', models.BooleanField(default=False, verbose_name='alcohol')), ('alcohol', models.BooleanField(default=False, verbose_name='alcohol')),
('basic_food', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='Allergen', to='food.Basic_food', verbose_name='basic food')), ('basic_food', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='Allergen', to='food.BasicFood', verbose_name='basic food')),
('transformed_food', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='Allergen', to='food.Transformed_food', verbose_name='transformed food')), ('transformed_food', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='Allergen', to='food.TransformedFood', verbose_name='transformed food')),
], ],
options={ options={
'verbose_name': 'Allergen', 'verbose_name': 'Allergen',

View File

@ -7,12 +7,18 @@ from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.db import models from django.db import models, transaction
from django.db.models import Q from django.db.models import Q
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from member.models import Club from member.models import Club
#################################################################
# TO DO
# - link allergen with one food (basic or transformed) with check
# - check on basic food
# - check on transformed food
#################################################################
class QR_code(models.Model): class QR_code(models.Model):
""" """
@ -23,7 +29,7 @@ class QR_code(models.Model):
) )
transformed_food_container = models.ForeignKey( transformed_food_container = models.ForeignKey(
'Transformed_food', 'TransformedFood',
on_delete = models.PROTECT, on_delete = models.PROTECT,
related_name = 'QR_code', related_name = 'QR_code',
null = True, null = True,
@ -32,7 +38,7 @@ class QR_code(models.Model):
) )
basic_food = models.ForeignKey( basic_food = models.ForeignKey(
'Basic_food', 'BasicFood',
on_delete = models.PROTECT, on_delete = models.PROTECT,
related_name = 'QR_code', related_name = 'QR_code',
null = True, null = True,
@ -128,7 +134,7 @@ class Allergen(models.Model):
) )
transformed_food = models.ForeignKey( transformed_food = models.ForeignKey(
'Transformed_food', 'TransformedFood',
on_delete = models.CASCADE, on_delete = models.CASCADE,
related_name = 'Allergen', related_name = 'Allergen',
blank = True, blank = True,
@ -137,7 +143,7 @@ class Allergen(models.Model):
) )
basic_food = models.ForeignKey( basic_food = models.ForeignKey(
'Basic_food', 'BasicFood',
on_delete = models.CASCADE, on_delete = models.CASCADE,
related_name = 'Allergen', related_name = 'Allergen',
blank = True, blank = True,
@ -150,10 +156,12 @@ class Allergen(models.Model):
verbose_name_plural = _('Allergens') verbose_name_plural = _('Allergens')
def __str__(self): def __str__(self):
return _('Allergens of #{id}').format(id=self.id) return _('Allergens of #{id}').format(id=self.id)
class Basic_food(models.Model): class BasicFood(models.Model):
""" """
Food which has been directly buy on supermarket Food which has been directly buy on supermarket
""" """
@ -171,11 +179,16 @@ class Basic_food(models.Model):
verbose_name=_("is DDM"), verbose_name=_("is DDM"),
default=False, default=False,
) )
arrival_date = models.DateTimeField(
verbose_name=_('arrival date'),
default=timezone.now,
)
expiry_date = models.DateTimeField( expiry_date = models.DateTimeField(
verbose_name=_('expiry date'), verbose_name=_('expiry date'),
default=timezone.now,
blank=True, blank=True,
null = True,
) )
owner = models.ForeignKey( owner = models.ForeignKey(
@ -191,7 +204,6 @@ class Basic_food(models.Model):
blank=False, blank=False,
null=False, null=False,
upload_to='label/', upload_to='label/',
default= 'pic/default.png',
) )
was_eaten = models.BooleanField( was_eaten = models.BooleanField(
@ -200,8 +212,8 @@ class Basic_food(models.Model):
) )
transformed_food = models.ManyToManyField( transformed_food = models.ManyToManyField(
'Transformed_food', 'TransformedFood',
related_name= 'Basic_food', related_name= 'BasicFood',
blank = True, blank = True,
verbose_name = _('transformed food'), verbose_name = _('transformed food'),
) )
@ -214,7 +226,15 @@ class Basic_food(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
class Transformed_food(models.Model): @transaction.atomic
def save(self, force_insert=False, force_update=False, using= None, update_fields=None):
# Check if is_DLC and is DDM are not both True
if self.is_DLC and self.is_DDM:
raise ValidationError("The product cannot be a DLC and a DDM")
return super().save(force_insert, force_update, using, update_fields)
class TransformedFood(models.Model):
""" """
Transformed food are a mix between basic food and meal Transformed food are a mix between basic food and meal
""" """
@ -264,4 +284,6 @@ class Transformed_food(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
@transaction.atomic
def save(self, force_insert=False, force_update=False, using= None, update_fields=None):
return super().save(force_insert, force_update, using, update_fields)

View File

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n crispy_forms_tags %}
{% block content %}
<div class="card bg-white mb-3">
<h3 class="card-header text-center">
HTML not finished <br>
{{ title }}
</h3>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
{{ allergenform|crispy }}
<button class="btn btn-primary" type="submit">{% trans "Submit"%}</button>
</form>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,21 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n crispy_forms_tags %}
{% block content %}
<div class="card bg-white mb-3">
<h3 class="card-header text-center">
HTML not finished <br>
{{ title }}
</h3>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary" type="submit">{% trans "Submit"%}</button>
</form>
</div>
</div>
{% endblock %}

View File

@ -1,7 +1,19 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path from django.urls import path
from . import views from . import views
###############################
# TO DO
# - name url correctly, thinking about the scheme of the app
###############################
app_name = 'food'
urlpatterns = [ urlpatterns = [
path('', views.index, name='index') path('0', views.BasicFoodCreateView.as_view(), name = 'basic_food'),
path('1', views.TransformedFoodCreateView.as_view(), name = 'transformed_food'),
] ]

View File

@ -1,5 +1,104 @@
from django.shortcuts import render # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
from django.http import HttpResponse # SPDX-License-Identifier: GPL-3.0-or-later
def index(request): from django.urls import reverse_lazy
return HttpResponse('test') from django.db import transaction
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from datetime import timedelta
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from django.shortcuts import render
from .forms import BasicFoodForms, TransformedFoodForms, AllergenForms
from .models import BasicFood, TransformedFood, Allergen
class BasicFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
#####################################################################
# TO DO
# - fix picture save
# - implement solution crop and convert image (reuse or recode ImageForm from members apps
# - implement AllergenForms
# - redirect to another view after the poll is submitted
#####################################################################
"""
A view to add a basic food
"""
model = BasicFood
form_class = BasicFoodForms
template_name = 'food/basic_food_form.html'
extra_context = {"title": _("Add a new aliment")}
def get_sample_object(self):
return BasicFood(
name="",
is_DLC=False,
is_DDM=False,
expiry_date=timezone.now(),
label='pic/default.png',
)
@transaction.atomic
def form_valid(self, form):
form.instance.creater = self.request.user
basic_food_form = BasicFoodForms(data=self.request.POST)
allergen_form = AllergenForms(data=self.request.POST)
if not basic_food_form.is_valid() or not allergen_form.is_valid():
return self.form_invalid(form)
# Save the aliment and the allergens associed
basic_food = form.save(commit=False)
# We assume the date of labeling and the same as the date of arrival
basic_food.arrival_date = timezone.now
basic_food._force_save = True
basic_food.save()
basic_food.refresh_from_db()
return super().form_valid(form)
def get_success_url(self, **kwargs):
self.object.refresh_from_db()
# TEMPORARY, I create a fonctionnal view before
# return reverse_lazy('food:basicfood', kwargs={"pk": self.object.pk})
return '0'
class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
###############################################
# TO DO
# -redirect to another view after submit
###############################################
"""
A view to add a tranformed food
"""
model = TransformedFood
template_name = 'food/transformed_food_form.html'
form_class = TransformedFoodForms
extra_context = {"title": _("Add a new meal")}
def get_sample_object(self):
return TransformedFood(
name="",
creation_date=timezone.now(),
)
@transaction.atomic
def form_valid(self, form):
form.instance.creater = self.request.user
transformed_food_form = TransformedFoodForms(data=self.request.POST)
if not transformed_food_form.is_valid():
return self.form_invalid(form)
# Save the aliment and allergens associated
transformed_food = form.save(commit=False)
# Without microbiological analyzes, the storage time is 3 days
transformed_food.expiry_date = transformed_food.creation_date + timedelta(days = 3)
transformed_food._force_save = True
transformed_food.save()
transformed_food.refresh_from_db()
return super().form_valid(form)
def get_success_url(self, **kwargs):
self.object.refresh_from_db()
# TEMPORARY, I create a fonctionnal view before
# return reverse_lazy('food:tranformed_food', kwargs={"pk": self.object.pk})
return '1'