Django form save error

I'm not sure how to properly raise a validation error in a model's save method and send back a clear message to the user. Basically I want to know how each part of the "if" should end, the one whe...

Bastian, I explain to you my code templating, I hope that helps to you:

Since django 1.2 it is able to write validation code on model. When we work with modelforms, instance.full_clean() is called on form validation.

In each model I overwrite clean() method with a custom function (this method is automatically called from full_clean() on modelform validation ):

from django.db import models
 
class Issue(models.Model):
    ....
    def clean(self): 
        rules.Issue_clean(self)  #<-- custom function invocation

from issues import rules
rules.connect()

Then in rules.py file I write bussiness rules. Also I connect pre_save() to my custom function to prevent save a model with wrong state:

from issues.models import Issue

def connect():    
    from django.db.models.signals import post_save, pre_save, pre_delete
    #issues 
    pre_save.connect(Issue_pre_save, sender = Incidencia ) 
    post_save.connect(Issue_post_save, sender = Incidencia )
    pre_delete.connect(Issue_pre_delete, sender= Incidencia) 

def Incidencia_clean( instance ):    #<-- custom function 
    import datetime as dt    
    errors = {}

    #dia i hora sempre informats     
    if not instance.dia_incidencia:   #<-- business rules
        errors.setdefault('dia_incidencia',[]).append(u'Data missing: ...')
        
    #dia i hora sempre informats     
    if not  instance.franja_incidencia: 
        errors.setdefault('franja_incidencia',[]).append(u'Falten Dades: ...')
 
    #Només es poden posar incidències més ennlà de 7 dies 
    if instance.dia_incidencia < ( dt.date.today() + dt.timedelta( days = -7) ): 
        errors.setdefault('dia_incidencia 1',[]).append(u'''blah blah error desc)''')
 
    #No incidències al futur. 
    if instance.getDate() > datetime.now(): 
        errors.setdefault('dia_incidencia 2',[]).append(u'''Encara no pots ....''') 
    ... 

    if len( errors ) > 0: 
        raise ValidationError(errors)  #<-- raising errors

def Issue_pre_save(sender, instance, **kwargs): 
    instance.clean()     #<-- custom function invocation

Then, modelform calls model’s clean method and my custon function check for a right state or raise a error that is handled by model form.

In order to show errors on form, you should include this on form template:

{% if form.non_field_errors %}
      {% for error in form.non_field_errors %}
        {{error}}
      {% endfor %}
{% endif %}  

The reason is that model validation erros ara binded to non_field_errors error dictionary entry.

When you save or delete a model out of a form you should remember that a error may be raised:

try:
    issue.delete()
except ValidationError, e:
    import itertools
    errors = list( itertools.chain( *e.message_dict.values() ) )

Also, you can add errors to a form dictionary on no modelforms:

    try:
        #provoco els errors per mostrar-los igualment al formulari.
        issue.clean()
    except ValidationError, e:
        form._errors = {}
        for _, v in e.message_dict.items():
            form._errors.setdefault(NON_FIELD_ERRORS, []).extend(  v  )

Remember that this code is not execute on save() method: Note that full_clean() will not be called automatically when you call your model’s save() method, nor as a result of ModelForm validation. Then, you can add errors to a form dictionary on no modelforms:

    try:
        #provoco els errors per mostrar-los igualment al formulari.
        issue.clean()
    except ValidationError, e:
        form._errors = {}
        for _, v in e.message_dict.items():
            form._errors.setdefault(NON_FIELD_ERRORS, []).extend(  v  )

ModelForm

class ModelForm

If you’re building a database-driven app, chances are you’ll have forms that
map closely to Django models. For instance, you might have a BlogComment
model, and you want to create a form that lets people submit comments. 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.

For 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¶

The generated Form class will have a form field for every model field
specified, in the order specified in the fields attribute.

Each model field has a corresponding default form field. For example, a
CharField on a model is represented as a CharField on a form. A model
ManyToManyField is represented as a MultipleChoiceField. Here is the
full list of conversions:

Model field Form field
AutoField Not represented in the form
BigAutoField Not represented in the form
BigIntegerField IntegerField with
min_value set to -9223372036854775808
and max_value set to 9223372036854775807.
BinaryField CharField, if
editable is set to
True on the model field, otherwise not
represented in the form.
BooleanField BooleanField, or
NullBooleanField if
null=True.
CharField CharField with
max_length set to the model field’s
max_length and
empty_value
set to None if null=True.
DateField DateField
DateTimeField DateTimeField
DecimalField DecimalField
DurationField DurationField
EmailField EmailField
FileField FileField
FilePathField FilePathField
FloatField FloatField
ForeignKey ModelChoiceField
(see below)
ImageField ImageField
IntegerField IntegerField
IPAddressField IPAddressField
GenericIPAddressField GenericIPAddressField
JSONField JSONField
ManyToManyField ModelMultipleChoiceField
(see below)
PositiveBigIntegerField IntegerField
PositiveIntegerField IntegerField
PositiveSmallIntegerField IntegerField
SlugField SlugField
SmallAutoField Not represented in the form
SmallIntegerField IntegerField
TextField CharField with
widget=forms.Textarea
TimeField TimeField
URLField URLField
UUIDField UUIDField

As you might expect, the ForeignKey and ManyToManyField model field
types are special cases:

  • ForeignKey is represented by django.forms.ModelChoiceField,
    which is a ChoiceField whose choices are a model QuerySet.
  • ManyToManyField is represented by
    django.forms.ModelMultipleChoiceField, which is a
    MultipleChoiceField whose choices are a model QuerySet.

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

  • If the model field has blank=True, then required is set to
    False on the form field. Otherwise, required=True.
  • The form field’s label is set to the verbose_name of the model
    field, with the first character capitalized.
  • The form field’s help_text is set to the help_text of the model
    field.
  • 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).

Finally, note that you can override the form field used for a given model
field. See Overriding the default fields below.

A full example¶

Consider this set of models:

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):
        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, which
we’ll discuss in a moment.):

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())

Validation on a ModelForm

There are two main steps involved in validating a ModelForm:

  1. Validating the form
  2. 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.

Warning

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.

Overriding the clean() method¶

You can override the clean() method on a model form to provide additional
validation in the same way you can on a normal form.

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

Warning

The ModelForm.clean() method sets a flag that makes the model
validation
step validate the uniqueness of model
fields that are marked as unique, unique_together or
unique_for_date|month|year.

If you would like to override the clean() method and maintain this
validation, you must call the parent class’s clean() method.

Interaction with model validation¶

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.
If you have excluded any model fields, validation will not be run on those
fields. See the form validation documentation
for more on how field cleaning and validation work.

The model’s clean() method will be called before any uniqueness checks are
made. See Validating objects for more information
on the model’s clean() hook.

Considerations regarding model’s 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.

You can override the error messages from NON_FIELD_ERRORS raised by model
validation by adding the NON_FIELD_ERRORS key
to the error_messages dictionary of the ModelForm’s inner Meta class:

from django.core.exceptions import NON_FIELD_ERRORS
from django.forms import ModelForm

class ArticleForm(ModelForm):
    class Meta:
        error_messages = {
            NON_FIELD_ERRORS: {
                'unique_together': "%(model_name)s's %(field_labels)s are not unique.",
            }
        }

The save() method¶

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()

Note that 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 an optional field doesn’t appear in the form’s data, the resulting model
instance uses the model field default, if
there is one, for that field. This behavior doesn’t apply to fields that use
CheckboxInput,
CheckboxSelectMultiple, or
SelectMultiple (or any custom widget whose
value_omitted_from_data() method always returns
False) since an unchecked checkbox and unselected <select multiple>
don’t appear in the data of an HTML form submission. Use a custom form field or
widget if you’re designing an API and want the default fallback behavior for a
field that uses one of these widgets.

This save() method accepts an optional commit keyword argument, which
accepts either True or False. If you call save() with
commit=False, then it will return an object that hasn’t yet been saved to
the database. In this case, it’s up to you to call save() on the resulting
model instance. This is useful if you want to do custom processing on the
object before saving it, or if you want to use one of the specialized
model saving options. commit is True
by default.

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. For example:

# 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()

Calling save_m2m() is only required if you use save(commit=False).
When you use a save() on a form, all data – including many-to-many data –
is saved without the need for any additional method calls. For example:

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

# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()

Other than the save() and save_m2m() methods, a ModelForm works
exactly the same way as any other forms form. For example, the
is_valid() method is used to check for validity, the is_multipart()
method is used to determine whether a form requires multipart file upload (and
hence whether request.FILES must be passed to the form), etc. See
Binding uploaded files to a form for more information.

Selecting the fields to use¶

It is strongly recommended that you explicitly set all fields that should be
edited in the form using the fields attribute. Failure to do so can easily
lead to security problems when a form unexpectedly allows a user to set certain
fields, especially when new fields are added to a model. Depending on how the
form is rendered, the problem may not even be visible on the web page.

The alternative approach would be to include all fields automatically, or
remove only some. This fundamental approach is known to be much less secure
and has led to serious exploits on major websites (e.g. GitHub).

There are, however, two shortcuts available for cases where you can guarantee
these security concerns do not apply to you:

  1. Set the fields attribute to the special value '__all__' to indicate
    that all fields in the model should be used. For example:

    from django.forms import ModelForm
    
    class AuthorForm(ModelForm):
        class Meta:
            model = Author
            fields = '__all__'
    
  2. Set the exclude attribute of the ModelForm’s inner Meta class to
    a list of fields to be excluded from the form.

    For example:

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

    Since the Author model has the 3 fields name, title and
    birth_date, this will result in the fields name and birth_date
    being present on the form.

If either of these are used, the order the fields appear in the form will be the
order the fields are defined in the model, with ManyToManyField instances
appearing last.

In addition, Django applies the following rule: if you set editable=False on
the model field, any form created from the model via ModelForm will not
include that field.

Note

Any fields not included in a form by the above logic
will not be set by the form’s save() method. Also, if you
manually add the excluded fields back to the form, they will not
be initialized from the model instance.

Django will prevent any attempt to save an incomplete model, so if
the model does not allow the missing fields to be empty, and does
not provide a default value for the missing fields, any attempt to
save() a ModelForm with missing fields will fail. To
avoid this failure, you must instantiate your model with initial
values for the missing, but required fields:

author = Author(title='Mr')
form = PartialAuthorForm(request.POST, instance=author)
form.save()

Alternatively, you can use save(commit=False) and manually set
any extra required fields:

form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
author.title = 'Mr'
author.save()

See the section on saving forms for more details on using
save(commit=False).

Overriding the default fields¶

The default field types, as described in the Field types table above, are
sensible defaults. If you have a DateField in your model, chances are you’d
want that to be represented as a DateField in your form. But ModelForm
gives you the flexibility of changing the form field for a given model.

To specify a custom widget for a field, use the widgets attribute of the
inner Meta class. This should be a dictionary mapping field names to widget
classes or instances.

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). Note that the widgets
dictionary is ignored for a model field with a non-empty choices attribute.
In this case, you must override the form field to use a different widget.

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

For example if you wanted to customize the wording of all user facing strings for
the name field:

from django.utils.translation import gettext_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.

If you want to specify a field’s validators, you can do so by defining
the field declaratively and setting its validators parameter:

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

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

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

Note

When you explicitly instantiate a form field like this, it is important to
understand how ModelForm and regular Form are related.

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.

Fields defined declaratively are left as-is, therefore any customizations
made to Meta attributes such as widgets, labels, help_texts,
or error_messages are ignored; these only apply to fields that are
generated automatically.

Similarly, fields defined declaratively do not draw their attributes like
max_length or required from the corresponding model. If you want to
maintain the behavior specified in the model, you must set the relevant
arguments explicitly when declaring the form field.

For example, if the Article model looks like this:

class Article(models.Model):
    headline = models.CharField(
        max_length=200,
        null=True,
        blank=True,
        help_text='Use puns liberally',
    )
    content = models.TextField()

and you want to do some custom validation for headline, while keeping
the blank and help_text values as specified, you might define
ArticleForm like this:

class ArticleForm(ModelForm):
    headline = MyFormField(
        max_length=200,
        required=False,
        help_text='Use puns liberally',
    )

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

You must ensure that the type of the form field can be used to set the
contents of the corresponding model field. When they are not compatible,
you will get a ValueError as no implicit conversion takes place.

See the form field documentation for more information
on fields and their arguments.

Enabling 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.

Form inheritance¶

As with basic forms, you can extend and reuse ModelForms by inheriting
them. This is useful if you need to declare extra fields or extra methods on a
parent class for use in a number of forms derived from models. For example,
using the previous ArticleForm class:

>>> class EnhancedArticleForm(ArticleForm):
...     def clean_pub_date(self):
...         ...

This creates a form that behaves identically to ArticleForm, except there’s
some extra validation and cleaning for the pub_date field.

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

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

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

There are a couple of things to note, however.

  • Normal Python name resolution rules apply. If you have multiple base
    classes that declare a Meta inner class, only the first one will be
    used. This means the child’s Meta, if it exists, otherwise the
    Meta of the first parent, etc.

  • It’s possible to inherit from both Form and ModelForm simultaneously,
    however, you must ensure that ModelForm appears first in the MRO. This is
    because these classes rely on different metaclasses and a class can only have
    one metaclass.

  • It’s possible to declaratively remove a Field inherited from a parent class by
    setting the name to be None on the subclass.

    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.

Providing initial values¶

As with regular forms, it’s possible to specify initial data for forms by
specifying an initial parameter when instantiating the form. 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'

ModelForm factory function¶

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"))

This can also be used to make 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()})

The fields to include can be specified using the fields and exclude
keyword arguments, or the corresponding attributes on the ModelForm inner
Meta class. Please see the ModelForm Selecting the fields to use
documentation.

… or enable localization for specific fields:

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

Model formsets¶

class models.BaseModelFormSet

Like regular formsets, Django provides a couple
of enhanced formset classes to make working with Django models more
convenient. Let’s reuse the Author model from above:

>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))

Using fields restricts the formset to use only the given fields.
Alternatively, you can take an “opt-out” approach, specifying which fields to
exclude:

>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))

This will create a formset that is capable of working with the data associated
with the Author model. It works just like a regular formset:

>>> 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-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS"><input type="hidden" name="form-MAX_NUM_FORMS" value="1000" 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>---------</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>

Note

When using multi-table inheritance, forms
generated by a formset factory will contain a parent link field (by default
<parent_model_name>_ptr) instead of an id field.

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().__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 preexisting
instances of the model, you can specify an empty QuerySet:

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

