Allow custom "BoundFields" on forms and make BoundField public API

456 views
Skip to first unread message

Moritz Sichert

unread,
Aug 8, 2015, 7:34:52 AM8/8/15
to django-d...@googlegroups.com
Hello,

I'd like to propose a new feature for forms.

Currently if you need a form field with some extra features you can just
subclass from django.forms.Field and add all the features you need.

However as soon as you're in a template and want to access the field you
will get a BoundField instead of your custom field. The need to
differentiate BoundField and Field is clear but it would be nice if it
was possible to also customize BoundField.

For this reason I propose adding a new method "bind_to_form()" to
django.forms.Field. It takes a form and a name and returns a BoundField
specific to the Field instance. The default implementation just returns
an instance of the existing BoundField but now it is possible to
customize it.

To make it easier, BoundField should become public API. That means it
should be able to import it with "from django.forms import BoundField"
in order to subclass it.

In [1] you can see an implementation of bind_to_form().
I also wrote BoundChoiceField as an example that allows better access to
the choices. In [2] you can see how this could be used.

Considering BoundField wasn't really public before, this change
shouldn't bring any backwards incompatibilities since the default
behaviour stays the same. In my test branch all tests pass.

I'd really like to hear your feedback on this.
If this sounds reasonable I will start writing documentation and
possibly drafting a DEP if that's needed.

Moritz

[1]: https://github.com/MoritzS/django/tree/bound-fields
[2]: https://gist.github.com/MoritzS/7bd792f2eaf37da28dfb

signature.asc

Tim Graham

unread,
Aug 8, 2015, 7:42:42 AM8/8/15
to Django developers (Contributions to Django itself)
Here is a ticket for making BoundField a public API: https://code.djangoproject.com/ticket/12856
Writing that documentation seems like a good place to start.

I would suggest not to move around so much code in your branch as that makes it difficult to tell what (if anything) has changed.

Moritz Sichert

unread,
Aug 10, 2015, 5:27:40 AM8/10/15
to Django developers (Contributions to Django itself)
I split the commit in my branch to make the actual changes more visible
and moved the example out of the branch into the gist.

I will look into the ticket and work out some documentation.
> <https://gist.github.com/MoritzS/7bd792f2eaf37da28dfb>
>

signature.asc

Tim Graham

unread,
Aug 13, 2015, 9:53:43 AM8/13/15
to Django developers (Contributions to Django itself)
I wonder if you have followed the ticket about template-based widget rendering (it might be completed for 1.9). https://code.djangoproject.com/ticket/15667

It looks to me like the functionality your branch enables might be better as a custom widget, but I didn't study it too closely and could have very well have missed the point.

Preston Timmons

unread,
Aug 13, 2015, 10:23:34 AM8/13/15
to Django developers (Contributions to Django itself)
Hi Moritz,

Is the purpose of this patch to have a more convenient form of this:

{% for choice in form.foo.field.choices %}
  {{ choice.0 }} {{ choice.1 }}
{% endfor %}

I'm not necessarily against the idea, but your patch seems to encourage looping over the field choices rather than the widget choices. These two values don't always match. A looping API should also account for the case of nested choices, as well.

Tim, #15667 makes it easier to loop through choices in the widget template, but doesn't provide anything beyond the currently documented API when accessing widget choices from the form instance:


I'm happy to see improvement here if the current API is a pain point. I'm not sure yet this is a better solution than the current API for accessing widget choices.

Preston

Moritz Sichert

unread,
Aug 13, 2015, 3:42:46 PM8/13/15
to django-d...@googlegroups.com
My point isn't making accessing choices more easily, that's just the
first thing I thought of for a simple example.
Whether the current method for accessing the choices is good or not was
not intended to be in the scope of my proposal.

I want it to be possible to write more complex fields that have more
properties than just their rendered html that also can't be represented
with subwidgets.

Take for example a "GPSCoordinatesField".
It would be nice to do something like this in the template:

{% if field.value %}
The current value for the coordinates are:
{{ field.city }} in {{ field.country }}
{% endif %}
Edit here:
{{ field }}

You could do this right now with custom template filters and do
something like {{ field|get_gps_city }} but I think the former approach
looks way better and it also makes sense to have the code for the city
and country properties right in the BoundField instead of in a separate
filter.

It would also be possible to move the "The current value for ..." html
inside a widget but here it really makes more sense having it in the
template.

Also my current real life use case I'd like to use this on is a
"TabularField" that lets the user edit a table (adding and removing rows
and editing individual cells) with the rows being defined by another
form. This is not possible by using template filters or custom widgets.
> --
> You received this message because you are subscribed to a topic in the
> Google Groups "Django developers (Contributions to Django itself)" group.
> To unsubscribe from this topic, visit
> https://groups.google.com/d/topic/django-developers/-Bbi8vg5c9s/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to
> django-develop...@googlegroups.com
> <mailto:django-develop...@googlegroups.com>.
> To post to this group, send email to django-d...@googlegroups.com
> <mailto:django-d...@googlegroups.com>.
> Visit this group at http://groups.google.com/group/django-developers.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/django-developers/9541ed1f-5070-4753-a046-9cf745cb79f2%40googlegroups.com
> <https://groups.google.com/d/msgid/django-developers/9541ed1f-5070-4753-a046-9cf745cb79f2%40googlegroups.com?utm_medium=email&utm_source=footer>.
> For more options, visit https://groups.google.com/d/optout.

signature.asc

Tim Graham

