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
# SPDX-License-Identifier: GPL-3.0-or-later
from crispy_forms.helper import FormHelper
from django import forms
from member.models import Club
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):
@ -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 Meta:
model = Meal

View File

@ -14,8 +14,73 @@ SPDX-License-Identifier: GPL-3.0-or-later
<form method="post">
{% csrf_token %}
{{ 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>
</form>
</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>
<ul>
{% 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 }})
{% if can_change_sheet %}
<a href="{% url 'sheets:meal_update' pk=meal.pk %}" class="badge badge-primary">
@ -40,7 +40,7 @@
{% endfor %}
<hr>
{% 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 }})
{% if can_change_sheet %}
<a href="{% url 'sheets:food_update' pk=food.pk %}" class="badge badge-primary">
@ -48,11 +48,11 @@
{% trans "Edit" %}
</a>
{% endif %}
{% if food.option_set.all %}
{% if food.foodoption_set.all %}
<ul>
{% for option in food.available_options %}
<li{% if not option.available %} class="text-danger" style="text-decoration: line-through !important;"{% endif %}>
{{ option }}{% if option.extra_price %} ({{ option.extra_price|pretty_money }}){% endif %}
{% for option in food.foodoption_set.all %}
<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_cost %} ({{ option.extra_cost|pretty_money }}){% endif %}
</li>
{% endfor %}
</ul>

View File

@ -1,6 +1,6 @@
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from crispy_forms.helper import FormHelper
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from django.utils import timezone
@ -11,7 +11,7 @@ from django_tables2 import SingleTableView
from permission.backends import PermissionBackend
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 .tables import SheetTable
@ -80,8 +80,34 @@ class FoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
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):
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)
def get_success_url(self):
@ -93,8 +119,36 @@ class FoodUpdateView(ProtectQuerysetMixin, UpdateView):
form_class = FoodForm
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):
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):

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \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"
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\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/club_form.html:16
#: 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/sheet_form.html:17 apps/treasury/forms.py:89
#: apps/treasury/forms.py:143
@ -2321,10 +2321,20 @@ msgstr "transaction de commande sur feuille de note"
msgid "sheet order transactions"
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
msgid "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
msgid "The menu is empty for now."
msgstr "Le menu est vide pour le moment."
@ -2365,15 +2375,15 @@ msgstr "Modifier une feuille de note"
msgid "Create new food"
msgstr "Créer un plat"
#: apps/sheets/views.py:94
#: apps/sheets/views.py:120
msgid "Update food"
msgstr "Modifier un plat"
#: apps/sheets/views.py:103
#: apps/sheets/views.py:157
msgid "Create new meal"
msgstr "Créer un menu"
#: apps/sheets/views.py:128
#: apps/sheets/views.py:182
msgid "Update meal"
msgstr "Modifier un menu"