I will try to briefly explain the logic behind it: the form for entering the recipe data appears –> once the recipe has been saved the hidden buttons appear –> I click on the Add Ingredient button –> the contents of AddIngredientRecipe.html.
When I go to click on the Load Ingredient from Inventory button, in the modal body, nothing appears. Instead, when, once the recipe has been saved, I go to add only the ingredient, by clicking the Load Ingredient from Inventory button the ingredients present in the inventory appear. Also the Save button of addIngredientRecipe.html works, so I don’t know why Load Ingredient from Inventory doesn’t work too.
P.s.: I decided not to embed addIngredientRecipe.html in addrecipe.html for code reuse reasons. Because once the recipe has been saved, I can only insert the ingredients.
Now I leave you the codes to make you understand better.
addRecipe.html
{% extends 'beerRecipe/baseHome.html' %}
{% load static %}
{% load crispy_forms_tags %}
{% block content %}
{% spaceless %}
{% if user.is_authenticated %}
<div id="message-container" class="d-none">
<div class="alert alert-dismissible" role="alert">
<span id="message-content"></span>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
<h3 style="margin-left: 12px">Recipe</h3>
<div class="container-fluid" id="recipe-container">
<form id="recipe-form" method="post"
action="{% if recipe %}
{% url 'add-recipe' recipe.id %}
{% else %}
{% url 'add-recipe' %}
{% endif %}">
{% csrf_token %}
{% crispy recipe_form %}
</form>
<button class="btn btn-success" type="button" id="saveRecipeButton">Save Recipe</button>
{% if not hide_button %}
<a class="btn btn-secondary" href="{% url 'home' %}" style="margin-left: 5px">Back Home</a>
{% else %}
<a class="btn btn-secondary" href="{% url 'view-recipe' recipe.id %}" style="margin-left: 5px">Back</a>
{% endif %}
</div>
<div class="container-fluid d-none" id="action-buttons">
<button type="button" id="addIngredientButton" class="btn btn-primary"
data-url-template="{% url 'add-ingredient-recipe' 0 %}">
Add Ingredient
</button>
<button type="button" id="addStepButton" class="btn btn-primary"
data-url-template="{% url 'add-step-recipe' 0 %}"
style="margin-left: 5px">
Add Step
</button>
<button class="btn btn-success" type="button" id="updateRecipeButton" style="margin-left: 5px"
data-base-url="{% url 'add-recipe' %}">
Update Recipe
</button>
</div>
<div class="container-fluid" id="ingredient-container" style="margin-top: 25px"></div>
<div class="container-fluid" id="step-container" style="margin-top: 25px"></div>
{% endif %}
<script src="{% static 'formset/jquery_formset.js' %}"></script>
<script type="text/javascript">
let globalRecipeId = null;
$(document).ready(function () {
$('#saveRecipeButton').on('click', function () {
const form_data = new FormData();
form_data.append('csrfmiddlewaretoken', document.getElementsByName('csrfmiddlewaretoken')[0].value);
form_data.append('name', document.getElementById('id_name').value);
form_data.append('litre', document.getElementById('id_litre').value);
form_data.append('ebc', document.getElementById('id_ebc').value);
form_data.append('ibu', document.getElementById('id_ibu').value);
$.ajax({
type: 'POST',
url: event.target.action,
data: form_data,
success: function (response) {
if (response.recipe_id) {
$('#saveRecipeButton').hide();
$('#action-buttons').removeClass('d-none');
globalRecipeId = response.recipe_id;
}
$('#message-content').text('Recipe added successfully');
$('#message-container').removeClass('d-none alert-danger').addClass('alert-success show');
console.log(response)
},
error: function (error) {
$('#message-content').text('An error occurred. Please try again.');
$('#message-container').removeClass('d-none alert-success').addClass('alert-danger show');
console.log(error);
},
contentType: false,
processData: false,
});
event.preventDefault();
});
$('#updateRecipeButton').on('click', function () {
const form_data = new FormData(document.getElementById('recipe-form'));
form_data.append('csrfmiddlewaretoken', document.getElementsByName('csrfmiddlewaretoken')[0].value);
const baseUrl = $(this).data('base-url');
const url = baseUrl + (globalRecipeId ? globalRecipeId + '/' : '');
$.ajax({
type: 'POST',
url: url,
data: form_data,
success: function (response) {
$('#message-content').text('Recipe update successfully');
$('#message-container').removeClass('d-none alert-danger').addClass('alert-success show');
console.log(response)
},
error: function (error) {
$('#message-content').text('An error occurred. Please try again.');
$('#message-container').removeClass('d-none alert-success').addClass('alert-danger show');
console.log(error);
},
contentType: false,
processData: false,
});
event.preventDefault();
});
$('#addIngredientButton').on('click', function () {
if (globalRecipeId) {
let url = $(this).data('url-template').replace('0', globalRecipeId);
$('#addIngredientButton').hide();
$('#ingredient-container').load(url);
}
});
$('#addStepButton').on('click', function () {
if (globalRecipeId) {
let url = $(this).data('url-template').replace('0', globalRecipeId);
$('#addStepButton').hide();
$('#step-container').load(url);
}
});
});
</script>
{% endspaceless %}
{% endblock %}
addIngredientRecipe.html
{% extends 'beerRecipe/baseHome.html' %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
{% spaceless %}
{% if user.is_authenticated %}
<div id="message-container" class="d-none">
<div class="alert alert-dismissible" role="alert">
<span id="message-content"></span>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
<div class="container-fluid">
<form method="post" id="ingredient-form"
action="{% if ingredient %}
{% url 'add-ingredient-recipe' recipe.id ingredient.id_ingredient.id %}
{% else %}
{% url 'add-ingredient-recipe' recipe.id %}
{% endif %}">
<h3>Recipe Ingredients</h3>
{% csrf_token %}
{% crispy ingredient_form %}
<h5>Property</h5>
<table class="table table-striped">
{{ property_ingredient_recipe.management_form }}
{% for form in property_ingredient_recipe.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="{% cycle 'row1' 'row2' %} formset_row">
{% for field in form.visible_fields %}
<td>
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<div role="group" style="margin-bottom: 10px">
<button class="btn btn-success" type="submit" id="saveIngredientButton"
style="margin-bottom: 15px">Save Ingredient
</button>
<input type="hidden" id="ingredient_selected" name="ingredient_selected" value="">
<button type="button" class="btn btn-primary" data-bs-toggle="modal"
data-bs-target="#ingredientModal" style="margin-bottom: 15px; margin-left: 10px"
data-url="{% url 'load-ingredient' %}">
Load ingredient from {{ default_inventory.name }}
</button>
{% if not hide_button %}
<a class="btn btn-secondary" type="button" href="{% url 'view-recipe' recipe.id %}" style="margin-bottom: 15px; margin-left: 10px">Back</a>
{% endif %}
</div>
</form>
<!-- Modal for load ingredient from default inventory-->
<div class="modal fade" id="ingredientModal" tabindex="-1" aria-labelledby="ingredientModalLabel"
aria-hidden="true" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="ingredientModalLabel">Ingredient
of {{ default_inventory.name }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="availableIngredients"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="addSelectedIngredients">
Add Selected Ingredients
</button>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<script src="{% static 'formset/jquery_formset.js' %}"></script>
<script type="text/javascript">
$('.formset_row').formset({
prefix: 'property',
addText: 'Add',
deleteText: 'Delete',
formCssClass: 'dynamic-formset1',
addCssClass: 'add-row btn btn-primary',
deleteCssClass: 'delete-row btn btn-danger',
added: function (row) {
row.find('input[name*="name"]').removeAttr('disabled')
}
});
$('#div_id_name_category').hide();
$('#div_id_name_new_category').hide();
const loadIngredientUrl = "{% url 'load-ingredient' %}";
const addSelectedIngredientUrl = "{% url 'add-selected-ingredients' %}";
$(document).ready(function () {
let name_category = $('#id_category_choices_0');
let name_new_category = $('#id_category_choices_1');
let toggle_category = function () {
$('#div_id_name_category').toggle();
};
let toggle_new_category = function () {
$('#div_id_name_new_category').toggle();
};
name_category.click(function () {
if (name_new_category.is(":checked")) {
toggle_new_category();
name_new_category.prop('checked', false);
}
toggle_category();
});
name_new_category.click(function () {
if (name_category.is(":checked")) {
toggle_category();
name_category.prop('checked', false);
}
toggle_new_category();
});
$('.popover-dismiss').popover({
trigger: 'focus'
});
$('#saveIngredientButton').on('click', function () {
const form_data = new FormData();
const ingredientId = $('#ingredient_selected').val() || '';
form_data.append('csrfmiddlewaretoken', document.getElementsByName('csrfmiddlewaretoken')[0].value);
form_data.append('name_ingredient', document.getElementById('id_name_ingredient').value);
form_data.append('name_category', document.getElementById('id_name_category').value);
form_data.append('name_new_category', document.getElementById('id_name_new_category').value);
form_data.append('quantity', document.getElementById('id_quantity').value);
form_data.append('measurement_unit', document.getElementById('id_measurement_unit').value);
form_data.append('comment', document.getElementById('id_comment').value);
form_data.append('ingredient_selected', ingredientId);
if (document.getElementById('id_name_category').value && document.getElementById('id_name_new_category').value) {
alert("Both 'Name Category' and 'New Category Name' cannot be filled at the same time.");
}
document.querySelectorAll('.formset_row').forEach((element, index) => {
if (element.style.display !== 'none') {
let propertyName = element.querySelector('input[name$="-name"]').value;
let propertyValue = element.querySelector('input[name$="-value"]').value;
if (propertyName && propertyValue) {
form_data.append(`property-${index}-name`, propertyName);
form_data.append(`property-${index}-value`, propertyValue);
}
}
});
const formIngredientActionUrl = $('#ingredient-form').attr('action');
$.ajax({
type: 'POST',
url: formIngredientActionUrl,
data: form_data,
success: function (response) {
clearFormFields();
$('#message-content').text('Ingredient added successfully');
$('#message-container').removeClass('d-none alert-danger').addClass('alert-success show');
console.log(response)
},
error: function (error) {
$('#message-content').text('An error occurred. Please try again.');
$('#message-container').removeClass('d-none alert-success').addClass('alert-danger show');
console.log(error);
},
contentType: false,
processData: false,
});
event.preventDefault();
});
$('#ingredientModal').on('show.bs.modal', function () {
$.ajax({
url: loadIngredientUrl,
type: 'GET',
success: function (response) {
$('#availableIngredients').empty();
response.forEach(function (ingredient) {
$('#availableIngredients').append(
`<input type="radio" name="ingredient" value="${ingredient.id}"> ${ingredient.name}<br>`
);
});
console.log(response)
},
error: function (error) {
console.log(error);
}
});
});
$('#addSelectedIngredients').on('click', function () {
const selectedIngredientId = $('#availableIngredients input[name="ingredient"]:checked').val();
$('#ingredient_selected').val(selectedIngredientId);
if (!selectedIngredientId) {
alert('Please select an ingredient before adding.')
return;
}
$.ajax({
url: addSelectedIngredientUrl,
type: 'GET',
data: {
'ingredient_id': selectedIngredientId,
},
success: function (data) {
$('#id_name_ingredient').val(data.name_ingredient);
$('#id_name_category').val(data.name_category);
$('#id_quantity').val(data.quantity);
$('#id_measurement_unit').val(data.measurement_unit);
updateFormset(data);
$('#ingredientModal').modal('hide');
$('#message-content').text('Ingredient added successfully');
$('#message-container').removeClass('d-none alert-danger').addClass('alert-success show');
},
error: function (error) {
$('#message-content').text('An error occurred. Please try again.');
$('#message-container').removeClass('d-none alert-success').addClass('alert-danger show');
console.log(error)
}
});
});
});
function updateFormset(data) {
let totalForms = $('#id_property-TOTAL_FORMS');
let maxForms = parseInt(totalForms.val());
let dataLength = data.properties.length;
if (dataLength < maxForms) {
for (let i = dataLength; i < maxForms; i++) {
$(`#id_property-${i}-name`).closest('tr').remove();
}
}
totalForms.val(dataLength);
data.properties.forEach((property, index) => {
let nameField = $(`#id_property-${index}-name`);
let valueField = $(`#id_property-${index}-value`);
if (nameField.length && valueField.length) {
nameField.val(property.name).prop('disabled', false);
valueField.val(property.value);
} else {
addForm(property,index);
}
});
}
function addForm(property, index) {
let rowClass = (index % 2 == 0) ? 'row1' : 'row2';
let newRow = $(` <tr class="${rowClass} formset_row dynamic-formset1">
<td>
<input type="text" name="property-${index}-name" value="${property.name}" class="form-control form-control-sm">
</td>
<td>
<input type="number" name="property-${index}-value" value="${property.value}" class="form-control form-control-sm" step="any">
</td>
<td>
<input type="hidden" name="property-${index}-DELETE">
<a class="delete-row btn btn-danger" href="javascript:void(0)">Delete</a>
</td>
</tr>`
);
$('.dynamic-formset1').closest('table').find('tbody').find('.dynamic-formset1-add').before(newRow);
}
function clearFormFields() {
$('#ingredient-form input[type="text"], #ingredient-form input[type="number"]').val('');
$('#ingredient-form select').prop('selectIndex', 0);
document.querySelectorAll('.formset_row').forEach((element) => {
element.querySelectorAll('input[type="number"]').forEach((input) => {
input.value = '';
});
});
}
</script>
{% endspaceless %}
{% endblock %}
views.py
@login_required
def load_ingredients_from_inventory(request):
inventory = Inventory.objects.filter(id_user=request.user)
default_inventory = inventory.filter(is_default=True).first()
if default_inventory:
inventory_ingredients = InventoryIngredient.objects.filter(id_inventory=default_inventory).select_related(
'id_ingredient')
ingredients = [{
'id': inv_ingredient.id_ingredient.id,
'name': inv_ingredient.id_ingredient.name,
'quantity': inv_ingredient.quantity,
'measurement_unit': inv_ingredient.measurement_unit
} for inv_ingredient in inventory_ingredients]
else:
ingredients = []
return JsonResponse(list(ingredients), safe=False)
My views.py method is correct, because when I go to add an ingredient from an already created recipe (so I go to directly load addIngredientRecipe.html) it loads the ingredients correctly. What I can’t figure out is why the script to load ingredients from inventory doesn’t work when I load addIngredientRecipe.html into addRecipe.html.
I would like to know how to solve this problem, and also if maybe I messed up the implementation to load addIngredientRecipe.html into addRecipe.html.