Newforms, part 3

Now that I’ve got the user-profiles application out in the wild, let’s take a look at one last important feature of Django’s newforms library: the ability to dynamically generate forms “on the fly” from model classes, via the helper functions form_for_model and form_for_instance . And, just as importantly, let’s look at why they shouldn’t always be the first thing you reach for when you need to build a form.

How model-generated forms work

We’ve already taken a look at how to introspect Django models using Django’s own internal API s, and if you’ve been following along with my posts this month you’re probably starting to see how the underlying pieces of the model APIs serve all sorts of useful purposes within Django (and can do the same in your own applications). This stuff is literally everywhere once you start looking for it, and the form_for_model and form_for_instance helpers are no exception.

As their names imply, form_for_model and form_for_instance are functions which take either a model class, or an instance of a model class, and return a newforms form class appropriate for accepting and validating data for that class or instance (in the case of form_for_instance , the current value of each field on the model is supplied as initial data for the form fields). So, for example, if you have a blog application with an Entry model, you can get a form for creating entries by doing something like this:

from blog.models import Entry from django.newforms import form_for_model blog_form = form_for_model ( Entry )

Similarly, you could retrieve an Entry from your database and pass it to form_for_instance to get a form class suitable for editing that entry. The exact arguments and usage are covered in the official newforms documentation, but there are at least three things you’ll want to make sure you know about:

Both form_for_model and form_for_instance accept a keyword argument, fields , which should be a list of field names on the model; the resulting form class will only contain form fields for those particular model fields. The form classes they generate — and, in fact, all newforms form classes — have an attribute base_fields , which is a dictionary whose keys are the names of the fields specified on the form class and whose values are the actual Field objects. Both form_for_model and form_for_instance accept a keyword argument, formfield_callback , which lets you pass a function; for each field on the model, this function will be called and passed the model field as an argument, and whatever it returns will be used as the form field for that model field.

The usefulness of the fields argument is pretty obvious; if you know the specific set of fields you want to generate (or can retrieve their names by doing introspection on the model), you can use fields to get only the form fields you want and no others. The utility of base_fields is somewhat trickier. the primary real-world use case so far is when there are one or two specific fields you want to leave out, and you don’t feel like specifying a long list in the fields argument; instead, simply delete the field from base_fields before using the form. For example, django-profiles uses this when falling back to a default form; the form generated by form_for_model or form_for_instance will include the foreign key to the User model (in a field named user ), so it just gets removed (as seen here, for example, in the profile-creation view):

if form_class is None : form_class = form_for_model ( profile_model ) del form_class . base_fields [ 'user' ]

The form class itself is generated by introspecting the model, or the model object, and building up a list of the model’s fields. Each model field class “knows” what the default form field should be to represent it, and has a method called formfield() which will return that form field. Building up the list of form fields, then, is fairly easy and generally looks like this:

opts = model_class . _meta form_fields = [] for f in opts . fields + opts . many_to_many : form_fields . append (( f . name , f . formfield ()))

The fields and many_to_many attributes of the model’s _meta , between them, represent all the actual database-level fields on the model, so combining them into one list and looping through like this results in form_fields being a list of tuples, where each tuple contains a field name and an appropriate form field. The default value of the formfield_callback argument to form_for_model is simply

lambda f : f . formfield ()

which means that it will get form fields corresponding to the default field types returned by the fields on the model. In form_for_instance a second model field method — value_from_object() — is used to get the initial value for the form field. This method simply takes a model object as argument, and returns the current value of that model field on that model object, which usually is as simple as calling getattr(model_obj, field_name) , but for some fields can be more complex. While this isn’t actually how form_for_instance does it, you can use this in your own code to build up a dictionary of field names and values from a model instance:

opts = model_obj . _meta current_data = {} for f in opts . fields + opts . many_to_many : current_data [ f . name ] = f . value_from_object ( model_obj )

In django-profiles, this is used to build up the dictionary of initial data for profile editing regardless of whether form_for_instance is being used to create a form.

Each form class generated by form_for_model or form_for_instance also has an automatically-generated save() method which takes care of saving the model object; an explanation of how this works (and an important note about creating objects which need to have many-to-many relationships set up, as well as a way to get the model object from the form without saving it) is in the official documentation.

When generating forms is a good idea, and when it’s not

The primary use case for form_for_model and form_for_instance is when you don’t know in advance the model you’ll be working with (and so can’t define a form class ahead of time to suit it). In that respect, these helpers are a lot like the functions in Django’s model-loading API; they provide a generic way to deal with models even when you don’t know which models until runtime.

They’re also useful when you just want a default form class to work with a specific model, or a form for a specific set of fields on that model; having a shortcut to get a form class in that case saves time and avoids needless duplication of effort (especially when you just want a form with all the same fields as the model). And that’s great, and it’s incredibly helpful to have these shortcuts available. The problem is that shortcuts can be too tempting sometimes.

At this point I’ve lost count of the number of times I’ve seen people spend hours, or even days, trying to hack up the form classes generated by form_for_model and form_for_instance in order to generate highly-customized forms, when writing a form class by hand to suit their specific needs would have taken maybe ten minutes. Usually it starts out innocently: you need a form to work with some model, and form_for_model or form_for_instance looks attractive because you’ll “just” need to hack in one or two quick customizations to the resulting form class. But generally the customizations aren’t really as easy as they seem, and rather than step back and write a custom form class to handle it, a lot of people determinedly keep hacking to try to get the result they want.

What this ends up meaning is that, aside from the two cases above — generating forms from a model class that’s not known in advance, or generating a default form or one with very little customization — form_for_model and form_for_instance often lead to more work than just writing a custom form class to suit your specific situation. Let me say that again because it’s very important: using the shortcuts outside of their primary use cases is often more work than writing a custom form. The newforms library goes to great lengths to make custom forms dead simple to write, and so any time you’re in a situation where you’d have to stop and think for a bit to figure out how to use form_for_model or form_for_instance , you’ve probably already spent more time thinking than you would have writing a custom form.