[Django] #28620: Loop on boundfields at init of inline sub-forms breaks the form set

9 views
Skip to first unread message

Django

unread,
Sep 20, 2017, 7:47:59 AM9/20/17
to django-...@googlegroups.com
#28620: Loop on boundfields at init of inline sub-forms breaks the form set
--------------------------------------+-------------------------------
Reporter: jxrossel | Owner: nobody
Type: Bug | Status: new
Component: Forms | Version: 1.11
Severity: Normal | Keywords: inlineformset
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
--------------------------------------+-------------------------------
When doing a loop (even a dummy one) on their boundfields at the init
phase, the sub-forms of an inline form set are "broken", i.e. **the parent
key field is rendered instead of being hidden and many additional SQL
requests are generated**.

In this illustration case, I have 2 related models - Fig and Line - where
each Fig can have many Line(s).

**forms.py**
{{{
class FigForm( ModelForm ):
class Meta:
model = Fig
fields = '__all__'

class LineForm( ModelForm ):
class Meta:
model = Line
fields = '__all__'

def __init__(self, *args, **kwargs):
super( LineForm, self ).__init__(*args, **kwargs)

for bf in self: # <- this screws everything
pass # even with a dummy loop

LineFormSet = inlineformset_factory( Fig, Line, form=LineForm, extra=1 )
}}}

In real life, I use this loop in a custom model form class to add default
CSS classes to the form widgets, but the additionnal SQL requests kill my
server.


----

== Additional information

**model.py**
{{{
class Fig(models.Model):
name = models.CharField(max_length=200,unique=True)
xlabel = models.CharField(max_length=200,blank=True)
ylabel = models.CharField(max_length=200,blank=True)
descr = models.TextField(blank=True)
pub_date = models.DateTimeField('date published',auto_now=True)

def __str__(self):
return self.name

def get_absolute_url(self):
return reverse('webplot:detail', kwargs={'pk':self.pk} )


class Line(models.Model):
fig = models.ForeignKey(Fig, on_delete=models.CASCADE)
x = models.CharField(max_length=200)
y = models.CharField(max_length=200)
label = models.CharField(max_length=20)

def __str__(self):
return self.label + ': y(x)=' + self.y + ' for x=' + self.x

}}}

**views.py**
{{{
def detail(request,pk='new'):
# Used for both CREATE and UPDATE
# create a form instance and populate it with data from the request if
any
if pk == 'new':
fig = Fig() # instanciate empty object
else:
fig = get_object_or_404( Fig, pk=pk )

form = FigForm( request.POST or None, instance=fig )
subform = LineFormSet( request.POST or None, instance=fig )

if request.method == 'POST':
if form.is_valid() and subform.is_valid():
form.save()
subform.save()
return redirect(fig)

ctx = {'form': form, 'subform': subform }

return render(request, 'webplot/detail.html', ctx )
}}}

