Django - несколько форм на одном представлении - передача несозданного объекта второй форме
Интересно, можно ли сделать две формы на основе модели django на одном представлении, одна из которых требует объект, который будет создан второй (Foreign Key). Я покажу пример, чтобы сделать его более понятным
У меня есть эти модели:
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
Как вы можете видеть, модель Indgredient принимает Recipe в качестве параметра. Я хочу сделать форму, которая позволит пользователю создать новый рецепт и ингредиент (или ингредиенты), поле которого будет заполнено рецептом, созданным в том же представлении
Я пытался сделать это таким образом
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)
Не обращайте внимания на то, что рецепт и ингредиент будут иметь одинаковые названия, я позже внесу некоторые изменения, чтобы исправить это.
Рецепт создается без проблем, но есть некоторые проблемы с ингредиентом, что я должен изменить, чтобы он работал, или может быть изменить мой подход и сделать форму совершенно другим способом?
редактирование: Я не уверен, что 'recipe_id' является правильным способом передачи id, но я получал ошибку, что поле с таким именем должно быть заполнено, поэтому я сделал его таким образом
Добавьте ввод формы имени во все ваши формы и элементы управления в def post вашего представления с
if request.POST['name_form] == 'form1':
#
elif request.POST['name_form'] == 'form2':
#
Сначала необходимо подготовить формы:
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 - функция формы модели Django docs
extra
- сколько наборов форм должно отображаться в шаблоне
Тогда в ваших представлениях:
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)
Здесь вы проверяете, действительны ли обе формы, затем сначала сохраняете форму родительской модели и присваиваете ее inlineformset (нам нужен id нового объекта Recipe).
И, наконец, ваша форма в шаблоне:
<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>
Это базовый пример. Не забудьте включить management_form из inlineformset. Не спешите, у меня ушло довольно много времени, чтобы понять это.