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

Django inline formset will always create new object instead of update them

我有 2 个模型 FirstSecond,FK 从 SecondFirst。我为 2 class 创建了一个表单,为 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 上创建新的第二个元素而不是更改它们。 我错过了什么?

我解决了! 我为 TOTAL_FORMSINITIAL_FORMS 设置了错误的值。来自 Django 的文档:

total_form_count returns the total number of forms in this formset. initial_form_count returns the number of forms in the formset that were pre-filled, and is also used to determine how many forms are required. You will probably never need to override either of these methods, so please be sure you understand what they do before doing so.

所以正确的使用方法是:

观看次数:

  • 使用 extra=0 生成 FormSets

在HTML中:

  • 设置 TOTAL_FORMS 您要发布的行数,如果动态 add/remove 行,则动态更改它;
  • 设置 INITIAL_FORMS 已填充的行数 (editing/deleting),永远不要更改;
  • 要删除预填充的行,请使用 DELETE 复选框而不是删除整行;

对我来说,我想更新我的图片,这里和其他所有关于处理隐藏表单的建议都没有用,直到我改变了这个。

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>