All notes
ModelForms

Fields

If you’re building a database-driven app, chances are you’ll have forms that map closely to Django models. In this case, it would be redundant to define the field types in your form, because you’ve already defined the fields in your model.

For this reason, Django provides a helper class that lets you create a Form class from a Django model.

Example:


from django.forms import ModelForm
from myapp.models import Article

# Create the form class.
class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter']

# Creating a form to add an article.
form = ArticleForm()

# Creating a form to change an existing article.
article = Article.objects.get(pk=1)
form = ArticleForm(instance=article)

Field types

Model field         Form field
-----               -----
AutoField 			Not represented in the form
CharField 			CharField with max_length set to the model field’s max_length
ForeignKey 			ModelChoiceField
ManyToManyField 	ModelMultipleChoiceField
TextField           CharField with widget=forms.Textare

The ForeignKey and ManyToManyField model field types are special cases:

Attributes

In addition, each generated form field has attributes set as follows:

Model               Form
---                 ---
blank=True          required is set to False
verbose_name        label text (first char will be capitalized)
help_text           help_text
choices             Select

If the model field has choices set, then the form field’s widget will be set to Select, with choices coming from the model field’s choices. The choices will normally include the blank choice which is selected by default. If the field is required, this forces the user to make a selection. The blank choice will not be included if the model field has blank=False and an explicit default value (the default value will be initially selected instead).

Fields attribute

It is strongly recommended that you explicitly set all fields that should be edited in the form using the fields attribute.

The alternative approach would be to include all fields automatically, or blacklist only some. This fundamental approach is known to be much less secure and has led to serious exploits on major websites (e.g. GitHub security accident: mass-assignment vulnerability).


from django.forms import ModelForm

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = '__all__'

class PartialAuthorForm(ModelForm):
    class Meta:
        model = Author
        exclude = ['title']

A complete example


from django.db import models
from django.forms import ModelForm

TITLE_CHOICES = (
    ('MR', 'Mr.'),
    ('MRS', 'Mrs.'),
    ('MS', 'Ms.'),
)

class Author(models.Model):
    name = models.CharField(max_length=100)
    title = models.CharField(max_length=3, choices=TITLE_CHOICES)
    birth_date = models.DateField(blank=True, null=True)

    def __str__(self):              # __unicode__ on Python 2
        return self.name

class Book(models.Model):
    name = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ['name', 'title', 'birth_date']

class BookForm(ModelForm):
    class Meta:
        model = Book
        fields = ['name', 'authors']

###############
# With these models, the ModelForm subclasses above would be roughly equivalent to this (the only difference being the save() method):

from django import forms

class AuthorForm(forms.Form):
    name = forms.CharField(max_length=100)
    title = forms.CharField(
        max_length=3,
        widget=forms.Select(choices=TITLE_CHOICES),
    )
    birth_date = forms.DateField(required=False)

class BookForm(forms.Form):
    name = forms.CharField(max_length=100)
    authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())

Customizing Fields

Overriding the default fields mapping

For example, if you want the CharField for the name attribute of Author to be represented by a <textarea> instead of its default <input type="text">, you can override the field’s widget:


from django.forms import ModelForm, Textarea
from myapp.models import Author

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        widgets = {
            'name': Textarea(attrs={'cols': 80, 'rows': 20}),
        }

The widgets dictionary accepts either widget instances (e.g., Textarea(...)) or classes (e.g., Textarea).

Customize fields' attributes

Specify the labels, help_texts and error_messages attributes of the inner Meta class if you want to further customize a field.

To customize the wording of all user facing strings for the name field:


from django.utils.translation import ugettext_lazy as _

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        labels = {
            'name': _('Writer'),
        }
        help_texts = {
            'name': _('Some useful help text.'),
        }
        error_messages = {
            'name': {
                'max_length': _("This writer's name is too long."),
            },
        }

You can also specify field_classes to customize the type of fields instantiated by the form.. For example, if you wanted to use MySlugFormField for the slug field, you could do the following:


from django.forms import ModelForm
from myapp.models import Article

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
        field_classes = {
            'slug': MySlugFormField,
        }

Finally, if you want complete control over of a field – including its type, validators, required, etc. – you can do this by declaratively specifying fields like you would in a regular Form. For example, if you want to specify a field’s validators:


from django.forms import ModelForm, CharField
from myapp.models import Article

class ArticleForm(ModelForm):
    slug = CharField(validators=[validate_slug])

    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']

ModelForm is a regular Form which can automatically generate certain fields. The fields that are automatically generated depend on the content of the Meta class and on which fields have already been defined declaratively. Basically, ModelForm will only generate fields that are missing from the form, or in other words, fields that weren’t defined declaratively.

localization of fields

By default, the fields in a ModelForm will not localize their data. To enable localization for fields, you can use the localized_fields attribute on the Meta class.


from django.forms import ModelForm
from myapp.models import Author
class AuthorForm(ModelForm):
    class Meta:
        model = Author
        localized_fields = ('birth_date',)

If localized_fields is set to the special value '__all__', all fields will be localized.

Validation on a ModelForm

There are two main steps involved: Validating the form, and Validating the model instance.

Just like normal form validation, model form validation is triggered implicitly when calling is_valid() or accessing the errors attribute and explicitly when calling full_clean(), although you will typically not use the latter method in practice.