Changing the form¶

By default, when you use modelformset_factory, a model form will
be created using modelform_factory().
Often, it can be useful to specify a custom model form. For example,
you can create a custom model form that has custom validation:

class AuthorForm(forms.ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title')

    def clean_name(self):
        # custom validation for the name field
        ...

Then, pass your model form to the factory function:

AuthorFormSet = modelformset_factory(Author, form=AuthorForm)

It is not always necessary to define a custom model form. The
modelformset_factory function has several arguments which are
passed through to modelform_factory, which are described below.

Enabling localization for fields with localized_fields

Using the localized_fields parameter, you can enable localization for
fields in the form.

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title', 'birth_date'),
...     localized_fields=('birth_date',))

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

Providing initial values¶

As with regular formsets, it’s possible to specify initial data for forms in the formset by specifying an initial
parameter when instantiating the model formset class returned by
modelformset_factory(). However, with model
formsets, the initial values only apply to extra forms, those that aren’t
attached to an existing model instance. If the length of initial exceeds
the number of extra forms, the excess initial data is ignored. If the extra
forms with initial data aren’t changed by the user, they won’t be validated or
saved.

Saving objects in the formset¶

As with a ModelForm, you can save the data as a model object. This is done
with the formset’s save() method:

# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)

# Assuming all is valid, save the data.
>>> instances = formset.save()

The save() method returns the instances that have been saved to the
database. If a given instance’s data didn’t change in the bound data, the
instance won’t be saved to the database and won’t be included in the return
value (instances, in the above example).

When fields are missing from the form (for example because they have been
excluded), these fields will not be set by the save() method. You can find
more information about this restriction, which also holds for regular
ModelForms, in Selecting the fields to use.

Pass commit=False to return the unsaved model instances:

# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
...     # do something with instance
...     instance.save()

This gives you the ability to attach data to the instances before saving them
to the database. If your formset contains a ManyToManyField, you’ll also
need to call formset.save_m2m() to ensure the many-to-many relationships
are saved properly.

After calling save(), your model formset will have three new attributes
containing the formset’s changes:

models.BaseModelFormSet.changed_objects
models.BaseModelFormSet.deleted_objects
models.BaseModelFormSet.new_objects

Limiting the number of editable objects¶

As with regular formsets, you can use the max_num and extra parameters
to modelformset_factory() to limit the number of
extra forms displayed.

max_num does not prevent existing objects from being displayed:

>>> Author.objects.order_by('name')
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']

Also, extra=0 doesn’t prevent creation of new model instances as you can
add additional forms with JavaScript
or send additional POST data. See Preventing new objects creation on how to do
this.

If the value of max_num is greater than the number of existing related
objects, up to extra additional blank forms will be added to the formset,
so long as the total number of forms does not exceed max_num:

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100"><input type="hidden" name="form-0-id" value="1" id="id_form-0-id"></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100"><input type="hidden" name="form-1-id" value="3" id="id_form-1-id"></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100"><input type="hidden" name="form-2-id" value="2" id="id_form-2-id"></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100"><input type="hidden" name="form-3-id" id="id_form-3-id"></td></tr>

A max_num value of None (the default) puts a high limit on the number
of forms displayed (1000). In practice this is equivalent to no limit.

Preventing new objects creation¶

New in Django 4.1.

Using the edit_only parameter, you can prevent creation of any new
objects:

>>> AuthorFormSet = modelformset_factory(
...     Author,
...     fields=('name', 'title'),
...     edit_only=True,
... )

Here, the formset will only edit existing Author instances. No other
objects will be created or edited.

Using a model formset in a view¶

Model formsets are very similar to formsets. Let’s say we want to present a
formset to edit Author model instances:

from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    if request.method == 'POST':
        formset = AuthorFormSet(request.POST, request.FILES)
        if formset.is_valid():
            formset.save()
            # do something.
    else:
        formset = AuthorFormSet()
    return render(request, 'manage_authors.html', {'formset': formset})

As you can see, the view logic of a model formset isn’t drastically different
than that of a “normal” formset. The only difference is that we call
formset.save() to save the data into the database. (This was described
above, in Saving objects in the formset.)

Overriding clean() on a ModelFormSet

Just like with ModelForms, by default the clean() method of a
ModelFormSet will validate that none of the items in the formset violate
the unique constraints on your model (either unique, unique_together or
unique_for_date|month|year). If you want to override the clean() method
on a ModelFormSet and maintain this validation, you must call the parent
class’s clean method:

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super().clean()
        # example custom validation across forms in the formset
        for form in self.forms:
            # your custom formset validation
            ...

Also note that by the time you reach this step, individual model instances
have already been created for each Form. Modifying a value in
form.cleaned_data is not sufficient to affect the saved value. If you wish
to modify a value in ModelFormSet.clean() you must modify
form.instance:

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super().clean()

        for form in self.forms:
            name = form.cleaned_data['name'].upper()
            form.cleaned_data['name'] = name
            # update the instance value.
            form.instance.name = name

Using a custom queryset¶

As stated earlier, you can override the default queryset used by the model
formset:

from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    queryset = Author.objects.filter(name__startswith='O')
    if request.method == "POST":
        formset = AuthorFormSet(
            request.POST, request.FILES,
            queryset=queryset,
        )
        if formset.is_valid():
            formset.save()
            # Do something.
    else:
        formset = AuthorFormSet(queryset=queryset)
    return render(request, 'manage_authors.html', {'formset': formset})

Note that we pass the queryset argument in both the POST and GET
cases in this example.

Using the formset in the template¶

There are three ways to render a formset in a Django template.

First, you can let the formset do most of the work:

<form method="post">
    {{ formset }}
</form>

Second, you can manually render the formset, but let the form deal with
itself:

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form }}
    {% endfor %}
</form>

When you manually render the forms yourself, be sure to render the management
form as shown above. See the management form documentation.

Third, you can manually render each field:

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {% for field in form %}
            {{ field.label_tag }} {{ field }}
        {% endfor %}
    {% endfor %}
</form>

If you opt to use this third method and you don’t iterate over the fields with
a {% for %} loop, you’ll need to render the primary key field. For example,
if you were rendering the name and age fields of a model:

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form.id }}
        <ul>
            <li>{{ form.name }}</li>
            <li>{{ form.age }}</li>
        </ul>
    {% endfor %}
</form>

Notice how we need to explicitly render {{ form.id }}. This ensures that
the model formset, in the POST case, will work correctly. (This example
assumes a primary key named id. If you’ve explicitly defined your own
primary key that isn’t called id, make sure it gets rendered.)

Inline formsets¶

class models.BaseInlineFormSet

Inline formsets is a small abstraction layer on top of model formsets. These
simplify the case of working with related objects via a foreign key. Suppose
you have these two models:

from django.db import models

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

class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)

If you want to create a formset that allows you to edit books belonging to
a particular author, you could do this:

>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=('title',))
>>> author = Author.objects.get(name='Mike Royko')
>>> formset = BookFormSet(instance=author)

BookFormSet’s prefix is 'book_set'
(<model name>_set ). If Book’s ForeignKey to Author has a
related_name, that’s used instead.

Overriding methods on an InlineFormSet

When overriding methods on InlineFormSet, you should subclass
BaseInlineFormSet rather than
BaseModelFormSet.

For example, if you want to override clean():

from django.forms import BaseInlineFormSet

class CustomInlineFormSet(BaseInlineFormSet):
    def clean(self):
        super().clean()
        # example custom validation across forms in the formset
        for form in self.forms:
            # your custom formset validation
            ...

See also Overriding clean() on a ModelFormSet.

Then when you create your inline formset, pass in the optional argument
formset:

>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=('title',),
...     formset=CustomInlineFormSet)
>>> author = Author.objects.get(name='Mike Royko')
>>> formset = BookFormSet(instance=author)

More than one foreign key to the same model¶

If your model contains more than one foreign key to the same model, you’ll
need to resolve the ambiguity manually using fk_name. For example, consider
the following model:

class Friendship(models.Model):
    from_friend = models.ForeignKey(
        Friend,
        on_delete=models.CASCADE,
        related_name='from_friends',
    )
    to_friend = models.ForeignKey(
        Friend,
        on_delete=models.CASCADE,
        related_name='friends',
    )
    length_in_months = models.IntegerField()

To resolve this, you can use fk_name to
inlineformset_factory():

>>> FriendshipFormSet = inlineformset_factory(Friend, Friendship, fk_name='from_friend',
...     fields=('to_friend', 'length_in_months'))

Using an inline formset in a view¶

You may want to provide a view that allows a user to edit the related objects
of a model. Here’s how you can do that:

def manage_books(request, author_id):
    author = Author.objects.get(pk=author_id)
    BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
    if request.method == "POST":
        formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
        if formset.is_valid():
            formset.save()
            # Do something. Should generally end with a redirect. For example:
            return HttpResponseRedirect(author.get_absolute_url())
    else:
        formset = BookInlineFormSet(instance=author)
    return render(request, 'manage_books.html', {'formset': formset})

Notice how we pass instance in both the POST and GET cases.

ModelForm

class ModelForm[исходный код]

Если вы создаете приложение, управляемое базой данных, то, скорее всего, у вас будут формы, которые тесно связаны с моделями Django. Например, у вас может быть модель BlogComment, и вы хотите создать форму, позволяющую людям оставлять комментарии. В этом случае было бы излишним определять типы полей в форме, потому что вы уже определили поля в модели.

По этой причине Django предоставляет вспомогательный класс, который позволяет вам создать класс Form из модели Django.

Например:

>>> 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)

Типы полей¶

Созданный класс Form будет иметь поле формы для каждого указанного поля модели, в порядке, указанном в атрибуте fields.

Каждое поле модели имеет соответствующее поле формы по умолчанию. Например, CharField в модели представляется как CharField в форме. Поле модели ManyToManyField представляется как MultipleChoiceField. Вот полный список преобразований:

Модельное поле Поле формы
AutoField Не представлены в форме
BigAutoField Не представлены в форме
BigIntegerField IntegerField с min_value, установленным на -9223372036854775808 и max_value, установленным на 9223372036854775807.
BinaryField CharField, если editable установлен в True на поле модели, иначе не представлен в форме.
BooleanField BooleanField, или NullBooleanField, если null=True.
CharField CharField с max_length, установленным на max_length модельного поля, и empty_value, установленным на None, если null=True.
DateField DateField
DateTimeField DateTimeField
DecimalField DecimalField
DurationField DurationField
EmailField EmailField
FileField FileField
FilePathField FilePathField
FloatField FloatField
ForeignKey ModelChoiceField (см. ниже)
ImageField ImageField
IntegerField IntegerField
IPAddressField IPAddressField
GenericIPAddressField GenericIPAddressField
JSONField JSONField
ManyToManyField ModelMultipleChoiceField (см. ниже)
PositiveBigIntegerField IntegerField
PositiveIntegerField IntegerField
PositiveSmallIntegerField IntegerField
SlugField SlugField
SmallAutoField Не представлены в форме
SmallIntegerField IntegerField
TextField CharField с widget=forms.Textarea
TimeField TimeField
URLField URLField
UUIDField UUIDField

Как и следовало ожидать, типы полей модели ForeignKey и ManyToManyField являются особыми случаями:

  • ForeignKey представлен django.forms.ModelChoiceField, который является ChoiceField, выбор которого представляет собой модель QuerySet.
  • ManyToManyField представлен django.forms.ModelMultipleChoiceField, который является MultipleChoiceField, выбор которого представляет собой модель QuerySet.

Кроме того, каждое сгенерированное поле формы имеет атрибуты, установленные следующим образом:

  • Если поле модели имеет blank=True, то required устанавливается в False на поле формы. В противном случае required=True.
  • Для поля формы label устанавливается verbose_name поля модели, причем первый символ пишется заглавными буквами.
  • Значение help_text поля формы устанавливается на значение help_text поля модели.
  • Если для поля модели установлено значение choices, то для поля формы widget будет установлено значение Select, а выбор будет осуществляться из поля модели choices. Варианты выбора обычно включают пустой вариант, который выбирается по умолчанию. Если поле является обязательным, это заставит пользователя сделать выбор. Пустой выбор не будет включен, если поле модели имеет blank=False и явное значение default (вместо него будет изначально выбрано значение default).

Наконец, обратите внимание, что вы можете переопределить поле формы, используемое для данного поля модели. См. Overriding the default fields ниже.

Полный пример¶

Рассмотрим этот набор моделей:

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):
        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']

С этими моделями подклассы ModelForm, приведенные выше, будут примерно эквивалентны этому (единственное отличие — метод save(), который мы обсудим в ближайшее время):

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())

Валидация на ModelForm

Существует два основных этапа проверки ModelForm:

  1. Validating the form
  2. Validating the model instance

Как и обычная проверка формы, проверка формы модели срабатывает неявно при вызове is_valid() или обращении к атрибуту errors и явно при вызове full_clean(), хотя на практике вы обычно не используете последний метод.

Валидация Model (Model.full_clean()) запускается внутри шага валидации формы, сразу после вызова метода формы clean().

Предупреждение

Процесс очистки изменяет экземпляр модели, переданный конструктору ModelForm, различными способами. Например, любые поля даты в модели преобразуются в реальные объекты даты. Неудачная валидация может оставить базовый экземпляр модели в противоречивом состоянии, поэтому не рекомендуется использовать его повторно.

Переопределение метода clean()

Вы можете переопределить метод clean() на форме модели, чтобы обеспечить дополнительную проверку так же, как и на обычной форме.

Экземпляр формы модели, присоединенный к объекту модели, будет содержать атрибут instance, который предоставляет его методам доступ к этому конкретному экземпляру модели.

Предупреждение

Метод ModelForm.clean() устанавливает флаг, который заставляет шаг model validation проверять уникальность полей модели, отмеченных как unique, unique_together или unique_for_date|month|year.

Если вы хотите переопределить метод clean() и сохранить эту валидацию, вы должны вызвать метод родительского класса clean().

Взаимодействие с валидацией модели¶

В процессе валидации ModelForm вызовет метод clean() каждого поля вашей модели, которое имеет соответствующее поле на вашей форме. Если вы исключили какие-либо поля модели, валидация не будет выполняться для этих полей. Подробнее о том, как работают очистка и валидация полей, см. документацию form validation.

Метод модели clean() будет вызван до того, как будут произведены проверки на уникальность. Более подробную информацию о хуке модели Validating objects смотрите в clean().

Соображения относительно модели error_messages

