I wonder if it is possible to make two Django model based forms on one view which one requires object which is gonna be created by second (Foreign Key). I will show an example to make it more understandable.
I got these models:
class Recipe(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Ingredient(models.Model):
name = models.CharField(max_length=200)
quantity = models.CharField(max_length=200)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
def __str__(self):
return self.name
As you can see, Ingredient model takes Recipe as an parameter. I'd like to make a form, which allow user to make new Recipe and Ingredient (or ingredients) which recipe field will be filled by a recipe creating in the same view
I was trying to make it this way
from .models import Ingredient, Recipe
from django.forms import ModelForm
class IngredientForm(ModelForm):
class Meta:
model = Ingredient
fields = ['name', 'quantity']
class RecipeForm(ModelForm):
class Meta:
model = Recipe
fields = ['name']
def recipeAndIngredientsCreationView(request):
form1 = RecipeForm()
form2 = IngredientForm()
if request.method == "POST":
data1 = {
'csrfmiddlewaretoken': request.POST['csrfmiddlewaretoken'],
'name': request.POST['name'],
}
form1 = RecipeForm(data1)
if form1.is_valid():
form1.save()
print("recipe created")
data2 = {
'csrfmiddlewaretoken': request.POST['csrfmiddlewaretoken'],
'name': request.POST['name'],
'quantity' : request.POST['quantity'],
'recipe_id': Recipe.objects.get(name=request.POST['name']).id,
}
form1 = IngredientForm(data2)
if form2.is_valid():
form2.save()
return redirect('home')
context = {'form1':form1, 'form2':form2}
return render(request, 'recipes/create_recipe.html', context)
Don't pay attention to the fact that recipe and ingredient will have the same names, I will make some changes later to fix it.
Recipe creates without any problem but there is some issue with ingredient, what should I change to make it working, or maybe change my approach and make form in totally different way ?
edit: I'm not sure if 'recipe_id' is correct way to pass id but I was getting error that field with that name need to be filled so I made it that way
edit2:
I made a little research and I got to this point:
def recipe_create_view(request):
context = {}
form = RecipeForm(request.POST or None)
IngredientFormset = formset_factory(IngredientForm)
formset = IngredientFormset(request.POST or None)
context['form'] = form
context['formset'] = formset
if request.method == "POST":
if form.is_valid() and formset.is_valid():
parent = form.save(commit=False)
parent.save()
for form in formset:
child = form.save(commit=False)
child.recipe = parent
child.save()
return render(request, 'recipes/create_recipe.html', context)
But it still does not work, what should I change ? Am I even close to reach what I wanted ?
CodePudding user response:
First you need to prepare your forms:
from django import forms
from django.forms.models import inlineformset_factory
from .models import Ingredient, Recipe
class RecipeForm(ModelForm):
class Meta:
model = Recipe
fields = ['name']
IngredientInlineFormset = inlineformset_factory(Recipe, Ingredient, fields='__all__', extra=1)
inlineformset_factory is model form function Django docs
The extra
is for how many formsets should show in template
Then in your views:
def new_recipe(request):
if request.method == 'POST':
form_inline = IngredientInlineFormset(request.POST)
form = RecipeForm(request.POST)
if form.is_valid() and form_inline.is_valid():
instance = form.save()
form_inline.instance = instance
form_inline.save()
return redirect('index')
else:
print(form.errors, form_inline.errors)
else:
form_inline = IngredientInlineFormset()
form = RecipeForm()
context = {
'form': form,
'form_inline': form_inline,
}
return render(request, 'new_recipe.html', context)
Here you check if both forms are valid, then first save parent model form and assign it to inlineformset (we need to have id of new Recipe object).
And finally your form in template:
<form method="post">
{% csrf_token %}
{{ form }}
{{ form_inline.management_form }}
{{ form_inline.non_form_errors }}
{% for form in form_inline %}
<p>{{ form }}</p>
{% endfor %}
<button type="submit">Add Recipe with Ingredients</button>
</form>
This is basic example. Remember to include management_form of inlineformset. Take it slow it took me quite sometime to figure this out.
CodePudding user response:
Add name form input to your all forms and control in def post your view with
if request.POST['name_form] == 'form1':
#
elif request.POST['name_form'] == 'form2':
#