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

Django内联表单集(Inline Formset)数据无法保存问题求助

Django内联表单集提交后数据未保存问题

我创建的Django内联表单集前端显示正常,但提交后相关数据没有保存到数据库,Admin后台只能看到Recipe模型的标准字段,VariantIngredient和Image的内联数据完全无记录。以下是相关代码:

views.py 保存逻辑

class RecipeInline():
    form_class = RecipeForm
    model = Recipe
    template_name = "recipes/recipe_form.html"

    def form_valid(self, form):
        named_formsets = self.get_named_formsets()
        if not all((x.is_valid() for x in named_formsets.values())):
            return self.render_to_response(self.get_context_data(form=form))
        
        self.object = form.save()

        for name, formset in named_formsets.items():
            formset_save_func = getattr(self, 'formset_{0}_valid'.format(name), None)
            if formset_save_func is not None:
                formset_save_func(formset)
            else:
                formset.save()
        return redirect('recipes-home')
    
    def formset_variantingredients_valid(self, formset):
        variantingredients = formset.save(commit=False)
        for variantingredient in variantingredients:
            variantingredient.recipe = self.object
            variantingredient.save()

    def formset_images_valid(self, formset):
        images = formset.save(commit=False)
        for image in images:
            image.recipe = self.object
            image.save()

models.py 相关模型

class Image(models.Model):
    recipe = models.ForeignKey(
        Recipe, on_delete=models.CASCADE, null=True
        )
    image = models.ImageField(blank=True, upload_to='images')

    def __str__(self):
        return self.recipe.title
    
class VariantIngredient(models.Model):
    recipe = models.ForeignKey(
        Recipe, on_delete=models.CASCADE
        )
    ingredient = models.CharField(max_length=100)
    quantity = models.PositiveIntegerField(default=1)
    unit = models.CharField(max_length=10, choices=unit_choice, default='g')

    def __str__(self):
        return self.recipe.title

forms.py 表单定义

class RecipeForm(forms.ModelForm):

    class Meta:
        model = Recipe
        fields = ['title','description','temperature','serving','prep_time', 'cook_time']
        
    def __init__(self, *args, **kwargs):
        super(RecipeForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_tag = True
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-3 create-label'
        self.helper.field_class = 'col-md-9'
        self.helper.layout = Layout(
            Div(
                Field('title'),
                Field('description'),
                Field('temperature'),
                Field('serving'),
                Field('prep_time'),
                Field('cook_time'),
            )
        )


class ImageForm(forms.ModelForm):

    class Meta:
        model = Image
        fields = '__all__'

class VariantIngredientForm(forms.ModelForm):

    class Meta:
        model = VariantIngredient
        fields = '__all__'
        widgets = {
            'ingredient': forms.TextInput(
                attrs={
                    'class': 'form-control'
                    }
                ),
            'quantity': forms.NumberInput(
                attrs={
                    'class': 'form-control'
                    }
                ),
            'unit': forms.TextInput(
                attrs={
                    'class': 'form-control'
                    }
                ),
        }
    

VariantIngredientFormSet = inlineformset_factory(
    Recipe, VariantIngredient, form=VariantIngredientForm,
    extra=1, can_delete=True
)
ImageFormSet = inlineformset_factory(
    Recipe, Image, form=ImageForm,
    extra=1, can_delete=True
)

问题根源

核心问题在表单集的初始化与数据绑定环节:

  1. RecipeInline未继承Django通用视图(如CreateView),导致get_context_data等核心方法无法正常工作
  2. 未实现get_named_formsets的具体逻辑,无法从请求中获取表单集提交的数据
  3. 表单包含外键字段(recipe),既冗余又可能干扰数据绑定
  4. 图片上传未处理request.FILES,且表单未设置正确的enctype

修正方案

1. 修复views.py

from django.views.generic.edit import CreateView
from .forms import RecipeForm, VariantIngredientFormSet, ImageFormSet

class RecipeInline(CreateView):
    form_class = RecipeForm
    model = Recipe
    template_name = "recipes/recipe_form.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # 根据请求类型初始化表单集
        if self.request.POST:
            context['variantingredients'] = VariantIngredientFormSet(
                self.request.POST, instance=self.object
            )
            context['images'] = ImageFormSet(
                self.request.POST, self.request.FILES, instance=self.object
            )
        else:
            context['variantingredients'] = VariantIngredientFormSet(instance=self.object)
            context['images'] = ImageFormSet(instance=self.object)
        return context

    def get_named_formsets(self):
        # 返回已绑定数据的表单集
        context = self.get_context_data()
        return {
            'variantingredients': context['variantingredients'],
            'images': context['images']
        }

    def form_valid(self, form):
        self.object = form.save(commit=False)
        named_formsets = self.get_named_formsets()
        
        # 验证所有表单集
        if not all(formset.is_valid() for formset in named_formsets.values()):
            return self.render_to_response(self.get_context_data(form=form))
        
        self.object.save()
        
        # 保存表单集,自动关联外键并处理删除
        for formset in named_formsets.values():
            formset.instance = self.object
            formset.save()
        
        return redirect('recipes-home')

2. 修复forms.py

排除外键字段,避免冗余控件:

class ImageForm(forms.ModelForm):
    class Meta:
        model = Image
        exclude = ['recipe']  # 由视图自动绑定外键

class VariantIngredientForm(forms.ModelForm):
    class Meta:
        model = VariantIngredient
        exclude = ['recipe']  # 由视图自动绑定外键
        widgets = {
            'ingredient': forms.TextInput(attrs={'class': 'form-control'}),
            'quantity': forms.NumberInput(attrs={'class': 'form-control'}),
            'unit': forms.TextInput(attrs={'class': 'form-control'}),
        }

# 内联表单集定义保持不变
VariantIngredientFormSet = inlineformset_factory(
    Recipe, VariantIngredient, form=VariantIngredientForm,
    extra=1, can_delete=True
)
ImageFormSet = inlineformset_factory(
    Recipe, Image, form=ImageForm,
    extra=1, can_delete=True
)

3. 模板补充说明

确保表单包含文件上传编码,且渲染表单集的管理字段:

<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.as_p }}

    <!-- 渲染VariantIngredient表单集 -->
    {{ variantingredients.management_form }}
    {% for form in variantingredients %}
        {{ form.as_p }}
    {% endfor %}

    <!-- 渲染Image表单集 -->
    {{ images.management_form }}
    {% for form in images %}
        {{ form.as_p }}
    {% endfor %}

    <button type="submit">保存</button>
</form>

关键说明

  • 必须继承CreateView才能复用Django通用视图的内置逻辑
  • 表单集初始化时,POST请求要绑定request.POST和request.FILES(针对文件上传)
  • 内联表单集设置formset.instance = self.object后,会自动处理外键关联,无需手动循环赋值
  • 图片上传必须设置表单enctype="multipart/form-data",否则无法获取上传文件

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 10:54:51