unread,
Aug 13, 2015, 5:13:36 PM8/13/15
to Django developers (Contributions to Django itself)
I guess you know it's possible to access the regular form field with BoundField.field, e.g. {{ field.field.city }} in {{ field.field.country }}. Are there other advantageous to pushing those lookups to the BoundField as opposed to the form Field besides the shorter access in the template? Maybe you could motivate it with the TabularField example you mentioned.

Moritz Sichert

unread,
Aug 13, 2015, 6:07:46 PM8/13/15
to django-d...@googlegroups.com
Yes I know that you can access the Field instance but it wouldn't be
possible to do {{ field.field.city }} since a Field is not supposed to
have any data stored with it.
That exactly is the BoundField's job and the reason there is a
distinction between bound and unbound field.

An implementation of GPSBoundField.city could look something like this:

@property
def city(self):
if self.value():
return get_city_of_coordinate(self.value())


Field wouldn't have self.value() in this case.

If that's not clear still, I can elaborate on the TabularField example.
> <https://groups.google.com/d/topic/django-developers/-Bbi8vg5c9s/unsubscribe>.
>
> > To unsubscribe from this group and all its topics, send an email to
> > django-develop...@googlegroups.com <javascript:>
> > <mailto:django-develop...@googlegroups.com
> <javascript:>>.
> > To post to this group, send email to django-d...@googlegroups.com
> <javascript:>
> > <mailto:django-d...@googlegroups.com <javascript:>>.
> <http://groups.google.com/group/django-developers>.
> <https://groups.google.com/d/msgid/django-developers/9541ed1f-5070-4753-a046-9cf745cb79f2%40googlegroups.com?utm_medium=email&utm_source=footer
> <https://groups.google.com/d/optout>.
>
> --
> You received this message because you are subscribed to a topic in the
> Google Groups "Django developers (Contributions to Django itself)" group.
> To unsubscribe from this topic, visit
> https://groups.google.com/d/topic/django-developers/-Bbi8vg5c9s/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to
> django-develop...@googlegroups.com
> <mailto:django-develop...@googlegroups.com>.
> To post to this group, send email to django-d...@googlegroups.com
> <mailto:django-d...@googlegroups.com>.
> Visit this group at http://groups.google.com/group/django-developers.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/django-developers/d3f74990-8b4e-433a-8442-97a678a96658%40googlegroups.com
> <https://groups.google.com/d/msgid/django-developers/d3f74990-8b4e-433a-8442-97a678a96658%40googlegroups.com?utm_medium=email&utm_source=footer>.
signature.asc

Preston Timmons

unread,
Aug 13, 2015, 6:36:34 PM8/13/15
to Django developers (Contributions to Django itself)
Yes I know that you can access the Field instance but it wouldn't be
possible to do {{ field.field.city }} since a Field is not supposed to
have any data stored with it.

This makes sense to me. Feel free to make a ticket and attach your PR once it's ready for review.

Preston

Tim Graham

unread,
Aug 21, 2015, 11:34:32 AM8/21/15
to Django developers (Contributions to Django itself)
While reviewing the proposed documentation for BoundField, I noticed the methods BoundField.as_textarea() and as_text() [1] introduced at the start of new forms [2]. According to the comment, their purpose is as "shortcuts for changing the output widget type". These methods aren't documented, and I don't think they should be preferred over specifying the widget on the form. Any objection to deprecating them? Same applies to as_hidden(), except that it's used by BoundField elsewhere, so we can't remove it. We could add an underscore prefix to show that it's "private", but I don't see much advantage to this.

[1] https://github.com/django/django/blob/d3bc86ec11bb22f06b5e30fac891ef3e43f82a6d/django/forms/forms.py#L587-L601
[2] https://github.com/django/django/commit/4d596a1f6443eaf5d18d70a18aaac25030c7fc81#diff-de8d446d202490f4ac045923ad6fcfb7R98

Moritz Sichert

unread,
Aug 21, 2015, 11:41:08 AM8/21/15
to django-d...@googlegroups.com
I replied to your comment on pr 5123 on github [1] but it seems to be
buried under "Show outdated diff" so here it is again:

I agree that as_text() and as_textarea() are not needed so I will remove
them from the docs.

There is a use case for as_widget() though. In one of my projects I have
a template filter that is called add_class and I use it like this:

{{ my_form.my_field|add_class:'class-added-to-input-field' }}

It uses the attrs argument to as_widget() to easily add the class
attribute. That wouldn't be possible like that if the code was just
private inside __str__().
Although this use case may be invalidated by the upcoming template based
widget rendering.

I'm not sure about as_hidden(). I personally never used it, since you
can easily emulate it with html_name but I'm sure there are a few people
using it.


[1] https://github.com/django/django/pull/5123#discussion_r37645967
> --
> You received this message because you are subscribed to a topic in the
> Google Groups "Django developers (Contributions to Django itself)" group.
> To unsubscribe from this topic, visit
> https://groups.google.com/d/topic/django-developers/-Bbi8vg5c9s/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to
> django-develop...@googlegroups.com
> <mailto:django-develop...@googlegroups.com>.
> To post to this group, send email to django-d...@googlegroups.com
> <mailto:django-d...@googlegroups.com>.
> Visit this group at http://groups.google.com/group/django-developers.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/django-developers/67f69d17-fb9a-472d-8d5b-fb5ba8f11c8d%40googlegroups.com
> <https://groups.google.com/d/msgid/django-developers/67f69d17-fb9a-472d-8d5b-fb5ba8f11c8d%40googlegroups.com?utm_medium=email&utm_source=footer>.
signature.asc
Reply all
Reply to author
Forward
0 new messages