Сообщения об ошибках, определенные на уровне form field или на уровне form Meta, всегда имеют приоритет над сообщениями об ошибках, определенными на уровне model field.

Сообщения об ошибках, определенные на model fields, используются только тогда, когда на шаге ValidationError поднимается вопрос model validation и на уровне формы не определены соответствующие сообщения об ошибках.

Вы можете переопределить сообщения об ошибках NON_FIELD_ERRORS, выдаваемые при валидации модели, добавив ключ NON_FIELD_ERRORS в словарь error_messages внутреннего ModelForm класса Meta:

from django.core.exceptions import NON_FIELD_ERRORS
from django.forms import ModelForm

class ArticleForm(ModelForm):
    class Meta:
        error_messages = {
            NON_FIELD_ERRORS: {
                'unique_together': "%(model_name)s's %(field_labels)s are not unique.",
            }
        }

Метод save()

Каждый ModelForm также имеет метод save(). Этот метод создает и сохраняет объект базы данных из данных, привязанных к форме. Подкласс ModelForm может принимать существующий экземпляр модели в качестве аргумента ключевого слова instance; если оно предоставлено, save() обновит этот экземпляр. Если он не указан, save() создаст новый экземпляр указанной модели:

>>> 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()

Обратите внимание, что если форма hasn’t been validated, вызов save() сделает это, проверив form.errors. Вызов ValueError будет вызван, если данные в форме не подтвердятся — т.е. если form.errors оценивается как True.

Если опциональное поле не появляется в данных формы, результирующий экземпляр модели использует поле модели default, если оно есть, для этого поля. Это поведение не относится к полям, использующим CheckboxInput, CheckboxSelectMultiple или SelectMultiple (или к любому пользовательскому виджету, чей метод value_omitted_from_data() всегда возвращает False), поскольку не установленный флажок и не выбранный <select multiple> не появляются в данных HTML-формы. Используйте пользовательское поле формы или виджет, если вы разрабатываете API и хотите, чтобы поле, использующее один из этих виджетов, возвращалось по умолчанию.

Этот метод save() принимает необязательный аргумент ключевого слова commit, который принимает либо True, либо False. Если вы вызовете save() с commit=False, то он вернет объект, который еще не был сохранен в базе данных. В этом случае вы сами должны вызвать save() на полученном экземпляре модели. Это полезно, если вы хотите выполнить пользовательскую обработку объекта перед его сохранением, или если вы хотите использовать один из специализированных model saving options. По умолчанию commit является True.

Другой побочный эффект использования commit=False проявляется, когда ваша модель имеет отношение «многие-ко-многим» с другой моделью. Если ваша модель имеет отношение «многие-ко-многим» и вы указываете commit=False при сохранении формы, Django не может немедленно сохранить данные формы для отношения «многие-ко-многим». Это происходит потому, что невозможно сохранить данные отношения «многие-ко-многим» для экземпляра, пока этот экземпляр не существует в базе данных.

Чтобы обойти эту проблему, каждый раз, когда вы сохраняете форму с помощью commit=False, Django добавляет метод save_m2m() к вашему подклассу ModelForm. После того, как вы вручную сохранили экземпляр, созданный формой, вы можете вызвать save_m2m() для сохранения данных формы «многие-ко-многим». Например:

# 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()

Вызов save_m2m() требуется только в том случае, если вы используете save(commit=False). Когда вы используете save() на форме, все данные, включая данные «многие ко многим», сохраняются без необходимости дополнительных вызовов методов. Например:

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

# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()

За исключением методов save() и save_m2m(), ModelForm работает точно так же, как и любая другая forms форма. Например, метод is_valid() используется для проверки валидности, метод is_multipart() используется для определения того, требует ли форма многокомпонентной загрузки файла (и, следовательно, нужно ли передавать форме request.FILES), и т.д. Более подробную информацию смотрите в Привязка загруженных файлов к форме.

Выбор полей для использования¶

Настоятельно рекомендуется явно задавать все поля, которые должны редактироваться в форме, с помощью атрибута fields. Невыполнение этого требования может легко привести к проблемам безопасности, когда форма неожиданно позволит пользователю установить определенные поля, особенно когда в модель добавляются новые поля. В зависимости от того, как отображается форма, проблема может быть даже не видна на веб-странице.

Альтернативным подходом было бы автоматическое включение всех полей или удаление только некоторых. Этот фундаментальный подход, как известно, гораздо менее безопасен и привел к серьезным эксплойтам на крупных сайтах (например, GitHub).

Тем не менее, есть два коротких пути для случаев, когда вы можете гарантировать, что эти проблемы безопасности к вам не относятся:

  1. Установите атрибут fields в специальное значение '__all__', чтобы указать, что все поля в модели должны быть использованы. Например:

    from django.forms import ModelForm
    
    class AuthorForm(ModelForm):
        class Meta:
            model = Author
            fields = '__all__'
    
  2. Установите атрибут exclude внутреннего ModelForm класса Meta в список полей, которые необходимо исключить из формы.

    Например:

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

    Поскольку модель Author имеет 3 поля name, title и birth_date, это приведет к тому, что на форме будут присутствовать поля name и birth_date.

Если используется любой из этих способов, то порядок появления полей в форме будет соответствовать порядку их определения в модели, при этом экземпляры ManyToManyField будут появляться последними.

Кроме того, Django применяет следующее правило: если вы установили editable=False на поле модели, любая форма, созданная на основе модели через ModelForm, не будет включать это поле.

Примечание

Любые поля, не включенные в форму по вышеуказанной логике, не будут установлены методом save() формы. Также, если вы вручную добавите исключенные поля обратно в форму, они не будут инициализированы из экземпляра модели.

Django предотвратит любую попытку сохранения неполной модели, поэтому, если модель не допускает, чтобы отсутствующие поля были пустыми, и не предоставляет значения по умолчанию для отсутствующих полей, любая попытка save() a ModelForm с отсутствующими полями будет неудачной. Чтобы избежать этого сбоя, вы должны инстанцировать свою модель с начальными значениями для отсутствующих, но необходимых полей:

author = Author(title='Mr')
form = PartialAuthorForm(request.POST, instance=author)
form.save()

В качестве альтернативы можно использовать save(commit=False) и вручную задать все дополнительные обязательные поля:

form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
author.title = 'Mr'
author.save()

Более подробную информацию об использовании section on saving forms см. в save(commit=False).

Переопределение полей по умолчанию¶

Типы полей по умолчанию, описанные в таблице Field types выше, являются разумными значениями по умолчанию. Если у вас есть DateField в вашей модели, скорее всего, вы захотите, чтобы оно было представлено как DateField в вашей форме. Но ModelForm дает вам возможность гибко изменять поле формы для данной модели.

Чтобы указать пользовательский виджет для поля, используйте атрибут widgets внутреннего класса Meta. Это должен быть словарь, отображающий имена полей на классы или экземпляры виджетов.

Например, если вы хотите, чтобы CharField для атрибута name в Author было представлено <textarea> вместо стандартного <input type="text">, вы можете переопределить виджет поля:

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}),
        }

Словарь widgets принимает либо экземпляры виджетов (например, Textarea(...)), либо классы (например, Textarea). Обратите внимание, что словарь widgets игнорируется для поля модели с непустым атрибутом choices. В этом случае вы должны переопределить поле формы, чтобы использовать другой виджет.

Аналогично, вы можете указать атрибуты labels, help_texts и error_messages внутреннего класса Meta, если хотите дополнительно настроить поле.

Например, если вы хотите настроить формулировку всех строк для поля name:

from django.utils.translation import gettext_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."),
            },
        }

Вы также можете указать field_classes, чтобы настроить тип полей, создаваемых формой.

Например, если вы хотите использовать MySlugFormField для поля slug, вы можете сделать следующее:

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,
        }

Наконец, если вам нужен полный контроль над полем — включая его тип, валидаторы, обязательность и т.д. – вы можете сделать это, декларативно указав поля, как в обычном Form.

Если вы хотите указать валидаторы поля, вы можете сделать это, определив поле декларативно и задав его параметр validators:

from django.forms import CharField, ModelForm
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 и обычное Form.

ModelForm — это обычный Form, который может автоматически генерировать определенные поля. Поля, которые генерируются автоматически, зависят от содержимого класса Meta и от того, какие поля уже были определены декларативно. В основном, ModelForm будет только генерировать поля, которые отсутствуют в форме, или, другими словами, поля, которые не были определены декларативно.

Поля, определенные декларативно, остаются как есть, поэтому любые настройки, сделанные для атрибутов Meta, таких как widgets, labels, help_texts или error_messages, игнорируются; они применяются только к полям, которые создаются автоматически.

Аналогичным образом, поля, определенные декларативно, не берут свои атрибуты типа max_length или required из соответствующей модели. Если вы хотите сохранить поведение, указанное в модели, вы должны задать соответствующие аргументы явно при объявлении поля формы.

Например, если модель Article выглядит следующим образом:

class Article(models.Model):
    headline = models.CharField(
        max_length=200,
        null=True,
        blank=True,
        help_text='Use puns liberally',
    )
    content = models.TextField()

и вы хотите сделать некоторую пользовательскую проверку для headline, сохраняя значения blank и help_text как указано, вы можете определить ArticleForm следующим образом:

class ArticleForm(ModelForm):
    headline = MyFormField(
        max_length=200,
        required=False,
        help_text='Use puns liberally',
    )

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

Вы должны убедиться, что тип поля формы может быть использован для задания содержимого соответствующего поля модели. Если они несовместимы, вы получите ValueError, так как неявного преобразования не происходит.

Более подробную информацию о полях и их аргументах см. в form field documentation.

Обеспечение локализации полей¶

По умолчанию поля в ModelForm не локализуют свои данные. Чтобы включить локализацию для полей, вы можете использовать атрибут localized_fields на классе Meta.

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

Если localized_fields установлено специальное значение '__all__', все поля будут локализованы.

Наследование формы¶

Как и в случае с базовыми формами, вы можете расширять и повторно использовать ModelForms, наследуя их. Это полезно, если вам нужно объявить дополнительные поля или дополнительные методы в родительском классе для использования в ряде форм, производных от моделей. Например, используя предыдущий ArticleForm класс:

>>> class EnhancedArticleForm(ArticleForm):
...     def clean_pub_date(self):
...         ...

Это создает форму, которая ведет себя идентично ArticleForm, за исключением дополнительной валидации и очистки поля pub_date.

Вы также можете подклассифицировать внутренний класс Meta родителя, если хотите изменить списки Meta.fields или Meta.exclude:

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

Это добавляет дополнительный метод из EnhancedArticleForm и модифицирует исходный ArticleForm.Meta, чтобы удалить одно поле.

Однако есть несколько моментов, которые следует отметить.

  • Применяются обычные правила разрешения имен Python. Если у вас есть несколько базовых классов, которые объявляют внутренний класс Meta, будет использоваться только первый. Это означает дочерний Meta, если он существует, иначе Meta первого родителя и т.д.

  • Можно наследоваться одновременно от Form и ModelForm, однако вы должны убедиться, что ModelForm появится первым в MRO. Это происходит потому, что эти классы полагаются на разные метаклассы, а класс может иметь только один метакласс.

  • Можно декларативно удалить Field, унаследованный от родительского класса, установив имя None в подклассе.

    Вы можете использовать эту технику только для отказа от поля, определенного декларативно родительским классом; это не помешает метаклассу ModelForm сгенерировать поле по умолчанию. Чтобы отказаться от полей по умолчанию, смотрите Выбор полей для использования.

Предоставление начальных значений¶

Как и в обычных формах, можно указать начальные данные для форм, указав параметр initial при инстанцировании формы. Начальные значения, указанные таким образом, будут переопределять как начальные значения поля формы, так и значения из присоединенного экземпляра модели. Например:

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

Функция фабрики ModelForm¶

Вы можете создавать формы из заданной модели с помощью отдельной функции modelform_factory(), вместо того, чтобы использовать определение класса. Это может быть удобнее, если вам не нужно делать много настроек:

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

Это также можно использовать для внесения изменений в существующие формы, например, указывая виджеты, которые будут использоваться для данного поля:

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

Поля для включения можно указать с помощью аргументов fields и exclude ключевых слов или соответствующих атрибутов внутреннего ModelForm класса Meta. Обратитесь к документации ModelForm Выбор полей для использования.

… или включить локализацию для определенных полей:

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

Модельные наборы форм¶

class models.BaseModelFormSet

Как и regular formsets, Django предоставляет пару расширенных классов формсет, чтобы сделать работу с моделями Django более удобной. Давайте повторно используем модель Author, описанную выше:

>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))

Использование fields ограничивает набор форм использованием только заданных полей. В качестве альтернативы можно использовать подход «opt-out», указав, какие поля следует исключить:

>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))

Это создаст набор форм, способный работать с данными, связанными с моделью Author. Он работает так же, как и обычный набор форм:

>>> 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-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS"><input type="hidden" name="form-MAX_NUM_FORMS" value="1000" 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>---------</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() использует formset_factory() для генерации наборов форм. Это означает, что модельный набор форм является расширением базового набора форм, который знает, как взаимодействовать с конкретной моделью.

Примечание

При использовании multi-table inheritance формы, сгенерированные фабрикой форм, будут содержать поле родительской ссылки (по умолчанию <parent_model_name>_ptr) вместо поля id.

Изменение набора queryset¶

По умолчанию, когда вы создаете набор форм из модели, набор форм будет использовать кверисет, включающий все объекты модели (например, Author.objects.all()). Вы можете отменить это поведение, используя аргумент queryset:

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

В качестве альтернативы можно создать подкласс, который устанавливает self.queryset в __init__:

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

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

Затем передайте свой класс BaseAuthorFormSet в функцию фабрики:

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

Если вы хотите вернуть набор форм, который не включает любые ранее существовавшие экземпляры модели, вы можете указать пустой QuerySet:

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

Изменение формы¶

По умолчанию, когда вы используете modelformset_factory, форма модели будет создана с помощью modelform_factory(). Часто бывает полезно указать пользовательскую форму модели. Например, вы можете создать пользовательскую форму модели, которая будет иметь пользовательскую валидацию:

class AuthorForm(forms.ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title')

    def clean_name(self):
        # custom validation for the name field
        ...

Затем передайте форму вашей модели в функцию фабрики:

AuthorFormSet = modelformset_factory(Author, form=AuthorForm)

Не всегда необходимо определять пользовательскую форму модели. Функция modelformset_factory имеет несколько аргументов, передаваемых в modelform_factory, которые описаны ниже.

Включение локализации для полей с localized_fields

Используя параметр localized_fields, вы можете включить локализацию для полей формы.

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title', 'birth_date'),
...     localized_fields=('birth_date',))

