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:
In HTML:
TOTAL_FORMS
with number of rows you are POSTing and change it dinamically if dinamically add/remove rows;INITIAL_FORMS
with number of alredy filled rows (editing/deleting) and never change this;DELETE
checkbox instead of removing entire row;