mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-11-21 05:48:26 +01:00
Add model Recipe
This commit is contained in:
@@ -47,6 +47,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<a class="btn btn-sm btn-secondary" href="{% url "food:manage_ingredients" pk=food.pk %}">
|
||||
{% trans "Manage ingredients" %}
|
||||
</a>
|
||||
<a class="btn btn-sm btn-secondary" href="{% url "food:recipe_use" pk=food.pk %}">
|
||||
{% trans "Use a recipe" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-sm btn-primary" href="{% url "food:food_list" %}">
|
||||
{% trans "Return to the food list" %}
|
||||
|
||||
@@ -70,6 +70,16 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% trans "New meal" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if can_view_recipes %}
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'food:recipe_list' %}">
|
||||
{% trans "View recipes" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if can_add_recipe %}
|
||||
<a class="btn btn-sm btn-primary" href="{% url 'food:recipe_create' %}">
|
||||
{% trans "New recipe" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% for activity in open_activities %}
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'food:dish_list' activity_pk=activity.pk %}">
|
||||
{% trans "View" %} {{ activity.name }}
|
||||
|
||||
@@ -90,7 +90,7 @@ function delete_form_data (form_id) {
|
||||
document.getElementById(prefix + "name").value = "";
|
||||
document.getElementById(prefix + "qrcode_pk").value = "";
|
||||
document.getElementById(prefix + "qrcode").value = "";
|
||||
document.getElementById(prefix + "fully_used").checked = true;
|
||||
document.getElementById(prefix + "fully_used").checked = false;
|
||||
}
|
||||
var form_count = {{ ingredients_count }} + 1;
|
||||
|
||||
|
||||
31
apps/food/templates/food/recipe_detail.html
Normal file
31
apps/food/templates/food/recipe_detail.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load i18n pretty_money %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-white mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
{{ title }} {{ recipe.name }}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
<ul>
|
||||
<li> {% trans "Creater" %} : {{ recipe.creater }}</li>
|
||||
<li> {% trans "Ingredients" %} :
|
||||
{% for ingredient in ingredients %} {{ ingredient }}{% if not forloop.last %},{% endif %}{% endfor %}
|
||||
</li>
|
||||
</ul>
|
||||
{% if update %}
|
||||
<a class="btn btn-sm btn-secondary" href="{% url "food:recipe_update" pk=recipe.pk %}">
|
||||
{% trans "Update" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-sm btn-primary" href="{% url "food:recipe_list" %}">
|
||||
{% trans "Return to recipe list" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
122
apps/food/templates/food/recipe_form.html
Normal file
122
apps/food/templates/food/recipe_form.html
Normal file
@@ -0,0 +1,122 @@
|
||||
{% 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>
|
||||
<form method="post" action="" id="recipe_form">
|
||||
{% csrf_token %}
|
||||
<div class="card-body">
|
||||
{% crispy recipe_form %}
|
||||
{# Keep all form elements in the same card-body for proper structure #}
|
||||
{{ formset.management_form }}
|
||||
<h3 class="text-center mt-4">{% trans "Add ingredients" %}</h3>
|
||||
<table class="table table-condensed table-striped">
|
||||
{% for form in formset %}
|
||||
{% if forloop.first %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ form.name.label }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="form_body">
|
||||
{% endif %}
|
||||
<tr class="row-formset ingredients">
|
||||
<td>
|
||||
{# Force prefix on the form fields #}
|
||||
{{ form.name.as_widget }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{# 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" form="recipe_form">{% trans "Submit"%}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{# Hidden div that store an empty supplement 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>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
<script>
|
||||
/* script that handles add and remove lines */
|
||||
$(document).ready(function() {
|
||||
const totalFormsInput = $('input[name$="-TOTAL_FORMS"]');
|
||||
const initialFormsInput = $('input[name$="-INITIAL_FORMS"]');
|
||||
|
||||
function updateTotalForms(n) {
|
||||
if (totalFormsInput.length) {
|
||||
totalFormsInput.val(n);
|
||||
}
|
||||
}
|
||||
|
||||
const initialCount = $('#form_body .row-formset').length;
|
||||
updateTotalForms(initialCount);
|
||||
|
||||
const foods = {{ ingredients | safe }};
|
||||
|
||||
function prepopulate () {
|
||||
for (var i = 0; i < {{ ingredients_count }}; i++) {
|
||||
let prefix = 'id_form-' + parseInt(i) + '-';
|
||||
document.getElementById(prefix + 'name').value = foods[i]['name'];
|
||||
};
|
||||
}
|
||||
prepopulate();
|
||||
|
||||
$('#add_more').click(function() {
|
||||
let formIdx = totalFormsInput.length ? parseInt(totalFormsInput.val(), 10) : $('#form_body .row-formset').length;
|
||||
let newForm = $('#for_real').html().replace(/__prefix__/g, formIdx);
|
||||
$('#form_body').append(newForm);
|
||||
updateTotalForms(formIdx + 1);
|
||||
});
|
||||
|
||||
$('#remove_one').click(function() {
|
||||
let formIdx = totalFormsInput.length ? parseInt(totalFormsInput.val(), 10) : $('#form_body .row-formset').length;
|
||||
if (formIdx > 1) {
|
||||
$('#form_body tr.row-formset:last').remove();
|
||||
updateTotalForms(formIdx - 1);
|
||||
}
|
||||
});
|
||||
|
||||
$('#recipe_form').on('submit', function() {
|
||||
const totalInput = $('input[name$="-TOTAL_FORMS"]');
|
||||
const prefix = totalInput.length ? totalInput.attr('name').replace(/-TOTAL_FORMS$/, '') : 'form';
|
||||
|
||||
$('#form_body tr.row-formset').each(function(i) {
|
||||
const input = $(this).find('input,select,textarea').first();
|
||||
if (input.length) {
|
||||
const newName = `${prefix}-${i}-name`;
|
||||
input.attr('name', newName).attr('id', `id_${newName}`).prop('disabled', false);
|
||||
}
|
||||
});
|
||||
|
||||
const visibleCount = $('#form_body tr.row-formset').length;
|
||||
if (totalInput.length) totalInput.val(visibleCount);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
32
apps/food/templates/food/recipe_list.html
Normal file
32
apps/food/templates/food/recipe_list.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-white mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
{{ title }}
|
||||
</h3>
|
||||
{% render_table table %}
|
||||
<div class="card-footer">
|
||||
{% if can_add_recipe %}
|
||||
<a class="btn btn-sm btn-success" href="{% url 'food:recipe_create' %}">{% trans "New recipe" %}</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-sm btn-primary" href="{% url "food:food_list" %}">
|
||||
{% trans "Return to the food list" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
<script type="text/javascript">
|
||||
$(".table-row").click(function () {
|
||||
window.document.location = $(this).data("href");
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
80
apps/food/templates/food/use_recipe_form.html
Normal file
80
apps/food/templates/food/use_recipe_form.html
Normal file
@@ -0,0 +1,80 @@
|
||||
{% 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 }} {{ object.name }}
|
||||
</h3>
|
||||
<div class="card-body" id="form">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form | crispy }}
|
||||
<button class="btn btn-primary" type="submit">{% trans "Submit"%}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
function refreshIngredients() {
|
||||
// 1️⃣ on récupère l'id de la recette sélectionnée
|
||||
let recipe_id = $("#id_recipe").val() || $("input[name='recipe']:checked").val();
|
||||
|
||||
if (!recipe_id) {
|
||||
// 2️⃣ rien sélectionné → on vide la zone d'ingrédients
|
||||
$("#div_id_ingredients > div").empty().html("<em>Aucune recette sélectionnée</em>");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3️⃣ on interroge le serveur
|
||||
$.getJSON("{% url 'food:get_ingredients' %}", { recipe_id: recipe_id })
|
||||
.done(function (data) {
|
||||
|
||||
// 4️⃣ on cible le bon conteneur
|
||||
const $container = $("#div_id_ingredients > div");
|
||||
$container.empty();
|
||||
|
||||
if (data.ingredients && data.ingredients.length > 0) {
|
||||
// 5️⃣ on crée les cases à cocher
|
||||
data.ingredients.forEach(function (ing, i) {
|
||||
const html = `
|
||||
<div class="form-check">
|
||||
<input type="checkbox"
|
||||
name="ingredients"
|
||||
value="${ing.id}"
|
||||
id="id_ingredients_${i}"
|
||||
class="form-check-input"
|
||||
checked>
|
||||
<label class="form-check-label" for="id_ingredients_${i}">
|
||||
${ing.name} (${ing.qr_code_numbers})
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
$container.append(html);
|
||||
});
|
||||
} else {
|
||||
$container.html("<em>Aucun ingrédient trouvé</em>");
|
||||
}
|
||||
})
|
||||
.fail(function (xhr) {
|
||||
console.error("Erreur AJAX:", xhr);
|
||||
$("#div_id_ingredients > div").html("<em>Erreur de chargement des ingrédients</em>");
|
||||
});
|
||||
}
|
||||
|
||||
// 6️⃣ déclenche quand la recette change
|
||||
$("#id_recipe, input[name='recipe']").change(refreshIngredients);
|
||||
|
||||
// 7️⃣ initial
|
||||
refreshIngredients();
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user