Spec-Zone.ru › Django 1.8

Создание форм из моделей

ModelForm

class ModelForm [source]

Если вы создаёте приложение, работающее с базой данных, скорее всего, у вас будут формы, которые тесно связаны с моделями 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 Не представлено в форме
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 представлен как 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 будет выбрано по умолчанию).

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

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

Рассмотрим эту группу моделей:

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 валидация (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 или на уровне метаданных формы, всегда имеют приоритет над сообщениями об ошибках, определенными на уровне model field.

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

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

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 наблюдается, когда у вашей модели есть отношение «многие ко многим» с другой моделью. Если у вашей модели есть отношение «многие ко многим», и вы указываете 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 в список полей, которые нужно исключить из формы.

    Например:

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

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

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

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

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

Примечание

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

Django предотвратит любую попытку сохранения неполной модели, поэтому если модель не допускает пустых значений для отсутствующих полей и не предоставляет значение по умолчанию для отсутствующих полей, любая попытка save() модели 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()

Дополнительные сведения об использовании 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."),
            },
        }

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

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

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

class ArticleForm(ModelForm):
    slug = MySlugFormField()

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

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

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

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

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

Примечание

Когда вы явно инициализируете поле формы таким образом, важно понимать, как ModelForm и обычные 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, так как не происходит неявного преобразования.

Дополнительную информацию о полях и их аргументах см. в документации по полям форм.

Включение локализации полей

По умолчанию поля в 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. См. документацию по Выбор полей для использования.

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

>>> 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 ограничивает набор форм использованием только указанных полей. В качестве альтернативы, можно использовать подход «исключения», указав поля, которые необходимо исключить:

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

В более старых версиях, опуская как 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 в __init__:

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

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

Затем передайте свой класс 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, которые описаны ниже.

Указание виджетов для использования в форме с widgets

Используя параметр widgets, вы можете указать словарь значений для настройки класса виджета ModelForm для определенного поля. Это работает так же, как и словарь widgets в внутреннем классе Meta класса ModelForm:

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title'),
...     widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20})})

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

Передайте 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() набора форм модели будет проверять, что ни один из элементов в наборе форм не нарушает уникальные ограничения вашей модели (либо 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 уже были созданы отдельные экземпляры модели. Изменение значения в 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. Если вы явно определили свой собственный первичный ключ, который не называется 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)
    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_factory() использует modelformset_factory() и отмечает can_delete=True.

См. также

Вручную отображаемые can_delete и can_order.

Переопределение методов в 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.

Затем, при создании набора форм inline, передайте необязательный аргумент 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, related_name='from_friends')
    to_friend = models.ForeignKey(Friend, 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'))

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

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

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.

Указание виджетов для использования в форме inline

inlineformset_factory использует modelformset_factory и передаёт большинство своих аргументов в modelformset_factory. Это означает, что вы можете использовать параметр widgets аналогичным образом, как и передавать его в modelformset_factory. См. Указание виджетов для использования в форме с виджетами выше.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.8/topics/forms/modelforms/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API