Создание форм из моделей¶
ModelForm¶
- class ModelForm¶
При разработке приложения, использующего базу данных, чаще всего вы будете работать с формами, которые аналогичны моделям. Например, имея модель BlogComment, вам может потребоваться создать форму, которая позволит людям отправлять комментарии. В этом случае явное определение полей формы будет дублировать код, так как все поля уже описаны в модели.
По этой причине Django предоставляет вспомогательный класс, который позволит вам создать класс Form по имеющейся модели.
Например:
>>> 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модели представлено полем формыModelChoiceField, которое является обычнымChoiceField, но с вариантами значений, полученными изQuerySet.Поле
ManyToManyFieldмодели представлено полем формыModelMultipleChoiceField, которое является обычнымMultipleChoiceField`, но с вариантами значений, полученными из ``QuerySet.
В дополнение, каждое поле созданной формы имеет следующие атрибуты:
Если у поля модели есть
blank=True, тогда к полю формы будет добавленоrequired=False, иначе –required=True.Значением атрибута
labelполя будет значение поляverbose_nameмодели, причём первый символ этого значения будет преобразован в верхний регистр.Значением атрибута
help_textполя формы будет значение атрибутаhelp_textполя модели.Если для поля модели установлен атрибут
choices, тогда для поля формы будет использоваться виджетSelect, который будет отображать содержимое этого атрибута. Варианты выбора обычно содержат пустой вариант, который выбран по умолчанию. Если поле является обязательным, то оно требует от пользователя сделать выбор. Пустой вариант не отображается, если у поля модели есть атрибутblank=Falseи явное значение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(), хотя на практике вы обычно не будете использовать последний метод.
Проверка модели запускается на этапе проверки формы сразу после вызова метода Clean() формы. Сначала вызывается full_clean() с validate_unique=False и validate_constraints=False, затем validate_unique() и Методы validate_constraints() вызываются по порядку.
Предупреждение
Процесс валидации изменяет объект модели переданный в конструктор ModelForm. Например, поля даты модели преобразуют значения в объект даты. Ошибка валидации может оставить объект в неопределенном состоянии и лучше не использовать его в последующем коде.
Переопределение метода clean()¶
Вы можете переопределить метод clean() модели для того, чтобы обеспечить дополнительную проверку. Всё это аналогично работе с обычной формой.
Экземпляр модельной формы, привязанный к объекту модели имеет атрибут instance, через который методы модельной формы имеют доступ к соответствующему экземпляру модели.
Предупреждение
Метод ModelForm.clean() устанавливает флаги, благодаря которым шаг проверки модели <validating-objects> проверяет уникальность полей модели, помеченных как unique, unique_together или unique_for_date|month|year, а также ограничения.
Если вы хотите переопределить метод clean(), вызовите метод clean() родительского класса.
Взаимодействие с механизмами модели¶
В процессе проверки данных ModelForm будет вызывать метод clean() каждого поля вашей модели, соответствующего полю формы. Для полей модели, которые были исключены из формы, проверка данных производиться не будет. Обратитесь к документации по проверке форм для получения информации о том, как работает проверка данных поля.
Метод модели clean() будет вызываться перед выполнением каких-либо проверок уникальности или ограничений. См. Проверка объектов для получения дополнительной информации о хуке clean() модели.
Определение error_messages¶
Сообщения ошибки из form field или form Meta имеют приоритет над сообщениями ошибок из model field.
Error messages defined on model fields are only used when the
ValidationError is raised during the model validation step and no corresponding error messages are defined at
the form level.
Вы можете переопределить сообщения об ошибке для NON_FIELD_ERRORS, который были вызваны при валидации модели, определив ключ NON_FIELD_ERRORS в атрибут error_messages класса ModelForm.Meta:
from django.core.exceptions import NON_FIELD_ERRORS
from django.forms import ModelForm
class ArticleForm(ModelForm):
class Meta:
error_messages = {
NON_FIELD_ERRORS: {
"unique_together": "%(model_name)s's %(field_labels)s are not unique.",
}
}
Метод save()¶
Каждая ModelForm также имеет метод save(). Этот метод создает и сохраняет объект базы данных из данных, привязанных к форме. Подкласс ModelForm может принимать существующий экземпляр модели в качестве аргумента ключевого слова instance; если это указано, save() обновит этот экземпляр. Если он не указан, save() создаст новый экземпляр указанной модели:
>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm
# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)
# Save a new Article object from the form's data.
>>> new_article = f.save()
# Create a form to edit an existing Article, but use
# POST data to populate the form.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()
Обратите внимание, если форма не была проверена, вызов save() выполнит ее, обратившись к form.errors. Если данные не верны, будет вызвано исключение 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 также полезно в случае, когда ваша модель имеет связь «многие-ко-многим» с другой моделью. Для такой модели, если метод save() вызван с аргументом 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 работает аналогично обычной Form. Например, метод 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.Meta. Этот атрибут, если он указан, должен содержать список имён полей, которые не должны отображаться на форме.Например:
class PartialAuthorForm(ModelForm): class Meta: model = Author exclude = ["title"]
Так как модель
Authorсодержит три поля: „name“, „title „ и „birth_date“, то форма будут отображать поляnameиbirth_date.
При использовании одного из этих способов, порядок полей в форме будет аналогичен порядку полей в модели, ManyToManyField поля будут в конце.
Если поле модели содержит 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()
Обратитесь к разделу section on saving forms для подробностей по использованию save(commit=False).
Переопределение стандартных типов полей или виджетов¶
Стандартные типы полей, описанные выше, имеют целесообразные настройки по умолчанию. Если ваша модель имеет поля типа DateField, то скорее всего вы пожелаете, чтобы форма использовала поле DateField для его отображения. Но класс ModelForm представляет широкие возможности по управлению типами полей формы, а также виджетами для их представления.
Для того, чтобы указать собственный виджет для поля, следует использовать атрибут widgets внутреннего класса Meta. Его значением должен быть словарь, ключами которого являются имена полей, а значениями — классы или экземпляры виджетов.
Например, если необходимо использовать CharField для того, чтобы поле name модели Author было представлено в виде <textarea> вместо стандартного <input type="text">, то вы можете переопределить виджет поля:
from django.forms import ModelForm, Textarea
from myapp.models import Author
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
widgets = {
"name": Textarea(attrs={"cols": 80, "rows": 20}),
}
Словарь widgets принимает либо экземпляры виджетов (например, Textarea(...)), либо классы (например, Textarea). Обратите внимание, что словарь widgets игнорируется для поля модели с непустым атрибутом choices. В этом случае вам необходимо переопределить поле формы, чтобы использовать другой виджет.
Аналогично можно переопределить параметры labels, help_texts и error_messages указав в Meta.
Например, для переопределим параметры поля name:
from django.utils.translation import gettext_lazy as _
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
labels = {
"name": _("Writer"),
}
help_texts = {
"name": _("Some useful help text."),
}
error_messages = {
"name": {
"max_length": _("This writer's name is too long."),
},
}
Вы также можете указать field_classes или formfield_callback, чтобы настроить тип полей, экземпляры которых создаются в форме.
Например, если вы хотите использовать MySlugFormField для поля slug, вы можете сделать следующее:
from django.forms import ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
field_classes = {
"slug": MySlugFormField,
}
или:
from django.forms import ModelForm
from myapp.models import Article
def formfield_for_dbfield(db_field, **kwargs):
if db_field.name == "slug":
return MySlugFormField()
return db_field.formfield(**kwargs)
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
formfield_callback = formfield_for_dbfield
Если вы желаете продолжить настойку поля, включая его тип, метку и так далее, то вы можете декларативно указать поля, как это делается при использовании Form.
Чтобы переопределить валидаторы поля, укажите их в аргументе validators:
from django.forms import CharField, ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
slug = CharField(validators=[validate_slug])
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
Примечание
Когда вы явно создаете поле формы, необходимо знать как связанны ModelForm и Form.
ModelForm это дочерний класс Form, который может автоматически создавать поля формы. При создании полей учитываются параметры класса Meta и явно определенные поля формы. ModelForm автоматически создаст только те поля, которые отсутствуют в форме.
Явно определенные в классе поля создаются как есть, параметры Meta, такие как widgets, labels, help_texts или error_messages, игнорируются, они учитываются только для создании дополнительных полей.
При явном создании поля, Django предполагает, что вы будете определять поведение формы в целом. Следовательно, стандартные атрибуты модели (такие как max_length или required) не передаются полям формы. Если вам потребуется обеспечить поведение, определённое в модели, вам потребуется явно установить соответствующие аргументы при определении поля формы.
Например, если модель Article выглядит так:
class Article(models.Model):
headline = models.CharField(
max_length=200,
null=True,
blank=True,
help_text="Use puns liberally",
)
content = models.TextField()
и вы желаете выполнить свою проверку поля headline, оставляя неизменными атрибуты blank и help_text, вы можете определить ArticleForm следующим образом:
class ArticleForm(ModelForm):
headline = MyFormField(
max_length=200,
required=False,
help_text="Use puns liberally",
)
class Meta:
model = Article
fields = ["headline", "content"]
Тип поля формы должен работать с типом значения соответствующего поля модели. Если они не соответствуют - вы получите ValueError.
Обратитесь к документации на поля формы для получения дополнительной информации о полях и их аргументах.
Локализация в полях¶
По умолчанию поля в ModelForm не локализируют свои данные. Для локализации полей можно использовать параметр localized_fields класса Meta.
>>> from django.forms import ModelForm
>>> from myapp.models import Author
>>> class AuthorForm(ModelForm):
... class Meta:
... model = Author
... localized_fields = ['birth_date']
Если в localized_fields указать '__all__', будут локализированы все поля.
Наследование форм¶
Как и в случае с базовыми формами, вы можете расширять и повторно использовать классы ModelForm, наследуя их. Это полезно, если вам нужно объявить дополнительные поля или дополнительные методы в родительском классе для использования в ряде форм, производных от моделей. Например, используя предыдущий класс ArticleForm:
>>> class EnhancedArticleForm(ArticleForm):
... def clean_pub_date(self): ...
...
Мы создали форму, аналогичную ArticleForm, добавив дополнительную проверку и обработку для поля pub_date.
Вы также можете создать подкласс внутреннего класса Meta родительского элемента, если хотите изменить списки 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_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 Указываем какие поля использовать.
… или включите локализацию для определенных полей:
>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=["birth_date"])
Наборы модельных форм¶
- class models.BaseModelFormSet¶
Как и обычные наборы форм, Django предоставляет несколько расширенных классов наборов форм, которые делают работу с моделями Django более удобной. Давайте повторно используем модель Автор, приведенную выше:
>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
Использование полей ограничивает набор форм использованием только заданных полей. Альтернативно вы можете воспользоваться подходом «отказа», указав, какие поля следует исключить:
>>> AuthorFormSet = modelformset_factory(Author, exclude=["birth_date"])
Это создаст набор форм, способный работать с данными, связанными с моделью «Автор». Он работает так же, как обычный набор форм:
>>> formset = AuthorFormSet()
>>> print(formset)
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS"><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS"><input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS"><input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS">
<div><label for="id_form-0-name">Name:</label><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100"></div>
<div><label for="id_form-0-title">Title:</label><select name="form-0-title" id="id_form-0-title">
<option value="" selected>---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select><input type="hidden" name="form-0-id" id="id_form-0-id"></div>
Примечание
modelformset_factory() использует фабрику обычных форм formset_factory() для создания набора форм. Это означает, что функционал модельных форм является надстройкой над функционалом набора обычных форм.
Примечание
При использовании многотабличного наследования формы, созданные фабрикой набора форм, будут содержать поле родительской ссылки (по умолчанию <parent_model_name>_ptr) вместо поля id.
Изменение выборки¶
По умолчанию, когда вы создаете набор форм из модели, набор форм будет использовать набор запросов, включающий все объекты модели (например, Author.objects.all()). Вы можете переопределить это поведение, используя аргумент queryset:
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith="O"))
Также вы можете унаследоваться от класса набора модельных форм и определить self.queryset в конструкторе, указав необходимые параметры выборки:
from django.forms import BaseModelFormSet
from myapp.models import Author
class BaseAuthorFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.queryset = Author.objects.filter(name__startswith="O")
Затем передайте класс BaseAuthorFormSet фабричной функции:
>>> AuthorFormSet = modelformset_factory(
... Author, fields=["name", "title"], formset=BaseAuthorFormSet
... )
Если вы хотите вернуть набор форм, который не включает какие-либо ранее существовавшие экземпляры модели, вы можете указать пустой QuerySet:
>>> AuthorFormSet(queryset=Author.objects.none())
Изменение формы¶
По умолчанию, когда вы используете modelformset_factory, форма будет создана с помощью modelform_factory(). Часто необходимо указать свою форму. Например, форму с собственной валидацией:
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
fields = ["name", "title"]
def clean_name(self):
# custom validation for the name field
...
Для этого передайте вашу форму в функцию фабрики:
AuthorFormSet = modelformset_factory(Author, form=AuthorForm)
Вам не обязательно создавать свою форму. Функция modelformset_factory принимает различный аргументы, которые будут переданы в modelform_factory.
Указываем виджеты полей с помощью widgets¶
Используя параметр Widgets, вы можете указать словарь значений, чтобы настроить класс виджета ModelForm для определенного поля. Это работает так же, как работает словарь widgets во внутреннем Meta классе ModelForm``:
>>> AuthorFormSet = modelformset_factory(
... Author,
... fields=["name", "title"],
... widgets={"name": Textarea(attrs={"cols": 80, "rows": 20})},
... )
Включить локализацию для полей с помощью localized_fields¶
Используя localized_fields можно включить локализацию для полей формы.
>>> AuthorFormSet = modelformset_factory(
... Author, fields=['name', 'title', 'birth_date'],
... localized_fields=['birth_date'])
Если в localized_fields указать '__all__', будут локализированы все поля.
Передача начальных значений¶
Аналогично набору обычных форм, есть возможность указать начальные данные для форм набора, передав параметр initial при создании экземпляра набора, возвращенного modelformset_factory(). Тем не менее, в случае набора модельных форм, начальными значениями заполняются только пустые, т.е. новые, формы.
Сохранение объектов набора форм¶
Как и в случае с ModelForm, вы можете сохранить данные как объект модели. Это делается с помощью метода save() набора форм:
# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)
# Assuming all is valid, save the data.
>>> instances = formset.save()
Метод save() возвращает экземпляры объектов, которые были сохранены в базе данных. Те объекты, данные которых не изменились, не сохраняются в базе данных и не отображаются в возвращаемом значении (instances из предыдущего примера).
Если поля отсутствуют в форме (например, потому что они были исключены), эти поля не будут установлены методом save(). Дополнительную информацию об этом ограничении, которое также действует для обычных форм модели, можно найти в разделе «Выбор полей для использования».
Передайте commit=False, чтобы вернуть несохраненные экземпляры модели:
# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
... # do something with instance
... instance.save()
...
Это позволяет вам добавлять данные к экземплярам моделей перед их сохранением в базе данных. Если ваш набор форм содержит ManyToManyField, вам также потребуется вызвать метод formset.save_m2m() для того, чтобы обеспечить сохранение связей «многие-ко-многим».
После вызова save(), в класс набора форм будут добавлены атрибуты, содержащие все изменения:
- models.BaseModelFormSet.changed_objects¶
- models.BaseModelFormSet.deleted_objects¶
- models.BaseModelFormSet.new_objects¶
Ограничение количества редактируемых объектов¶
Как и в случае набора обычных форм, вы можете использовать аргументы max_num и extra функции modelformset_factory() для ограничения числа дополнительно отображаемых форм.
max_num не предотвращает отображение существующих объектов:
>>> Author.objects.order_by("name")
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>
>>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']
Кроме того, extra=0 не предотвращает создание новых экземпляров модели, поскольку вы можете добавлять дополнительные формы с помощью JavaScript или отправлять дополнительные данные POST. См. Предотвращение создания новых объектов о том, как это сделать.
Если значение max_num больше, чем количество существующих связанных объектов, в набор форм будет добавлено до extra дополнительных пустых форм, при условии, что общее количество форм не превышает max_num:
>>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-name">Name:</label><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100"><input type="hidden" name="form-0-id" value="1" id="id_form-0-id"></div>
<div><label for="id_form-1-name">Name:</label><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100"><input type="hidden" name="form-1-id" value="3" id="id_form-1-id"></div>
<div><label for="id_form-2-name">Name:</label><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100"><input type="hidden" name="form-2-id" value="2" id="id_form-2-id"></div>
<div><label for="id_form-3-name">Name:</label><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100"><input type="hidden" name="form-3-id" id="id_form-3-id"></div>
Присвоение свойству max_num значения None (по умолчанию) устанавливает достаточное ограничение на количество отображаемых форм (1000). На практике это эквивалентно отсутствию ограничения.
Предотвращение создания новых объектов¶
Используя параметр edit_only, вы можете запретить создание любых новых объектов:
>>> AuthorFormSet = modelformset_factory(
... Author,
... fields=["name", "title"],
... edit_only=True,
... )
Здесь набор форм будет редактировать только существующие экземпляры Author. Никакие другие объекты не будут создаваться или редактироваться.
Использование набора модельных форм в представлении¶
Наборы модельных форм во многом похожи на наборы обычных форм. Для отображения набора форм для редактирования экземпляров модели Author:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
if request.method == "POST":
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
# do something.
else:
formset = AuthorFormSet()
return render(request, "manage_authors.html", {"formset": formset})
Как вы можете видеть, логика представления не сильно отличается отличается логики обычного набора. Отличием является вызов formset.save() для сохранения данных. (Это было описано ранее в Сохранение объектов набора форм.)
Переопределение clean() у ModelFormSet¶
Как и в случае с ModelForm, по умолчанию метод clean() класса ModelFormSet проверяет, что ни один из элементов в наборе форм не нарушает ограничения уникальности вашей модели (ни уникального, ни уникального_together, ни уникального_для_даты|месяца|года). Если вы хотите переопределить метод «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"])
queryset = Author.objects.filter(name__startswith="O")
if request.method == "POST":
formset = AuthorFormSet(
request.POST,
request.FILES,
queryset=queryset,
)
if formset.is_valid():
formset.save()
# Do something.
else:
formset = AuthorFormSet(queryset=queryset)
return render(request, "manage_authors.html", {"formset": formset})
Следует отметить, что мы передаём аргумент queryset в обе ветки POST и GET в этом примере.
Использование набора форм в шаблоне¶
Существует три способа отображения набора форм в шаблоне Django.
Во-первых, вы можете позволить набору форм выполнять большую часть работы:
<form method="post">
{{ formset }}
</form>
Во-вторых, вы можете вручную визуализировать набор форм, но пусть форма сама разбирается:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
При самостоятельном отображении форм, не забудьте отобразить техническую форму, как было показано выше. Обратитесь к документации на технические формы.
В-третьих, вы можете вручную визуализировать каждое поле:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{% for field in form %}
{{ field.label_tag }} {{ field }}
{% endfor %}
{% endfor %}
</form>
Если вы решите использовать этот третий метод и не перебираете поля с помощью цикла {% for %}, вам нужно будет визуализировать поле первичного ключа. Например, если вы отображали поля name и возраст модели:
<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. Если вы изменили имя первичного ключа, то учтите это в данном примере.)
Встраиваемые наборы форм¶
- 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)
Префикс <formset-prefix>``BookFormSet`` — 'book_set' (<имя модели>_set ). Если ForeignKey для Author в Book имеет 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. Смотрите Specifying widgets to use in the form with widgets выше.