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

The*_*ist 2 python django django-forms formset inline-formset

我有 2 个模型First,并且SecondFK 从SecondFirst。我为 2 类创建了一个表单,并为Second. 在模板上,我手动设计了表单,并使用 jQuery,我可以添加Second.

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

我正在使用 Django 2.2.12 和 Python 3.6

这是我所做的:

模型.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)
Run Code Online (Sandbox Code Playgroud)

表格.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)
Run Code Online (Sandbox Code Playgroud)

视图.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)
        ...
Run Code Online (Sandbox Code Playgroud)

.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>
Run Code Online (Sandbox Code Playgroud)

我检查了 Django 如何创建表单,它只会在其上添加 DELETE 复选框,但所有其他信息都正确存储到表单集中。当我这样做时,.save()它将在数据库上创建新的第二个元素,而不是更改它们。我缺少什么?

The*_*ist 5

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

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

所以正确的使用方法是:

在视图中:

  • 生成带有 extra=0 的 FormSet

在 HTML 中:

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