Создание форм из моделей
ModelForm
-
class ModelForm[исходный код]
Если вы создаёте приложение, работающее с базой данных, вам, скорее всего, понадобятся формы, тесно связанные с моделями Django. Например, у вас может быть модель BlogComment, и вы хотите создать форму, позволяющую пользователям отправлять комментарии. В этом случае определение типов полей в форме будет избыточным, поскольку поля уже определены в модели.
По этой причине Django предоставляет вспомогательный класс, позволяющий создать класс формы Form из модели Django.
Например:
>>> from django.forms import ModelForm >>> from myapp.models import Article # Create the form class. >>> class ArticleForm(ModelForm): ... class Meta: ... model = Article ... fields = ['pub_date', 'headline', 'content', 'reporter'] # Creating a form to add an article. >>> form = ArticleForm() # Creating a form to change an existing article. >>> article = Article.objects.get(pk=1) >>> form = ArticleForm(instance=article)
Типы полей
Сгенерированный класс Form будет содержать поле формы для каждого поля модели в порядке, указанном в атрибуте fields.
Каждое поле модели имеет соответствующее поле формы по умолчанию. Например, CharField в модели представлен как CharField в форме. Модель ManyToManyField представлена как MultipleChoiceField. Вот полный список преобразований:
Как можно ожидать, типы полей модели ForeignKey и ManyToManyField являются специальными случаями:
-
ForeignKeyпредставленdjango.forms.ModelChoiceField, который являетсяChoiceField, у которого варианты выбора — модельQuerySet. -
ManyToManyFieldпредставленdjango.forms.ModelMultipleChoiceField, который являетсяMultipleChoiceField, у которого варианты выбора — модельQuerySet.
Кроме того, каждое сгенерированное поле формы имеет атрибуты, установленные следующим образом:
- Если поле модели имеет
blank=True, тогдаrequiredустанавливается вFalseв поле формы. В противном случае,required=True. - Поле формы
labelустанавливается вverbose_nameполя модели, с заглавной первой буквой. - Поле формы
help_textустанавливается вhelp_textполя модели. - Если в поле модели установлено
choices, то поле формыwidgetбудет установлено вSelect, используя варианты изchoicesполя модели. Варианты обычно включают пустой выбор, который выбран по умолчанию. Если поле обязательно, это заставляет пользователя сделать выбор. Пустой выбор не будет включен, если поле модели имеетblank=Falseи явное значениеdefault(значениеdefaultбудет выбрано по умолчанию).
Наконец, обратите внимание, что вы можете переопределить поле формы, используемое для данного поля модели. См. Переопределение стандартных полей ниже.
Полный пример
Рассмотрим следующий набор моделей:
from django.db import models
from django.forms import ModelForm
TITLE_CHOICES = (
('MR', 'Mr.'),
('MRS', 'Mrs.'),
('MS', 'Ms.'),
)
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __str__(self): # __unicode__ on Python 2
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.forms import ModelForm
from django.core.exceptions import NON_FIELD_ERRORS
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 и хотите стандартное поведение по умолчанию для поля, использующего один из этих виджетов.
В более старых версиях нет исключения для CheckboxInput, что означает, что неактивные флажки получают значение True , если это значение по умолчанию для поля модели.
Был добавлен метод value_omitted_from_data().
Этот метод 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, если хотите далее настроить поле.
Например, если вы хотели настроить текст для всех отображаемых пользователю строк поля name,
from django.utils.translation import ugettext_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 ModelForm, CharField
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находится в начале цепочки наследования. Это связано с тем, что эти классы используют разные метаклассы, и класс может иметь только один метакласс. -
Возможно декларативно удалить
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 класса. См. документацию ModelForm Выбор полей для использования.
… или включить локализация для определённых полей:
>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=("birth_date",))
Наборы форм модели
-
class models.BaseModelFormSet
Как и обычные наборы форм, Django предоставляет несколько улучшенных классов наборов форм, которые упрощают работу с моделями Django. Давайте повторно используем модель Author из вышеприведённого примера:
>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
Использование fields ограничивает набор форм использованием только указанных полей. В качестве альтернативы, можно применить подход «исключения», указав поля для исключения:
>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))
Это создаст набор форм, способный работать с данными, связанными с моделью Author. Он работает так же, как и обычный набор форм:
>>> formset = AuthorFormSet() >>> print(formset) <input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" /><input type="hidden" name="form-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(BaseAuthorFormSet, self).__init__(*args, **kwargs)
self.queryset = Author.objects.filter(name__startswith='O')
Затем передайте ваш класс BaseAuthorFormSet в функцию-фабрику:
>>> AuthorFormSet = modelformset_factory(
... Author, fields=('name', 'title'), formset=BaseAuthorFormSet)
Если вы хотите вернуть набор форм, который не включает никакие существующие экземпляры модели, вы можете указать пустой набор запросов:
>>> AuthorFormSet(queryset=Author.objects.none())
Изменение формы
По умолчанию, при использовании modelformset_factory, будет создана форма модели с использованием modelform_factory(). Часто бывает полезно указать пользовательскую форму модели. Например, вы можете создать пользовательскую форму модели с пользовательской валидацией:
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
fields = ('name', 'title')
def clean_name(self):
# custom validation for the name field
...
Затем передайте вашу форму модели в функцию-фабрику:
AuthorFormSet = modelformset_factory(Author, form=AuthorForm)
Определять пользовательскую форму модели не всегда необходимо. Функция modelformset_factory имеет несколько аргументов, которые передаются в modelform_factory, которые описаны ниже.
Указание виджетов для использования в форме с widgets
Используя параметр widgets, вы можете указать словарь значений для настройки класса виджета ModelForm для определённого поля. Это работает так же, как и словарь widgets вложенного класса Meta класса ModelForm:
>>> AuthorFormSet = modelformset_factory(
... Author, fields=('name', 'title'),
... widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20})})
Включение локализации для полей с localized_fields
Используя параметр localized_fields, вы можете включить локализация для полей в форме.
>>> AuthorFormSet = modelformset_factory(
... Author, fields=('name', 'title', 'birth_date'),
... localized_fields=('birth_date',))
Если localized_fields установлено в специальное значение '__all__', все поля будут локализованы.
Предоставление начальных значений
Как и с обычными наборами форм, можно указать начальные данные для форм в наборе форм, указав параметр initial при создании класса набора форм модели, возвращаемого modelformset_factory(). Однако с наборами форм модели начальные значения применяются только к дополнительным формам, которые не привязаны к существующему экземпляру модели. Если дополнительные формы с начальными данными не изменены пользователем, они не будут проверены или сохранены.
Сохранение объектов в наборе форм
Как и с ModelForm, вы можете сохранить данные как объект модели с помощью метода save() набора форм:
# Create a formset instance with POST data. >>> formset = AuthorFormSet(request.POST) # Assuming all is valid, save the data. >>> instances = formset.save()
Метод save() возвращает экземпляры, которые были сохранены в базе данных. Если данные определённого экземпляра не изменились в привязанных данных, экземпляр не будет сохранён в базе данных и не будет включён в возвращаемое значение (instances, в приведённом выше примере).
Если поля отсутствуют в форме (например, потому что они были исключены), эти поля не будут установлены методом save(). Более подробную информацию об этом ограничении, которое также действует для обычных ModelForms, можно найти в разделе Выбор полей для использования.
Передайте commit=False для возврата несохранённых экземпляров модели:
# don't save to the database >>> instances = formset.save(commit=False) >>> for instance in instances: ... # do something with instance ... instance.save()
Это даёт возможность привязать данные к экземплярам перед сохранением их в базу данных. Если ваш набор форм содержит ManyToManyField, вам также необходимо вызвать formset.save_m2m() для правильного сохранения взаимосвязей «многие ко многим».
После вызова save(), ваш набор форм модели получит три новых атрибута, содержащих изменения набора форм:
-
models.BaseModelFormSet.changed_objects
-
models.BaseModelFormSet.deleted_objects
-
models.BaseModelFormSet.new_objects
Ограничение числа редактируемых объектов
Как и с обычными наборами форм, вы можете использовать параметры max_num и extra для modelformset_factory() для ограничения числа отображаемых дополнительных форм.
max_num не препятствует отображению существующих объектов:
>>> Author.objects.order_by('name')
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>
>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']
Кроме того, extra=0 не препятствует созданию новых экземпляров моделей, так как вы можете добавить дополнительные формы с помощью JavaScript или просто отправить дополнительные данные POST. Наборы форм ещё не обеспечивают функциональность для «только для редактирования» представления, которое предотвращает создание новых экземпляров.
Если значение max_num больше числа существующих связанных объектов, до extra дополнительных пустых форм будут добавлены в набор форм, при условии, что общее количество форм не превысит max_num:
>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset:
... print(form.as_table())
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-0-id" value="1" id="id_form-0-id" /></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100" /><input type="hidden" name="form-1-id" value="3" id="id_form-1-id" /></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100" /><input type="hidden" name="form-2-id" value="2" id="id_form-2-id" /></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></td></tr>
Значение max_num в None (значение по умолчанию) устанавливает высокое ограничение на количество отображаемых форм (1000). На практике это эквивалентно отсутствию ограничения.
Использование набора форм модели в представлении
Наборы форм модели очень похожи на наборы форм. Предположим, что мы хотим отобразить набор форм для редактирования экземпляров модели Author:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
if request.method == 'POST':
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
# do something.
else:
formset = AuthorFormSet()
return render(request, 'manage_authors.html', {'formset': formset})
Как вы можете видеть, логика представления набора форм модели не сильно отличается от логики «обычного» набора форм. Единственное различие состоит в том, что мы вызываем formset.save() для сохранения данных в базе данных. (Это было описано выше, в Сохранение объектов в наборе форм.)
Переопределение clean() в наборе форм модели ModelFormSet
Как и с ModelForms, по умолчанию метод clean() набора форм модели ModelFormSet проверяет, что ни один из элементов в наборе форм не нарушает уникальные ограничения вашей модели (либо unique, unique_together или unique_for_date|month|year). Если вы хотите переопределить метод clean() набора форм модели ModelFormSet и сохранить эту проверку, вы должны вызвать метод родительского класса clean:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super(MyModelFormSet, self).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(MyModelFormSet, self).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" action="">
{{ formset }}
</form>
Во-вторых, вы можете вручную отобразить набор форм, но позволить форме справиться с собой:
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
При ручном рендеринге форм убедитесь, что форма управления рендерится так, как показано выше. См. документацию по форме управления.
В-третьих, вы можете вручную рендерить каждое поле:
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset %}
{% for field in form %}
{{ field.label_tag }} {{ field }}
{% endfor %}
{% endfor %}
</form>
Если вы выбрали этот третий метод и не итерируете по полям с помощью {% for %} цикла, вам нужно будет рендерить поле первичного ключа. Например, если вы рендерите поля name и age модели:
<form method="post" action="">
{{ 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)
Примечание
inlineformset_factory() использует modelformset_factory() и помечает can_delete=True.
См. также
Переопределение методов в InlineFormSet
При переопределении методов в InlineFormSet, вы должны наследовать от BaseInlineFormSet, а не от BaseModelFormSet.
Например, если вы хотите переопределить clean():
from django.forms import BaseInlineFormSet
class CustomInlineFormSet(BaseInlineFormSet):
def clean(self):
super(CustomInlineFormSet, self).clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
См. также Переопределение clean() в наборе форм модели.
Затем, при создании набора форм, передайте необязательный аргумент 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/1.11/topics/forms/modelforms/