2019-10-16
Some magical weirdness in Django's HTML form classes
In Django, HTML forms are defined in what seems to be the conventional approach; each form is a Python class and fields are variables defined in the class definition. This looks like this, to crib a skeleton from our Django web application :
class RequestForm(forms.Form): login = forms.CharField([...]) name = forms.CharField([...]) email = forms.EmailField([...]) aup_agree = forms.BooleanField([...])
You instantiate an instance of RequestForm
either with initial
data (when you're displaying the form for the first time) or with
data from the POST
or GET
form, and then call various standard
Django API functions on it and so on. It's all covered in the
Django documentation.
What I didn't remember until I started looking just now is that
this Python class definition we wrote out is kind of a lie. Django
model classes mostly work look like they look, so for example the
model version of a Request has a Request.login
attribute just as
its class definition does. Forms are significantly different.
Although we set up what looks like class attributes here, our actual
RequestForm
class and class instances do not have, say, a
RequestForm.login
attribute. All of the form fields we seemed to
define here get swept up and put in Form.fields
.
At one level this is documented and probably the safest option, given that the data ultimately comes from an untrusted source (ie, a HTTP request). It also means that you mostly can't accidentally use a form instance as a model instance (for example, by passing the wrong thing to some function); if you try, it will blow up with attribute errors.
(The 'mostly' is because you can create a login
attribute on a
RequestForm instance if you write to it, so if a function that writes to
fields of a model instance is handed a form instance by accident, it may
at least half work.)
At another level, this is another way that Django's classes are non-Python magic. What looks like class attributes aren't even properties; they've just vanished. Conventional Python knowledge is not as much use for dealing with Django as it looks, and you have to know the API (or look it up) even for things that look like they should be basic and straightforward.
(I don't have an opinion any more about whether the tradeoffs here are worth it. Our Django app just works, which is what we really care about.)