Если localized_fields установлено специальное значение '__all__', все поля будут локализованы.

Предоставление начальных значений¶

Как и в обычных наборах форм, можно specify initial data для форм в наборе форм, указав параметр initial при инстанцировании класса набора форм модели, возвращаемого modelformset_factory(). Однако в модельных наборах форм начальные значения применяются только к дополнительным формам, тем, которые не прикреплены к существующему экземпляру модели. Если длина initial превышает количество дополнительных форм, избыточные начальные данные игнорируются. Если дополнительные формы с начальными данными не были изменены пользователем, они не будут проверены или сохранены.

Сохранение объектов в наборе форм¶

Как и в случае с ModelForm, вы можете сохранить данные как объект модели. Это делается с помощью метода save() набора форм:

# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)

# Assuming all is valid, save the data.
>>> instances = formset.save()

Метод save() возвращает экземпляры, которые были сохранены в базе данных. Если данные экземпляра не изменились в связанных данных, экземпляр не будет сохранен в базе данных и не будет включен в возвращаемое значение (instances, в примере выше).

Если поля отсутствуют в форме (например, потому что они были исключены), эти поля не будут установлены методом save(). Более подробную информацию об этом ограничении, которое также действует для обычных ModelForms, можно найти в Selecting the fields to use.

Передайте commit=False, чтобы вернуть несохраненные экземпляры модели:

# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
...     # do something with instance
...     instance.save()

Это дает вам возможность прикреплять данные к экземплярам перед сохранением их в базе данных. Если ваш набор форм содержит ManyToManyField, вам также необходимо вызвать formset.save_m2m(), чтобы обеспечить правильное сохранение отношений «многие-ко-многим».

После вызова save() у вашего набора форм модели появятся три новых атрибута, содержащих изменения набора форм:

models.BaseModelFormSet.changed_objects
models.BaseModelFormSet.deleted_objects
models.BaseModelFormSet.new_objects

Ограничение количества редактируемых объектов¶

Как и в случае с обычными наборами форм, вы можете использовать параметры max_num и extra к modelformset_factory(), чтобы ограничить количество отображаемых дополнительных форм.

max_num не препятствует отображению существующих объектов:

>>> Author.objects.order_by('name')
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']

Также extra=0 не препятствует созданию новых экземпляров модели, как вы можете add additional forms with JavaScript, или отправке дополнительных POST-данных. Смотрите Предотвращение создания новых объектов о том, как это сделать.

Если значение max_num больше, чем количество существующих связанных объектов, в набор форм будет добавлено до extra дополнительных пустых форм, пока общее количество форм не превысит max_num:

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100"><input type="hidden" name="form-0-id" value="1" id="id_form-0-id"></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100"><input type="hidden" name="form-1-id" value="3" id="id_form-1-id"></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100"><input type="hidden" name="form-2-id" value="2" id="id_form-2-id"></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100"><input type="hidden" name="form-3-id" id="id_form-3-id"></td></tr>

Значение max_num None (по умолчанию) устанавливает высокий предел на количество отображаемых форм (1000). На практике это эквивалентно отсутствию ограничения.

Предотвращение создания новых объектов¶

New in Django 4.1.

Используя параметр edit_only, вы можете предотвратить создание любых новых объектов:

>>> AuthorFormSet = modelformset_factory(
...     Author,
...     fields=('name', 'title'),
...     edit_only=True,
... )

Здесь набор форм будет редактировать только существующие экземпляры Author. Никакие другие объекты не будут созданы или отредактированы.

Использование набора форм модели в представлении¶

Формсеты моделей очень похожи на наборы форм. Допустим, мы хотим представить набор форм для редактирования Author экземпляров модели:

from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    if request.method == 'POST':
        formset = AuthorFormSet(request.POST, request.FILES)
        if formset.is_valid():
            formset.save()
            # do something.
    else:
        formset = AuthorFormSet()
    return render(request, 'manage_authors.html', {'formset': formset})

Как вы можете видеть, логика представления модельного набора форм не сильно отличается от логики «обычного» набора форм. Единственное отличие заключается в том, что мы вызываем formset.save() для сохранения данных в базе данных. (Это было описано выше, в Сохранение объектов в наборе форм).

Переопределение clean() на ModelFormSet

Как и в случае с ModelForms, по умолчанию метод clean() в ModelFormSet будет проверять, что ни один из элементов набора форм не нарушает уникальные ограничения вашей модели (либо unique, либо unique_together, либо unique_for_date|month|year). Если вы хотите переопределить метод clean() на ModelFormSet и сохранить эту проверку, вы должны вызвать метод clean родительского класса:

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super().clean()
        # example custom validation across forms in the formset
        for form in self.forms:
            # your custom formset validation
            ...

Также обратите внимание, что к тому времени, когда вы дойдете до этого шага, отдельные экземпляры модели уже будут созданы для каждого Form. Изменения значения в form.cleaned_data недостаточно, чтобы повлиять на сохраненное значение. Если вы хотите изменить значение в ModelFormSet.clean(), вы должны изменить form.instance:

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super().clean()

        for form in self.forms:
            name = form.cleaned_data['name'].upper()
            form.cleaned_data['name'] = name
            # update the instance value.
            form.instance.name = name

Использование пользовательского набора запросов¶

Как говорилось ранее, вы можете переопределить набор запросов по умолчанию, используемый набором форм модели:

from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    queryset = Author.objects.filter(name__startswith='O')
    if request.method == "POST":
        formset = AuthorFormSet(
            request.POST, request.FILES,
            queryset=queryset,
        )
        if formset.is_valid():
            formset.save()
            # Do something.
    else:
        formset = AuthorFormSet(queryset=queryset)
    return render(request, 'manage_authors.html', {'formset': formset})

Обратите внимание, что в этом примере мы передаем аргумент queryset в обоих случаях POST и GET.

Использование набора форм в шаблоне¶

Существует три способа отображения набора форм в шаблоне Django.

Во-первых, вы можете позволить набору форм сделать большую часть работы:

<form method="post">
    {{ formset }}
</form>

Во-вторых, вы можете вручную отобразить набор форм, но позволить форме самой разобраться с собой:

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form }}
    {% endfor %}
</form>

Если вы сами вручную выводите формы, убедитесь, что форма управления выводится так, как показано выше. Смотрите management form documentation.

В-третьих, вы можете вручную визуализировать каждое поле:

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {% for field in form %}
            {{ field.label_tag }} {{ field }}
        {% endfor %}
    {% endfor %}
</form>

Если вы решите использовать этот третий метод и не будете перебирать поля с помощью цикла {% for %}, вам нужно будет отобразить поле первичного ключа. Например, если вы отображаете поля name и age модели:

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form.id }}
        <ul>
            <li>{{ form.name }}</li>
            <li>{{ form.age }}</li>
        </ul>
    {% endfor %}
</form>

Обратите внимание, что нам нужно явно отобразить {{ form.id }}. Это гарантирует, что набор форм модели, в случае POST, будет работать правильно. (В этом примере предполагается первичный ключ с именем id. Если вы явно определили свой собственный первичный ключ, который не называется id, убедитесь, что он будет отображен).

Встроенные наборы форм¶

class models.BaseInlineFormSet

Inline formsets — это небольшой слой абстракции поверх model formsets. Они упрощают работу со связанными объектами через внешний ключ. Предположим, у вас есть две модели:

from django.db import models

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

class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)

Если вы хотите создать набор форм, позволяющий редактировать книги, принадлежащие определенному автору, вы можете сделать следующее:

>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=('title',))
>>> author = Author.objects.get(name='Mike Royko')
>>> formset = BookFormSet(instance=author)

BookFormSet’s prefix is 'book_set' (<model name>_set ). Если у Book от ForeignKey до Author есть related_name, то он используется вместо него.

Переопределение методов на InlineFormSet

При переопределении методов на InlineFormSet следует подкласс BaseInlineFormSet, а не BaseModelFormSet.

Например, если вы хотите переопределить clean():

from django.forms import BaseInlineFormSet

class CustomInlineFormSet(BaseInlineFormSet):
    def clean(self):
        super().clean()
        # example custom validation across forms in the formset
        for form in self.forms:
            # your custom formset validation
            ...

См. также Переопределение clean() на ModelFormSet.

Затем, когда вы создаете свой набор встроенных форм, передайте необязательный аргумент formset:

>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=('title',),
...     formset=CustomInlineFormSet)
>>> author = Author.objects.get(name='Mike Royko')
>>> formset = BookFormSet(instance=author)

Более одного внешнего ключа к одной и той же модели¶

Если ваша модель содержит более одного внешнего ключа к одной и той же модели, вам придется разрешить неоднозначность вручную с помощью fk_name. Например, рассмотрим следующую модель:

class Friendship(models.Model):
    from_friend = models.ForeignKey(
        Friend,
        on_delete=models.CASCADE,
        related_name='from_friends',
    )
    to_friend = models.ForeignKey(
        Friend,
        on_delete=models.CASCADE,
        related_name='friends',
    )
    length_in_months = models.IntegerField()

Чтобы решить эту проблему, вы можете использовать fk_name для inlineformset_factory():

>>> FriendshipFormSet = inlineformset_factory(Friend, Friendship, fk_name='from_friend',
...     fields=('to_friend', 'length_in_months'))

Использование встроенного набора форм в представлении¶

Вы можете захотеть предоставить представление, позволяющее пользователю редактировать связанные объекты модели. Вот как это можно сделать:

def manage_books(request, author_id):
    author = Author.objects.get(pk=author_id)
    BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
    if request.method == "POST":
        formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
        if formset.is_valid():
            formset.save()
            # Do something. Should generally end with a redirect. For example:
            return HttpResponseRedirect(author.get_absolute_url())
    else:
        formset = BookInlineFormSet(instance=author)
    return render(request, 'manage_books.html', {'formset': formset})

Обратите внимание, что мы передаем instance в обоих случаях POST и GET.

When using model forms you can save values entered into a form by calling the form’s save() method as shown below.

if form.is_valid():
    form.save()

If you are not using model forms an attempt to call form.save() results in an error such as:

The save() method works with model forms because there is a model associated with it so Django knows what to save and how. When you are using simple forms the link between the form and the model is not implicitly available so you have to explicitly tell Django what form values you want to save. The following example borrowed from this project shows how this can be achieved.

The goal of the example project is to display the distance between two post codes either in kilometres or miles. The model therefore has three fields, a starting post code, an ending post code and the measurement units.

models.py

from django.db import models

class Postcode(models.Model):
    start_postcode = models.CharField(max_length=4)
    end_postcode = models.CharField(max_length=4)
    result_measurement_unit = models.CharField(max_length=4)

Similar to the model the form also has three fields and named similar to the model with the exception that the field used to determine the measure units is called distance_unit.

forms.py

from django import forms

MEASUREMENT_UNITS = [
    ('KM', 'km'),
    ('M', 'miles'),
] 

class PostcodeForm(forms.Form):
    start_postcode = forms.CharField(max_length=4)
    end_postcode = forms.CharField(max_length=4)
    distance_unit = forms.ChoiceField(choices=MEASUREMENT_UNITS, label='Distance in', widget=forms.RadioSelect(choices=MEASUREMENT_UNITS), initial='KM'  )

The logic to save the form values to the database are added to views.py.

views.py

if request.method == "POST":

        form = PostcodeForm(request.POST)

        if form.is_valid():
            
            cd = form.cleaned_data
            
            pc = Postcode(
                start_postcode = cd['start_postcode'],
                end_postcode = cd['end_postcode'],
                result_measurement_unit = cd['distance_unit']
            )
            
            pc.save()
            ...

Stepping through the view.py code:

Line 1: Checks that this is a post request

Line 3: A reference to the form is obtained and stored in the variable form.

Line 5: The form is tested to ensure the values entered are valid.

Line 7: The form values are obtained using form.cleaned data and stored in the cd variable.

Line 9: A new Postcode object, defined in the models.py file is created and it’s attributes are populated using the values from the form.

Line 15: The save method of the object is called.

Acknowledgements

My thanks to the author and contributes of the pgeocode library which was used in the example project.

ModelForm

class ModelForm¶

При разработке приложения, использующего базу данных, чаще всего вы будете работать с формами, которые аналогичны моделям. Например, имея модель BlogComment, вам может потребоваться создать форму, которая позволит людям отправлять комментарии. В этом случае явное определение полей формы будет дублировать код, так как все поля уже описаны в модели.

По этой причине Django предоставляет вспомогательный класс, который позволит вам создать класс Form по имеющейся модели.

Например:

>>> 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)

Типы полей¶

Сгенерированный класс Form будет содержать поле формы для каждого поля модели в порядке указанном в атрибуте fields.

Каждому полю модели соответствует стандартное поле формы. Например, CharField поле модели будет представлено на форме как CharField, а ManyToManyField поле модели будет представлено как MultipleChoiceField. Ниже представлен полный список соответствия полей модели и формы:

Поле модели

Поле формы

AutoField

Не представлено на форме

BigIntegerField

IntegerField с атрибутом min_value равным -9223372036854775808 и атрибутом max_value равным 9223372036854775807.

BooleanField BooleanField
CharField

CharField с атрибутом max_length равным значению атрибута max_length модели

CommaSeparatedIntegerField CharField
DateField DateField
DateTimeField DateTimeField
DecimalField DecimalField
EmailField EmailField
FileField FileField
FilePathField FilePathField
FloatField FloatField
ForeignKey

ModelChoiceField (смотри далее)

ImageField ImageField
IntegerField IntegerField
IPAddressField IPAddressField
GenericIPAddressField GenericIPAddressField
ManyToManyField

ModelMultipleChoiceField (смотри далее)

NullBooleanField NullBooleanField
PositiveIntegerField IntegerField
PositiveSmallIntegerField IntegerField
SlugField SlugField
SmallIntegerField IntegerField
TextField

CharField с widget=forms.Textarea

TimeField TimeField
URLField URLField

