Создание форм из моделей
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. Ниже приведен полный список преобразований:
Как можно предположить, типы полей модели 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 (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() используется для определения того, требует ли форма загрузку файлов multipart (и, следовательно, необходимо ли передавать request.FILES в форму). См. Привязывание загруженных файлов к форме для получения дополнительной информации.
Выбор полей для использования
Сильно рекомендуется явно задавать все поля, которые должны редактироваться в форме, используя атрибут fields. Если этого не сделать, легко могут возникнуть проблемы с безопасностью, когда форма неожиданно позволяет пользователю устанавливать определённые поля, особенно при добавлении новых полей в модель. В зависимости от того, как отображается форма, проблема может даже не быть видна на веб-странице.
Альтернативный подход заключался бы в автоматическом включении всех полей или удалении только некоторых. Этот фундаментальный подход, как известно, намного менее безопасен и привёл к серьёзным уязвимостям на крупных веб-сайтах (например, GitHub).
Однако существуют два сокращения для случаев, когда вы можете гарантировать, что эти проблемы безопасности к вам не относятся:
-
Установите атрибут
fieldsв специальное значение'__all__', чтобы указать, что все поля в модели должны использоваться. Например:from django.forms import ModelForm class AuthorForm(ModelForm): class Meta: model = Author fields = "__all__" -
Установите атрибут
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). Обратите внимание, что словарь 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, чтобы настроить тип создаваемых формой полей.
Например, если вы хотите использовать 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,
}
или:
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__', все поля будут локализованы.
Наследование форм
Как и в базовых формах, вы можете расширить и повторно использовать 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"])
Это создаст набор форм, способный работать с данными, связанными с моделью 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.
Изменение набора запросов
По умолчанию, при создании набора форм из модели, набор форм будет использовать набор запросов, включающий все объекты в модели (например, 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(). Однако, с наборами форм модели начальные значения применяются только к дополнительным формам, которые не привязаны к существующему экземпляру модели. Если длина initial превышает количество дополнительных форм, избыточные начальные данные игнорируются. Если дополнительные формы с начальными данными не изменены пользователем, они не будут валидироваться или сохраняться.
Сохранение объектов в наборе форм
Как и с ModelForm, вы можете сохранить данные как объект модели. Это делается с помощью метода save() набора форм:
# Create a formset instance with POST data. >>> formset = AuthorFormSet(request.POST) # Assuming all is valid, save the data. >>> instances = formset.save()
Метод save() возвращает экземпляры, которые были сохранены в базе данных. Если данные данного экземпляра не изменились в связанных данных, экземпляр не будет сохранён в базе данных и не будет включён в возвращаемое значение (instances, в примере выше).
Когда поля отсутствуют в форме (например, потому что они были исключены), эти поля не будут установлены методом save(). Дополнительную информацию об этом ограничении, которое также действует для обычных наборов форм ModelForms, вы найдёте в разделе Выбор полей для использования.
Передайте 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>
Значение max_num равное None (по умолчанию) устанавливает высокое ограничение на количество отображаемых форм (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
Так же, как и с ModelForms, по умолчанию метод clean() набора форм модели будет проверять, что ни один из элементов набора форм не нарушает уникальные ограничения вашей модели (либо unique, unique_together или unique_for_date|month|year). Если вы хотите переопределить метод clean() в ModelFormSet и сохранить эту валидацию, вам необходимо вызвать метод родительского класса clean:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
Также обратите внимание, что к тому времени, когда вы дойдёте до этого шага, для каждого элемента Form уже были созданы отдельные экземпляры моделей. Изменение значения в form.cleaned_data недостаточно для изменения сохранённого значения. Если вы хотите изменить значение в ModelFormSet.clean(), вы должны изменить form.instance:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
for form in self.forms:
name = form.cleaned_data["name"].upper()
form.cleaned_data["name"] = name
# update the instance value.
form.instance.name = name
Использование настраиваемого набора запросов
Как уже упоминалось ранее, вы можете переопределить используемый по умолчанию набор запросов набора форм модели:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
queryset = Author.objects.filter(name__startswith="O")
if request.method == "POST":
formset = AuthorFormSet(
request.POST,
request.FILES,
queryset=queryset,
)
if formset.is_valid():
formset.save()
# Do something.
else:
formset = AuthorFormSet(queryset=queryset)
return render(request, "manage_authors.html", {"formset": formset})
Обратите внимание, что мы передаём аргумент queryset как в случае POST, так и в случае GET в этом примере.
Использование набора форм в шаблоне
Существует три способа отображения набора форм в шаблоне Django.
Во-первых, вы можете позволить набору форм выполнить большую часть работы:
<form method="post">
{{ formset }}
</form>
Во-вторых, вы можете вручную отобразить набор форм, но позволить форме самой справиться с собой:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
При ручном отображении форм убедитесь, что вы отображаете форму управления, как показано выше. См. документацию по форме управления.
В-третьих, вы можете вручную отобразить каждое поле:
<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’s префикс равен 'book_set' (<model name>_set ). Если Book’s ForeignKey to 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"] ... )
Использование inline formset в представлении
Возможно, вам потребуется предоставить представление, которое позволит пользователю редактировать связанные объекты модели. Вот как вы можете это сделать:
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.
Указание виджетов для использования в inline форме
inlineformset_factory использует modelformset_factory и передает большинство своих аргументов modelformset_factory. Это означает, что вы можете использовать параметр widgets аналогичным образом, как вы передаете его modelformset_factory. См. Указание виджетов для использования в форме с виджетами выше.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.1/topics/forms/modelforms/