An irritating gotcha with Django newforms and ModelForm

Django    2008-07-07

ModelForm seems to be the quick, easy, and reliable, way to go to create a form that stays in sync with the model. I've seen a lot of applications that use a separate forms.py, but with ModelForm, you can create a clean, simple form definition just by adding a few extra lines to your models.py:

- Create the model

- Subclass ModelForm with an inner Meta class that references the original model class

Make sure you're importing ModelForm:

from django.newforms import ModelForm

Create the UserProfile model:

class UserProfile(models.Model):
    """
    A basic profile which stores user information after the account has been activated.

    Use this model as the value of the ""AUTH_PROFILE_MODULE"" setting
    """
    first_name = models.CharField(max_length=40) # 'blank=True' means the field doesn't require a value
    last_name = models.CharField(max_length=40)
    user = models.ForeignKey(User, unique=True)

Create the form:

class UserProfileForm(ModelForm):
    class Meta:
        model = UserProfile
        exclude = ('user',)

Note: "user" is part of the model (to tie our model to the auth_user table), but we don't want that field included or required as part of the form. Using "exclude = ('user',)" insures that the user field won't be rendered or required on form submission.

(Complete documentation is here, but Dan Fairs has done a nice job of boiling it down to the basics here.)

Then instantiate the form in views.py, and you can reference it in your template like so:

    <form action="." method="post">
    {{ form }}
    <input type="submit" value="Submit" />
    </form>

Voila! You get a form rendered that matches your model exactly.

But what if you need to customize the display of the form?

When you subclass Form, you have the option to pass in arguments that let you, for example, override the form field label, or set attributes/styles. (more info here)

However, when you subclass ModelForm, you're more limited. You only get what you can specify when you define the model - a field type, a max_length, blank=True or required=True, some options for dealing with choices, and that's it.

So if you want to apply styles to the form fields, you have to do it in the template, by referencing each field individually:

    <form action="." method="post">
    <div class="required"><label for="id_first_name">First Name</label></div>
    {{ form.first_name }}
    <div class="required"><label for="id_last_name">Last Name</label></div>
    {{ form.last_name }}
    <br />
    <input type="submit" value="Submit" />
    </form>

You get control over the form's appearance where it probably should be - in the template - but you lose the ability to sync directly with the model. If the model changes, you also have to change your template.