Создание форм из шаблонов ¶
ModelForm
¶
-
класс
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)
Типы полей ¶
Сгенерированный класс формы будет содержать одно поле формы для каждого включенного поля модели в порядке, указанном атрибутом 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
¶
Валидация a 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()
.
Рекомендации по сообщениям об ошибках модели ¶
Сообщения об ошибках, определенные на уровне или на уровне метакласса формы, всегда имеют приоритет над сообщениями об ошибках, определенными на уровне .champs de formulaire
champs de modèle
Сообщения об ошибках, определенные на уровне , используются только тогда, когда ошибка генерируется на этапе проверки модели, и на уровне формы не определено соответствующее сообщение об ошибке.champs de modèle
ValidationError
Вы можете переопределить сообщения об ошибках от NON_FIELD_ERRORS
и сгенерированные при проверке моделей, добавив ключ NON_FIELD_ERRORS
в словарь внутреннего error_messages
класса Meta
классов 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
) , так как непроверенный или неотобранным флажок не включен при передаче данных. 'HTML-форма. Используйте настраиваемое поле или компонент формы, если вы разрабатываете API и хотите добиться базового поведения по умолчанию для поля, которое использует один из этих компонентов.<select multiple>
Этот метод save()
принимает необязательный именованный параметр commit
, который принимает значения True
или False
. Если вы вызываете save()
with commit=False
, он возвращает объект, который еще не был сохранен в базе данных. В этом случае вы должны вызвать save()
получившийся экземпляр модели. Это полезно, если вы хотите выполнить специальную обработку объекта перед его сохранением или если вы хотите использовать одну из специальных опций сохранения модели . commit
по True
умолчанию.
Другой побочный эффект использования 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
внутреннего класса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
непустым атрибутом . В этом случае вы должны переопределить поле формы, чтобы указать widget
другой компонент .
Кроме того, вы можете задать атрибуты 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
чтобы настроить тип полей, создаваемых формой.
Например, если вы хотите использовать 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
путем наследования. Это полезно, когда вам нужно объявить дополнительные поля или методы в подклассе для использования в формах, производных от моделей. Например, используя 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
подкласса, если он есть, если не класс первого родителя и т. Д.Возможно наследование как от, так
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",))
Сгруппированные формы моделей ¶
-
класс
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()
для создания сгруппированных форм. Это означает, что сгруппированная модель является расширением основных сгруппированных форм, которые знают, как взаимодействовать с конкретной моделью.
Заметка
В контекстах наследования нескольких таблиц формы, созданные фабрикой наборов форм, содержат поле родительской ссылки (по умолчанию <nom_modèle_parent>_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)
Если вы хотите вернуть сгруппированные формы, которые не содержат каких - либо ранее существовавших экземпляров шаблона, вы можете указать 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
a 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()
. Дополнительную информацию об этом ограничении, которое также действует для ModelForm
обычных форм , можно найти в разделе Выбор полей для использования .
Переключитесь commit=False
на получение незарегистрированных экземпляров шаблона:
# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
... # do something with instance
... instance.save()
Это позволяет вам определять дополнительные данные в экземплярах перед их сохранением в базе данных. Если ваши сгруппированные формы содержат поле ManyToManyField
, вам также потребуется позвонить, formset.save_m2m()
чтобы убедиться, что ссылки «многие ко многим» сохранены правильно.
После вызова save()
сгруппированные формы модели будут иметь три новых атрибута, содержащих модификации формы:
-
models.BaseModelFormSet.
changed_objects
¶
-
models.BaseModelFormSet.
deleted_objects
¶
-
models.BaseModelFormSet.
new_objects
¶
Ограничение количества редактируемых объектов ¶
Что касается обычных сгруппированных форм, вы можете использовать настройки max_num
и extra
для modelformset_factory()
ограничения количества дополнительных форм отображения.
max_num
не предотвращает отображение существующих объектов:
>>> Author.objects.order_by('name')
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>
>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']
Кроме того, extra=0
не препятствует созданию новых экземпляров модели, поскольку можно добавлять дополнительные формы с помощью JavaScript или отправлять дополнительные данные POST. Наборы форм еще не предоставляют функциональные возможности представления только для редактирования, которое предотвратило бы создание новых экземпляров.
Если значение max_num
больше, чем количество существующих связанных объектов, extra
к сгруппированным формам будет добавлено максимум дополнительных пустых форм, если общее количество форм не превышает max_num
:
>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset:
... print(form.as_table())
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100"><input type="hidden" name="form-0-id" value="1" id="id_form-0-id"></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100"><input type="hidden" name="form-1-id" value="3" id="id_form-1-id"></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100"><input type="hidden" name="form-2-id" value="2" id="id_form-2-id"></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100"><input type="hidden" name="form-3-id" id="id_form-3-id"></td></tr>
Значение max_num
None
(по умолчанию) устанавливает высокий предел количества отображаемых форм (1000). На практике это означает отсутствие ограничений.
Использование сгруппированных форм модели на виде ¶
Сгруппированные шаблоны форм очень похожи на обычные сгруппированные формы. Скажем, мы хотим отображать сгруппированные формы для изменения экземпляров модели Author
:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
if request.method == 'POST':
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
# do something.
else:
formset = AuthorFormSet()
return render(request, 'manage_authors.html', {'formset': formset})
Как видите, логика представления сгруппированных форм модели принципиально не отличается от логики обычных сгруппированных форм. Единственное отличие состоит в том, что мы вызываем formset.save()
для сохранения данных в базе данных (как описано выше в разделе « Сохранение объектов для сгруппированных форм» ).
Перегрузка ¶clean()
ModelFormSet
Что касается ModelForms
метода clean()
по ModelFormSet
умолчанию, то никакие допустимые элементы в объединенной форме не нарушают уникальные ограничения вашей модели (либо unique
, unique_together
либо unique_for_date|month|year
). Если вы хотите перегрузить метод clean()
из ModelFormSet
и поддерживать эту проверку, вы должны вызвать метод clean
родительского класса:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
Также обратите внимание, что как только вы достигли этого шага, отдельные экземпляры шаблона для каждой формы уже были созданы. Таким образом, для изменения form.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>
Если вы выберете этот третий метод и не пропустите поля с помощью символа a , вы также должны включить поле первичного ключа. Например, если вы отображаете поля и шаблона:{% 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
Сгруппированные подчиненные формы ¶
-
класс
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
IS 'book_set'
( <nom_modèle>_set
). Если внешний ключ Book
червя Author
имеет атрибут related_name
, используется атрибут .
Заметка
inlineformset_factory()
использует modelformset_factory()
и определяет can_delete=True
.
Смотрите также
Методы перегрузки InlineFormSet
¶
При переопределении сгруппированных методов subform ( 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
. См. Раздел Определение компонентов формы для использования с виджетами выше.