Создание форм из моделей¶
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(), но на практике вы не будете использовать последний метод.
Валидация модели (Model.full_clean()) выполняется после валидации формы, сразу после завершения метода clean().
Предупреждение
Процесс валидации изменяет объект модели переданный в конструктор ModelForm. Например, поля даты модели преобразуют значения в объект даты. Ошибка валидации может оставить объект в неопределенном состоянии и лучше не использовать его в последующем коде.
Overriding the clean() method¶
Вы можете переопределить метод clean() модели для того, чтобы обеспечить дополнительную проверку. Всё это аналогично работе с обычной формой.
Экземпляр модельной формы, привязанный к объекту модели имеет атрибут instance, через который методы модельной формы имеют доступ к соответствующему экземпляру модели.
Предупреждение
Метод ModelForm.clean() устанавливает флаг, который указывает валидации модели провалидировать уникальность полей отмеченных 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()¶
Every ModelForm also has a save() method. This method creates and saves
a database object from the data bound to the form. A subclass of ModelForm
can accept an existing model instance as the keyword argument instance; if
this is supplied, save() will update that instance. If it’s not supplied,
save() will create a new instance of the specified model:
>>> 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 не может немедленно сохранить данные для такой связи, т.к. невозможно создать связи для объекта, который не сохранен в базе данных.
To work around this problem, every time you save a form using commit=False,
Django adds a save_m2m() method to your ModelForm subclass. After
you’ve manually saved the instance produced by the form, you can invoke
save_m2m() to save the many-to-many form data. For example:
# 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()
Calling save_m2m() is only required if you use save(commit=False).
When you use a save() on a form, all data – including many-to-many data –
is saved without the need for any additional method calls. For example:
# 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 будет препятствовать всем попыткам сохранить неполную модель. Таким образом, если модель требует заполнения определённых полей и для них не предоставлено значение по умолчанию, то сохранить форму для такой модели не получится. Для решения этой проблемы вам потребуется создать экземпляр такой модели, передав ему начальные значения для обязательных, но незаполненных полей:
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."),
},
}
You can also specify field_classes to customize the type of fields
instantiated by the form.
Например, если вы хотите использовать 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, игнорируются, они учитываются только для создании дополнительных полей.
При явном создании поля, 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__', будут локализированы все поля.
Наследование форм¶
As with basic forms, you can extend and reuse ModelForms by inheriting
them. This is useful if you need to declare extra fields or extra methods on a
parent class for use in a number of forms derived from models. For example,
using the previous ArticleForm class:
>>> class EnhancedArticleForm(ArticleForm):
... def clean_pub_date(self):
... ...
Мы создали форму, аналогичную ArticleForm, добавив дополнительную проверку и обработку для поля pub_date.
You can also subclass the parent’s Meta inner class if you want to change
the Meta.fields or Meta.exclude lists:
>>> class RestrictedArticleForm(EnhancedArticleForm):
... class Meta(ArticleForm.Meta):
... exclude = ('body',)
Здесь мы добавили метод из EnhancedArticleForm и изменили оригинальный ArticleForm.Meta, убрав одно поле.
Тем не менее, надо уточнить несколько моментов.
Применяются стандартные правила языка Python для разрешения имён. Если ваш класс унаследован от нескольких базовых классов, которые обладают внутренним классом
Meta, и для него не определён собственныйMetaкласс, то этот класс будет унаследован из первого базового.Можно унаследоваться одновременно от
FormиModelForm, однако,ModelFormдолжен быть первым в MRO. Т.к. эти классы используют разные мета-классы, а класс может использовать только один метакласс.Можно декларативно удалить
Fieldродительского класса, указав в названииNoneв дочернем классе.Таким способом можно исключить только те поля, которые были декларативно описаны в родительском классе. Поле
ModelFormбудет в любом случае созданы мета-классом. Чтобы переопределить их, используйте метод описанный в Указываем какие поля использовать.
Передача начальных значений¶
As with regular forms, it’s possible to specify initial data for forms by
specifying an initial parameter when instantiating the form. Initial
values provided this way will override both initial values from the form field
and values from an attached model instance. For example:
>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
>>> form = ArticleForm(initial={'headline': 'Initial headline'}, instance=article)
>>> form['headline'].value()
'Initial headline'
Функция-фабрика модельных форм¶
You can create forms from a given model using the standalone function
modelform_factory(), instead of using a class
definition. This may be more convenient if you do not have many customizations
to make:
>>> from django.forms import modelform_factory
>>> from myapp.models import Book
>>> BookForm = modelform_factory(Book, fields=("author", "title"))
This can also be used to make modifications to existing forms, for example by specifying the widgets to be used for a given field:
>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm,
... widgets={"title": Textarea()})
Указать используемые поля можно с помощью аргументов fields и exclude. Смотрите описание ModelForm Указываем какие поля использовать.
… or enable localization for specific fields:
>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=("birth_date",))
Наборы модельных форм¶
- class models.BaseModelFormSet¶
Like regular formsets, Django provides a couple
of enhanced formset classes to make working with Django models more
convenient. Let’s reuse the Author model from above:
>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
Using fields restricts the formset to use only the given fields.
Alternatively, you can take an «opt-out» approach, specifying which fields to
exclude:
>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))
This will create a formset that is capable of working with the data associated
with the Author model. It works just like a regular formset:
>>> 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">
<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() для создания набора форм. Это означает, что функционал модельных форм является надстройкой над функционалом набора обычных форм.
Примечание
При использовании многотабличного наследования формы, созданные фабрикой набора форм, будут содержать поле родительской ссылки (по умолчанию <parent_model_name>_ptr) вместо поля id.
Изменение выборки¶
By default, when you create a formset from a model, the formset will use a
queryset that includes all objects in the model (e.g.,
Author.objects.all()). You can override this behavior by using the
queryset argument:
>>> 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')
Then, pass your BaseAuthorFormSet class to the factory function:
>>> AuthorFormSet = modelformset_factory(
... Author, fields=('name', 'title'), formset=BaseAuthorFormSet)
If you want to return a formset that doesn’t include any pre-existing instances of the model, you can specify an empty 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¶
Using the widgets parameter, you can specify a dictionary of values to
customize the ModelForm’s widget class for a particular field. This
works the same way as the widgets dictionary on the inner Meta
class of a ModelForm works:
>>> 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(). Тем не менее, в случае набора модельных форм, начальными значениями заполняются только пустые, т.е. новые, формы.
Сохранение объектов набора форм¶
As with a ModelForm, you can save the data as a model object. This is done
with the formset’s save() method:
# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)
# Assuming all is valid, save the data.
>>> instances = formset.save()
Метод save() возвращает экземпляры объектов, которые были сохранены в базе данных. Те объекты, данные которых не изменились, не сохраняются в базе данных и не отображаются в возвращаемом значении (instances из предыдущего примера).
Когда форма содержит не все поля модели (например, потому что некоторые из них были явно исключены), то отсутствующие поля не будут сохранены через метод save(). Подробнее об этом ограничении модельных форм написано в Указываем какие поля использовать.
Pass commit=False to return the unsaved model instances:
# 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 does not prevent existing objects from being displayed:
>>> 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']
Also, extra=0 doesn’t prevent creation of new model instances as you can
add additional forms with JavaScript
or send additional POST data. Formsets don’t yet provide functionality for an «edit only» view that prevents creation of new instances.
If the value of max_num is greater than the number of existing related
objects, up to extra additional blank forms will be added to the formset,
so long as the total number of forms does not exceed 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(), следует вызывать метод 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.
First, you can let the formset do most of the work:
<form method="post">
{{ formset }}
</form>
Second, you can manually render the formset, but let the form deal with itself:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
При самостоятельном отображении форм, не забудьте отобразить техническую форму, как было показано выше. Обратитесь к документации на технические формы.
Third, you can manually render each field:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{% for field in form %}
{{ field.label_tag }} {{ field }}
{% endfor %}
{% endfor %}
</form>
If you opt to use this third method and you don’t iterate over the fields with
a {% for %} loop, you’ll need to render the primary key field. For example,
if you were rendering the name and age fields of a model:
<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)
If you want to create a formset that allows you to edit books belonging to a particular author, you could do this:
>>> 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.
Then when you create your inline formset, pass in the optional argument
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()
To resolve this, you can use fk_name to
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 выше.