Manage food options

Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
This commit is contained in:
Emmy D'ANELLO 2022-08-18 14:50:45 +02:00
parent 51e5e3669e
commit 5174c84b33
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
5 changed files with 172 additions and 17 deletions

View File

@ -1,12 +1,12 @@
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay # Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from crispy_forms.helper import FormHelper
from django import forms from django import forms
from member.models import Club from member.models import Club
from note_kfet.inputs import AmountInput, Autocomplete, DateTimePickerInput from note_kfet.inputs import AmountInput, Autocomplete, DateTimePickerInput
from .models import Food, Meal, Sheet from .models import Food, FoodOption, Meal, Sheet
class SheetForm(forms.ModelForm): class SheetForm(forms.ModelForm):
@ -31,6 +31,32 @@ class FoodForm(forms.ModelForm):
} }
class FoodOptionForm(forms.ModelForm):
class Meta:
model = FoodOption
fields = '__all__'
widgets = {
'extra_cost': AmountInput(),
}
FoodOptionsFormSet = forms.inlineformset_factory(
Food,
FoodOption,
form=FoodOptionForm,
extra=0,
)
class FoodOptionFormSetHelper(FormHelper):
def __init__(self, form=None):
super().__init__(form)
self.form_tag = False
self.form_method = 'POST'
self.form_class = 'form-inline'
self.template = 'bootstrap4/table_inline_formset.html'
class MealForm(forms.ModelForm): class MealForm(forms.ModelForm):
class Meta: class Meta:
model = Meal model = Meal

View File

