mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-11-04 09:12:11 +01:00 
			
		
		
		
	Add manage ingredient feature, fix some bug
This commit is contained in:
		@@ -3,7 +3,7 @@
 | 
			
		||||
 | 
			
		||||
from rest_framework import serializers
 | 
			
		||||
 | 
			
		||||
from ..models import Allergen, BasicFood, TransformedFood, QRCode
 | 
			
		||||
from ..models import Allergen, Food, BasicFood, TransformedFood, QRCode
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AllergenSerializer(serializers.ModelSerializer):
 | 
			
		||||
@@ -16,6 +16,16 @@ class AllergenSerializer(serializers.ModelSerializer):
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FoodSerializer(serializers.ModelSerializer):
 | 
			
		||||
    """
 | 
			
		||||
    REST API Serializer for Food.
 | 
			
		||||
    The djangorestframework plugin will analyse the model `Food` and parse all fields in the API.
 | 
			
		||||
    """
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Food
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BasicFoodSerializer(serializers.ModelSerializer):
 | 
			
		||||
    """
 | 
			
		||||
    REST API Serializer for BasicFood.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from .views import AllergenViewSet, BasicFoodViewSet, TransformedFoodViewSet, QRCodeViewSet
 | 
			
		||||
from .views import AllergenViewSet, FoodViewSet, BasicFoodViewSet, TransformedFoodViewSet, QRCodeViewSet
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def register_food_urls(router, path):
 | 
			
		||||
@@ -9,6 +9,7 @@ def register_food_urls(router, path):
 | 
			
		||||
    Configure router for Food REST API.
 | 
			
		||||
    """
 | 
			
		||||
    router.register(path + '/allergen', AllergenViewSet)
 | 
			
		||||
    router.register(path + '/food', FoodViewSet)
 | 
			
		||||
    router.register(path + '/basicfood', BasicFoodViewSet)
 | 
			
		||||
    router.register(path + '/transformedfood', TransformedFoodViewSet)
 | 
			
		||||
    router.register(path + '/qrcode', QRCodeViewSet)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,8 @@ from api.viewsets import ReadProtectedModelViewSet
 | 
			
		||||
from django_filters.rest_framework import DjangoFilterBackend
 | 
			
		||||
from rest_framework.filters import SearchFilter
 | 
			
		||||
 | 
			
		||||
from .serializers import AllergenSerializer, BasicFoodSerializer, TransformedFoodSerializer, QRCodeSerializer
 | 
			
		||||
from ..models import Allergen, BasicFood, TransformedFood, QRCode
 | 
			
		||||
from .serializers import AllergenSerializer, FoodSerializer, BasicFoodSerializer, TransformedFoodSerializer, QRCodeSerializer
 | 
			
		||||
