Создание форм из моделей ¶
ModelForm
¶
-
класс
ModelForm
¶
Если вы создаете приложение на основе базы данных, скорее всего, у вас будут формы, которые близко соответствуют моделям Django. Например, у вас может быть BlogComment
модель, и вы хотите создать форму, позволяющую людям отправлять комментарии. В этом случае было бы излишним определять типы полей в вашей форме, потому что вы уже определили поля в своей модели.
По этой причине Django предоставляет вспомогательный класс, который позволяет вам создавать Form
класс из модели Django.
Например:
>>> from django.forms import ModelForm
>>> from myapp.models import Article
# Create the form class.
>>> class ArticleForm(ModelForm):
... class Meta:
... model = Article
... fields = ['pub_date', 'headline', 'content', 'reporter']
# Creating a form to add an article.
>>> form = ArticleForm()
# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)
Типы полей ¶
Созданный Form
класс будет иметь поле формы для каждого указанного поля модели в порядке, указанном в fields
атрибуте.
Каждое поле модели имеет соответствующее поле формы по умолчанию. Например, значок
CharField
на модели отображается как значок CharField
на форме. Модель
ManyToManyField
представлена в виде файла MultipleChoiceField
. Вот полный список преобразований:
Как и следовало ожидать, ForeignKey
и ManyToManyField
типы полей модели являются частными случаями:
ForeignKey
представленdjango.forms.ModelChoiceField
какChoiceField
, выбор которого является модельюQuerySet
.ManyToManyField
представленdjango.forms.ModelMultipleChoiceField
какMultipleChoiceField
, выбор которого является модельюQuerySet
.
Кроме того, каждое сгенерированное поле формы имеет следующие атрибуты:
- Если в поле модели есть
blank=True
, то в поле формыrequired
устанавливается значениеFalse
. В противном случаеrequired=True
. - В поле формы
label
устанавливается значениеverbose_name
поля модели, с первым символом с заглавной буквы. - Поле формы
help_text
устанавливаетсяhelp_text
равным полю модели. - Если поле модели
choices
установлено, тогда поле формыwidget
будет установлено наSelect
, с выбором, поступающим из поля моделиchoices
. Варианты выбора обычно включают пустой вариант, который выбран по умолчанию. Если поле является обязательным, это заставляет пользователя сделать выбор. Пустой вариант не будет включен, если поле модели имеетblank=False
явноеdefault
значение (default
вместо этого будет изначально выбрано значение).
Наконец, обратите внимание, что вы можете переопределить поле формы, используемое для данного поля модели. См. Раздел « Замена полей по умолчанию» ниже.
Полный пример ¶
Рассмотрим этот набор моделей:
from django.db import models
from django.forms import ModelForm
TITLE_CHOICES = [
('MR', 'Mr.'),
('MRS', 'Mrs.'),
('MS', 'Ms.'),
]
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ['name', 'title', 'birth_date']
class BookForm(ModelForm):
class Meta:
model = Book
fields = ['name', 'authors']
С этими моделями приведенные ModelForm
выше подклассы были бы примерно эквивалентны этому (единственное отличие состоит в save()
методе, который мы обсудим чуть позже):
from django import forms
class AuthorForm(forms.Form):
name = forms.CharField(max_length=100)
title = forms.CharField(
max_length=3,
widget=forms.Select(choices=TITLE_CHOICES),
)
birth_date = forms.DateField(required=False)
class BookForm(forms.Form):
name = forms.CharField(max_length=100)
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
Проверка на ModelForm
¶
Есть два основных этапа проверки ModelForm
:
Как и обычная проверка формы, проверка формы модели запускается неявно при вызове is_valid()
или доступе к
errors
атрибуту и явно при вызове
full_clean()
, хотя вы обычно не будете использовать последний метод на практике.
Model
validation ( Model.full_clean()
) запускается на этапе проверки формы сразу после вызова clean()
метода формы .
Предупреждение
Процесс очистки изменяет экземпляр модели, переданный
ModelForm
конструктору, различными способами. Например, любые поля даты в модели преобразуются в фактические объекты даты. Неудачная проверка может привести к тому, что базовый экземпляр модели окажется в несогласованном состоянии, поэтому повторно использовать его не рекомендуется.
Переопределение метода clean () ¶
Вы можете переопределить clean()
метод в форме модели, чтобы обеспечить дополнительную проверку так же, как и в обычной форме.
Экземпляр формы модели, прикрепленный к объекту модели, будет содержать instance
атрибут, который дает его методам доступ к этому конкретному экземпляру модели.
Предупреждение
ModelForm.clean()
Метод устанавливает флаг , который делает проверки модели шаг проверки уникальности модельных полей, которые помечены как unique
, unique_together
или
unique_for_date|month|year
.
Если вы хотите переопределить clean()
метод и сохранить эту проверку, вы должны вызвать метод родительского класса clean()
.
Взаимодействие с валидацией модели ¶
В рамках процесса проверки ModelForm
будет вызывать clean()
метод каждого поля в вашей модели, у которого есть соответствующее поле в вашей форме. Если вы исключили какие-либо поля модели, проверка этих полей выполняться не будет. См. Документацию по проверке формы, чтобы узнать больше о том, как работают очистка и проверка полей.
Метод модели clean()
будет вызываться до того, как будут выполнены какие-либо проверки уникальности. См. Раздел Проверка объектов для получения дополнительной информации о ловушке модели clean()
.
Соображения относительно модели error_messages
¶
Сообщения об ошибках, определенные на
уровне или на
уровне мета-формы, всегда имеют приоритет над сообщениями об ошибках, определенными на
уровне.form field
model field
Сообщения об ошибках , заданных на используются только тогда , когда
возникают во время проверки модели шага и никаких соответствующих сообщений об ошибках не будут определены на уровне формы.model fields
ValidationError
Вы можете переопределить сообщения об ошибках, 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
. A ValueError
будет вызываться, если данные в форме не проверяются, т. Е. Если form.errors
оценивается как True
.
Если необязательное поле не отображается в данных формы, полученный экземпляр модели использует поле модели default
, если оно есть, для этого поля. Это поведение не применяется к полям, которые используют
CheckboxInput
,
CheckboxSelectMultiple
или
SelectMultiple
(или любой настраиваемый виджет, value_omitted_from_data()
метод которого
всегда возвращает
False
), поскольку не отмеченный флажок и невыделенный флажок
не отображаются в данных отправки формы HTML. Используйте настраиваемое поле формы или виджет, если вы разрабатываете API и хотите использовать резервное поведение по умолчанию для поля, которое использует один из этих виджетов.<select multiple>
Этот save()
метод принимает необязательный commit
аргумент ключевого слова, который принимает либо True
или False
. Если вы вызовете save()
with
commit=False
, он вернет объект, который еще не был сохранен в базе данных. В этом случае вы должны вызвать save()
полученный экземпляр модели. Это полезно, если вы хотите выполнить пользовательскую обработку объекта перед его сохранением или если вы хотите использовать одну из специальных
опций сохранения модели . commit
это True
по умолчанию.
Другой побочный эффект использования commit=False
проявляется, когда ваша модель имеет отношение «многие ко многим» с другой моделью. Если ваша модель имеет отношение «многие ко многим» и вы указываете 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
внутреннегоMeta
класса в список полей, которые нужно исключить из формы.Например:
class PartialAuthorForm(ModelForm): class Meta: model = Author exclude = ['title']
Так как
Author
модель имеет 3 поляname
,title
иbirth_date
это приведет к полямname
иbirth_date
присутствует в форме.
Если используется любой из них, порядок отображения полей в форме будет соответствовать порядку, в котором поля определены в модели, при этом ManyToManyField
экземпляры будут отображаться последними.
Кроме того, Django применяет следующее правило: если вы установите editable=False
поле модели, любая форма, созданная на основе модели ModelForm
, не будет включать это поле.
Примечание
Любые поля, не включенные в форму с помощью вышеуказанной логики, не будут установлены методом формы save()
. Кроме того, если вы вручную добавите исключенные поля обратно в форму, они не будут инициализированы из экземпляра модели.
Джанго предотвратить любую попытку спасти неполную модель, поэтому , если модель не позволяет недостающие поля , чтобы быть пустым, и не обеспечивает значения по умолчанию для отсутствующих полей, любая попытка
с отсутствующими полями потерпит неудачу. Чтобы избежать этого сбоя, вы должны создать экземпляр своей модели с начальными значениями для отсутствующих, но обязательных полей: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
был представлен a <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
тип полей, создаваемых формой.
Например, если вы хотите использовать MySlugFormField
для slug
поля, вы можете сделать следующее:
from django.forms import ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
field_classes = {
'slug': MySlugFormField,
}
Наконец, если вам нужен полный контроль над полем, включая его тип, валидаторы, обязательные и т. Д., Вы можете сделать это, декларативно указав поля, как в обычном Form
.
Если вы хотите указать валидаторы поля, вы можете сделать это, декларативно определив поле и установив его validators
параметр:
from django.forms import CharField, ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
slug = CharField(validators=[validate_slug])
class Meta:
model = Article
fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
Примечание
Когда вы явно создаете экземпляр такого поля формы, важно понимать, как связаны ModelForm
и регулярные Form
.
ModelForm
это обычный, Form
который может автоматически генерировать определенные поля. Поля, которые создаются автоматически, зависят от содержимого Meta
класса и от того, какие поля уже были определены декларативно. По сути, ModelForm
будут генерироваться только поля, которые отсутствуют в форме, или, другими словами, поля, которые не были определены декларативно.
Поля определенно декларативно осталось как есть, поэтому любые настройки сделаны Meta
атрибуты , такие как widgets
, labels
, help_texts
или error_messages
игнорируются; они применимы только к полям, которые создаются автоматически.
Точно так же декларативно определенные поля не извлекают свои атрибуты, как
max_length
или required
из соответствующей модели. Если вы хотите сохранить поведение, указанное в модели, вы должны явно указать соответствующие аргументы при объявлении поля формы.
Например, если Article
модель выглядит так:
class Article(models.Model):
headline = models.CharField(
max_length=200,
null=True,
blank=True,
help_text='Use puns liberally',
)
content = models.TextField()
и вы хотите сделать некоторые пользовательские проверки для headline
, сохраняя при этом blank
и help_text
значения , как указано, можно определить
ArticleForm
следующим образом:
class ArticleForm(ModelForm):
headline = MyFormField(
max_length=200,
required=False,
help_text='Use puns liberally',
)
class Meta:
model = Article
fields = ['headline', 'content']
Вы должны убедиться, что тип поля формы можно использовать для установки содержимого соответствующего поля модели. Если они несовместимы, вы получите сообщение, так ValueError
как неявное преобразование не происходит.
См. Документацию по полям формы для получения дополнительной информации о полях и их аргументах.
Включение локализации полей ¶
По умолчанию поля в a 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
появляется первым в MRO. Это потому, что эти классы полагаются на разные метаклассы, а у класса может быть только один метакласс.Можно декларативно удалить
Field
унаследованный от родительского класса, указав имяNone
подкласса.Вы можете использовать этот метод только для отказа от поля, декларативно определенного родительским классом; это не помешает
ModelForm
метаклассу создать поле по умолчанию. Чтобы отказаться от использования полей по умолчанию, см. Выбор полей для использования .
Предоставление начальных значений ¶
Как и в случае с обычными формами, можно указать начальные данные для форм, указав initial
параметр при создании экземпляра формы. Предоставленные таким образом начальные значения переопределяют как начальные значения из поля формы, так и значения из присоединенного экземпляра модели. Например:
>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
>>> form = ArticleForm(initial={'headline': 'Initial headline'}, instance=article)
>>> form['headline'].value()
'Initial headline'
Заводская функция ModelForm ¶
Вы можете создавать формы из данной модели, используя автономную функцию
modelform_factory()
, вместо использования определения класса. Это может быть более удобным, если у вас не так много настроек:
>>> from django.forms import modelform_factory
>>> from myapp.models import Book
>>> BookForm = modelform_factory(Book, fields=("author", "title"))
Это также можно использовать для внесения изменений в существующие формы, например, указав виджеты, которые будут использоваться для данного поля:
>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm,
... widgets={"title": Textarea()})
Поля для включения могут быть определены с помощью fields
и exclude
именованные аргументы, или соответствующие атрибуты на ModelForm
внутреннем
Meta
классе. См.
Документацию « ModelForm
Выбор полей для использования» .
… Или включите локализацию для определенных полей:
>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=("birth_date",))
Модельные наборы форм ¶
-
класс
models.
BaseModelFormSet
¶
Как и обычные наборы форм , Django предоставляет несколько расширенных классов наборов форм, которые делают работу с моделями Django более удобной. Давайте повторно воспользуемся Author
моделью сверху:
>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
Использование fields
ограничивает набор форм использовать только указанные поля. В качестве альтернативы вы можете воспользоваться методом отказа, указав, какие поля следует исключить:
>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))
Это создаст набор форм, способный работать с данными, связанными с Author
моделью. Он работает как обычный набор форм:
>>> formset = AuthorFormSet()
>>> print(formset)
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS"><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS"><input type="hidden" name="form-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
поля.
Изменение набора запросов ¶
По умолчанию, когда вы создаете набор форм из модели, набор форм будет использовать набор запросов, который включает все объекты в модели (например,
Author.objects.all()
). Вы можете изменить это поведение, используя
queryset
аргумент:
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
Кроме того , вы можете создать подкласс , который устанавливает self.queryset
в
__init__
:
from django.forms import BaseModelFormSet
from myapp.models import Author
class BaseAuthorFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.queryset = Author.objects.filter(name__startswith='O')
Затем передайте свой BaseAuthorFormSet
класс фабричной функции:
>>> AuthorFormSet = modelformset_factory(
... Author, fields=('name', 'title'), formset=BaseAuthorFormSet)
Если вы хотите вернуть набор форм, который не включает какие - либо ранее существовавшие экземпляры модели, вы можете указать пустой QuerySet:
>>> AuthorFormSet(queryset=Author.objects.none())
Изменение формы ¶
По умолчанию, когда вы используете modelformset_factory
, модельная форма будет создана с использованием modelform_factory()
. Часто бывает полезно указать настраиваемую форму модели. Например, вы можете создать настраиваемую форму модели с настраиваемой проверкой:
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
fields = ('name', 'title')
def clean_name(self):
# custom validation for the name field
...
Затем передайте форму модели в функцию factory:
AuthorFormSet = modelformset_factory(Author, form=AuthorForm)
Не всегда необходимо определять пользовательскую форму модели.
modelformset_factory
Функция имеет несколько аргументов , которые передаются через modelform_factory
, которые описаны ниже.
Указание виджетов для использования в форме с помощью widgets
¶
Используя widgets
параметр, вы можете указать словарь значений для настройки ModelForm
класса виджета для определенного поля. Это работает так же, как widgets
словарь во внутреннем Meta
классе ModelForm
произведения:
>>> AuthorFormSet = modelformset_factory(
... Author, fields=('name', 'title'),
... widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20})})
Включение локализации для полей с localized_fields
¶
С помощью localized_fields
параметра вы можете включить локализацию полей в форме.
>>> AuthorFormSet = modelformset_factory(
... Author, fields=('name', 'title', 'birth_date'),
... localized_fields=('birth_date',))
Если localized_fields
установлено специальное значение '__all__'
, все поля будут локализованы.
Предоставление начальных значений ¶
Как и в случае с обычными наборами форм, можно указать начальные данные для форм в наборе форм, указав initial
параметр при создании экземпляра класса набора форм модели, возвращаемого
modelformset_factory()
. Однако с наборами форм модели начальные значения применяются только к дополнительным формам, которые не прикреплены к существующему экземпляру модели. Если длина initial
превышает количество дополнительных форм, лишние исходные данные игнорируются. Если дополнительные формы с исходными данными не изменены пользователем, они не будут проверены или сохранены.
Сохранение объектов в наборе форм ¶
Как и в случае ModelForm
, вы можете сохранить данные как объект модели. Это делается с помощью save()
метода набора форм :
# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)
# Assuming all is valid, save the data.
>>> instances = formset.save()
save()
Метод возвращает экземпляры , которые были сохранены в базе данных. Если данные данного экземпляра не изменились в связанных данных, экземпляр не будет сохранен в базе данных и не будет включен в возвращаемое значение ( instances
в приведенном выше примере).
Если поля отсутствуют в форме (например, потому что они были исключены), эти поля не будут установлены save()
методом. Дополнительную информацию об этом ограничении, которое также действует для обычных ModelForms
, можно найти
в разделе Выбор полей для использования .
Передайте, commit=False
чтобы вернуть несохраненные экземпляры модели:
# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
... # do something with instance
... instance.save()
Это дает вам возможность прикреплять данные к экземплярам перед их сохранением в базе данных. Если ваш набор форм содержит ManyToManyField
, вам также потребуется вызвать, formset.save_m2m()
чтобы гарантировать правильное сохранение отношений «многие ко многим».
После вызова save()
ваш модельный набор форм будет иметь три новых атрибута, содержащих изменения набора форм:
-
models.BaseModelFormSet.
changed_objects
¶
-
models.BaseModelFormSet.
deleted_objects
¶
-
models.BaseModelFormSet.
new_objects
¶
Ограничение количества редактируемых объектов ¶
Как и обычные FormSets, вы можете использовать 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()
метод a
ModelFormSet
будет проверять, что ни один из элементов в наборе форм не нарушает уникальные ограничения вашей модели (либо unique
, unique_together
или
unique_for_date|month|year
). Если вы хотите переопределить clean()
метод в a 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
. Изменения значения
form.cleaned_data
недостаточно, чтобы повлиять на сохраненное значение. Если вы хотите изменить значение в, ModelFormSet.clean()
вы должны изменить
form.instance
:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
for form in self.forms:
name = form.cleaned_data['name'].upper()
form.cleaned_data['name'] = name
# update the instance value.
form.instance.name = name
Использование настраиваемого набора запросов ¶
Как указывалось ранее, вы можете переопределить набор запросов по умолчанию, используемый набором форм модели:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
if request.method == "POST":
formset = AuthorFormSet(
request.POST, request.FILES,
queryset=Author.objects.filter(name__startswith='O'),
)
if formset.is_valid():
formset.save()
# Do something.
else:
formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
return render(request, 'manage_authors.html', {'formset': formset})
Обратите внимание, что
в этом примере мы передаем queryset
аргумент как в случаях, так POST
и в GET
случаях.
Использование набора форм в шаблоне ¶
Есть три способа визуализировать набор форм в шаблоне Django.
Во-первых, вы можете позволить набору форм выполнять большую часть работы:
<form method="post">
{{ formset }}
</form>
Во-вторых, вы можете визуализировать набор форм вручную, но пусть форма сама сама себя обрабатывает:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
Когда вы вручную визуализируете формы, обязательно визуализируйте форму управления, как показано выше. См. Документацию по форме управления .
В-третьих, вы можете вручную отобразить каждое поле:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{% for field in form %}
{{ field.label_tag }} {{ field }}
{% endfor %}
{% endfor %}
</form>
Если вы решите использовать этот третий метод и не перебираете поля с помощью цикла, вам необходимо отобразить поле первичного ключа. Например, если вы отдавая и поле модели:{% for %}
name
age
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
<ul>
<li>{{ form.name }}</li>
<li>{{ form.age }}</li>
</ul>
{% endfor %}
</form>
Обратите внимание, как нам нужно явно выполнить рендеринг . Это гарантирует правильную работу модельного набора форм . (В этом примере предполагается, что первичный ключ имеет имя . Если вы явно определили свой собственный первичный ключ, который не вызывается , убедитесь, что он отображается.){{ form.id }}
POST
id
id
Встроенные наборы форм ¶
-
класс
models.
BaseInlineFormSet
¶
Встроенные наборы форм - это небольшой слой абстракции над наборами форм модели. Это упрощает работу со связанными объектами через внешний ключ. Предположим, у вас есть эти две модели:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
Если вы хотите создать набор форм, позволяющий редактировать книги, принадлежащие определенному автору, вы можете сделать это:
>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=('title',))
>>> author = Author.objects.get(name='Mike Royko')
>>> formset = BookFormSet(instance=author)
BookFormSet
«ы префикса есть 'book_set'
( ). Если «s , чтобы есть
, что вместо этого используется.<model name>_set
Book
ForeignKey
Author
related_name
Примечание
inlineformset_factory()
использует
modelformset_factory()
и маркирует
can_delete=True
.
Смотрите также
Переопределение методов на InlineFormSet
¶
При переопределении методов InlineFormSet
вы должны создавать подклассы,
BaseInlineFormSet
а не
BaseModelFormSet
.
Например, если вы хотите переопределить clean()
:
from django.forms import BaseInlineFormSet
class CustomInlineFormSet(BaseInlineFormSet):
def clean(self):
super().clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
См. Также Переопределение clean () в ModelFormSet .
Затем, когда вы создаете свой встроенный набор форм, передайте необязательный аргумент
formset
:
>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=('title',),
... formset=CustomInlineFormSet)
>>> author = Author.objects.get(name='Mike Royko')
>>> formset = BookFormSet(instance=author)
Более одного внешнего ключа для одной модели ¶
Если ваша модель содержит более одного внешнего ключа для одной и той же модели, вам необходимо разрешить неоднозначность вручную, используя fk_name
. Например, рассмотрим следующую модель:
class Friendship(models.Model):
from_friend = models.ForeignKey(
Friend,
on_delete=models.CASCADE,
related_name='from_friends',
)
to_friend = models.ForeignKey(
Friend,
on_delete=models.CASCADE,
related_name='friends',
)
length_in_months = models.IntegerField()
Чтобы решить эту проблему, вы можете использовать fk_name
для
inlineformset_factory()
:
>>> FriendshipFormSet = inlineformset_factory(Friend, Friendship, fk_name='from_friend',
... fields=('to_friend', 'length_in_months'))
Использование встроенного набора форм в представлении ¶
Вы можете захотеть предоставить представление, которое позволяет пользователю редактировать связанные объекты модели. Вот как это сделать:
def manage_books(request, author_id):
author = Author.objects.get(pk=author_id)
BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
if request.method == "POST":
formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
if formset.is_valid():
formset.save()
# Do something. Should generally end with a redirect. For example:
return HttpResponseRedirect(author.get_absolute_url())
else:
formset = BookInlineFormSet(instance=author)
return render(request, 'manage_books.html', {'formset': formset})
Обратите внимание , как мы проходим instance
в обоих POST
и GET
случаях.
Указание виджетов для использования во встроенной форме ¶
inlineformset_factory
использует modelformset_factory
и передает большинство своих аргументов modelformset_factory
. Это означает, что вы можете использовать widgets
параметр так же, как и передать его
modelformset_factory
. См. Указание виджетов для использования в форме с виджетами выше.