Создание форм из моделей
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() используется для определения необходимости многочастной загрузки файлов (и, следовательно, необходимости передачи 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).
Аналогично, вы можете указать атрибуты labels, help_texts и error_messages внутреннего класса Meta, если хотите дополнительно настроить поле.
Например, если вы хотите настроить формулировку всех отображаемых пользователем строк для поля %%%CODE_BLOCK_225%%:
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 для поля slug, вы могли бы сделать следующее:
from django.forms import ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
field_classes = {
'slug': MySlugFormField,
}
Наконец, если вы хотите полностью контролировать поле, включая его тип, валидаторы, обязательность и т. д., вы можете сделать это, декларативно определив поля, как в обычной Form.
Если вы хотите указать валидаторы поля, вы можете сделать это, декларативно определив поле и задав его параметр validators:
from django.forms import 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_262%%:
>>> 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-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(). Однако с наборами форм модели начальные значения применяются только к дополнительным формам, которые не привязаны к существующему экземпляру модели. Если длина 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.as_table())
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100"><input type="hidden" name="form-0-id" value="1" id="id_form-0-id"></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100"><input type="hidden" name="form-1-id" value="3" id="id_form-1-id"></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100"><input type="hidden" name="form-2-id" value="2" id="id_form-2-id"></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100"><input type="hidden" name="form-3-id" id="id_form-3-id"></td></tr>
Значение max_num равное None (по умолчанию) устанавливает высокий лимит на количество отображаемых форм (1000). На практике это эквивалентно отсутствию ограничения.
Использование набора форм модели в представлении
Наборы форм модели очень похожи на наборы форм. Предположим, что мы хотим представить набор форм для редактирования экземпляров модели Author:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
if request.method == 'POST':
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
# do something.
else:
formset = AuthorFormSet()
return render(request, 'manage_authors.html', {'formset': formset})
Как видите, логика представления набора форм модели не сильно отличается от логики «обычного» набора форм. Единственное различие заключается в том, что мы вызываем formset.save() для сохранения данных в базе данных. (Это было описано выше, в Сохранение объектов в наборе форм.)
Переопределение clean() на ModelFormSet
Так же, как и с ModelForms, по умолчанию метод clean() набора форм модели будет проверять, что ни один из элементов набора форм не нарушает уникальные ограничения вашей модели (либо unique, unique_together или unique_for_date|month|year). Если вы хотите переопределить метод clean() на наборе форм модели и сохранить эту валидацию, вы должны вызвать метод родительского класса clean:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().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.
См. также
Переопределение методов в 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.1/topics/forms/modelforms/