Django 内联表单集将始终创建新对象而不是更新它们

Posted

技术标签:

【中文标题】Django 内联表单集将始终创建新对象而不是更新它们【英文标题】:Django inline formset will always create new object instead of update them 【发布时间】:2020-09-17 02:11:24 【问题描述】:

我有两个模型 FirstSecond,FK 从 SecondFirst。我为 2 类创建了一个表单,为Second 创建了一个内联表单集。在模板上,我手动设计了我的表单,并使用 jQuery 我能够添加Second 的动态表单。

在 UpdateView 上,表单已正确填充,但当我提交表单时,所有 Second 实例都会使用新 ID 再次创建,而不是更新它们。我仔细检查了 html 上是否有 name=PREFIX-FORM_COUNT-id 具有正确的 ID,但似乎 Django 忽略了它。

我正在使用 Django 2.2.12 和 Python 3.6

这是我做的:

models.py

class First(models.Model):
    name = models.CharField(max_length=100, null=False)

class Second(models.Model):
    first= models.ForeignKey(First, null=False, on_delete=models.CASCADE)
    number= models.FloatField(null=False, default=0)

form.py

class FirstForm(forms.ModelForm):

    class Meta:
        model = First
        fields = "__all__"


class SecondForm(forms.ModelForm):
    class Meta:
        model = Second
        fields = "__all__"


SecondsFormset = inlineformset_factory(First, Second, SecondForm)

view.py

class FirstUpdateView(UpdateView):
    template_name = "first.html"
    model = First
    form_class = FirstForm
    context_object_name = "first_obj"

    def get_success_url(self):
        return reverse(...)

    def forms_valid(self, first, seconds):
        try:
            first.save()
            seconds.save()
            messages.success(self.request, "OK!")

        except DatabaseError as err:
            print(err)
            messages.error(self.request, "Ooops!")
        return HttpResponseRedirect(self.get_success_url())

    def post(self, request, *args, **kwargs):
        first_form = FirstForm(request.POST, instance=self.get_object())
        second_forms = SecondsFormset(request.POST, instance=self.get_object(), prefix="second")
        if first_form .is_valid() and second_forms.is_valid():
            return self.forms_valid(first_form , second_forms)
        ...

.html (仅放置必要的标签)

<form method="post">
    % csrf_token %
    <input type="text" id="name" value=" first_obj.name " name="name" required>
    <input type="hidden" name="second-TOTAL_FORMS" value="0" id="second-TOTAL_FORMS">
    <input type="hidden" name="second-INITIAL_FORMS" value="0" id="second-INITIAL_FORMS">
    <input type="hidden" name="second-MIN_NUM_FORMS" value="0" id="second-MIN_NUM_FORMS">
    <div id="seconds_container">
        % for s in first_obj.second_set.all %
            <input type="hidden"  name="second-forloop.counter0-id" value="s.pk">
            <input type="hidden"  name="second-forloop.counter0-first" value="first_obj.pk">
            <input type="number" min="0" max="10" step="1" value="s.number" name="second-forloop.counter0-number" required>
        % endfor %
    </div>
    <button class="btn btn-success" type="submit">Update</button>
</form>

我检查了 Django 如何创建表单,它只会在其上添加 DELETE 复选框,但所有其他信息都正确存储到表单集中。当我执行 .save() 时,它将在 db 上创建新的 Second 元素,而不是更改它们。 我错过了什么?

【问题讨论】:

【参考方案1】:

我解决了这个问题! 我用错误的值设置了TOTAL_FORMSINITIAL_FORMS。来自 Django 的文档:

total_form_count 返回此表单集中的表单总数。 initial_form_count 返回表单集中预填表单的数量,也用于确定需要多少表单。您可能永远不需要覆盖这些方法中的任何一个,因此请确保您在这样做之前了解它们的作用。

所以正确的使用方法是:

在视图中:

使用 extra=0 生成 FormSets

在 HTML 中:

TOTAL_FORMS 设置为您要发布的行数,并在动态添加/删除行时动态更改它; 将INITIAL_FORMS设置为已填充的行数(编辑/删除),并且永远不要更改它; 要删除预先填充的行,请使用DELETE 复选框,而不是删除整行;

【讨论】:

我遇到了类似的问题,你的回答给了我什么问题的提示。我正在将表单动态添加到表单集中,但是使用已经在数据库中实例化的行(通过模式创建)。所以我实际上不得不 +1 INITIAL_FORMS(以及 TOTAL_FORMS),因为所有非初始形式显然都被视为新记录。因此,即使 formset.cleaned_data 显示了现有的 pk,一些 form.instance 显示了一个新对象。当告诉 Django 它们都是初始形式时,情况发生了变化。【参考方案2】:

对于我来说,我想更新我的图像,这里建议的所有内容以及所有其他有关处理隐藏表单的论坛都没有工作,直到我更改了这个。

product_img_form = ProductImageFormSet(data=request.FILES or None, instance=your_model_instance)

到这里。

product_img_form = ProductImageFormSet(request.POST or None, request.FILES or None, instance=your_model_instance)

然后像魔术一样,这个丑陋的错误停止显示,我的新图像成功更新

<tr><td colspan="2">
<ul class="errorlist nonfield">
<li>(Hidden field TOTAL_FORMS) This field is required.</li>
<li>(Hidden field INITIAL_FORMS) This field is required.</li>
</ul>
<input type="hidden" name="product_images-TOTAL_FORMS" id="id_product_images-TOTAL_FORMS">
<input type="hidden" name="product_images-INITIAL_FORMS" id="id_product_images-INITIAL_FORMS">
<input type="hidden" name="product_images-MIN_NUM_FORMS" id="id_product_images-MIN_NUM_FORMS">
<input type="hidden" name="product_images-MAX_NUM_FORMS" id="id_product_images-MAX_NUM_FORMS">
</td></tr>

【讨论】:

以上是关于Django 内联表单集将始终创建新对象而不是更新它们的主要内容,如果未能解决你的问题,请参考以下文章

Django:如何显示内联表单集中每个模型对象的表单错误

Django 编辑表单数据:数据被复制而不是被更新

如果引发 ValidationError,删除链接会在 Django 管理内联表单集中消失

带有内联表单集的 Django 表单验证

Django 的内联表单

单元测试 django 内联表单集