You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django如何在同一表单中创建Recipe父模型与RecipeIngredient子模型

问题解决方案

不需要预先生成空白Recipe实例,这种方案会产生大量无效脏数据,还要额外处理未提交数据的清理逻辑,成本很高,推荐使用Django原生功能实现。

标准实现方案:使用Django Inline Formset

Django内置的Inline Formset是官方专门为父子关联模型同表单提交的场景设计的功能,核心逻辑是先校验所有表单数据,校验通过后再保存父模型获取主键,最后用主键关联保存所有子模型数据,天然解决外键依赖问题。

具体实现步骤如下:

  • 第一步:定义表单和表单集
    在forms.py中声明父模型表单和子模型内联表单集:
from django.forms import ModelForm
from django.forms.models import inlineformset_factory
from .models import Recipe, RecipeIngredient

# 父模型Recipe的表单
class RecipeForm(ModelForm):
    class Meta:
        model = Recipe
        fields = ['name', 'description', 'image']

# 子模型RecipeIngredient的内联表单集
RecipeIngredientFormSet = inlineformset_factory(
    Recipe,
    RecipeIngredient,
    fields=['name', 'description'],
    extra=3, # 默认展示3个空白食材输入行
    can_delete=True # 允许用户删除多余的输入行
)
  • 第二步:视图层处理提交逻辑
    在views.py中处理表单提交的完整流程:
from django.shortcuts import render, redirect
from .forms import RecipeForm, RecipeIngredientFormSet

def recipe_create(request):
    if request.method == 'POST':
        recipe_form = RecipeForm(request.POST, request.FILES)
        ingredient_formset = RecipeIngredientFormSet(request.POST)
        # 同时校验父表单和子表单集所有数据
        if recipe_form.is_valid() and ingredient_formset.is_valid():
            # 先保存Recipe父模型,补充当前登录用户的外键值
            recipe = recipe_form.save(commit=False)
            recipe.user = request.user
            recipe.save()
            # 绑定外键后保存所有食材数据
            ingredients = ingredient_formset.save(commit=False)
            for ingredient in ingredients:
                ingredient.recipe = recipe
                ingredient.save()
            # 处理用户标记删除的食材数据
            for del_obj in ingredient_formset.deleted_objects:
                del_obj.delete()
            # 保存完成后跳转到食谱详情页
            return redirect('recipe_detail', pk=recipe.pk)
    else:
        recipe_form = RecipeForm()
        ingredient_formset = RecipeIngredientFormSet()
    return render(request, 'recipe_create.html', {
        'recipe_form': recipe_form,
        'ingredient_formset': ingredient_formset
    })
  • 第三步:模板层渲染表单
    在模板文件中渲染表单,注意必须渲染表单集的management_form字段,这是Django用来记录表单集行数、删除状态的内置字段,缺失会导致校验失败:
<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <!-- 渲染食谱基础信息表单 -->
    {{ recipe_form.as_p }}
    <!-- 渲染表单集管理字段,不可省略 -->
    {{ ingredient_formset.management_form }}
    <!-- 渲染所有食材输入表单 -->
    {% for form in ingredient_formset %}
        <div class="ingredient-item">
            {{ form.as_p }}
        </div>
    {% endfor %}
    <button type="submit">保存食谱</button>
</form>

自定义实现方案(前后端分离场景适用)

如果是前后端分离架构,不需要使用Django内置表单渲染,可以用数据库事务实现:

  • 前端把Recipe基础信息和所有Ingredient的信息组装为统一的请求参数传给后端
  • 后端开启数据库事务,先校验并保存Recipe实例获取主键,再循环创建所有Ingredient实例绑定该Recipe主键,全部操作成功后提交事务,任意一步报错则回滚事务,不会产生脏数据。

内容的提问来源于stack exchange,提问作者Nick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 02:54:05