Создание форм на основе моделей
ModelForm
-
class ModelForm[исходный код]
Если вы создаёте приложение, работающее с базой данных, скорее всего, вам понадобятся формы, тесно связанные с моделями Django. Например, у вас может быть модель BlogComment, и вы захотите создать форму, с помощью которой пользователи смогут отправлять комментарии. В этом случае определять типы полей в форме излишне, поскольку они уже заданы в модели.
Поэтому Django предоставляет вспомогательный класс, позволяющий создать класс Form на основе модели Django.
Например:
>>> from django.forms import ModelForm >>> from myapp.models import Article # Create the form class. >>> class ArticleForm(ModelForm): ... class Meta: ... model = Article ... fields = ["pub_date", "headline", "content", "reporter"] ... # Creating a form to add an article. >>> form = ArticleForm() # Creating a form to change an existing article. >>> article = Article.objects.get(pk=1) >>> form = ArticleForm(instance=article)
Типы полей
Сгенерированный класс Form будет содержать поле формы для каждого указанного поля модели и в том порядке, который задан в атрибуте fields.
Каждому полю модели соответствует поле формы по умолчанию. Например, CharField в модели представлено как CharField в форме. Поле модели ManyToManyField представлено как MultipleChoiceField. Ниже приведён полный список преобразований:
Поле модели | Поле формы |
|---|---|
Не представлено в форме | |
Не представлено в форме | |
| |
| |
| |
| |
| |
|
|
| |
Не представлено в форме | |
| |
Как и следовало ожидать, поля модели типов 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 состоит из двух основных этапов:
Как и при обычной проверке формы, проверка корректности модельной формы запускается неявно при вызове is_valid() или обращении к атрибуту errors, а также явно при вызове full_clean(), хотя на практике обычно не требуется использовать последний метод.
Проверка корректности Model запускается на этапе проверки формы сразу после вызова метода clean() формы. Сначала вызывается метод модели full_clean() с параметрами validate_unique=False и validate_constraints=False, затем по порядку вызываются методы модели validate_unique() и validate_constraints().
Предупреждение
В процессе очистки экземпляр модели, переданный конструктору 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 формы Meta:
from django.core.exceptions import NON_FIELD_ERRORS
from django.forms import ModelForm
class ArticleForm(ModelForm):
class Meta:
error_messages = {
NON_FIELD_ERRORS: {
"unique_together": "%(model_name)s's %(field_labels)s are not unique.",
}
}
Метод save()
У каждого экземпляра ModelForm также есть метод save(). Этот метод создаёт и сохраняет объект базы данных на основе данных, связанных с формой. Подкласс ModelForm может принимать существующий экземпляр модели в качестве именованного аргумента instance; если он передан, save() обновит этот экземпляр. Если аргумент не передан, save() создаст новый экземпляр указанной модели:
>>> from myapp.models import Article >>> from myapp.forms import ArticleForm # Create a form instance from POST data. >>> f = ArticleForm(request.POST) # Save a new Article object from the form's data. >>> new_article = f.save() # Create a form to edit an existing Article, but use # POST data to populate the form. >>> a = Article.objects.get(pk=1) >>> f = ArticleForm(request.POST, instance=a) >>> f.save()
Обратите внимание: если форма не была проверена, при вызове save() проверка будет выполнена посредством проверки form.errors. Если данные формы не пройдут проверку — то есть если form.errors имеет значение True, — будет вызвано исключение ValueError.
Если необязательное поле отсутствует в данных формы, в результирующем экземпляре модели для этого поля используется значение 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).
Однако доступны два сокращённых варианта для случаев, когда вы уверены, что эти проблемы безопасности вас не касаются:
-
Задайте для атрибута
fieldsспециальное значение'__all__', чтобы указать, что нужно использовать все поля модели. Например:from django.forms import ModelForm class AuthorForm(ModelForm): class Meta: model = Author fields = "__all__" -
Задайте для атрибута
excludeвнутреннего классаMetaформыModelFormсписок полей, которые нужно исключить из формы.Например:
class PartialAuthorForm(ModelForm): class Meta: model = Author exclude = ["title"]Поскольку модель
Authorсодержит три поля: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). Обратите внимание: словарь widgets игнорируется для поля модели, у которого атрибут choices не пуст. В этом случае необходимо переопределить поле формы, чтобы использовать другой виджет.
Аналогичным образом можно указать атрибуты labels, help_texts и error_messages внутреннего класса Meta, если вы хотите дополнительно настроить поле.
Например, если вы хотите изменить текст всех строк, видимых пользователю, для поля name:
from django.utils.translation import gettext_lazy as _
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
labels = {
"name": _("Writer"),
}
help_texts = {
"name": _("Some useful help text."),
}
error_messages = {
"name": {
"max_length": _("This writer's name is too long."),
},
}
Также можно указать field_classes или formfield_callback, чтобы настроить тип создаваемых формой полей.
Например, если для поля slug вы хотите использовать MySlugFormField, можно сделать следующее:
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,
}
или:
from django.forms import ModelForm
from myapp.models import Article
def formfield_for_dbfield(db_field, **kwargs):
if db_field.name == "slug":
return MySlugFormField()
return db_field.formfield(**kwargs)
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
formfield_callback = formfield_for_dbfield
Наконец, если вы хотите полностью контролировать поле — в том числе его тип, валидаторы, обязательность и т. д. — можно объявить поля так же, как в обычном 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__', будут локализованы все поля.
Наследование форм
Как и базовые формы, классы ModelForm можно расширять и повторно использовать с помощью наследования. Это удобно, если нужно объявить дополнительные поля или методы в родительском классе, чтобы использовать их в нескольких производных от моделей формах. Например, воспользуемся классом ArticleForm из предыдущего примера:
>>> class EnhancedArticleForm(ArticleForm): ... def clean_pub_date(self): ... ...
В результате создаётся форма, которая ведёт себя так же, как ArticleForm, но включает дополнительную проверку и очистку поля pub_date.
Если вы хотите изменить списки Meta.fields или Meta.exclude, можно также создать подкласс внутреннего класса Meta родительского класса:
>>> 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 либо соответствующих атрибутов внутреннего класса Meta в ModelForm. См. документацию ModelForm Выбор используемых полей.
… или включить локализацию для отдельных полей:
>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=["birth_date"])
Наборы модельных форм
-
class models.BaseModelFormSet
Как и для обычных наборов форм, Django предоставляет несколько расширенных классов наборов форм, упрощающих работу с моделями Django. Воспользуемся моделью Author из предыдущего примера:
>>> from django.forms import modelformset_factory >>> from myapp.models import Author >>> AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
Использование fields ограничивает набор форм указанными полями. Также можно выбрать противоположный подход и указать поля, которые следует исключить:
>>> AuthorFormSet = modelformset_factory(Author, exclude=["birth_date"])
Так будет создан набор форм, позволяющий работать с данными модели Author. Он работает так же, как обычный набор форм:
>>> formset = AuthorFormSet() >>> print(formset) <input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS"><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS"><input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS"><input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS"> <div><label for="id_form-0-name">Name:</label><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100"></div> <div><label for="id_form-0-title">Title:</label><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"></div>
Примечание
modelformset_factory() использует formset_factory() для создания наборов форм. Это означает, что набор модельных форм расширяет базовый набор форм, добавляя возможность взаимодействовать с определённой моделью.
Примечание
При использовании наследования с несколькими таблицами формы, созданные фабрикой наборов форм, будут содержать поле связи с родительской моделью (по умолчанию <parent_model_name>_ptr) вместо поля id.
Изменение QuerySet
По умолчанию при создании набора форм для модели в него включается QuerySet со всеми объектами этой модели (например, Author.objects.all()). Это поведение можно изменить с помощью аргумента queryset:
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith="O"))
Кроме того, можно создать подкласс и задать self.queryset в __init__:
from django.forms import BaseModelFormSet
from myapp.models import Author
class BaseAuthorFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.queryset = Author.objects.filter(name__startswith="O")
Затем передайте свой класс BaseAuthorFormSet фабричной функции:
>>> AuthorFormSet = modelformset_factory( ... Author, fields=["name", "title"], formset=BaseAuthorFormSet ... )
Если нужно вернуть набор форм, не содержащий ни одного существующего экземпляра модели, можно указать пустой QuerySet:
>>> AuthorFormSet(queryset=Author.objects.none())
Изменение формы
По умолчанию при использовании modelformset_factory модельная форма создаётся с помощью modelform_factory(). Часто бывает полезно указать пользовательскую модельную форму. Например, можно создать модельную форму с пользовательской проверкой:
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
fields = ["name", "title"]
def clean_name(self):
# custom validation for the name field
...
Затем передайте свою модельную форму фабричной функции:
AuthorFormSet = modelformset_factory(Author, form=AuthorForm)
Создавать пользовательскую модельную форму требуется не всегда. Функция modelformset_factory принимает несколько аргументов, которые передаются в modelform_factory и описаны ниже.
Указание виджетов формы с помощью 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(). Однако в наборах модельных форм начальные значения применяются только к дополнительным формам, не связанным с существующим экземпляром модели. Если длина initial превышает количество дополнительных форм, лишние начальные данные игнорируются. Если пользователь не изменит дополнительные формы с начальными данными, они не будут проверены и сохранены.
Сохранение объектов в наборе форм
Как и в случае с ModelForm, данные можно сохранить как объект модели. Для этого используется метод набора форм save():
# Create a formset instance with POST data. >>> formset = AuthorFormSet(request.POST) # Assuming all is valid, save the data. >>> instances = formset.save()
Метод save() возвращает экземпляры, сохранённые в базе данных. Если данные конкретного экземпляра не изменились, экземпляр не будет сохранён в базе данных и не войдёт в возвращаемое значение (instances в примере выше).
Если в форме отсутствуют поля (например, потому что они были исключены), метод save() не будет задавать их значения. Подробнее об этом ограничении, которое распространяется и на обычные модельные формы, см. в разделе Выбор используемых полей.
Передайте 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)
...
<div><label for="id_form-0-name">Name:</label><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"></div>
<div><label for="id_form-1-name">Name:</label><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"></div>
<div><label for="id_form-2-name">Name:</label><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"></div>
<div><label for="id_form-3-name">Name:</label><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"></div>
Значение None для max_num (по умолчанию) устанавливает высокий предел числа отображаемых форм — 1000. На практике это равносильно отсутствию ограничения.
Запрет создания новых объектов
С помощью параметра edit_only можно запретить создание новых объектов:
>>> AuthorFormSet = modelformset_factory( ... Author, ... fields=["name", "title"], ... edit_only=True, ... )
В этом случае набор форм будет редактировать только существующие экземпляры Author. Другие объекты не будут создаваться или редактироваться.
Использование набора модельных форм в представлении
Наборы модельных форм очень похожи на обычные наборы форм. Допустим, мы хотим предоставить набор форм для редактирования экземпляров модели Author:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
if request.method == "POST":
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
# do something.
else:
formset = AuthorFormSet()
return render(request, "manage_authors.html", {"formset": formset})
Как видите, логика представления для набора модельных форм не слишком отличается от логики для «обычного» набора форм. Единственное отличие — вызов formset.save() для сохранения данных в базу данных. (Этот метод описан выше в разделе Сохранение объектов в наборе форм.)
Переопределение clean() в ModelFormSet
Как и в случае с ModelForm, по умолчанию метод 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
Использование пользовательского QuerySet
Как упоминалось выше, можно переопределить QuerySet, используемый набором модельных форм по умолчанию:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
queryset = Author.objects.filter(name__startswith="O")
if request.method == "POST":
formset = AuthorFormSet(
request.POST,
request.FILES,
queryset=queryset,
)
if formset.is_valid():
formset.save()
# Do something.
else:
formset = AuthorFormSet(queryset=queryset)
return render(request, "manage_authors.html", {"formset": formset})
Обратите внимание, что в этом примере мы передаём аргумент queryset и в случае POST, и в случае GET.
Использование набора форм в шаблоне
Набор форм можно отобразить в шаблоне Django тремя способами.
Первый способ — поручить большую часть работы набору форм:
<form method="post">
{{ formset }}
</form>
Второй способ — отобразить набор форм вручную, предоставив форме возможность обработать себя самостоятельно:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
Если вы отображаете формы вручную, не забудьте вывести форму управления, как показано выше. См. документацию по форме управления.
Третий способ — вручную отобразить каждое поле:
<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.
См. также
Переопределение методов в 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/6.0/topics/forms/modelforms/