Model validation (Model.full_clean()) is triggered from within the form validation step, right after the form’s clean() method is called.

The cleaning process modifies the model instance passed to the ModelForm constructor in various ways. For instance, any date fields on the model are converted into actual date objects. Failed validation may leave the underlying model instance in an inconsistent state and therefore it’s not recommended to reuse it.

A model form instance attached to a model object will contain an instance attribute that gives its methods access to that specific model instance.

As part of the validation process, ModelForm will call the clean() method of each field on your model that has a corresponding field on your form.

error_messages

Error messages defined at the form field level or at the form Meta level always take precedence over the error messages defined at the model field level.
Error messages defined on model fields are only used when the ValidationError is raised during the model validation step and no corresponding error messages are defined at the form level.

save()

Every ModelForm also has a save() method. This method creates and saves a database object from the data bound to the form. A subclass of ModelForm can accept an existing model instance as the keyword argument instance; if this is supplied, save() will update that instance. If it’s not supplied, save() will create a new instance of the specified model.


from myapp.models import Article
from myapp.forms import ArticleForm

# Create a form instance from POST data.
f = ArticleForm(request.POST)

# Save a new Article object from the form's data.
new_article = f.save()

# Create a form to edit an existing Article, but use
# POST data to populate the form.
a = Article.objects.get(pk=1)
f = ArticleForm(request.POST, instance=a)
f.save()

If the form hasn’t been validated, calling save() will do so by checking form.errors. A ValueError will be raised if the data in the form doesn’t validate – i.e., if form.errors evaluates to True.

If you call save() with commit=False, then it will return an object that hasn’t yet been saved to the database. commit is True by default.

save_m2m()

Another side effect of using commit=False is seen when your model has a many-to-many relation with another model. If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn’t possible to save many-to-many data for an instance until the instance exists in the database.

To work around this problem, every time you save a form using commit=False, Django adds a save_m2m() method to your ModelForm subclass. After you’ve manually saved the instance produced by the form, you can invoke save_m2m() to save the many-to-many form data.


# Create a form instance with POST data.
f = AuthorForm(request.POST)

# Create, but don't save the new author instance.
new_author = f.save(commit=False)

# Modify the author in some way.
new_author.some_field = 'some_value'

# Save the new instance.
new_author.save()

# Now, save the many-to-many data for the form.
f.save_m2m()

Other than the save() and save_m2m() methods, a ModelForm works exactly the same way as any other forms form.

Initial values

Initial values provided this way will override both initial values from the form field and values from an attached model instance. For example:


article = Article.objects.get(pk=1)
article.headline
# 'My headline'
form = ArticleForm(initial={'headline': 'Initial headline'}, instance=article)
form['headline'].value()
# 'Initial headline'

Form Inheritance

You can subclass the parent’s Meta inner class if you want to change the Meta.fields or Meta.exclude lists.

This adds the extra method from the EnhancedArticleForm and modifies the original ArticleForm.Meta to remove one field:


class RestrictedArticleForm(EnhancedArticleForm):
    class Meta(ArticleForm.Meta):
        exclude = ('body',)

There are a couple of things to note, however.

You can only use this technique to opt out from a field defined declaratively by a parent class; it won’t prevent the ModelForm metaclass from generating a default field. To opt-out from default fields, see Selecting the fields to use.

Factory

You can create forms from a given model using the standalone function modelform_factory(), instead of using a class definition. This may be more convenient if you do not have many customizations to make:


from django.forms import modelform_factory
from myapp.models import Book
BookForm = modelform_factory(Book, fields=("author", "title"))

# Make simple modifications to existing forms, for example by specifying the widgets to be used for a given field:

from django.forms import Textarea
Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()})

# Enable localization for specific fields:

Form = modelform_factory(Author, form=AuthorForm, localized_fields=("birth_date",))

Model Formsets


from django.forms import modelformset_factory
from myapp.models import Author
AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
# Or
AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))

formset = AuthorFormSet()
print(formset)
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" /><input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS" />
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></td></tr>
<tr><th><label for="id_form-0-title">Title:</label></th><td><select name="form-0-title" id="id_form-0-title">
<option value="" selected="selected">---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select><input type="hidden" name="form-0-id" id="id_form-0-id" /></td></tr>

modelformset_factory() uses formset_factory() to generate formsets. This means that a model formset is just an extension of a basic formset that knows how to interact with a particular model.

Changing the queryset

By default, when you create a formset from a model, the formset will use a queryset that includes all objects in the model (e.g., Author.objects.all()). You can override this behavior by using the queryset argument:


formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))

# Alternatively, you can create a subclass that sets self.queryset in __init__:

from django.forms import BaseModelFormSet
from myapp.models import Author

class BaseAuthorFormSet(BaseModelFormSet):
    def __init__(self, *args, **kwargs):
        super(BaseAuthorFormSet, self).__init__(*args, **kwargs)
        self.queryset = Author.objects.filter(name__startswith='O')

# Then, pass your BaseAuthorFormSet class to the factory function:

AuthorFormSet = modelformset_factory(
    Author, fields=('name', 'title'), formset=BaseAuthorFormSet)

# If you want to return a formset that doesn’t include any pre-existing instances of the model, you can specify an empty QuerySet:

AuthorFormSet(queryset=Author.objects.none())

Mathematical formulae powered by MathJax.