from ..models import Allergen, Food, BasicFood, TransformedFood, QRCode
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AllergenViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
@@ -22,6 +22,19 @@ class AllergenViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    search_fields = ['$name', ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FoodViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    """
 | 
			
		||||
    REST API View set.
 | 
			
		||||
    The djangorestframework plugin will get all `Food` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/food/food/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Food.objects.order_by('id')
 | 
			
		||||
    serializer_class = FoodSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['name', ]
 | 
			
		||||
    search_fields = ['$name', ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BasicFoodViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    """
 | 
			
		||||
    REST API View set.
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ from note_kfet.inputs import Autocomplete
 | 
			
		||||
from note_kfet.middlewares import get_current_request
 | 
			
		||||
from permission.backends import PermissionBackend
 | 
			
		||||
 | 
			
		||||
from .models import BasicFood, TransformedFood, QRCode
 | 
			
		||||
from .models import Food, BasicFood, TransformedFood, QRCode
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QRCodeForms(forms.ModelForm):
 | 
			
		||||
@@ -22,7 +22,6 @@ class QRCodeForms(forms.ModelForm):
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.fields['food_container'].queryset = self.fields['food_container'].queryset.filter(
 | 
			
		||||
            is_ready=False,
 | 
			
		||||
            end_of_life__isnull=True,
 | 
			
		||||
            polymorphic_ctype__model='transformedfood',
 | 
			
		||||
        ).filter(PermissionBackend.filter_queryset(
 | 
			
		||||
@@ -151,3 +150,38 @@ class AddIngredientForms(forms.ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = TransformedFood
 | 
			
		||||
        fields = ('ingredients',)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ManageIngredientsForm(forms.Form):
 | 
			
		||||
    """
 | 
			
		||||
    Form to manage ingredient
 | 
			
		||||
    """
 | 
			
		||||
    fully_used = forms.BooleanField()
 | 
			
		||||
    fully_used.initial = True
 | 
			
		||||
    fully_used.required = True
 | 
			
		||||
    fully_used.label = _('Fully used')
 | 
			
		||||
 | 
			
		||||
    name = forms.CharField()
 | 
			
		||||
    name.widget = Autocomplete(
 | 
			
		||||
        model=Food,
 | 
			
		||||
        resetable=True,
 | 
			
		||||
        attrs={"api_url": "/api/food/food",
 | 
			
		||||
               "class": "autocomplete"},
 | 
			
		||||
    )
 | 
			
		||||
    name.label = _('Name')
 | 
			
		||||
 | 
			
		||||
    qrcode = forms.IntegerField()
 | 
			
		||||
    qrcode.widget = Autocomplete(
 | 
			
		||||
        model=QRCode,
 | 
			
		||||
        resetable=True,
 | 
			
		||||
        attrs={"api_url": "/api/food/qrcode/",
 | 
			
		||||
               "name_field": "qr_code_number",
 | 
			
		||||
               "class": "autocomplete"},
 | 
			
		||||
    )
 | 
			
		||||
    qrcode.label = _('QR code number')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ManageIngredientsFormSet = forms.formset_factory(
 | 
			
		||||
    ManageIngredientsForm,
 | 
			
		||||
    extra=1,
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
	<a class="btn btn-sm btn-primary" href="{% url "food:add_ingredient" pk=food.pk %}">
 | 
			
		||||
	  {% trans "Add to a meal" %}
 | 
			
		||||
	</a>
 | 
			
		||||
      {% endif %}
 | 
			
		||||
      {% if manage_ingredients %}
 | 
			
		||||
        <a class="btn btn-sm btn-secondary" href="{% url "food:manage_ingredients" pk=food.pk %}">
 | 
			
		||||
	  {% trans "Manage ingredients" %}
 | 
			
		||||
	</a>
 | 
			
		||||
      {% endif %}
 | 
			
		||||
	<a class="btn btn-sm btn-primary" href="{% url "food:food_list" %}">
 | 
			
		||||
	  {% trans "Return to the food list" %}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										116
									
								
								apps/food/templates/food/manage_ingredients.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								apps/food/templates/food/manage_ingredients.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% comment %}
 | 
			
		||||
Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
 | 
			
		||||
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">
 | 
			
		||||
    {{ title }}
 | 
			
		||||
  </h3>
 | 
			
		||||
  <div class="card-body" id="form"></div>
 | 
			
		||||
  <form method="post" action="">
 | 
			
		||||
    {% csrf_token %}
 | 
			
		||||
    <table class="table table-condensed table-striped">
 | 
			
		||||
      {# Fill initial data #}
 | 
			
		||||
      {% for display, form in formset %}
 | 
			
		||||
      {% if forloop.first %}
 | 
			
		||||
      <thead>
 | 
			
		||||
	<tr>
 | 
			
		||||
	  <th>{{ form.name.label }}</th>
 | 
			
		||||
	  <th>{{ form.qrcode.label }}</th>
 | 
			
		||||
	  <th>{{ form.fully_used.label }}</th>
 | 
			
		||||
	</tr>
 | 
			
		||||
      </thead>
 | 
			
		||||
      <tbody id="form_body">
 | 
			
		||||
	{% endif %}
 | 
			
		||||
	{% if display %}
 | 
			
		||||
	<tr class="row-formset ingredients">
 | 
			
		||||
	{% else %}
 | 
			
		||||
	<tr class="row-formset ingredients" style="display: none">
 | 
			
		||||
	{% endif %}
 | 
			
		||||
	  <td>{{ form.name }}</td>
 | 
			
		||||
	  <td>{{ form.qrcode }}</td>
 | 
			
		||||
	  <td>{{ form.fully_used }}</td>
 | 
			
		||||
	</tr>
 | 
			
		||||
      {% endfor %}
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
 | 
			
		||||
    {# Display buttons to add and remove ingredients #}
 | 
			
		||||
    <div class="card-body">
 | 
			
		||||
      <div class="btn-group btn-block" role="group">
 | 
			
		||||
	<button type="button" id="add_more" class="btn btn-success">{% trans "Add ingredient" %}</button>
 | 
			
		||||
	<button type="button" id="remove_one" class="btn btn-danger">{% trans "Remove ingredient" %}</button>
 | 
			
		||||
      </div>
 | 
			
		||||
    <button class="btn btn-primary" type="submit">{% trans "Submit"%}</button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </form>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
{% block extrajavascript %} 
 | 
			
		||||
<script>
 | 
			
		||||
/* script that handles add and remove lines */
 | 
			
		||||
 | 
			
		||||
const foods = {{ ingredients | safe }};
 | 
			
		||||
 | 
			
		||||
function set_ingredient_id () {
 | 
			
		||||
	let ingredients = document.getElementsByClassName('ingredients');
 | 
			
		||||
	for (var i = 0; i < ingredients.length; i++) {
 | 
			
		||||
		ingredients[i].id = 'ingredients-' + parseInt(i);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
set_ingredient_id();
 | 
			
		||||
 | 
			
		||||
function prepopulate () {
 | 
			
		||||
	for (var i = 0; i < {{ ingredients_count }}; i++) {
 | 
			
		||||
		let prefix = 'id_form-' + parseInt(i) + '-';
 | 
			
		||||
		document.getElementById(prefix + 'name_pk').value = parseInt(foods[i]['food_pk']);
 | 
			
		||||
		document.getElementById(prefix + 'name').value = foods[i]['food_name'];
 | 
			
		||||
		document.getElementById(prefix + 'qrcode_pk').value = parseInt(foods[i]['qr_pk']);
 | 
			
		||||
		if (foods[i]['qr_number'] === '') {
 | 
			
		||||
			document.getElementById(prefix + 'qrcode').value = '';
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
		document.getElementById(prefix + 'qrcode').value = parseInt(foods[i]['qr_number']);
 | 
			
		||||
		};
 | 
			
		||||
		document.getElementById(prefix + 'fully_used').checked = Boolean(foods[i]['fully_used']);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
prepopulate();
 | 
			
		||||
 | 
			
		||||
function delete_form_data (form_id) {
 | 
			
		||||
	let prefix = "id_form-" + parseInt(form_id) + "-";
 | 
			
		||||
	document.getElementById(prefix + "name_pk").value = "";
 | 
			
		||||
	document.getElementById(prefix + "name").value = "";
 | 
			
		||||
	document.getElementById(prefix + "qrcode_pk").value = "";
 | 
			
		||||
	document.getElementById(prefix + "qrcode").value = "";
 | 
			
		||||
	document.getElementById(prefix + "fully_used").checked = true;
 | 
			
		||||
}
 | 
			
		||||
var form_count = {{ ingredients_count }} + 1;
 | 
			
		||||
 | 
			
		||||
$('#add_more').click(function () {
 | 
			
		||||
	let ingredient_form = document.getElementById('ingredients-' + parseInt(form_count));
 | 
			
		||||
	if (ingredient_form === null) {
 | 
			
		||||
		addMsg(gettext("You can't add more ingredient"), "danger",  5000);
 | 
			
		||||
		return;};
 | 
			
		||||
	ingredient_form.style = "display: true";
 | 
			
		||||
	form_count += 1;
 | 
			
		||||
});
 | 
			
		||||
  
 | 
			
		||||
$('#remove_one').click(function () {
 | 
			
		||||
	let ingredient_form = document.getElementById('ingredients-' + parseInt(form_count - 1));
 | 
			
		||||
	if (ingredient_form === null) {
 | 
			
		||||
		return;};
 | 
			
		||||
	ingredient_form.style = "display: none";
 | 
			
		||||
	delete_form_data(form_count - 1);
 | 
			
		||||
	form_count -= 1;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
addMsg(gettext("Add ingredient with their name or their qrcode, if two different priority is given to qrcode"), "warning"); 
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										87
									
								
								apps/food/templates/food/transformedfood_update.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								apps/food/templates/food/transformedfood_update.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% comment %}
 | 
			
		||||
Copyright (C) by BDE ENS Paris-Saclay
 | 
			
		||||
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">
 | 
			
		||||
    {{ title }}
 | 
			
		||||
  </h3>
 | 
			
		||||
  <div class="card-body" id="form">
 | 
			
		||||
    <form method="post">
 | 
			
		||||
      {% csrf_token %}
 | 
			
		||||
      {{ form | crispy }}
 | 
			
		||||
      <table class="table table-condensed table-striped">
 | 
			
		||||
        {# Fill initial data #}
 | 
			
		||||
        {% for ingredient_form in formset %}
 | 
			
		||||
        {% if forloop.first %}
 | 
			
		||||
        <thead>
 | 
			
		||||
          <tr>
 | 
			
		||||
	    <th>{% trans "Name" %}</th>
 | 
			
		||||
	    <th>{% trans "QR-code number" %}</th>
 | 
			
		||||
	    <th>{% trans "Fully used" %}<th>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody id="form_body">
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        <tr class="row-formset">
 | 
			
		||||
		{{ ingredient_form | crispy }}
 | 
			
		||||
          <td>{{ ingredient_form.name }}</td>
 | 
			
		||||
	  <td>{{ ingredient_form.qrcode }}</td>
 | 
			
		||||
	  <td>{{ ingredient_form.fully_used }}</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
      {# Display buttons to add and remove products #}
 | 
			
		||||
      <div class="card-body">
 | 
			
		||||
        <div class="btn-group btn-block" role="group">
 | 
			
		||||
          <button type="button" id="add_more" class="btn btn-success">{% trans "Add ingredient" %}</button>
 | 
			
		||||
	  <button type="button" id="remove_one" class="btn btn-danger">{% trans "Remove ingredient" %}</button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <button type="submit" class="btn btn-block btn-primary">{% trans "Submit" %}</button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </form>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{# 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.qrcode }}</td>
 | 
			
		||||
		<td>{{ formset.empty_form.fully_used }}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
        </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
{% block extrajavascript %}
 | 
			
		||||
<script>
 | 
			
		||||
    /* script that handles add and remove lines */
 | 
			
		||||
    IDS = {};
 | 
			
		||||
 | 
			
		||||
    $("#id_form-TOTAL_FORMS").val($(".row-formset").length - 1);
 | 
			
		||||
 | 
			
		||||
    $('#add_more').click(function () {
 | 
			
		||||
        let form_idx = $('#id_form-TOTAL_FORMS').val();
 | 
			
		||||
        $('#form_body').append($('#for_real').html().replace(/__prefix__/g, form_idx));
 | 
			
		||||
        $('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1);
 | 
			
		||||
        $('#id_form-' + parseInt(form_idx) + '-id').val(IDS[parseInt(form_idx)]);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $('#remove_one').click(function () {
 | 
			
		||||
        let form_idx = $('#id_form-TOTAL_FORMS').val();
 | 
			
		||||
        if (form_idx > 0) {
 | 
			
		||||
            IDS[parseInt(form_idx) - 1] = $('#id_form-' + (parseInt(form_idx) - 1) + '-id').val();
 | 
			
		||||
            $('#form_body tr:last-child').remove();
 | 
			
		||||
            $('#id_form-TOTAL_FORMS').val(parseInt(form_idx) - 1);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -13,6 +13,7 @@ urlpatterns = [
 | 
			
		||||
    path('<int:slug>/add/basic', views.BasicFoodCreateView.as_view(), name='basicfood_create'),
 | 
			
		||||
    path('add/transformed', views.TransformedFoodCreateView.as_view(), name='transformedfood_create'),
 | 
			
		||||
    path('update/<int:pk>', views.FoodUpdateView.as_view(), name='food_update'),
 | 
			
		||||
    path('update/ingredients/<int:pk>', views.ManageIngredientsView.as_view(), name='manage_ingredients'),
 | 
			
		||||
    path('detail/<int:pk>', views.FoodDetailView.as_view(), name='food_view'),
 | 
			
		||||
    path('detail/basic/<int:pk>', views.BasicFoodDetailView.as_view(), name='basicfood_view'),
 | 
			
		||||
    path('detail/transformed/<int:pk>', views.TransformedFoodDetailView.as_view(), name='transformedfood_view'),
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,9 @@ from permission.backends import PermissionBackend
 | 
			
		||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView, LoginRequiredMixin
 | 
			
		||||
 | 
			
		||||
from .models import Food, BasicFood, TransformedFood, QRCode
 | 
			
		||||
from .forms import AddIngredientForms, BasicFoodForms, TransformedFoodForms, BasicFoodUpdateForms, TransformedFoodUpdateForms, QRCodeForms
 | 
			
		||||
from .forms import QRCodeForms, BasicFoodForms, TransformedFoodForms, \
 | 
			
		||||
    ManageIngredientsForm, ManageIngredientsFormSet, AddIngredientForms, \
 | 
			
		||||
    BasicFoodUpdateForms, TransformedFoodUpdateForms
 | 
			
		||||
from .tables import FoodTable
 | 
			
		||||
from .utils import pretty_duration
 | 
			
		||||
 | 
			
		||||
@@ -73,7 +75,7 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li
 | 
			
		||||
                    PermissionBackend.filter_queryset(self.request, Food, 'view'))
 | 
			
		||||
        # table served
 | 
			
		||||
        served_table = self.get_queryset().order_by('-pk').filter(
 | 
			
		||||
            end_of_life='', is_ready=True).filter(
 | 
			
		||||
            end_of_life='', is_ready=True).exclude(
 | 
			
		||||
                Q(polymorphic_ctype__model='basicfood',
 | 
			
		||||
                  basicfood__date_type='DLC',
 | 
			
		||||
                  expiry_date__lte=timezone.now(),)
 | 
			
		||||
@@ -106,7 +108,7 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QRCodeCreateView(ProtectQuerysetMixin, CreateView):
 | 
			
		||||
class QRCodeCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
 | 
			
		||||
    """
 | 
			
		||||
    A view to add qrcode
 | 
			
		||||
    """
 | 
			
		||||
@@ -238,12 +240,82 @@ class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
 | 
			
		||||
        form.instance.is_ready = False
 | 
			
		||||
        return super().form_valid(form)
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, *args, **kwargs):
 | 
			
		||||
        context = super().get_context_data(*args, **kwargs)
 | 
			
		||||
        context['title'] += ' ' + self.object.name
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
    def get_success_url(self, **kwargs):
 | 
			
		||||
        self.object.refresh_from_db()
 | 
			
		||||
        return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AddIngredientView(ProtectQuerysetMixin, UpdateView):
 | 
			
		||||
MAX_FORMS = 10
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ManageIngredientsView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
    """
 | 
			
		||||
    A view to manage ingredient for a transformed food
 | 
			
		||||
    """
 | 
			
		||||
    model = TransformedFood
 | 
			
		||||
    fields = ['ingredients']
 | 
			
		||||
    extra_context = {"title": _("Manage ingredients of:")}
 | 
			
		||||
    template_name = 'food/manage_ingredients.html'
 | 
			
		||||
 | 
			
		||||
    @transaction.atomic
 | 
			
		||||
    def form_valid(self, form):
 | 
			
		||||
        old_ingredients = list(self.object.ingredients.all()).copy()
 | 
			
		||||
        old_allergens = list(self.object.allergens.all()).copy()
 | 
			
		||||
        self.object.ingredients.clear()
 | 
			
		||||
        for i in range(self.object.ingredients.all().count() + 1 + MAX_FORMS):
 | 
			
		||||
            prefix = 'form-' + str(i) + '-'
 | 
			
		||||
            if form.data[prefix + 'qrcode'] not in ['0', '']:
 | 
			
		||||
                ingredient = QRCode.objects.get(pk=form.data[prefix + 'qrcode']).food_container
 | 
			
		||||
                self.object.ingredients.add(ingredient)
 | 
			
		||||
                if (prefix + 'fully_used') in form.data and form.data[prefix + 'fully_used'] == 'on':
 | 
			
		||||
                    ingredient.end_of_life = _('Fully used in {meal}'.format(
 | 
			
		||||
                        meal=self.object.name))
 | 
			
		||||
                    ingredient.save()
 | 
			
		||||
 | 
			
		||||
            elif form.data[prefix + 'name'] != '':
 | 
			
		||||
                ingredient = Food.objects.get(pk=form.data[prefix + 'name'])
 | 
			
		||||
                self.object.ingredients.add(ingredient)
 | 
			
		||||
                if (prefix + 'fully_used') in form.data and form.data[prefix + 'fully_used'] == 'on':
 | 
			
		||||
                    ingredient.end_of_life = _('Fully used in {meal}'.format(
 | 
			
		||||
                        meal=self.object.name))
 | 
			
		||||
                    ingredient.save()
 | 
			
		||||
 | 
			
		||||
        self.object.save(old_ingredients=old_ingredients, old_allergens=old_allergens)
 | 
			
		||||
        return HttpResponseRedirect(self.get_success_url())
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, *args, **kwargs):
 | 
			
		||||
        context = super().get_context_data(*args, **kwargs)
 | 
			
		||||
        context['title'] += ' ' + self.object.name
 | 
			
		||||
        formset = ManageIngredientsFormSet()
 | 
			
		||||
        ingredients = self.object.ingredients.all()
 | 
			
		||||
        formset.extra += ingredients.count() + MAX_FORMS
 | 
			
		||||
        context['form'] = ManageIngredientsForm()
 | 
			
		||||
        context['ingredients_count'] = ingredients.count()
 | 
			
		||||
        display = [True] * (1 + ingredients.count()) + [False] * (formset.extra - ingredients.count() - 1)
 | 
			
		||||
        context['formset'] = zip(display, formset)
 | 
			
		||||
        context['ingredients'] = []
 | 
			
		||||
        for ingredient in ingredients:
 | 
			
		||||
            qr = QRCode.objects.filter(food_container=ingredient)
 | 
			
		||||
 | 
			
		||||
            context['ingredients'].append({
 | 
			
		||||
                'food_pk': ingredient.pk,
 | 
			
		||||
                'food_name': ingredient.name,
 | 
			
		||||
                'qr_pk': '' if qr.count() == 0 else qr[0].pk,
 | 
			
		||||
                'qr_number': '' if qr.count() == 0 else qr[0].qr_code_number,
 | 
			
		||||
                'fully_used': 'true' if ingredient.end_of_life else '',
 | 
			
		||||
            })
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
    def get_success_url(self, **kwargs):
 | 
			
		||||
        return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AddIngredientView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
    """
 | 
			
		||||
    A view to add ingredient to a meal
 | 
			
		||||
    """
 | 
			
		||||
@@ -404,6 +476,7 @@ class TransformedFoodDetailView(FoodDetailView):
 | 
			
		||||
            pretty_duration(self.object.shelf_life)
 | 
			
		||||
        ))
 | 
			
		||||
        context["foods"] = self.object.ingredients.all()
 | 
			
		||||
        context["manage_ingredients"] = True
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
    def get(self, *args, **kwargs):
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user