Remembering that Django template code is not quite your Django Python code

October 18, 2019

Recently I was doing some work with our Django web application where I wanted to do something special for one particular field of a form. As is usual in Django forms, we iterate through the form fields with the '{% for %}' template tag. Stripped down, this is something like:

{% for field in form %}
  {{ field.label_tag }} <br>
  {{ field }}
{% endfor %}

I had to make one field generate extra HTML, which meant that I had to recognize it somehow. At first I used a '{% if %}' on the label, but that felt wrong; the label is text that's shown to the user and we might want to change it. So I did some Internet searches and found 'field.name' (perhaps from here), which is the actual name of each field. I put it in, everything worked, I went on, and then a few days later I was struck by a Python question, namely how did the form field know its name?

Form fields are defined as what looks like class attributes:

class RequestForm(forms.Form):
  login = forms.CharField([...])
  [...]

However, Python objects don't know their names (for good reasons), including attributes on classes or class instances. At first I thought that Django was using metaclass magic so that RequestForm.login.name was set up when the RequestForm class was defined, but form classes turn out to be more magic than that and once you extract the relevant objects, they don't have .name attributes.

At this point I realized (and remembered) that the pseudo-Python code you write in Django templates is not the same as the Python code you write in your app. You can't assume that what works for one of them (here, referring to 'field.name') will work for the other, and it goes both ways. It's possible to rip the covers off the magic and find an explanation for what Django is doing, but it will lead you several levels deep and your Django template code is still not the same as what you'll write in your Python view code.

PS: One bit of magic that is showing already in my example is that field.label_tag is a method, not an attribute. The Django template language automatically calls it and uses the result.

Sidebar: What is going on here

The 'field' template variable being used here winds up holding a real Python object, but it is not from the class you think it is. As covered in Looping over the form's fields, the 'field' object is a BoundField instance. The actual Field instance, what your view code deals with, is hiding in field.field. BoundField does have a .name attribute, which is set up when it's initialized by the overall form code. The overall form code does know the name of each field (and has to).

(Somewhat to my surprise, .field is a plain ordinary instance attribute, not any sort of property or fancy thing. You can see it being set up in boundfield.py.)

Written on 18 October 2019.
« Some magical weirdness in Django's HTML form classes
My little irritation with Firefox's current handling of 'Do-Not-Track' »

Page tools: View Source, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Fri Oct 18 00:15:31 2019
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.