Spec-Zone.ru › Django 1.9

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

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

Также можно указать 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']

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

Примечание

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

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

По умолчанию, при использовании 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']

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

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

Формовые наборы моделей очень похожи на формовые наборы. Допустим, мы хотим представить формовой набор для редактирования экземпляров модели 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() формового набора модели будет проверять, что ни один из элементов формового набора не нарушает уникальные ограничения вашей модели (либо 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
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(request, '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, 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_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.

Затем, при создании набора форм встраивания, передайте необязательный аргумент 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.

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

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.9/topics/forms/modelforms/

Spec-Zone.ru

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