简体   繁体   中英

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

I've 2 model First and Second with a FK from Second to First . I created a form for the 2 class and a inline formset for Second . On template I manually designed my form and with jQuery I'm able to add dynamic forms of Second .

On UpdateView the form is correctly populated, but when I submit the form, all Second instances are created again with new ids instead of updating them. I double checked that on HTML there are name= PREFIX-FORM_COUNT-id with correct ids, but seems that Django ignores it.

I'm using Django 2.2.12 & Python 3.6

Here what I made:

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 (putted only essential tags)

<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>

I checked how Django creates forms and it will only add DELETE checkbox on it, but all other infos are correctly stored into the formset. When I do .save() it will create new Second element on db instead of change them. What am I missing?

I solved this! I setted TOTAL_FORMS and INITIAL_FORMS with wrong values. From Django's docs:

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.

So the correct way to use it is:

In views:

  • Generate FormSets with extra=0

In HTML:

  • Set TOTAL_FORMS with number of rows you are POSTing and change it dinamically if dinamically add/remove rows;
  • Set INITIAL_FORMS with number of alredy filled rows (editing/deleting) and never change this;
  • To delete a pre-filled row use DELETE checkbox instead of removing entire row;

For me i wanted to update my images, everything suggested here and every other forums about handling the hidden form didn't worked until i changed this.

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

To this.

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

Then like magic this ugly error stopped showing, and my new image successfully got updated

<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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM