I have recipe model with ingredients and ingredients quantities.
MEAL_TYPES = (("midi", "Midi"), ("soir", "Soir"), ('all', 'Non spécifié'))
class Ingredient(models.Model):
name = models.CharField(max_length=100)
# Vous pouvez ajouter d'autres champs pour stocker des informations supplémentaires sur les ingrédients
def __str__(self):
return self.name
class Recipe(models.Model):
"""
A model to create and manage recipes
"""
user = models.ForeignKey(
User, related_name="recipe_owner", on_delete=models.CASCADE
)
title = models.CharField(max_length=300, null=False, blank=False)
description = models.CharField(max_length=500, null=False, blank=False)
instructions = RichTextField(max_length=10000, null=False, blank=False)
ingredients = RichTextField(max_length=10000, null=False, blank=False)
image = ResizedImageField(
size=[400, None],
quality=75,
upload_to="recipes/",
force_format="WEBP",
blank=False,
null=False,
)
image_alt = models.CharField(max_length=100, default="Recipe image")
meal_type = models.CharField(max_length=50, choices=MEAL_TYPES, default="all")
calories = models.IntegerField(default=0)
posted_date = models.DateTimeField(auto_now=True)
newingredient = models.ManyToManyField(Ingredient, through='IngredientQuantite')
class Meta:
ordering = ["-posted_date"]
def __str__(self):
return str(self.title)
class IngredientQuantite(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE)
quantity = models.FloatField(default=0)
unite = models.CharField(default="g", max_length=20, choices=[("g", "g"),('mg', 'mg'), ("ml", "ml"),('kg', "kg"), ('cl', "cl"), ('l', "l"), ('caf', "cuillère à café"), ('cas', "cuillère à soupe"), ('verre', "verre"), ('bol', "bol"), ('pincee', "pincée"), ('unite', "unité")])
# Le champ "quantity" stocke la quantité de cet ingrédient dans la recette.
# 'g', 'kg', 'ml', 'cl', 'l', 'cuillère à café', 'cuillère à soupe', 'verre', 'bol', 'pincée', 'unité'
def __str__(self):
return f"{self.quantity} {self.ingredient} in {self.recipe}"
To add new recipes, I have an AddRecipe view and a DuplicateRecipe view which work perfectly before I introduce the newIngredients.
The inlinesforms looks like this: end of my form with inlines form
It works for all the fields except the inline ones.
Here my views.py code
class AddRecipe(LoginRequiredMixin, CreateView):
template_name = "recipes/add_recipe.html"
model = Recipe
form_class = RecipeForm
success_url = "/recipes/"
def get_context_data(self, **kwargs):
ctx=super().get_context_data(**kwargs)
if self.request.POST:
# ctx['form']=RecipeForm(self.request.POST)
ctx['inlines']=IngredientQuantiteFormSet(self.request.POST)
else:
# ctx['form']=RecipeForm()
ctx['inlines']=IngredientQuantiteFormSet()
return ctx
def form_valid(self, form):
form.instance.user = self.request.user
ctx = self.get_context_data()
inlines = ctx['inlines']
if inlines.is_valid() and form.is_valid():
req = form.save()
inlines.instance = req
print(inlines.instance)
inlines.save()
return super(AddRecipe, self).form_valid(form)
class DuplicateRecipe(LoginRequiredMixin, CreateView):
template_name = "recipes/duplicate_recipe.html"
model = Recipe
form_class = RecipeForm
success_url = "/recipes/" # Redirigez l'utilisateur vers la liste des recettes après la duplication
def get_initial(self):
# Récupérez la recette d'origine par clé primaire
original_recipe = get_object_or_404(Recipe, pk=self.kwargs["pk"])
# Créez un dictionnaire d'initialisation pour le formulaire
initial = {
"title": f"Copy of {original_recipe.title}",
"description": original_recipe.description,
"ingredients": original_recipe.ingredients,
"instructions": original_recipe.instructions,
# Ajoutez d'autres champs liés à votre modèle Recipe ici
"image": original_recipe.image,
"image alt": original_recipe.image_alt,
"meal_type": original_recipe.meal_type,
"calories": original_recipe.calories,
}
return initial
# def form_valid(self, form, ):
# form.instance.user = self.request.user
# return super(DuplicateRecipe, self).form_valid(form)
def get_context_data(self, **kwargs):
ctx=super().get_context_data(**kwargs)
original_recipe = get_object_or_404(Recipe, pk=self.kwargs["pk"])
if self.request.POST:
# ctx['form']=RecipeForm(self.request.POST)
ctx['inlines']=IngredientQuantiteFormSet(self.request.POST)
else:
# ctx['form']=RecipeForm()
ctx['inlines']=IngredientQuantiteFormSet(instance=original_recipe)
return ctx
def form_valid(self, form):
form.instance.user = self.request.user
ctx = self.get_context_data()
inlines = ctx['inlines']
if inlines.is_valid() and form.is_valid():
req = form.save()
inlines.instance = req
inlines.save()
return super(DuplicateRecipe, self).form_valid(form)
# def form_invalid(self, form, formset):
# return self.render_to_response(self.get_context_data(form=form, formset=formset))
If I go the addRecipe form, the form is empty at the start and the validation form create the recipe as expected. If i go the DuplicateRecipe form, all the fields are already complete when I valid the duplicateRecipe form, all fields are saved in a new recipe except the inlines. If I remove the "instance=original_recipe" it works as well as addRecipe but without the autocompletion that I expect for a duplication.
Anyone have an idea that why it doesn't work with the instance=original_recipe ? or how to fix the form validation problem?
In case anyone has the same problem later, I will detail the solution I found.
Thanks this link, I managed to refactor my code more cleanly. I then redid the duplicate function using get_initial which I already had as well as using a custom inlineformset_factory with the "initial" parameter.
The template code
add_recipe_bis.html: