Spec-Zone.ru › Django 2.0

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

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 Не представлено в форме
BigAutoField Не представлено в форме
BigIntegerField IntegerField с атрибутом min_value равным -9223372036854775808 и атрибутом max_value равным 9223372036854775807.
BooleanField BooleanField
CharField CharField с атрибутом max_length установленным в значение поля модели max_length и empty_value равным None, если null=True.
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):
        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() модели см. в разделе «Валидация объектов».

Соображения по сообщениям об ошибках модели

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

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

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

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

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

Примечание

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

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

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 для поля %%%CODE_BLOCK_223%%, вы можете сделать следующее:

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, так как не происходит неявного преобразования.

См. документацию по полям форм для получения дополнительной информации о полях и их аргументах.

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

По умолчанию поля в 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 путём наследования. Это полезно, если вам нужно объявить дополнительные поля или методы в базовом классе для использования в нескольких формах, полученных от моделей. Например, используя предыдущий класс %%%CODE_BLOCK_256%%:

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

Это создаёт форму, которая ведёт себя идентично ArticleForm, за исключением некоторых дополнительных проверок и очистки для поля %%%CODE_BLOCK_259%%.

Вы также можете наследовать внутренний класс Meta родительского класса, если хотите изменить списки Meta.fields или %%%CODE_BLOCK_262%%:

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

Это создаст набор форм, способный работать с данными, связанными с моделью 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>---------</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().__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')
<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 не препятствует созданию новых экземпляров модели, так как вы можете добавить дополнительные формы с помощью 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() объекта 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'))
    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">
    {{ formset }}
</form>

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

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

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

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

<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

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

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 составляет 'book_set' (<model name>_set ). Если у Book атрибут ForeignKey до Author имеет related_name, он используется вместо него.

Примечание

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().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/2.0/topics/forms/modelforms/

Spec-Zone.ru

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