@ -14,8 +14,73 @@ SPDX-License-Identifier: GPL-3.0-or-later
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
{# The next part concerns the option formset #}
{# Generate some hidden fields that manage the number of options, and make easier the parsing #}
{{ formset.management_form }}
<table class="table table-condensed table-striped">
{# Fill initial data #}
{% for form in formset %}
{% if forloop.first %}
<thead>
<tr>
<th>{{ form.name.label }}<span class="asteriskField">*</span></th>
<th>{{ form.extra_cost.label }}<span class="asteriskField">*</span></th>
<th>{{ form.available.label }}<span class="asteriskField">*</span></th>
</tr>
</thead>
<tbody id="form_body">
{% endif %}
<tr class="row-formset">
<td>{{ form.name }}</td>
<td>{{ form.extra_cost }}</td>
<td>{{ form.available }}</td>
{# These fields are hidden but handled by the formset to link the id and the invoice id #}
{{ form.food }}
{{ form.id }}
</tr>
{% endfor %}
</tbody>
</table>
{# Display buttons to add and remove options #}
<div class="card-body">
<button type="button" id="add_more" class="btn btn-success">{% trans "Add option" %}</button>
</div>
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button> <button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
</form> </form>
</div> </div>
</div> </div>
{% endblock %}
{# Hidden div that store an empty product form, to be copied into new forms #}
<div id="empty_form" style="display: none;">
<table class='no_error'>
<tbody id="for_real">
<tr class="row-formset">
<td>{{ formset.empty_form.name }}</td>
<td>{{ formset.empty_form.extra_cost }} </td>
<td>{{ formset.empty_form.available }}</td>
{{ formset.empty_form.food }}
{{ formset.empty_form.id }}
</tr>
</tbody>
</table>
</div>
{% endblock %}
{% block extrajavascript %}
<script>
/* script that handles add and remove lines */
IDS = {};
$("#id_foodoption_set-TOTAL_FORMS").val($(".row-formset").length - 1);
$('#add_more').click(function () {
let form_idx = $('#id_foodoption_set-TOTAL_FORMS').val();
$('#form_body').append($('#for_real').html().replace(/__prefix__/g, form_idx));
$('#id_foodoption_set-TOTAL_FORMS').val(parseInt(form_idx) + 1);
$('#id_foodoption_set-' + parseInt(form_idx) + '-id').val(IDS[parseInt(form_idx)]);
});
</script>
{% endblock %}

View File

@ -28,7 +28,7 @@
<h3>{% trans "menu"|capfirst %} :</h3> <h3>{% trans "menu"|capfirst %} :</h3>
<ul> <ul>
{% for meal in sheet.meal_set.all %} {% for meal in sheet.meal_set.all %}
<li{% if not meal.available %} class="text-danger" style="text-decoration: line-through !important;"{% endif %}> <li{% if not meal.available %} class="text-danger" style="text-decoration: line-through !important;" title="{% trans "This product is unavailable." %}"{% endif %}>
{{ meal }} ({{ meal.price|pretty_money }}) {{ meal }} ({{ meal.price|pretty_money }})
{% if can_change_sheet %} {% if can_change_sheet %}
<a href="{% url 'sheets:meal_update' pk=meal.pk %}" class="badge badge-primary"> <a href="{% url 'sheets:meal_update' pk=meal.pk %}" class="badge badge-primary">
@ -40,7 +40,7 @@
{% endfor %} {% endfor %}
<hr> <hr>
{% for food in sheet.food_set.all %} {% for food in sheet.food_set.all %}
<li{% if not food.available %} class="text-danger" style="text-decoration: line-through !important;"{% endif %}> <li{% if not food.available %} class="text-danger" style="text-decoration: line-through !important;" title="{% trans "This product is unavailable." %}"{% endif %}>
{{ food }} ({{ food.price|pretty_money }}) {{ food }} ({{ food.price|pretty_money }})
{% if can_change_sheet %} {% if can_change_sheet %}
<a href="{% url 'sheets:food_update' pk=food.pk %}" class="badge badge-primary"> <a href="{% url 'sheets:food_update' pk=food.pk %}" class="badge badge-primary">
@ -48,11 +48,11 @@
{% trans "Edit" %} {% trans "Edit" %}
</a> </a>
{% endif %} {% endif %}
{% if food.option_set.all %} {% if food.foodoption_set.all %}
<ul> <ul>
{% for option in food.available_options %} {% for option in food.foodoption_set.all %}
<li{% if not option.available %} class="text-danger" style="text-decoration: line-through !important;"{% endif %}> <li{% if not option.available %} class="text-danger" style="text-decoration: line-through !important;" title="{% trans "This product is unavailable." %}"{% endif %}>
{{ option }}{% if option.extra_price %} ({{ option.extra_price|pretty_money }}){% endif %} {{ option }}{% if option.extra_cost %} ({{ option.extra_cost|pretty_money }}){% endif %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@ -1,6 +1,6 @@
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay # Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from crispy_forms.helper import FormHelper
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
@ -11,7 +11,7 @@ from django_tables2 import SingleTableView
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms import FoodForm, MealForm, SheetForm from .forms import FoodForm, MealForm, SheetForm, FoodOptionsFormSet, FoodOptionFormSetHelper
from .models import Sheet, Food, Meal from .models import Sheet, Food, Meal
from .tables import SheetTable from .tables import SheetTable
@ -80,8 +80,34 @@ class FoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
price=500, price=500,
) )
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form = context['form']
form.helper = FormHelper()
# Remove form tag on the generation of the form in the template (already present on the template)
form.helper.form_tag = False
# The formset handles the set of the products
form_set = FoodOptionsFormSet(instance=form.instance)
context['formset'] = form_set
context['helper'] = FoodOptionFormSetHelper()
return context
def form_valid(self, form): def form_valid(self, form):
form.instance.sheet_id = self.kwargs['pk'] form.instance.sheet_id = self.kwargs['pk']
# For each product, we save it
formset = FoodOptionsFormSet(self.request.POST, instance=form.instance)
if formset.is_valid():
for f in formset:
# We don't save the product if the designation is not entered, ie. if the line is empty
if f.is_valid() and f.instance.name:
f.save()
f.instance.save()
else:
f.instance = None
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
@ -93,8 +119,36 @@ class FoodUpdateView(ProtectQuerysetMixin, UpdateView):
form_class = FoodForm form_class = FoodForm
extra_context = {"title": _("Update food")} extra_context = {"title": _("Update food")}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form = context['form']
form.helper = FormHelper()
# Remove form tag on the generation of the form in the template (already present on the template)
form.helper.form_tag = False
# The formset handles the set of the products
form_set = FoodOptionsFormSet(instance=form.instance)
context['formset'] = form_set
context['helper'] = FoodOptionFormSetHelper()
return context
def form_valid(self, form):
# For each product, we save it
formset = FoodOptionsFormSet(self.request.POST, instance=form.instance)
if formset.is_valid():
for f in formset:
# We don't save the product if the designation is not entered, ie. if the line is empty
if f.is_valid() and f.instance.name:
f.save()
f.instance.save()
else:
f.instance = None
return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
return reverse_lazy('sheets:sheet_detail', args=(self.kwargs['pk'],)) return reverse_lazy('sheets:sheet_detail', args=(self.object.sheet_id,))
class MealCreateView(ProtectQuerysetMixin, ProtectedCreateView): class MealCreateView(ProtectQuerysetMixin, ProtectedCreateView):

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-08-18 14:24+0200\n" "POT-Creation-Date: 2022-08-18 14:49+0200\n"
"PO-Revision-Date: 2022-04-11 22:05+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n"
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n" "Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
"Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n" "Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
@ -337,7 +337,7 @@ msgstr "Entrée effectuée !"
#: apps/member/templates/member/add_members.html:46 #: apps/member/templates/member/add_members.html:46
#: apps/member/templates/member/club_form.html:16 #: apps/member/templates/member/club_form.html:16
#: apps/note/templates/note/transactiontemplate_form.html:18 #: apps/note/templates/note/transactiontemplate_form.html:18
#: apps/sheets/templates/sheets/food_form.html:17 #: apps/sheets/templates/sheets/food_form.html:51
#: apps/sheets/templates/sheets/meal_form.html:17 #: apps/sheets/templates/sheets/meal_form.html:17
#: apps/sheets/templates/sheets/sheet_form.html:17 apps/treasury/forms.py:89 #: apps/sheets/templates/sheets/sheet_form.html:17 apps/treasury/forms.py:89
#: apps/treasury/forms.py:143 #: apps/treasury/forms.py:143
@ -2321,10 +2321,20 @@ msgstr "transaction de commande sur feuille de note"
msgid "sheet order transactions" msgid "sheet order transactions"
msgstr "transactions de commande sur feuille de note" msgstr "transactions de commande sur feuille de note"
#: apps/sheets/templates/sheets/food_form.html:48
msgid "Add option"
msgstr "Ajouter une option"
#: apps/sheets/templates/sheets/sheet_detail.html:28 #: apps/sheets/templates/sheets/sheet_detail.html:28
msgid "menu" msgid "menu"
msgstr "menu" msgstr "menu"
#: apps/sheets/templates/sheets/sheet_detail.html:31
#: apps/sheets/templates/sheets/sheet_detail.html:43
#: apps/sheets/templates/sheets/sheet_detail.html:54
msgid "This product is unavailable."
msgstr "Ce produit est indisponible."
#: apps/sheets/templates/sheets/sheet_detail.html:63 #: apps/sheets/templates/sheets/sheet_detail.html:63
msgid "The menu is empty for now." msgid "The menu is empty for now."
msgstr "Le menu est vide pour le moment." msgstr "Le menu est vide pour le moment."
@ -2365,15 +2375,15 @@ msgstr "Modifier une feuille de note"
msgid "Create new food" msgid "Create new food"
msgstr "Créer un plat" msgstr "Créer un plat"
#: apps/sheets/views.py:94 #: apps/sheets/views.py:120
msgid "Update food" msgid "Update food"
msgstr "Modifier un plat" msgstr "Modifier un plat"
#: apps/sheets/views.py:103 #: apps/sheets/views.py:157
msgid "Create new meal" msgid "Create new meal"
msgstr "Créer un menu" msgstr "Créer un menu"
#: apps/sheets/views.py:128 #: apps/sheets/views.py:182
msgid "Update meal" msgid "Update meal"
msgstr "Modifier un menu" msgstr "Modifier un menu"