Как вы могли ожидать, ForeignKey и ManyToManyField поля модели являются особыми случаями:

  • Поле ForeignKey модели представлено полем формы ModelChoiceField, которое является обычным ChoiceField, но с вариантами значений, полученными из QuerySet.

  • Поле ManyToManyField модели представлено полем формы ModelMultipleChoiceField, которое является обычным MultipleChoiceField`, но с вариантами значений, полученными из «QuerySet.

В дополнение, каждое поле созданной формы имеет следующие атрибуты:

  • Если у поля модели есть blank=True, тогда к полю формы будет добавлено required=False, иначе – required=True.

  • Значением атрибута label поля будет значение поля verbose_name модели, причём первый символ этого значения будет преобразован в верхний регистр.

  • Значением атрибута help_text поля формы будет значение атрибута help_text поля модели.

  • Если для поля модели установлен атрибут choices, тогда для поля формы будет использоваться виджет Select, который будет отображать содержимое этого атрибута. Варианты выбора обычно содержат пустой вариант, который выбран по умолчанию. Если поле является обязательным, то оно требует от пользователя сделать выбор. Пустой вариант не отображается, если у поля модели есть атрибут blank=False и явное значение default (при этом, это значение будет выбрано по умолчанию).

В конце, следует отметить, что вы можете переопределить поле формы, используемое для определённого поля модели. Переопределение стандартных полей описано ниже.

Полный пример¶

Рассмотрим этот набор полей:

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']

Для этих моделей показанные выше классы ModelForm будут аналогичны следующим формам (разница будет только в методе save(), что мы вскоре рассмотрим.):

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())

Валидация в ModelForm

Есть два основных шага при валидации ModelForm:

  1. Валидация форм

  2. Валидация объекта модели

Как и валидация в обычной форме валидация в модельной форме выполняется при вызове is_valid() или при обращении к атрибуту errors, или при явном вызове full_clean(), но на практике вы не будете использовать последний метод.

Валидация модели (Model.full_clean()) выполняется после валидации формы, сразу после завершения метода clean().

Предупреждение

Процесс валидации изменяет объект модели переданный в конструктор ModelForm. Например, поля даты модели преобразуют значения в объект даты. Ошибка валидации может оставить объект в неопределенном состоянии и лучше не использовать его в последующем коде.

Переопределение метода clean()¶

Вы можете переопределить метод clean() модели для того, чтобы обеспечить дополнительную проверку. Всё это аналогично работе с обычной формой.

Экземпляр модельной формы, привязанный к объекту модели имеет атрибут instance, через который методы модельной формы имеют доступ к соответствующему экземпляру модели.

Предупреждение

Метод ModelForm.clean() устанавливает флаг, который указывает валидации модели провалидировать уникальность полей отмеченных unique, unique_together или unique_for_date|month|year.

Если вы хотите переопределить метод clean(), вызовите метод clean() родительского класса.

Взаимодействие с механизмами модели¶

В процессе проверки данных ModelForm будет вызывать метод clean() каждого поля вашей модели, соответствующего полю формы. Для полей модели, которые были исключены из формы, проверка данных производиться не будет. Обратитесь к документации по проверке форм для получения информации о том, как работает проверка данных поля.

Метод модели clean() вызывается перед проверкой уникальности полей. Смотрите валидацию объектов модели, чтобы узнать как работает clean().

Определение error_messages

Сообщения ошибки из form field или form Meta имеют приоритет над сообщениями ошибок из model field.

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.

Вы можете переопределить сообщения об ошибке для NON_FIELD_ERRORS, который были вызваны при валидации модели, определив ключ NON_FIELD_ERRORS в атрибут error_messages класса ModelForm.Meta:

from django.forms import ModelForm
from django.core.exceptions import NON_FIELD_ERRORS

class ArticleForm(ModelForm):
    class Meta:
        error_messages = {
            NON_FIELD_ERRORS: {
                'unique_together': "%(model_name)s's %(field_labels)s are not unique.",
            }
        }

Метод save()

Каждая форма, созданная с помощью ModelForm, обладает методом save(). Этот метод создаёт и сохраняет объект в базе данных, используя для этого данные, введённые в форму. Класс, унаследованный от ModelForm, может принимать существующий экземпляр модели через именованный аргумент instance. Если такой аргумент указан, то save() обновит переданную модель. В противном случае, save() создаст новый экземпляр указанной модели:

>>> 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()

Обратите внимание, если форма не была проверена, вызов save() выполнит ее, обратившись к form.errors. Если данные не верны, будет вызвано исключение ValueError – то есть, если form.errors равно True.

Метод save() принимает необязательный именованный аргумент commit, который может иметь значения True или False. При вызове save() с commit=False метод вернёт объект, который ещё не был сохранён в базе данных. В этом случае, вам самостоятельно придётся вызвать метод save() у полученного объекта. Это бывает полезно, когда требуется выполнить дополнительные действия над объектом до его сохранения или если вам требуется воспользоваться одним из параметров сохранения модели. Атрибут commit по умолчанию имеет значение True.

Использование commit=False также полезно в случае, когда ваша модель имеет связь “многие-ко-многим” с другой моделью. Для такой модели, если метод save() вызван с аргументом commit=False, то Django не может немедленно сохранить данные для такой связи, т.к. невозможно создать связи для объекта, который не сохранен в базе данных.

Чтобы решить эту задачу, каждый раз, когда вы сохраняете форму, указывая commit=False, Django добавляет метод save_m2m() к вашему классу ModelForm. После того, как вы вручную сохранили экземпляр формы, вы можете вызвать метод save_m2m() для сохранения данных, связанных через “многие-ко-многим”. Например:

# 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()

Вызов метода save_m2m() требуется только в случае, если вы используете save(commit=False). Если вы просто используете save() для формы, то все данные (включая связи “многие-ко-многим”), будут сохранены, не требуя для этого дополнительных действий. Например:

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

# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()

Если не принимать во внимание методы save() и save_m2m(), то ModelForm работает аналогично обычной Form. Например, метод is_valid() используется для проверки данных, метод is_multipart() используется для определения загрузки файла (в этом случае request.FILES должен быть передан форме) и так далее. Обратитесь к документу Привязка загруженных файлов к форме для получения подробностей.

Указываем какие поля использовать¶

Настоятельно рекомендуем явно указать все поля отображаемые в форме, используя параметр fields. Иначе по ошибке, при добавлении нового поля в модель, можно позволить его редактировать пользователям и таким образом создать уязвимость. В зависимости от способа рендеринга формы, такая ошибка может быть не легко заметна на сайте.

Самый простой способ указать поля — автоматически добавить все или исключить определенные. Но такой способ не безопасен (например, случай на GitHub).

Но если вы уверены в том, что делаете, вот как использовать этот подход:

  1. В параметре fields указать специальное значение ‘__all__’, которое указывает использовать все поля модели. Например:

    from django.forms import ModelForm
    
    class AuthorForm(ModelForm):
        class Meta:
            model = Author
            fields = '__all__'
    
  2. Используйте атрибут exclude внутреннего класса ModelForm.Meta. Этот атрибут, если он указан, должен содержать список имён полей, которые не должны отображаться на форме.

    Например:

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

    Так как модель Author содержит три поля: ‘name’, ‘title ‘ и ‘birth_date’, то форма будут отображать поля name и birth_date.

При использовании одного из этих способов, порядок полей в форме будет аналогичен порядку полей в модели, ManyToManyField поля будут в конце.

Если поле модели содержит editable=False, каждая форма, созданная по модели с помощью ModelForm, не будет включать в себя это поле.

Изменено в Django 1.8:

В старых версиях, в форме будут представлены все поля модели, если не определить значения для fields и exclude. Сейчас такое поведение вызовет исключение ImproperlyConfigured.

Примечание

Поля, которые не определены в форме, не будут учитываться при вызове метода save(). Также, если вы вручную добавите в форму исключенные поля, то они не будут заполняться из экземпляра модели.

Django будет препятствовать всем попыткам сохранить неполную модель. Таким образом, если модель требует заполнения определённых полей и для них не предоставлено значение по умолчанию, то сохранить форму для такой модели не получится. Для решения этой проблемы вам потребуется создать экземпляр такой модели, передав ему начальные значения для обязательных, но незаполненных полей:

author = Author(title='Mr')
form = PartialAuthorForm(request.POST, instance=author)
form.save()

В качестве альтернативы, вы можете использовать save(commit=False) и вручную определить все необходимые поля:

form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
author.title = 'Mr'
author.save()

Обратитесь к разделу section on saving forms для подробностей по использованию save(commit=False).

Переопределение стандартных типов полей или виджетов¶

Стандартные типы полей, описанные выше, имеют целесообразные настройки по умолчанию. Если ваша модель имеет поля типа DateField, то скорее всего вы пожелаете, чтобы форма использовала поле DateField для его отображения. Но класс ModelForm представляет широкие возможности по управлению типами полей формы, а также виджетами для их представления.

Для того, чтобы указать собственный виджет для поля, следует использовать атрибут widgets внутреннего класса Meta. Его значением должен быть словарь, ключами которого являются имена полей, а значениями — классы или экземпляры виджетов.

Например, если необходимо использовать CharField для того, чтобы поле name модели Author было представлено в виде <textarea> вместо стандартного <input type=»text»>, то вы можете переопределить виджет поля:

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}),
        }

Ещё раз напомним, что аргумент widgets принимает словарь с экземплярами (т.е., Textarea(…)) или классами (т.е., Textarea) виджетов.

Аналогично можно переопределить параметры labels, help_texts и error_messages указав в Meta.

Например, для переопределим параметры поля name:

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."),
            },
        }

Вы также можете указать field_classes, чтобы настроить типы полей, которые будут использовать формой.

Например, если вы хотите использовать MySlugFormField для поля slug, вы можете сделать следующее:

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,
        }

Если вы желаете продолжить настойку поля, включая его тип, метку и так далее, то вы можете декларативно указать поля, как это делается при использовании Form.

Чтобы переопределить валидаторы поля, укажите их в аргументе 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']

Добавлено в Django 1.9:

Был добавлен атрибут Meta.field_classes.

Примечание

Когда вы явно создаете поле формы, необходимо знать как связанны ModelForm и Form.

ModelForm это дочерний класс Form, который может автоматически создавать поля формы. При создании полей учитываются параметры класса Meta и явно определенные поля формы. ModelForm автоматически создаст только те поля, которые отсутствуют в форме.

Явно определенные в классе поля создаются как есть, параметры Meta, такие как widgets, labels, help_texts или error_messages, игнорируются, они учитываются только для создании дополнительных полей.

При явном создании поля, Django предполагает, что вы будете определять поведение формы в целом. Следовательно, стандартные атрибуты модели (такие как max_length или required) не передаются полям формы. Если вам потребуется обеспечить поведение, определённое в модели, вам потребуется явно установить соответствующие аргументы при определении поля формы.

Например, если модель Article выглядит так:

class Article(models.Model):
    headline = models.CharField(max_length=200, null=True, blank=True,
                                help_text="Use puns liberally")
    content = models.TextField()

и вы желаете выполнить свою проверку поля headline, оставляя неизменными атрибуты blank и help_text, вы можете определить ArticleForm следующим образом:

class ArticleForm(ModelForm):
    headline = MyFormField(max_length=200, required=False,
                           help_text="Use puns liberally")

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

Тип поля формы должен работать с типом значения соответствующего поля модели. Если они не соответствуют — вы получите ValueError.

Обратитесь к документации на поля формы для получения дополнительной информации о полях и их аргументах.

Локализация в полях¶

По умолчанию поля в ModelForm не локализируют свои данные. Для локализации полей можно использовать параметр localized_fields класса Meta.

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

Если в localized_fields указать ‘__all__’, будут локализированы все поля.

Наследование форм¶

Аналогично обычным формам, вы можете наследоваться ModelForm. Это удобно когда надо добавить дополнительные поля или методы к базовому классу и использовать результат для создания других модельных форм. Например, для класса ArticleForm:

>>> class EnhancedArticleForm(ArticleForm):
...     def clean_pub_date(self):
...         ...

Мы создали форму, аналогичную ArticleForm, добавив дополнительную проверку и обработку для поля pub_date.

Вы также можете наследовать внутренний класс Meta, если требуется внести изменения в списки Meta.fields или Meta.excludes:

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

Здесь мы добавили метод из EnhancedArticleForm и изменили оригинальный ArticleForm.Meta, убрав одно поле.

Тем не менее, надо уточнить несколько моментов.

  • Применяются стандартные правила языка Python для разрешения имён. Если ваш класс унаследован от нескольких базовых классов, которые обладают внутренним классом Meta, и для него не определён собственный Meta класс, то этот класс будет унаследован из первого базового.

  • Можно унаследоваться одновременно от Form и ModelForm, однако, ModelForm должен быть первым в MRO. Т.к. эти классы используют разные мета-классы, а класс может использовать только один метакласс.

  • Можно декларативно удалить Field родительского класса, указав в названии None в дочернем классе.

    Таким способом можно исключить только те поля, которые были декларативно описаны в родительском классе. Поле ModelForm будет в любом случае созданы мета-классом. Чтобы переопределить их, используйте метод описанный в Указываем какие поля использовать.

Передача начальных значений¶

Аналогично обычным формам, есть возможность указать начальные данные, передав параметр initial при создании экземпляра формы. Предоставленные таким образом начальные данные переопределят начальные данные самих полей формы и значения из подключенного экземпляра модели. Например:

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

Функция-фабрика модельных форм¶

Вы можете создать форму по модели используя функцию modelform_factory(), вместо создания класса. В некоторых случаях это более удобный способ:

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

Можно указать определенные настройки для формы, например отображаемые поля:

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

Указать используемые поля можно с помощью аргументов fields и exclude. Смотрите описание ModelForm Указываем какие поля использовать.

… или включить локализацию для полей:

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

Наборы модельных форм¶

class models.BaseModelFormSet¶

Аналогично наборам обычных форм, Django представляет ряд расширенных классов наборов форм, которые упрощают взаимодействие с моделями Django. Давайте воспользуемся моделью Author:

>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))

Использование аргумента«fields« ограничивает набор форм указанным списком полей. В качестве альтернативы можно определить список полей, которые не должны отображаться на формах. Сделать это можно с помощью аргумента exclude:

>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))

Изменено в Django 1.8:

В старых версиях, в наборе форм будут представлены все поля модели, если не определить значения для fields и exclude. Сейчас такое поведение вызовет исключение ImproperlyConfigured.

Этот код создаст набор форм, которые будут работать с данными модели Author. По функционалу набор модельных форм аналогичен набору обычных форм:

>>> 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() использует фабрику обычных форм formset_factory() для создания набора форм. Это означает, что функционал модельных форм является надстройкой над функционалом набора обычных форм.

Изменение выборки¶

По умолчанию, при создании набора модельных форм используется выборка, которая содержит все объекты модели (т.е., Author.objects.all()). Такое поведение можно скорректировать, используя аргумент queryset:

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

Также вы можете унаследоваться от класса набора модельных форм и определить self.queryset в конструкторе, указав необходимые параметры выборки:

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')

Теперь передадим ваш класс BaseAuthorFormSet в функцию фабрики:

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

Если требуется создать пустой набор форм, т.е. который не включает ни одну существующую модель, то передайте в набор пустую выборку:

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

Настройка form

По умолчанию, когда вы используете modelformset_factory, форма будет создана с помощью modelform_factory(). Часто необходимо указать свою форму. Например, форму с собственной валидацией:

class AuthorForm(forms.ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title')

    def clean_name(self):
        # custom validation for the name field
        ...

Для этого передайте вашу форму в функцию фабрики:

AuthorFormSet = modelformset_factory(Author, form=AuthorForm)

Вам не обязательно создавать свою форму. Функция modelformset_factory принимает различный аргументы, которые будут переданы в modelform_factory.

Включить локализацию для полей с помощью localized_fields

Используя localized_fields можно включить локализацию для полей формы.

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title', 'birth_date'),
...     localized_fields=('birth_date',))

Если в localized_fields указать ‘__all__’, будут локализированы все поля.

Передача начальных значений¶

Аналогично набору обычных форм, есть возможность указать начальные данные для форм набора, передав параметр initial при создании экземпляра набора, возвращенного modelformset_factory(). Тем не менее, в случае набора модельных форм, начальными значениями заполняются только пустые, т.е. новые, формы.

Сохранение объектов набора форм¶

Аналогично ModelForm, вы можете сохранять данные в модели. Для этого надо использовать метод save() набора форм:

# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)

# Assuming all is valid, save the data.
>>> instances = formset.save()

Метод save() возвращает экземпляры объектов, которые были сохранены в базе данных. Те объекты, данные которых не изменились, не сохраняются в базе данных и не отображаются в возвращаемом значении (instances из предыдущего примера).

Когда форма содержит не все поля модели (например, потому что некоторые из них были явно исключены), то отсутствующие поля не будут сохранены через метод save(). Подробнее об этом ограничении модельных форм написано в Указываем какие поля использовать.

Передайте commit=False, чтобы получить экземпляры моделей, которые ещё не сохранены в базе данных:

# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
...     # do something with instance
...     instance.save()

Это позволяет вам добавлять данные к экземплярам моделей перед их сохранением в базе данных. Если ваш набор форм содержит ManyToManyField, вам также потребуется вызвать метод formset.save_m2m() для того, чтобы обеспечить сохранение связей «многие-ко-многим».

После вызова save(), в класс набора форм будут добавлены атрибуты, содержащие все изменения:

models.BaseModelFormSet.changed_objects¶
models.BaseModelFormSet.deleted_objects¶
models.BaseModelFormSet.new_objects¶

Ограничение количества редактируемых объектов¶

Как и в случае набора обычных форм, вы можете использовать аргументы max_num и extra функции modelformset_factory() для ограничения числа дополнительно отображаемых форм.

Аргумент max_num не препятствует отображению существующих объектов:

>>> Author.objects.order_by('name')
[<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']

Если значение max_num больше чем количество существующих объектов, то к будет добавлено extra пустых форм к набору. Так будет происходить до достижения максимального количества форм, ограниченного параметром max_num:

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-0-id" value="1" id="id_form-0-id" /></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100" /><input type="hidden" name="form-1-id" value="3" id="id_form-1-id" /></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100" /><input type="hidden" name="form-2-id" value="2" id="id_form-2-id" /></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></td></tr>

Присвоение max_num значения None (по умолчанию) устанавливает ограничение на количество отображаемых набором форм равное 1000. На практике это аналогично безлимитному количеству.

Использование набора модельных форм в представлении¶

Наборы модельных форм во многом похожи на наборы обычных форм. Для отображения набора форм для редактирования экземпляров модели Author:

from django.forms import modelformset_factory
from django.shortcuts import render_to_response
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    if request.method == 'POST':
        formset = AuthorFormSet(request.POST, request.FILES)
        if formset.is_valid():
            formset.save()
            # do something.
    else:
        formset = AuthorFormSet()
    return render_to_response("manage_authors.html", {
        "formset": formset,
    })

Как вы можете видеть, логика представления не сильно отличается отличается логики обычного набора. Отличием является вызов formset.save() для сохранения данных. (Это было описано ранее в Сохранение объектов набора форм.)

Переопределение clean() у ModelFormSet

Подобно ModelForms, по умолчанию метод clean() класса ModelFormSet будет проверять все данные на нарушение ограничений уникальности, определённых в вашей модели (unique, unique_together или unique_for_date|month|year). Желая сохранить данный функционал при переопределении метода clean(), следует вызывать метод clean() базового класса:

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super(MyModelFormSet, self).clean()
        # example custom validation across forms in the formset
        for form in self.forms:
            # your custom formset validation
            ...

На этом этапе уже будут созданы экземпляры модели для каждой формы. Поменяв form.cleaned_data, вы не поменяете сохраняемые значения. Для этого в ModelFormSet.clean() необходимо изменить form.instance:

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super(MyModelFormSet, self).clean()

        for form in self.forms:
            name = form.cleaned_data['name'].upper()
            form.cleaned_data['name'] = name
            # update the instance value.
            form.instance.name = name

Использование собственной выборки¶

Как было сказано ранее, в можете переопределить стандартную выборку, которая используется набором модельных форм:

from django.forms import modelformset_factory
from django.shortcuts import render_to_response
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    if request.method == "POST":
        formset = AuthorFormSet(request.POST, request.FILES,
                                queryset=Author.objects.filter(name__startswith='O'))
        if formset.is_valid():
            formset.save()
            # Do something.
    else:
        formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
    return render_to_response("manage_authors.html", {
        "formset": formset,
    })

Следует отметить, что мы передаём аргумент queryset в обе ветки POST и GET в этом примере.

Использование набора форм в шаблоне¶

Существует три способа отображения набора форм в шаблоне Django.

Во-первых, вы можете позволить набору форм самому сделать всю работу:

<form method="post" action="">
    {{ formset }}
</form>

Во-вторых, в можете вручную вывести набор форм, но позволить его формам отображаться самостоятельно:

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form }}
    {% endfor %}
</form>

При самостоятельном отображении форм, не забудьте отобразить техническую форму, как было показано выше. Обратитесь к документации на технические формы.

В-третьих, вы можете вывести все поля:

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset %}
        {% for field in form %}
            {{ field.label_tag }} {{ field }}
        {% endfor %}
    {% endfor %}
</form>

Если вы предпочтёте третий способ и не будете использовать {% for %} для итерации по полям, то вам понадобится вывести поле для первичного ключа. Рассмотрим случай, когда требуется вывести поля name и age модели:

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form.id }}
        <ul>
            <li>{{ form.name }}</li>
            <li>{{ form.age }}</li>
        </ul>
    {% endfor %}
</form>

Обратите внимание на то, как мы явно выводим {{ form.id }}. Это гарантирует, что набор модельных форм, в случае POST, будет работать правильно. (Этот пример предполагает, что первичный ключ имеет имя id. Если вы изменили имя первичного ключа, то учтите это в данном примере.)

Встраиваемые наборы форм¶

class models.BaseInlineFormSet¶

Встраиваемые наборы форм являются небольшим абстрактным слоем над набором модельных форм. Они упрощают работу со связанными через внешний ключ объектами. Предположим, у вас есть следующие две модели:

from django.db import models

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

class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)

Если вам требуется создать набор форм для редактирования книг, принадлежащих определенному автору, вы можете сделать следующее:

>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=('title',))
>>> author = Author.objects.get(name='Mike Royko')
>>> formset = BookFormSet(instance=author)

Переопределение методов в InlineFormSet

Переопределяя методы InlineFormSet, лучше наследоваться от BaseInlineFormSet, чем от BaseModelFormSet.

Например, если вы хотите переопределить clean():

from django.forms import BaseInlineFormSet

class CustomInlineFormSet(BaseInlineFormSet):
    def clean(self):
        super(CustomInlineFormSet, self).clean()
        # example custom validation across forms in the formset
        for form in self.forms:
            # your custom formset validation
            ...

Смотрите также Переопределение clean() у ModelFormSet.

Потом при создании набора форм укажите аргумент formset:

>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=('title',),
...     formset=CustomInlineFormSet)
>>> author = Author.objects.get(name='Mike Royko')
>>> formset = BookFormSet(instance=author)

Более одного внешнего ключа к одной модели¶

Если ваша модель имеет больше одного внешнего ключа на одну и ту же модель, вам следует разрешить эту путаницу, указав fk_name. Например, рассмотрим следующую модель:

class Friendship(models.Model):
    from_friend = models.ForeignKey(
        Friend,
        on_delete=models.CASCADE,
        related_name='from_friends',
    )
    to_friend = models.ForeignKey(
        Friend,
        on_delete=models.CASCADE,
        related_name='friends',
    )
    length_in_months = models.IntegerField()

Чтобы разрешить эту неопределенность, вы можете использовать fk_name в inlineformset_factory():

>>> FriendshipFormSet = inlineformset_factory(Friend, Friendship, fk_name='from_friend',
...     fields=('to_friend', 'length_in_months'))

Использование вторичного набора форм в представлении¶

Вам может понадобиться создать представление, которое позволит пользователю редактировать связанные объекты модели. Вот как это можно сделать:

def manage_books(request, author_id):
    author = Author.objects.get(pk=author_id)
    BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
    if request.method == "POST":
        formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
        if formset.is_valid():
            formset.save()
            # Do something. Should generally end with a redirect. For example:
            return HttpResponseRedirect(author.get_absolute_url())
    else:
        formset = BookInlineFormSet(instance=author)
    return render_to_response("manage_books.html", {
        "formset": formset,
    })

Следует отметить, что мы передаём instance в обоих (POST и GET) случаях.

In this post we’ll learn to create user-defined functions, displaying validation errors in the template for Django Form Validations.

Table Of Contents

  • Introduction
  • Creating Form
  • Rendering Form
  • Saving Form
  • Form Validation User-Defined Functions
  • Conclusion

Introduction

The forms are a Django Module which deals with all form-related functions from binding POST data to form, Validating form and rendering HTML field in the template.
We’ll be using below models.py file as an example to demonstrate form validations.

from django.db import models
from django.contrib.auth.models import User
from datetime import datetime

class AuthUserProfile(models.Model):
    user_profile_id = models.AutoField(primary_key=True)
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='auth_user_profile')
    dob =  models.DateField(blank=True, null=True)
    is_deleted = models.PositiveSmallIntegerField(default=0)
    created_at = models.DateTimeField(auto_now=datetime.now(), null=True)
    updated_at = models.DateTimeField(auto_now=datetime.now(), null=True)

    class Meta():
        db_table = 'auth_user_profile'
        verbose_name = 'User Profile'
        verbose_name_plural = 'User Profiles'

    def __str__(self):
        return self.user

Create a form which has these fields (first_name, last_name, username, password, email) from User models and field (dob) in AuthUserProfile Model and also will add custom field and non-field level validation.

Creating Form

In forms.py file import forms from Django. Inherit forms.Form to UserForm and add attributes to the field.

from django import forms
from datetime import datetime
from django.contrib.auth.models import User

class UserForm(forms.Form):
    first_name = forms.CharField(label="First Name*",widget=forms.TextInput(attrs={'required':True,'class':"form-control"}))
    last_name = forms.CharField(label="Last Name*",widget=forms.TextInput(attrs={'required':True,'class':"form-control"}))
    username = forms.CharField(label="User Name*",widget=forms.TextInput(attrs={'required':True,'class':"form-control"}))
    email = forms.CharField(label="Email",widget=forms.TextInput(attrs={'type':'email','required':False,'class':"form-control"}))
    date_of_birth = forms.CharField(label="Date of Birth",widget=forms.TextInput(attrs={'type':'date','required':True,'class':"form-control"}))
    password = forms.CharField(label="Password*",widget=forms.TextInput(attrs={'required':True,'class':"form-control", 'type' : "password"}))
    confirm_password = forms.CharField(label="Confirm Password*",widget=forms.TextInput(attrs={'required':True,'class':"form-control", 'type' : "password"}))
    
    def clean(self):
        # user age must be above 18 to register
        if self.cleaned_data.get('date_of_birth'):
            dob = datetime.strptime(self.cleaned_data.get('date_of_birth'),"%Y-%m-%d")
            now = datetime.now()
            diff = now.year-dob.year

            if diff < 18: msg="User must be atleast 18 years old" self.add_error(None, msg) #check if user name is unique username_count = User.objects.filter(username=self.cleaned_data.get('username')).count() if username_count>0:
            msg="Username '{}' has already been used.".format(self.cleaned_data.get('username'))
            self.add_error(None, msg)
        
    def clean_confirm_password(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if confirm_password!=password:
            msg = "Password and Confirm Passwords must match."
            self.add_error('confirm_password', msg)

You may notice clean() and clean_confirm_password() methods in UserForm the form they are validation methods.
The clean() the method is form level validation this can also be used to perform field-level validation.

And the clean_confirm_password() is a field-level validation for confirm_password the field it checks if confirm_password!=password then adds error to a then particular field.

Rendering Form

Rendering of forms is an easy part we must pass the form object as an argument to render function.

In views.py create a function user_profile_create which will display rendered form.

from django.contrib.auth.models import User
from users.models import AuthUserProfile
from forms.forms import UserForm
from django.contrib.auth.hashers import make_password
from django.contrib import messages

def user_profile_create(request):
    form = UserForm()
    template="forms/user_profile_create_form.html"
    return render(request,template,{"form":form})

form = UserForm() creates form object of UserForm and is passed as an argument to the render() function.

In urls.py file add routes to view.

urlpatterns = [
    path('user/profile/create', views.user_profile_create, name='user-profile-create'),
]

Create an HTML file in your apps template folder naming user_profile_create_form.html.

<!DOCTYPE html>
<html lang="en">            
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Django Form Validation</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
        <style>
            .error{
                color:red;
            }
        </style>
    </head>            
    <body>
        <div class="container">
        
            {% if messages %}
                {% for message in messages %}
                    {% if message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %}
                    <div class="alert alert-success"
                    role="alert">
                        <div id="primary-notification-div">
                            {{ message }}
                        </div>
                    </div>
                    {% endif %}
                {% endfor %}
            {% endif %}
                
            <h1>User Profile</h1>
            <form action="{% url 'forms:user-profile-save' %}" method="post">
                {% csrf_token %}
    
                {% if form.errors %}
                    {% for error in form.non_field_errors %}
                        <div class="alert alert-danger">
                            <strong>{{ error|escape }}</strong>
                        </div>
                    {% endfor %}
    
                {% endif %}
    
    
                <div class="row">
                    <div class="col-md-3">
                        {{form.first_name.label}}
                        {{form.first_name}}
    
                        {% if form.errors.first_name %}
                            <label for="" class="error">{{form.errors.first_name|striptags}}</label>
                        {% endif %}
                    </div>
    
                    <div class="col-md-3">
                        {{form.last_name.label}}
                        {{form.last_name}}
                        {% if form.errors.last_name %}
                            <label for="" class="error">{{form.errors.last_name|striptags}}</label>
                        {% endif %}
                    </div>
    
                    <div class="col-md-3">
                        {{form.username.label}}
                        {{form.username}}
                        {% if form.errors.username %}
                            <label for="" class="error">{{form.errors.username|striptags}}</label>
                        {% endif %}
                    </div>
    
                    <div class="col-md-3">
                        {{form.email.label}}
                        {{form.email}}
                        {% if form.errors.email %}
                            <label for="" class="error">{{form.errors.email|striptags}}</label>
                        {% endif %}
                    </div>
    
                    <div class="col-md-3">
                        {{form.date_of_birth.label}}
                        {{form.date_of_birth}}
                        {% if form.errors.date_of_birth %}
                            <label for="" class="error">{{form.errors.date_of_birth|striptags}}</label>
                        {% endif %}
                    </div>
                </div>
    
                <div class="row" style="margin-top: 25px;">
                    <div class="col-md-3">
                        {{form.password.label}}
                        {{form.password}}
                        {% if form.errors.password %}
                            <label for="" class="error">{{form.errors.password|striptags}}</label>
                        {% endif %}
                    </div>
                </div>
    
                <div class="row" style="margin-top: 25px;">
    
                    <div class="col-md-3">
                        {{form.confirm_password.label}}
                        {{form.confirm_password}}
                        {% if form.errors.confirm_password %}
                        <label for="" class="error">{{form.errors.confirm_password|striptags}}</label>
                        {% endif %}
                    </div>
    
                    <div class="col-md-12" style="margin-top: 25px;">
                        <input type="submit" class="btn btn-sm btn-primary" value="submit">
                    </div>
                </div>
            </form>
        </div>
    </body>                
</html>

This is how our form will look when we go to route user/profile/create.

User Profile Form

  • The message displays success message once messages.add_message(request, messages.SUCCESS, ".....") is called it is just like the flash message.
  • The form.errors is called on validation has failed.
  • The form.non_field_errors display errors on top of form this errors are not associated to a particular field.
  • The form.errors. displays the error of the particular field.

This is how errors are displayed in the form.

User Profile Form Displaying Validation Errors

Saving Form

In urls.py file add routes to save the form.

urlpatterns = [
    path('user/profile/save', views.user_profile_create, name='user-profile-save'),
]

In views.py file add function user_profile_save() to save form data.

def user_profile_save(request):

    form = UserForm(request.POST)

    if form.is_valid():

        query = {
            "first_name" : form.cleaned_data.get('first_name'),
            "last_name" : form.cleaned_data.get('last_name'),
            "username" : form.cleaned_data.get('username'),
            "password" : make_password(form.cleaned_data.get('password')),
            "email" : form.cleaned_data.get('email'),
            "is_superuser" : 0,
            "is_staff" : 1,
            "is_active" : 1,
        }

        user = User.objects.create(**query)
        
        query={
            "user_id" : user.id,
            "dob" : form.cleaned_data.get('dob'),
        }
        
        AuthUserProfile.objects.create(**query)
        
        messages.add_message(request, messages.SUCCESS, "User Profile created successfully.")
        
        return HttpResponseRedirect(reverse('forms:user-profile-create'))
    
    template="forms/user_profile_create_form.html"
    
    return render(request,template,{"form":form})

The request.POST is passed to UserForm(request.POST) this binds the submitted data to Form Class.
The form.is_valid() returns a Boolean value if True then the form is clean and if False then there may be validation error.
To view validation errors after .is_valid() method we can print form.errors to view validation errors.

Calling form.cleaned_data.get('') gives use of the sanitized value to that field. Inside .is_valid() we have called model methods to save form data.

Showing success message on successfully validating form and saving its contents into the database

Form Validation User-Defined functions

To defined a custom validation function in Form Class name function with prefix clean followed by underscore and field name which must be validated.

Example

def clean_first_name(self):
    pass #this validates field first_name

def clean_username(self):
    pass #this validates field username

If the value of the field is not as expected that you can raise validation error or add error by mentioning field name self.add_error('field_name', "Error Message").
If you want to raise non-field error than set the first argument of add_error() method None followed by the message you want to be displayed.

self.add_error(None, msg) #this creates a non-field error

Conclusion

We have come to the end of our post on Django Form Validation.
If you have any doubts or suggestions please mention in the comments section and we’ll reach you soon and we would also love to hear requests and your recommendations for new tutorials/posts.

Related Posts

  • Python Django Forms | Creating, Rendering, Validating and Saving Forms
  • Django – Multiple Files Validation and Uploads

Summary

Review Date

2020-06-15

Reviewed Item

Django Forms | Custom Form Validations

Author Rating

51star1star1star1star1star

Software Name

Django Web Framework

Software Name

Windows Os, Mac Os, Ubuntu Os

Software Category

Web Development

  1. Home
  2. Django 1.10 Tutorial
  3. Django Form Basics

Last updated on July 27, 2020


The syntax of creating forms in Django is almost similar to that of creating models, the only differences are:

  1. Django form inherits from forms.Form instead of models.Model.
  2. Each form fields inherit forms.FieldName instead of models.FieldName.

Let’s start off by creating an AuthorForm class.

Create a new file called forms.py, if not already exists in the blog app i.e TGDB/django_project/blog (same place where models.py file is located) and add the following code to it.

TGDB/django_project/blog/forms.py

from django import forms


class AuthorForm(forms.Form):
    name = forms.CharField(max_length=50)
    email = forms.EmailField()
    active = forms.BooleanField(required=False) # required=False makes the field optional
    created_on = forms.DateTimeField()
    last_logged_in = forms.DateTimeField()

Form fields are similar to model fields in the following ways:

  1. Both correspond to a Python type.
  2. Both validate data in the form.
  3. Both fields are required by default.
  4. Both types of fields know how to represent them in the templates as HTML. Every form fields are displayed in the browser as an HTML widget. Each form field is assigned a reasonable Widget class, but you can also override this setting.

Here is an important difference between the model fields and form fields.

Model fields know how to represent themselves in the database whereas form fields do not.

Form States #

A form in Django can be either in a Bound state or Unbound state. What is Bound and Unbound state?

Unbound State: In Unbound state the form has no data associated with it. For example, an empty form displayed for the first time is in unbound state.

Bound State: The act of giving data to the form is called binding a form. A form is in the bound state if it has user submitted data. It doesn’t matter whether the data is valid or not.

If the form is in bound state but contains invalid data then the form is bound and invalid. On the other hand, if the form is bound and data is valid then the form is bound and valid.

is_bound attribute and is_valid() method #

We can use is_bound attribute to know whether the form is inbound state or not. If the form is in the bound state then the is_bound returns True, otherwise False.

Similarly, we can use the is_valid() method to check whether the entered data is valid or not. If the data is valid then is_valid() returns True, otherwise False. It is important to note that if is_valid() returns True then is_bound attribute is bound to return True.

Accessing Cleaned Data #

When a user enter submits the data via form Django first validate and clean the data. Does this mean that the data entered by the user were not clean? Yes, for the two good reasons:

  1. When it comes to submitting data using forms you must never trust the user. It takes a single malicious user to wreak havoc on your site. That’s why Django validates the form data before you can use them.

  2. Any data the user submits through a form will be passed to the server as strings. It doesn’t matter which type of form field was used to create the form. Eventually, the browser would will everything as strings. When Django cleans the data it automatically converts data to the appropriate type. For example IntegerField data would be converted to an integer, CharField data would be converted to a string, BooleanField data would be converted to a bool i.e True or False and so on. In Django, this cleaned and validated data is commonly known as cleaned data. We can access cleaned data via cleaned_data dictionary:

    cleaned_date['field_name']
    

    You must never access the data directly using self.field_name as it may not be safe.

Django Forms in Django Shell #

In this section we will learn how to bind data and validate a form using Django Shell. Start Django Shell by typing python manage.py shell command in the command prompt or terminal. Next, import the AuthorForm class and instantiate a AuthorForm object:

(env) C:UsersQTGDBdjango_project>python manage.py shell
Python 3.4.4 (v3.4.4:737efcadf5a6, Dec 20 2015, 20:20:57) [MSC v.1600 64 bit (AM
D64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from blog.forms import AuthorForm
>>> f = AuthorForm()
>>>

At this point, our form object i.e f is unbound because there is no data in the form. We can verify this fact by using is_bound attribute.

>>>
>>> f.is_bound
False
>>>

As expected is_bound attribute returns False. We can also check whether the form is valid or not by calling is_valid() method.

>>>
>>> f.is_valid()
False
>>>

It is important to understand that is_bound and is_valid() are related. If is_bound is False then is_valid() will always return False no matter what. Similarly, if is_valid() returns True then is_bound must be True.

Calling is_valid() method results in validation and cleaning of the form data. In the process, Django creates an attribute called cleaned_data, a dictionary which contains cleaned data only from the fields which have passed the validation tests. Note that cleaned_data attribute will only be available to you after you have invoked the is_valid() method. Trying to access cleaned_data before invoking is_valid() will throw an AttributeError exception.

Obviously, now the question arises «How do we bind data to the form»?

To bind data to a form simply pass a dictionary as an argument to the form class(in this case AuthorForm) while creating a new form object.

>>>
>>> data = {
... 'name': 'jon',
... 'created_on': 'today',
... 'active': True,
... }
>>>
>>>
>>> f = AuthorForm(data)
>>>

Our form object f has data now, so we can say that it is bound. Let’s verify that by using is_bound attribute.

>>>
>>> f.is_bound
True
>>>

As expected, our form is bound now. We could also get a bound form by passing an empty dictionary ({}).

>>>
>>> data = {}
>>> f2 = AuthorForm(data)
>>> f2.is_bound
True
>>>

Okay let’s now try accessing cleaned_data attribute before invoking is_valid() method.

>>>
>>> f.cleaned_data
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'CategoryForm' object has no attribute 'cleaned_data'
>>>
>>>

As expected, we got an AttributeError exception. Now we will validate the form by calling is_valid() method.

>>>
>>> f.is_valid()
False
>>>
>>> f.cleaned_data
{'active': True, 'name': 'jon'}
>>>

Our validation fails but we now have cleaned_data dictionary available. Notice that there is no created_on key in the cleaned_data dictionary because Django failed to validate this field. In addition to that, the form validation also failed to validate email and last_logged_in field of the AuthorForm because we haven’t provided any data to it.

Always remember that the cleaned_data attribute will only contain validated and cleaned data, nothing else.

To access errors, the form object provides an errors attribute which is an object of type ErrorDict, but for the most part you can use it as a dictionary. Here is how it works:

>>> 
>>> f.errors
{'created_on': ['Enter a valid date/time.'], 'email': ['This field is required.'
], 'last_logged_in': ['This field is required.']}
>>>
>>>

Notice that there are three fields which failed the validation process. By default, f.errors returns error messages for all the fields which failed to pass validation. Here is how to get the error message for a particular field.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
>>>
>>> f.errors['email']
['This field is required.']
>>>
>>>
>>> f.errors['created_on']
['Enter a valid date/time.']
>>>
>>>
>>> f.errors['last_logged_in']
['This field is required.']
>>>

The errors object provides two methods to ouput errors in different formats:

Method Explanation
as_data() returns a dictionary with ValidationError object instead of a string.
as_json() returns errors as JSON
>>>
>>>
>>> f.errors
{'created_on': ['Enter a valid date/time.'], 'email': ['This field is required.'
], 'last_logged_in': ['This field is required.']}
>>>
>>>
>>> f.errors.as_data()
{'created_on': [ValidationError(['Enter a valid date/time.'])], 'email': [Valida
tionError(['This field is required.'])], 'last_logged_in': [ValidationError(['Th
is field is required.'])]}
>>>
>>>
>>>
>>> f.errors.as_json()
'{"created_on": [{"code": "invalid", "message": "Enter a valid date/time."}], "e
mail": [{"code": "required", "message": "This field is required."}], "last_logge
d_in": [{"code": "required", "message": "This field is required."}]}'
>>>
>>>

Note that unlike the cleaned_data attribute the errors attribute is available to you all the time without first calling the is_valid() method. But there is a caveat, trying to access errors attribute before calling is_valid() method results in validation and cleaning of form data first, consequently creating cleaned_data attribute in the process. In other words, trying to access errors attribute first will result in a call to is_valid() method implicitly. However, in your code should always call is_valid() method explicitly.

To demonstrate the whole process one more time, let’s create another form object, but this time we will bind the form with data that will pass the validation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
>>>
>>> import datetime
>>>
>>>
>>> data = {
...  'name': 'tim',
...  'email': 'tim@mail.com',
...  'active': True,
...  'created_on': datetime.datetime.now(),
...  'last_logged_in': datetime.datetime.now()
... }
>>>
>>>
>>> f = AuthorForm(data)
>>>
>>>
>>> f.is_bound
True
>>>
>>>
>>> f.is_valid()
True
>>>
>>>
>>> f.cleaned_data
{'name': 'tim', 'created_on': datetime.datetime(2017, 4, 29, 14, 11, 59, 433661,
 tzinfo=<UTC>), 'last_logged_in': datetime.datetime(2017, 4, 29, 14, 11, 59, 433
661, tzinfo=<UTC>), 'email': 'tim@mail.com', 'active': True}
>>>
>>>
>>> f.errors
{}
>>>

Digging deep into Form Validation #

When is_valid() method is called Django does the following things behind the scenes:

  1. The first step is to call Field’s clean() method. Every form field has a clean() method, which does the following two things:

    1. Convert the field data (recall that the data is sent by the browser as a string to the server) to the appropriate Python type. For example, if the field is defined as IntegerField then the clean() method will convert the data to Python int, if it fails to do so, it raises a ValidationError exception.

    2. Validate the converted data received from the step 1. If validation succeeds, cleaned and validated data is inserted into the cleaned_data attribute. If it fails a ValidationError is raised. We usually don’t override field’s clean() method.

  2. In step 2, Field’s clean_<fieldname>() method is called to provide some additional validation to the field. Notice that <fieldname> is a placeholder, it is not an actual Python method. By default, Django doesn’t define these methods. These methods are usually written by developers to provide some additional validation to the field. They do not accept any arguments, but they must return the new value of the field. This method is called only when ValidationError is not raised by the Field’s clean() method. That means, if this method is called, it is guaranteed that the field’s data is cleaned and validated. Consequently, you must always access field’s data inside this method using cleaned_data['fieldname']. The value returned by this method replaces the existing value of the field in the cleaned_data dictionary.

Django repeats step 1 and 2 for all form fields.

Finally, Form’s class clean() method is called. If you want to perform validation which requires access to multiple fields override this method in your form class.

Note: This is an oversimplified view of Django Validation Process. The reality is much more involved but that’s enough, to begin with.

Let’s take an example to understand how Django performs cleaning and validation when is_valid() method is called on AuthorForm class.

1st step — The name field’s clean() method is called to clean and validate data. On success, it puts the clean and validated data into the cleaned_data dictionary. If cleaning or validation failed ValidationError exception is raised and call to clean_name() is skipped. Nonetheless, the clean() method of the following field will be called.

2nd step — The name field’s clean_name() method is called (assuming this method is defined in the form class and ValidationError is not raised in step 1) to perform some additional validations.

3rd step — The email field’s clean() method is called to clean and validate data. On success, it puts the clean and validated data into the cleaned_data dictionary. If cleaning or validation failed ValidationError exception is raised and call to clean_email() method is skipped. Nonetheless, the clean() method of the following field will be called.

4th step — The email field’s clean_email() is a method called (assuming this method is defined in the form class and ValidationError is not raised in step 3) to perform some additional validation. At this point, it is guaranteed that email field is cleaned and validated, so the following code is perfectly valid inside clean_email() method.

email= self.cleaned_data['email']; ## that's okay

However, there is no guarantee that the data from other fields, for example, the name field is available inside clean_email() method. So, you should not attempt to access name field inside clean_email() method like this:

name = self.cleaned_data['name']; # Not good, you may get an error for doing this

If you want to provide additional validation to the name field, do it in the clean_name() method because it is guaranteed to be available there.

This process repeats for every form field. At last, Form’s clean() method or its override is called. An important thing to remember about the Form’s clean() method is that none of the fields is guaranteed to exists here. To access field data you must always use dictionary’s object get() method like this:

self.cleaned_data.get('name')

If the name key is not available in the cleaned_data dictionary then get() method will return None.

Implementing Custom Validators #

In this section, we are going to implement some custom validators in our AuthorForm class. Here are things we want to achieve.

  1. Prevent users to create Author named "admin" and "author".
  2. Save the email in lowercase only. At this point, nothing is stopping us to save the email in uppercase.

Open forms.py and modify the code as follows:

TGDB/django_project/blog/forms.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from django import forms
from django.core.exceptions import ValidationError


class AuthorForm(forms.Form):
    #...
    last_logged_in = forms.DateTimeField()

    def clean_name(self):
        name = self.cleaned_data['name']
        name_l = name.lower()
        if name_l == "admin" or name_l == "author":
            raise ValidationError("Author name can't be 'admin/author'")
        return name_l

    def clean_email(self):
        return self.cleaned_data['email'].lower()

Restart the Django shell for the changes to take effect and then enter the following code. Here we are trying to validate a form where author name is "author".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
>>>
>>> from blog.forms import AuthorForm
>>>
>>> import datetime
>>>
>>>
>>> data = {
...  'name': 'author',
...  'email': 'TIM@MAIL.COM',
...  'active': True,
...  'created_on': datetime.datetime.now(),
...  'last_logged_in': datetime.datetime.now()
... }
>>>
>>>
>>> f = AuthorForm(data)
>>>
>>>
>>> f.is_bound
True
>>>
>>>
>>> f.is_valid()
False
>>>
>>>
>>> f.cleaned_data
{'last_logged_in': datetime.datetime(2017, 9, 12, 22, 17, 26, 441359, tzinfo=<UT
C>), 'created_on': datetime.datetime(2017, 9, 12, 22, 17, 26, 441359, tzinfo=<UT
C>), 'active': True, 'email': 'tim@mail.com'}
>>>
>>>
>>> f.errors
{'name': ["Author name can't be 'admin/author'"]}
>>>
>>>

As expected, form validation failed because "author" is not a valid author name. In addition to that cleaned_data contains email in lowercase, thanks to the clean_email() method.

Notice that form’s errors attribute returns the same error message we specified in the clean_name() method. Let’s try validating form data once more, this time we will provide valid data in every field.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
>>>
>>>
>>> data = {
...  'name': 'Mike',
...  'email': 'mike@mail.com',
...  'active': True,
...  'created_on': datetime.datetime.now(),
...  'last_logged_in': datetime.datetime.now()
...  }
>>>
>>>
>>> f = AuthorForm(data)
>>>
>>>
>>> f.is_bound
True
>>>
>>>
>>> f.is_valid()
True
>>>
>>>
>>> f.cleaned_data
{'last_logged_in': datetime.datetime(2017, 9, 12, 22, 20, 25, 935625, tzinfo=<UT
C>), 'name': 'Mike', 'created_on': datetime.datetime(2017, 9, 12, 22, 20, 25, 93
5625, tzinfo=<UTC>), 'active': True, 'email': 'mike@mail.com'}
>>>
>>>
>>> f.errors
{}
>>>
>>>

This time validation succeeds because data in every field is correct.

Saving the form data to the database #

So, how do we save data received via the form to the database? Earlier in this chapter, we have already discussed that unlike models fields, form fields don’t know how to represent themselves in the database. Further, unlike models.Model class, the forms.Form class doesn’t provide save() method to save the form data to the database.

The solution is to implement our own save() method. There is no restriction on method name you can call it anything you like. Open forms.py file and add save() method towards the end of AuthorForm class:

TGDB/django_project/blog/forms.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from django import forms
from django.core.exceptions import ValidationError
from .models import Author, Tag, Category, Post


class AuthorForm(forms.Form):
    #...

    def clean_email(self):
        return self.cleaned_data['email'].lower()

    def save(self):
        new_author = Author.objects.create(
            name = self.cleaned_data['name'],
            email = self.cleaned_data['email'],
            active = self.cleaned_data['active'],
            created_on = self.cleaned_data['created_on'],
            last_logged_in = self.cleaned_data['last_logged_in'],
        )
        return new_author

Nothing new here, in line 3, we are importing models from the blog app. In lines 12-20, we are defining the save() method which uses form data to create a new Author object. Notice that while creating new Author object we are accessing form data via cleaned_data dictionary.

Restart the Django shell again and Let’s try creating a new Author via AuthorForm.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
>>>
>>> from blog.forms import AuthorForm
>>>
>>> import datetime
>>>
>>> data = {
...  'name': 'jetson',
...  'email': 'jetson@mail.com',
...  'active': True,
...  'created_on': datetime.datetime.now(),
...  'last_logged_in': datetime.datetime.now()
...  }
>>>
>>> f = AuthorForm(data)
>>>
>>> f.is_bound
True
>>>
>>> f.is_valid()
True
>>>
>>> f.save()
<Author: jetson : jetson@mail.com>
>>>
>>>
>>> from blog.models import Author
>>>
>>> a = Author.objects.get(name='jetson')
>>>
>>> a.pk
11
>>>
>>> a
<Author: jetson : jetson@mail.com>
>>>
>>>

Sure enough, our newly created category object is now saved in the database.

Our form is fully functional. At this point, we could move on to create form classes for the rest of the objects like Post, Tag etc; but there is a big problem.

The problem with this approach is that fields in the AuthorForm class map closely to that of Author models. As a result, redefining them in the AuthorForm is redundant. If we add or modify any field in the Author model then we would have to update our AuthorForm class accordingly.

Further, as you might have noticed there are few differences in the way we have defined model fields and form fields. For example:

The email field in Author model is defined like this:

email = models.EmailField(unique=True)

On the other hand, the same field in AuthorForm is defined like this:

email = forms.EmailField()

Notice that the email field in AuthorForm doesn’t have unique=True attribute because unique=True attribute is only defined for the model fields, not for the form fields. One way to solve this problem is to create a custom validator by implementing clean_email() method like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from django import forms
from .models import Author, Tag, Category, Post

class AuthorForm(forms.Form):
    #...

    def clean_email(self):
        email = self.cleaned_data['email'].lower()
        r = Author.objects.filter(email=email)
        if r.count:
            raise ValidationError("{0} already exists".format(email))

        return email.lower()

Similarly, Form fields don’t provide default, auto_add_now and auto_now parameters. If you want to implement functionalities provided by these attributes then you would need the write custom validation method for each of these fields.

As you can see, for each functionality provided by the Django models, we would have to add various cleaning methods as well as custom validators. Certainly, this involves a lot of work. We can avoid all these issues by using ModelForm.

Removing redundancy using ModelForm #

The ModelForm class allows us to connect a Form class to the Model class.

To use ModelForm do the following:

  1. Change inheritance of the form class from forms.Form to forms.ModelForm.
  2. Inform the form class in forms.py which model to use by using the model attribute of the Meta class.

After these two steps, we can remove all the form fields we have defined in the AuthorForm class. Furthermore, we can remove the save() method too, because ModelForm provides this method. It is important to mention here that the save() method we implemented earlier in this chapter can only create objects, it can’t update them. On the other hand, the save() method coming from ModelForm can do both.

Here is the modified code.

TGDB/django_project/blog/forms.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from django import forms
from django.core.exceptions import ValidationError
from .models import Author, Tag, Category, Post


class AuthorForm(forms.ModelForm):
    class Meta:
        model = Author        

    def clean_name(self):
        name = self.cleaned_data['name']
        name_l = name.lower()
        if name_l == "admin" or name_l == "author":
            raise ValidationError("Author name can't be 'admin/author'")
        return name_l

    def clean_email(self):
        return self.cleaned_data['email'].lower()

There is still one thing missing in our AuthorForm class. We have to tell AuthorForm class which fields we want to show in the form. To do that we use fields attribute of the Meta class. It accepts a list or tuple of field names you want to show in the form. If you want to show all the fields just use "__all__" (that’s double underscore).

fields = '__all__'      # display all the fields in the form
fields = ['title', 'content']  # display only title and content field in the form

Similarly, there exists a complementary attribute called exclude which accepts a list of field names which you don’t want to show in the form.

exclude = ['slug', 'pub_date'] # show all the fields except slug and pub_date

Let’s update our code to use the fields attribute.

TGDB/django_project/blog/forms.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from django import forms
from django.core.exceptions import ValidationError
from .models import Author, Tag, Category, Post


class AuthorForm(forms.Form):
    class Meta:
        model = Author
        fields = '__all__'

    def clean_name(self):
        name = self.cleaned_data['name']
        name_l = name.lower()
        if name_l == "admin" or name_l == "author":
            raise ValidationError("Author name can't be 'admin/author'")
        return name_l

    def clean_email(self):
        return self.cleaned_data['email'].lower()

Notice that we haven’t changed clean_name() and clean_email() method because they work with
ModelForm too.

Additional Validation in ModelForm #

In addition to Form validation, ModelForm also performs its own validation. What is meant by that? It simply means that ModelForm performs validation at the database level.

ModelForm Validation takes place in 3 steps.

Model.clean_fields() — This method validates all the fields in the model

Model.clean() — Works just like Form’s clean() method. If you want to perform some validation at the database level which requires access to multiple fields override this method in the Model class. By default, this method does nothing.

Model.validate_unique() — This method checks uniqueness constraints imposed on your model (using unique parameter).

Which validation occurs first Form validation or Model validation?

Form Validation occurs first.

How do I trigger this Model validation?

Just call is_valid() method as usual and Django will run Form validation followed by ModelForm validation.

Creating Form classes for the remaning objects #

Before we move ahead, let’s create PostForm, CategoryForm and TagForm class in the forms.py file.

TGDB/django_project/blog/forms.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#...
from .models import Author, Tag, Category, Post
from django.template.defaultfilters import slugify

#...


class AuthorForm(forms.ModelForm):
    #...

class TagForm(forms.ModelForm):

    class Meta:
        model = Tag
        fields = '__all__'

    def clean_name(self):
        n = self.cleaned_data['name']
        if n.lower() == "tag" or n.lower() == "add" or n.lower() == "update":
            raise ValidationError("Tag name can't be '{}'".format(n))
        return n

    def clean_slug(self):
        return self.cleaned_data['slug'].lower()


class CategoryForm(forms.ModelForm):

    class Meta:
        model = Category
        fields = '__all__'

    def clean_name(self):
        n = self.cleaned_data['name']
        if n.lower() == "tag" or n.lower() == "add" or n.lower() == "update":
            raise ValidationError("Category name can't be '{}'".format(n))
        return n

    def clean_slug(self):
        return self.cleaned_data['slug'].lower()


class PostForm(forms.ModelForm):

    class Meta:
        model = Post
        fields = ('title', 'content', 'author', 'category', 'tags',)

    def clean_name(self):
        n = self.cleaned_data['title']
        if n.lower() == "post" or n.lower() == "add" or n.lower() == "update":
            raise ValidationError("Post name can't be '{}'".format(n))
        return n

    def clean(self):
        cleaned_data = super(PostForm, self).clean() # call the parent clean method
        title  = cleaned_data.get('title')
        # if title exists create slug from title
        if title:
            cleaned_data['slug'] = slugify(title)
        return cleaned_data

The TagForm and CategoryForm are very similar to AuthorForm class but PostForm is a little different. In PostForm, we are overriding Form’s clean() method for the first time. Recall that we commonly use Form’s clean() method when we want to perform some validation which requires access to two or more fields at the same time.

In line 56, we are calling Form’s parent clean() method which by itself does nothing, except returning cleaned_data dictionary.

Next, we are using dictionary object’s get() method to access the title field of the PostForm, recall that in the Form’s clean() method none of the fields is guaranteed to exist.

Then we test the value of the title field. If the title field is not empty, then we use slugify() method to create slug from the title field and assign the result to cleaned_data['slug']. At last, we return cleaned_data from the clean() method.

It is important to note that by the time Form’s clean() method is called, clean() methods of the individual field would have already been executed.

That’all for now. This chapter was quite long. Nonetheless, we have learned a lot about Django forms. In the next chapter, we will learn how to render forms in templates.

Note: To checkout this version of the repository type git checkout 20a.



Ezoic

Понравилась статья? Поделить с друзьями:
  • Django form error text
  • Django form error messages
  • Django form error message
  • Django form error list
  • Django form error css