**detail.html**
{{{
<form method="post" class="w3-container">{% csrf_token %}

<h4>Figure settings</h4>
<table class="w3-table">
{{ form.as_table }}
</table>

<h4>Line settings</h4>
{{ subform.management_form }}
{{ subform.non_field_errors }}
<table class="w3-table">
{% for thisform in subform.forms %}
{% if forloop.first %}
<thead>
<tr class="w3-border-bottom">
{% for field in thisform.visible_fields %}
<th>{{ field.label_tag }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% endif %}
<tr>
{{ thisform.non_field_errors }}
{% for field in thisform.hidden_fields %}
{{ field }}
{% endfor %}
{% for field in thisform.visible_fields %}
<td>{{ field.errors }}{{ field }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<input type="submit" value="Submit" class="w3-btn" />
</form>
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28620>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Sep 25, 2017, 11:09:09 AM9/25/17
to django-...@googlegroups.com
#28620: Loop on boundfields at init of inline sub-forms breaks the form set
-------------------------------------+-------------------------------------
Reporter: Jonathan Rossel | Owner:
| DmitriySelischev
Type: Bug | Status: assigned
Component: Forms | Version: 1.11
Severity: Normal | Resolution:

Keywords: inlineformset | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by DmitriySelischev):

* status: new => assigned
* owner: nobody => DmitriySelischev


--
Ticket URL: <https://code.djangoproject.com/ticket/28620#comment:1>

Django

unread,
Sep 25, 2017, 11:42:56 AM9/25/17
to django-...@googlegroups.com
#28620: Loop on boundfields at init of inline sub-forms breaks the form set
-------------------------------------+-------------------------------------
Reporter: Jonathan Rossel | Owner:
| DmitriySelischev
Type: Bug | Status: assigned
Component: Forms | Version: master
Severity: Normal | Resolution:

Keywords: inlineformset | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by DmitriySelischev):

* version: 1.11 => master


--
Ticket URL: <https://code.djangoproject.com/ticket/28620#comment:2>

Django

unread,
Sep 28, 2017, 9:05:51 AM9/28/17
to django-...@googlegroups.com
#28620: Loop on boundfields at init of inline sub-forms breaks the form set
-------------------------------------+-------------------------------------
Reporter: Jonathan Rossel | Owner:
| DmitriySelischev
Type: Bug | Status: assigned
Component: Forms | Version: master
Severity: Normal | Resolution:
Keywords: inlineformset | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Tim Graham):

I don't think I've every seen a need to iterate over bound fields in
`__init__()` -- certainly that's not a documented pattern. Can you instead
make your modifications using `self.fields['field_name'].widget` in
`Form.__init__()`?

--
Ticket URL: <https://code.djangoproject.com/ticket/28620#comment:3>

Django

unread,
Sep 29, 2017, 7:58:30 AM9/29/17
to django-...@googlegroups.com
#28620: Loop on boundfields at init of inline sub-forms breaks the form set
-------------------------------------+-------------------------------------
Reporter: Jonathan Rossel | Owner:
| DmitriySelischev
Type: Bug | Status: assigned
Component: Forms | Version: master
Severity: Normal | Resolution:
Keywords: inlineformset | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Jonathan Rossel):

Replying to [comment:3 Tim Graham]:


> I don't think I've every seen a need to iterate over bound fields in
`__init__()` -- certainly that's not a documented pattern. Can you instead
make your modifications using `self.fields['field_name'].widget` in
`Form.__init__()`?

Hi,

Thanks for the hint! It does work nicely when accessing `.widget` from
`fields['field_name']` instead of passing by the bound field. I guess it
must come from a difference in the modification location in the
initialization sequence (?). After re-reading the docs, I still don't
grasp the exact difference between fields and bound fields though, and why
what I've done should go against a documented pattern. The only things
I've found are:
* "''If you want to make one widget instance look different from another,
you will need to specify additional attributes at the time when the widget
object is instantiated and assigned to a form field (and perhaps add some
rules to your CSS files)''."
[https://docs.djangoproject.com/en/1.11/ref/forms/widgets/#styling-widget-
instances]. That does not say "but before the bound field is instanciated"
* "''If you want to provide some additional classes in addition to the
error and required classes that may be required, you can provide those
classes as an argument (of the BoundField instance **css_classes**
method)''." [https://docs.djangoproject.com/en/1.11/ref/forms/api
/#methods-of-boundfield]. That was probably what led me to this loop on
bound fields...

At this point, I am not sure if this ticket should stay as a bug or should
be converted to a request for documentation improvement (possibly combined
with a modification of the code to raise an exception if someone does what
I did).

Anyway, thanks for the help !

--
Ticket URL: <https://code.djangoproject.com/ticket/28620#comment:4>

Django

unread,
Sep 29, 2017, 2:16:40 PM9/29/17
to django-...@googlegroups.com
#28620: Loop on boundfields at init of inline sub-forms breaks the form set
-------------------------------------+-------------------------------------
Reporter: Jonathan Rossel | Owner:
| DmitriySelischev
Type: Bug | Status: closed
Component: Forms | Version: master
Severity: Normal | Resolution: invalid

Keywords: inlineformset | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Tim Graham):

* status: assigned => closed
* resolution: => invalid


Comment:

I opened #28655 to add some documentation about your use case. I don't
think trying to prevent iterating bound fields is worthwhile and I can't
think of a simple way to do that, offhand.

--
Ticket URL: <https://code.djangoproject.com/ticket/28620#comment:5>

Reply all
Reply to author
Forward
0 new messages