So I havent really been doing much writing lately. That’s mostly a consequence of the fact that:
Anyway, I’m going to force myself to break out of that every once in a while and write something useful and fun, because if I don’t I’ll probably go insane. Fortunately, there’s a whole community full of people talking about Django these days, so I’m not short on topics.
Most recently, I’ve been quietly watching some discussion in the Django ticket tracker and on IRC about how to build “dynamic forms” in Django, where a “dynamic” form is one whose behavior or fields change depending on, say, the current user or some other bit of current information. This isn’t something that’s really documented, but personally I think that’s because it falls into a sort of gray area; like many useful things you can do with Django, it’s not actually Django-specific. It just requires a little bit of knowledge of Python and of some standard programming practices. In a nutshell, there are two ways you’ll want to go about this:
__init__() (and possibly taking some custom arguments to tell it what sort of shuffling to do).
So let’s dive in.
The easiest and most common way to do a little bit of dynamic customization in any Python class is by overriding its __init__() method and adding your own special-case logic there. For sake of an example, let’s consider a contact form: you want a form that lets people get in touch and send you messages. But since there are a lot of evil spammers out there, you also want to avoid automated submissions. Suppose you put together a CAPTCHA system and implement it as a Django form field, maybe called CaptchaField; then you’d just add it to the definition of your form, like any other field.
But let’s add a twist to this: let’s say that we only want to have this field when the form is being filled out by an anonymous visitor, and not when it’s coming from an authenticated user of the site. How could we do that?
One way would be to write two form classes — one with the CaptchaField and one without — and have the view select one after checking request.user.is_authenticated(). But that’s messy and leaves duplicated code lying around (even if you have one form subclass the other), and Django’s all about helping you avoid that sort of thing. So how about a form which automatically sprouts a CaptchaField as needed? We can do that pretty easily with a custom __init__() method and a single extra argument: we’ll have the view pass in the value of request.user, and work out what to do based on that.
Here’s how it might look:
class ContactForm(forms.Form): def __init__(self, user, *args, **kwargs): super(ContactForm, self).__init__(*args, **kwargs) if not user.is_authenticated(): self.fields['captcha'] = CaptchaField()
That’s not so hard. All it requires is a little bit of knowledge of Python (namely, overriding __init__() to change the way instances of the class are set up, and using super() to get the parent class’ behavior as a starting point), and a tiny bit of knowledge about how a Django form works: it has a dictionary attached to it, named fields, whose values are the names of the form fields and whose values are the fields themselves. Once we have that, it’s easy to just stick things in self.fields as needed.
Similarly, you can use this to modify the behavior of a field in the form; if you wanted to change the max_length of a CharField, for example, you could simply pull it out of self.fields and make the changes you wanted. You could also overwrite things there with completely new fields according to what your form will need. And you can even compare the fields as they currently exist with the fields as they were originally defined on the form class: the base set of fields defined for the class lives in a dictionary called base_fields and isn’t overwritten when you change fields, so comparing the contents of self.fields to the contents of self.base_fields lets you see quickly what’s changed (and unless you really know what you’re doing, don’t mess around in base_fields; you’ll thank me for that advice later).
These sorts of techniques can take you a long way; I’ve written some fairly interesting and complex forms using nothing but __init__() tricks (features at work like this one are constructed using some extremely-dynamic form systems, for example), and generally it’s pretty easy and pretty useful.
Of course, there are limits to what’s practical to do in an overridden __init__(); sooner or later the amount of code required (and the gymnastics you’ll hve to do for things like custom validation methods) simply gets to be too much and you find yourself needing something a little more powerful. Typically, the solution for those cases is a factory function (or factory method, if you prefer) which knows how to build form classes.
One good example is in django-profiles, which needs to generate forms based on the user-profile model you’re using, and also needs to ensure that certain fields never show up in them (since, for example, we don’t want you being able to change the User instance associated with your own profile). This is solved by two functions in django-profiles: one knows how to retrieve the currently-active user-profile model, and the other knows how to build a form for it, minus the User field. The second function is the one we’re interested in, and the code for it is short and sweet:
def get_profile_form(): """ Return a form class (a subclass of the default ``ModelForm``) suitable for creating/editing instances of the site-specific user profile model, as defined by the ``AUTH_PROFILE_MODULE`` setting. If that setting is missing, raise ``django.contrib.auth.models.SiteProfileNotAvailable``. """ profile_mod = get_profile_model() class _ProfileForm(forms.ModelForm): class Meta: model = profile_mod exclude = ('user',) # User will be filled in by the view. return _ProfileForm
You’ll notice that the docstring for this function is almost as long as the actual function itself; all it does is fetch the user-profile model (that’s what get_profile_model() does), then define a ModelForm subclass based on that model and exclude the user field (because of the way user profiles work in Django, the field must be named user; if that requirement didn’t exist, introspection of the profile model could just as easily determine which field to exclude). Then it returns that newly-defined class.
Then, whenever a form for editing user profiles is needed, the view which will display the form can simply do something like this (the get_profile_form() method lives in a module in django-profiles named utils):
form_class = utils.get_profile_form() if request.method == 'POST': form = form_class(data=request.POST, files=request.FILES) if form.is_valid(): # Standard form-handling behavior goes here...
This works because Python supports a few features (most notably closures, a topic far too big to tackle here) which make it easy to define and work with new classes while your code is running. And in general, this is an extremely powerful feature to have: it means that instead of writing huge, complex classes which try to anticipate everything you might need to do, you can usually just write one or two simple functions which build and return brand-new tailor-made classes that do exactly what you want.
The example above was fairly easy because it just defined a ModelForm subclass, and those are typically short and easy to write, but it works just as well with more complex forms; you can define a full form, with fields, custom validation and whatever else you like, inside a function and then return it to be used immediately. There’s also a useful shortcut you can use if you know you’re just going to slap together a collection of fields and don’t want to bother with writing them all out: Python’s built-in type() function, which puts together a class on the fly. The type() function takes three arguments:
Let’s return to our original example: a contact form which sprouts a CAPTCHA field if the user isn’t authenticated. We could write a function which takes a User as an argument, and then returns either of two possible form classes, like so:
def make_contact_form(user): # The basic form class _ContactForm(forms.Form): name = forms.CharField(max_length=50) email = forms.EmailField() message = forms.CharField(widget=forms.Textarea) if user.is_authenticated(): return _ContactForm class _CaptchaContactForm(_ContactForm): captcha = CaptchaField() return _CaptchaContactForm
This defines the form class on the fly, and if the user isn’t authenticated immediately defines a subclass which includes the CAPTCHA. But this is pretty ugly; let’s see how it works using type():
def make_contact_form(user): fields = { 'name': forms.CharField(max_length=50), 'email': forms.EmailField(), 'message': forms.CharField(widget=forms.Textarea) } if not user.is_authenticated(): fields['captcha'] = CaptchaField() return type('ContactForm', (forms.BaseForm,), { 'base_fields': fields })
That’s much nicer; type just wants to know the name of our new class (ContactForm), what classes, if any, it should inherit from (when doing this, use django.forms.BaseForm instead of django.forms.Form; the reasons are a bit obscure and technical, but any time you’re not declaring the fields directly on the class in the normal fashion you should use BaseForm) and a dictionary of attributes. Recall from our earlier look at fiddling with a form’s fields that base_fields is the attribute which holds the baseline set of fields for the form class, so our attribute dictionary simply supplies base_fields with a collection of form fields, and adds the CAPTCHA only when needed.
This is a bit more advanced in terms of Python programming, but is an extremelyuseful technique; Django uses it all over the place to build classes on the fly, and you can too.
There is one further way to dynamically control the way a class is set up in Python, and it gives you absolute control over every step of the process; writing a “metaclass”. But, in the words of Python guru Tim Peters: “Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t.” If you ever do get to the point where you’re convinced you do need them, there are plenty of tutorials on Python metaclasses floating around the Web. Django uses them in a few places (in fact, in more places than it strictly needs to), but that doesn’t mean you have to; doing work in __init__() or writing a factory function which knows how to build the class you want will easily cover 99% or more of all the cases you’re like to run into in the real world.
And though here we’ve been talking about Django forms specifically, I’d like to point out once again that this isn’t really Django-specific; these approaches work well with lots of different types of probems in Python and even in programming in other languages (although not every language makes it as easy to, for example, define new classes on the fly). Reading up on Python and getting to know some standard idioms and programming patterns will drastically improve your ability to reason out solutions like the ones I’ve given above, so if you’re interested in further reading, curl up with either the Python documentation or some material on programming practice; they’ll do you good.
Comments for this entry are closed. If you'd like to share your thoughts on this entry with me, please contact me directly.
Good stuff - thanks.
My earlier comment isn’t clear due to formatting. The class definition is missing an underscore for the base class name.
I found Guido’s paper “Unifying types and classes in Python 2.2” a very good explanation of how-stuff-works. Ignoring the pre-2.2 related content is a good idea. Although I didn’t realize ‘type’ could be used to define new classes in a functional manner, and does not require the use of closures. Always more to learn I guess. Another interesting topic is dynamic dispatch which can get pretty hairy in Python - maybe a blog post from you will make it less scary? :p
Great post. I’m not a pro at Python so can I ask, what is the reason for placing a _ in front of the class name. I assume it’s just a standard, but normally class names don’t have it. It is standard to place _ in front of the class name if it’s generated via a function like that?
Otherwise that was a great explanation of using django techniques even if you’re not a pro at Python (which I’m not!) :)
Bart,
It’s a Python convention, and only advisory. Python does not have private classes or methods. The ‘_’ advises programmers that a class or method is considered private to the scope that binds it, and should not be used directly.
What are the obscure and technical reasons to use BaseForm rather than Form when using type()?
If you want to keep a certain ordering of the form fields, you must use SortedDict from django.utils.datastructures.
Steve,
Thank you that clears it up.
@David:
Formuses a metaclass to do the necessary rewiring that changes declared fields on your form class intobase_fieldsandfields. But when you’re doing this manually via something liketype(), there’s no point to involving the metaclass;BaseFormis what you want, since you’re specifying the attributes in the manner it expects.I like this python’ punk-like OOP :-)
Fantastic stuff, much appreciated.
Any particular recommendations spring to mind for Python-related programming practice material?
Do you purposely avoid forms.models.modelform_factory? I find myself using that then manipulating the returned form. I’m afraid it will eventually disappear however.
James, thanks a lot for your code, this helps a lot! However, I believe there is a small typo in the last line of code:
Should it not say: return type(‘ContactForm’, (forms.BaseForm, ), { ‘base_fields’: fields })
since type requires the second argument to be a tuple?
Can you please elaborate on how one might add a method, say a custom clean() to the dynamically generated form? Thanks.