Создание форм из шаблонов

ModelForm

класс ModelForm

Если вы создаете приложение, управляемое базой данных, скорее всего, ваши формы будут точно соответствовать моделям Django. Например, у вас может быть шаблон BlogComment и вы хотите создать форму для отправки комментариев. В этом случае было бы излишним определять типы полей в форме, потому что вы уже определили поля на уровне шаблона.

По этой причине Django предоставляет служебный класс для создания класса формы Form из модели Django.

Например :

>>> from django.forms import ModelForm
>>> from myapp.models import Article

# Create the form class.
>>> class ArticleForm(ModelForm):
...     class Meta:
...         model = Article
...         fields = ['pub_date', 'headline', 'content', 'reporter']

# Creating a form to add an article.
>>> form = ArticleForm()

# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)

Типы полей

Сгенерированный класс формы будет содержать одно поле формы для каждого включенного поля модели в порядке, указанном атрибутом fields .

Каждое поле шаблона имеет поле формы по умолчанию. Например, поле CharField шаблона представлено полем формы CharField . Поле ManyToManyField в шаблоне представлено полем формы MultipleChoiceField . Вот полный список совпадений:

Поле модели Поле формы
AutoField Не отображается в форме
BigAutoField Не отображается в форме
BigIntegerField IntegerField с min_value -9223372036854775808 и max_value 9223372036854775807.
BinaryField CharField , Если editable установлено значение True на поле шаблона, в противном случае оно не отображается в форме.
BooleanField BooleanField , или NullBooleanField если null=True .
CharField CharField with max_length установлено в то же значение, что max_length и поле шаблона, и empty_value установлено в None if null=True .
DateField DateField
DateTimeField DateTimeField
DecimalField DecimalField
DurationField DurationField
EmailField EmailField
FileField FileField
FilePathField FilePathField
FloatField FloatField
ForeignKey ModelChoiceField (увидеть ниже)
ImageField ImageField
IntegerField IntegerField
IPAddressField IPAddressField
GenericIPAddressField GenericIPAddressField
ManyToManyField ModelMultipleChoiceField (увидеть ниже)
NullBooleanField NullBooleanField
PositiveBigIntegerField IntegerField
PositiveIntegerField IntegerField
PositiveSmallIntegerField IntegerField
SlugField SlugField
SmallAutoField Не отображается в форме
SmallIntegerField IntegerField
TextField CharField с участием widget=forms.Textarea
TimeField TimeField
URLField URLField
UUIDField UUIDField

Как и следовало ожидать, модели полей типа ForeignKey и ManyToManyField являются особыми случаями:

  • ForeignKey представлен django.forms.ModelChoiceField как поле ChoiceField , возможные варианты выбора которого определяются QuerySet элементами модели.
  • ManyToManyField представлен django.forms.ModelMultipleChoiceField полем MultipleChoiceField , возможные варианты которого определяются QuerySet шаблоном.

Кроме того, каждое созданное поле формы имеет следующие атрибуты:

  • Если в поле шаблона есть blank=True , required то False в поле формы устанавливается значение . В противном случае required=True .
  • Метка ( label ) поля формы устанавливается равной значению verbose_name поля шаблона, с первым символом в верхнем регистре.
  • Значение help_text поля формы устанавливается равным значению help_text поля шаблона.
  • Если значение choices поля шаблона установлено, компонент ( widget ) поля формы будет иметь тип Select , выбор которого будет происходить из значения choices поля шаблона. Эти варианты обычно включают пустой вариант, выбранный по умолчанию. Если поле является обязательным, оно заставляет пользователя сделать выбор. Пустой вариант не включается, если поле шаблона также имеет явное blank=False значение по умолчанию ( default ) (это значение, default которое будет выбрано изначально).

Наконец, обратите внимание, что вы можете переопределить поле формы, используемое для данного поля шаблона. См. Раздел « Перегрузка полей по умолчанию» ниже.

Полный пример

Рассмотрим этот набор моделей:

from django.db import models
from django.forms import ModelForm

TITLE_CHOICES = [
    ('MR', 'Mr.'),
    ('MRS', 'Mrs.'),
    ('MS', 'Ms.'),
]

class Author(models.Model):
    name = models.CharField(max_length=100)
    title = models.CharField(max_length=3, choices=TITLE_CHOICES)
    birth_date = models.DateField(blank=True, null=True)

    def __str__(self):
        return self.name

class Book(models.Model):
    name = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ['name', 'title', 'birth_date']

class BookForm(ModelForm):
    class Meta:
        model = Book
        fields = ['name', 'authors']

С этими моделями приведенные ModelForm выше подклассы были бы примерно эквивалентны этому (с той лишь разницей, что метод, который save() мы рассмотрим позже):

from django import forms

class AuthorForm(forms.Form):
    name = forms.CharField(max_length=100)
    title = forms.CharField(
        max_length=3,
        widget=forms.Select(choices=TITLE_CHOICES),
    )
    birth_date = forms.DateField(required=False)

class BookForm(forms.Form):
    name = forms.CharField(max_length=100)
    authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())

Проверка ModelForm

Валидация a ModelForm различается на два важных этапа:

  1. Форма проверки
  2. Проверка экземпляра модели

Как и в случае обычной проверки формы, проверка форм модели запускается неявно при вызове is_valid() или доступе к атрибуту errors или явно при вызове full_clean() , хотя на практике последний метод используется редко.

Проверка Model ( Model.full_clean() ) запускается на этапе проверки формы, сразу после вызова метода clean() формы.

Предупреждение

Процесс очистки изменяет экземпляр модели, переданный конструктору, ModelForm несколькими способами. Например, любое значение поля даты в модели преобразуется в реальный объект даты. Неудачная проверка может привести к тому, что базовый экземпляр модели окажется в несогласованном состоянии, поэтому повторно использовать его не рекомендуется.

Перегрузка метода clean ()

Вы можете переопределить метод clean() формы шаблона для выполнения дополнительных проверок так же, как для обычной формы.

Экземпляр формы модели, связанный с объектом модели, содержит атрибут, instance который дает его методам доступ к этому конкретному экземпляру модели.

Предупреждение

Метод ModelForm.clean() устанавливает флаг, который заставляет этап проверки модели проверять уникальность полей модели, отмеченных как unique , unique_together или unique_for_date|month|year .

Если вы хотите переопределить метод clean() и сохранить эту проверку, вы должны вызвать метод clean() родительского класса.

Взаимодействие с проверкой модели

В рамках процесса проверки ModelForm вызывает метод clean() каждого поля в модели, имеющего соответствующее поле формы. Если вы исключили поля шаблона, проверка для этих полей выполняться не будет. См. Документацию по проверке формы, чтобы узнать больше о том, как работают очистка и проверка полей.

Метод clean() модели вызывается перед проверкой уникальности. Дополнительную информацию о методе модели см. В разделе « Проверка объекта»clean() .

Рекомендации по сообщениям об ошибках модели

Сообщения об ошибках, определенные на уровне или на уровне метакласса формы, всегда имеют приоритет над сообщениями об ошибках, определенными на уровне .champs de formulaire champs de modèle

Сообщения об ошибках, определенные на уровне , используются только тогда, когда ошибка генерируется на этапе проверки модели, и на уровне формы не определено соответствующее сообщение об ошибке.champs de modèle ValidationError

Вы можете переопределить сообщения об ошибках от NON_FIELD_ERRORS и сгенерированные при проверке моделей, добавив ключ NON_FIELD_ERRORS в словарь внутреннего error_messages класса Meta классов ModelForm :

from django.core.exceptions import NON_FIELD_ERRORS
from django.forms import ModelForm

class ArticleForm(ModelForm):
    class Meta:
        error_messages = {
            NON_FIELD_ERRORS: {
                'unique_together': "%(model_name)s's %(field_labels)s are not unique.",
            }
        }

Метод save()

У каждого ModelForm тоже есть свой метод save() . Это создает и сохраняет объект в базе данных из данных, введенных в форму. Подкласс ModelForm может принимать существующий экземпляр модели по названному параметру instance . Если он присутствует, save() обновите этот экземпляр. В противном случае save() создайте новый экземпляр рассматриваемой модели:

>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm

# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)

# Save a new Article object from the form's data.
>>> new_article = f.save()

# Create a form to edit an existing Article, but use
# POST data to populate the form.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()

Обратите внимание: если форма не была проверена , вызов save() позаботится об этом путем проверки form.errors . Ошибка ValueError генерируется, если данные формы недействительны, например, если form.errors оцениваются в True .

Если необязательное поле отсутствует в данных формы, полученный экземпляр модели использует содержимое default поля модели как значение поля , если оно определено. Такое поведение не относится к полям , которые используют CheckboxInput , CheckboxSelectMultiple или SelectMultiple (или любой другой пользовательский компонент метод которого value_omitted_from_data() всегда возвращается False ) , так как непроверенный или неотобранным флажок не включен при передаче данных. 'HTML-форма. Используйте настраиваемое поле или компонент формы, если вы разрабатываете API и хотите добиться базового поведения по умолчанию для поля, которое использует один из этих компонентов.<select multiple>

Этот метод save() принимает необязательный именованный параметр commit , который принимает значения True или False . Если вы вызываете save() with commit=False , он возвращает объект, который еще не был сохранен в базе данных. В этом случае вы должны вызвать save() получившийся экземпляр модели. Это полезно, если вы хотите выполнить специальную обработку объекта перед его сохранением или если вы хотите использовать одну из специальных опций сохранения модели . commit по True умолчанию.

Другой побочный эффект использования commit=False - это когда модель имеет отношение «многие ко многим» с другой моделью. В этом случае Django не может сразу сохранить данные формы для отношения «многие ко многим». Это связано с тем, что невозможно сохранить данные «многие ко многим» для конкретного экземпляра, если экземпляр еще не существует в базе данных.

Чтобы обойти эту проблему, каждый раз, когда вы сохраняете форму с помощью commit=False , Django добавляет метод save_m2m() в ваш подкласс ModelForm . После того, как вы вручную зарегистрируете производный экземпляр формы, вы можете вызвать save_m2m() для регистрации данных формы «многие ко многим». Например :

# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)

# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)

# Modify the author in some way.
>>> new_author.some_field = 'some_value'

# Save the new instance.
>>> new_author.save()

# Now, save the many-to-many data for the form.
>>> f.save_m2m()

Звонить полезно только в том save_m2m() случае, если вы используете save(commit=False) . Когда вы вызываете метод save() формы, все данные, включая данные «многие ко многим», сохраняются без необходимости делать что-либо еще. Например :

# Create a form instance with POST data.
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)

# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()

Другие , чем save() и методов save_m2m() , один ModelForm работает точно так же , как и в любой другой форме forms . Например, метод is_valid() используется для проверки действительности, метод is_multipart() используется для определения того, требует ли форма отправки файла multipart (и, следовательно, что request.FILES должно быть передано в форму) и т. Д. См. « Связывание загруженных файлов с формой» для получения дополнительной информации.

Выбор полей для использования

Настоятельно рекомендуется явно определить все поля, которые должны присутствовать в форме, с помощью атрибута fields . Если вы этого не сделаете, это может легко стать источником проблем с безопасностью, когда форма позволяет пользователю определять определенные поля неожиданным образом, особенно при добавлении новых полей в шаблон. В зависимости от того, как отображается форма, возможно, проблема даже не отображается на веб-странице.

Альтернативный подход - включить все поля автоматически или удалить только некоторые. Этот фундаментальный подход, как известно, гораздо менее безопасен и привел к серьезным атакам на крупных веб-сайтах (например, GitHub ).

Однако есть два ярлыка, когда вы можете убедиться, что эти вопросы безопасности не применимы к вашей ситуации:

  1. Задайте для атрибута fields специальное значение, '__all__' чтобы указать, что следует использовать все поля в шаблоне. Например :

    from django.forms import ModelForm
    
    class AuthorForm(ModelForm):
        class Meta:
            model = Author
            fields = '__all__'
    
  2. Установите атрибут exclude внутреннего класса Meta из ModelForm к списку полей , чтобы исключить из формы.

    Например :

    class PartialAuthorForm(ModelForm):
        class Meta:
            model = Author
            exclude = ['title']
    

    Поскольку в модели Author есть три поля name , title и birth_date , в итоге мы получаем поля name и birth_date отображаются в форме.

Если используется один из этих методов, порядок появления полей в форме соответствует порядку определения полей модели, причем экземпляры ManyToManyField появляются последними.

Кроме того, Django применяет следующее правило: если вы устанавливаете editable=False на уровне поля модели, любая форма, созданная из модели с помощью ModelForm , не будет содержать это поле.

Заметка

Любое поле, не включенное в форму с помощью вышеуказанной логики, не будет затронуто методом save() формы. Аналогичным образом, если вы вручную добавляете в форму изначально исключенное поле, это поле не будет инициализировано данными из экземпляра модели.

Django предотвращает любые попытки сохранить неполную модель. Если шаблон не позволяет отсутствующие поля , чтобы быть пустым и эти поля не имеют значения по умолчанию, любые попытки вызова save() на один ModelForm с отсутствующими полями потерпит неудачу. Чтобы избежать этой проблемы, вам нужно создать экземпляр шаблона с начальными значениями для отсутствующих обязательных полей:

author = Author(title='Mr')
form = PartialAuthorForm(request.POST, instance=author)
form.save()

Другой вариант - использовать save(commit=False) и вручную определять дополнительные обязательные поля:

form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
author.title = 'Mr'
author.save()

См. Раздел о сохранении форм для получения более подробной информации об использовании save(commit=False) .

Перегрузка полей по умолчанию

Типы полей по умолчанию, как описано в таблице типов полей выше , являются разумным выбором. Если ваш шаблон содержит поле DateField , скорее всего, вы хотите представить это поле в форме с помощью поля формы DateField . Но ModelForm дает гибкость, позволяющую изменять поле формы для определенного шаблона.

Чтобы указать настраиваемый компонент для поля, используйте атрибут внутреннего widgets класса Meta . Это должен быть словарь, сопоставляющий имена полей с классами или экземплярами компонентов.

Например, если вы хотите, чтобы поле CharField атрибута name модели Author было представлено компонентом <textarea> вместо компонента по умолчанию, вы можете переопределить компонент поля:<input type="text">

from django.forms import ModelForm, Textarea
from myapp.models import Author

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        widgets = {
            'name': Textarea(attrs={'cols': 80, 'rows': 20}),
        }

Словарь widgets принимает экземпляры компонентов (например Textarea(...) ) или классов (например Textarea ). Обратите внимание, что словарь widgets игнорируется для полей модели с choices непустым атрибутом . В этом случае вы должны переопределить поле формы, чтобы указать widget другой компонент .

Кроме того, вы можете задать атрибуты labels , help_texts а error_messages внутренний класс , Meta если вам необходимо дополнительно настроить поле.

Например, если вы хотите настроить формулировку всех видимых пользователю текстов, касающихся элемента управления name :

from django.utils.translation import gettext_lazy as _

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        labels = {
            'name': _('Writer'),
        }
        help_texts = {
            'name': _('Some useful help text.'),
        }
        error_messages = {
            'name': {
                'max_length': _("This writer's name is too long."),
            },
        }

Вы также можете указать, field_classes чтобы настроить тип полей, создаваемых формой.

Например, если вы хотите использовать MySlugFormField для поля slug , вы можете сделать следующее:

from django.forms import ModelForm
from myapp.models import Article

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
        field_classes = {
            'slug': MySlugFormField,
        }

Наконец, если вы хотите иметь полный контроль над полем, включая его тип, валидаторы, обязательный символ и т. Д., Вы можете сделать это, определив поля декларативно, как в Form обычной форме .

Если вы хотите указать валидаторы для поля, вы можете сделать это, декларативно определив поле и указав параметр validators :

from django.forms import CharField, ModelForm
from myapp.models import Article

class ArticleForm(ModelForm):
    slug = CharField(validators=[validate_slug])

    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']

Заметка

Когда вы создаете экземпляр формы явно таким образом, важно понимать, как формы типа ModelForm и Form связаны между собой.

ModelForm похожа на Form обычную форму, которая может автоматически генерировать некоторые поля. Автоматически сгенерированные поля зависят от содержимого класса Meta и любых полей, определенных декларативно. По сути, ModelForm будут генерироваться только те поля, которые отсутствуют в форме, или, другими словами, поля, которые не были определены декларативно.

Поля определенно декларативно осталось как есть, так что любые настройки сделаны в терминах атрибутов Meta , такие как widgets , labels , help_texts или error_messages игнорируются; они применимы только к автоматически созданным полям.

Точно так же, декларативно определенные поля не получают свои атрибуты max_length или required из соответствующей модели. Если вы хотите сохранить поведение, определенное на уровне модели, вы должны явно определить соответствующие параметры в объявлении поля формы.

Например, если модель Article выглядит так:

class Article(models.Model):
    headline = models.CharField(
        max_length=200,
        null=True,
        blank=True,
        help_text='Use puns liberally',
    )
    content = models.TextField()

и вы хотите , чтобы выполнить некоторые специальные проверки для headline при сохранении blank и help_text оригинальные ценности , вот как вы можете определить ArticleForm :

class ArticleForm(ModelForm):
    headline = MyFormField(
        max_length=200,
        required=False,
        help_text='Use puns liberally',
    )

    class Meta:
        model = Article
        fields = ['headline', 'content']

Вы должны убедиться, что тип поля формы может использоваться для определения содержимого соответствующего поля шаблона. Если они несовместимы, вы получите сообщение об ошибке, ValueError потому что неявное преобразование не выполняется.

См. Документацию по полям формы для получения дополнительной информации о полях и их параметрах.

Обеспечение регионализации полей

По умолчанию поля ModelForm не разделяют свои данные на регионы. Чтобы активировать эту регионализацию, вы можете использовать атрибут localized_fields класса Meta .

>>> from django.forms import ModelForm
>>> from myapp.models import Author
>>> class AuthorForm(ModelForm):
...     class Meta:
...         model = Author
...         localized_fields = ('birth_date',)

Если localized_fields установлено специальное значение '__all__' , значения всех полей будут регионализированы.

Наследование форм

Как и в случае с основными формами, их можно расширять и повторно использовать ModelForms путем наследования. Это полезно, когда вам нужно объявить дополнительные поля или методы в подклассе для использования в формах, производных от моделей. Например, используя ArticleForm представленный ранее класс :

>>> class EnhancedArticleForm(ArticleForm):
...     def clean_pub_date(self):
...         ...

Это создает форму, которая ведет себя как ArticleForm , за исключением некоторых дополнительных проверок и чисток для поля pub_date .

Также возможно наследование от Meta внутреннего класса родителя, если вам нужно изменить списки Meta.fields или Meta.exclude :

>>> class RestrictedArticleForm(EnhancedArticleForm):
...     class Meta(ArticleForm.Meta):
...         exclude = ('body',)

Здесь мы сохраняем дополнительный метод EnhancedArticleForm и удаляем поле из ArticleForm.Meta исходного класса .

Однако следует отметить некоторые моменты.

  • Применяются обычные правила Python для разрешения имен. Если у вас есть несколько базовых классов, объявляющих внутренний класс Meta , будет рассматриваться только первый. То есть класс Meta подкласса, если он есть, если не класс первого родителя и т. Д.

  • Возможно наследование как от, так Form и от ModelForm . Однако убедитесь, что он ModelForm появляется первым в канале MRO. Это потому, что эти классы основаны на разных метаклассах, и у класса может быть только один метакласс.

  • Можно декларативно удалить поле, Field унаследованное от родительского класса, задав его имя None в подклассе.

    Этот метод можно использовать только для удаления полей, которые были декларативно объявлены в родительском классе; это не помешает метаклассу ModelForm создать поле по умолчанию. Чтобы удалить поля по умолчанию, см. Выбор полей для использования .

Указание начальных значений

Как и в случае с обычными формами, можно указать начальные данные для форм, указав параметр initial при создании экземпляра формы. Исходные значения, предсказанные таким образом, переопределят начальное значение, определенное для поля, и значение, определенное для связанной модели. например

>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
>>> form = ArticleForm(initial={'headline': 'Initial headline'}, instance=article)
>>> form['headline'].value()
'Initial headline'

Заводская функция ModelForm

Вы можете создавать формы из заданного шаблона, используя автономную функцию, modelform_factory() вместо того, чтобы проходить через определение класса. Это может быть более удобным, если вам не нужно вносить слишком много изменений:

>>> from django.forms import modelform_factory
>>> from myapp.models import Book
>>> BookForm = modelform_factory(Book, fields=("author", "title"))

Это также можно использовать для внесения изменений в существующие формы, например, чтобы указать, какие компоненты использовать для определенного поля:

>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm,
...                          widgets={"title": Textarea()})

Поля для включения могут быть заданы с помощью именованных параметров fields и exclude , или соответствующие атрибуты внутреннего класса Meta в ModelForm . Пожалуйста , обратитесь к документации Выбор полей для использования в ModelForm .

… Или активировать регионализацию определенных элементов управления:

>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=("birth_date",))

Сгруппированные формы моделей

класс models.BaseModelFormSet

Как и в случае с обычными сгруппированными формами , Django предоставляет несколько улучшенных классов сгруппированных форм, которые упрощают работу с моделями Django. Давайте повторно воспользуемся моделью, Author определенной выше:

>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))

Использование fields сообщает сгруппированным формам, какие поля использовать. Также можно использовать метод исключения, указав, какие поля следует исключить:

>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))

Это создает набор сгруппированных форм, которые могут обрабатывать данные, связанные с моделью Author . Операция такая же, как и для обычных сгруппированных форм:

>>> formset = AuthorFormSet()
>>> print(formset)
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS"><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS"><input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS">
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100"></td></tr>
<tr><th><label for="id_form-0-title">Title:</label></th><td><select name="form-0-title" id="id_form-0-title">
<option value="" selected>---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select><input type="hidden" name="form-0-id" id="id_form-0-id"></td></tr>

Заметка

modelformset_factory() используется formset_factory() для создания сгруппированных форм. Это означает, что сгруппированная модель является расширением основных сгруппированных форм, которые знают, как взаимодействовать с конкретной моделью.

Заметка

В контекстах наследования нескольких таблиц формы, созданные фабрикой наборов форм, содержат поле родительской ссылки (по умолчанию <nom_modèle_parent>_ptr ) вместо поля id .

Изменение набора запросов

По умолчанию, когда вы создаете сгруппированные формы из шаблона, они используют набор запросов, который содержит все объекты в шаблоне (например, Author.objects.all() ). Вы можете изменить это поведение с помощью параметра queryset :

>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))

Как вариант, вы можете создать подкласс, определяя self.queryset в его методе __init__ :

from django.forms import BaseModelFormSet
from myapp.models import Author

class BaseAuthorFormSet(BaseModelFormSet):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.queryset = Author.objects.filter(name__startswith='O')

Затем передайте свой класс BaseAuthorFormSet фабричной функции:

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title'), formset=BaseAuthorFormSet)

Если вы хотите вернуть сгруппированные формы, которые не содержат каких - либо ранее существовавших экземпляров шаблона, вы можете указать QuerySet пустой объект :

>>> AuthorFormSet(queryset=Author.objects.none())

Редактирование формы

По умолчанию при использовании modelformset_factory шаблон формы создается с помощью modelform_factory() . Часто бывает полезно указать настраиваемую форму шаблона. Например, вы можете создать настраиваемую форму шаблона с настраиваемой проверкой:

class AuthorForm(forms.ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title')

    def clean_name(self):
        # custom validation for the name field
        ...

Затем укажите эту модельную форму производственной функции:

AuthorFormSet = modelformset_factory(Author, form=AuthorForm)

Не всегда необходимо определять настраиваемую форму шаблона. Функция modelformset_factory принимает несколько параметров, которые сами передаются modelform_factory и описаны ниже.

Определение компонентов формы для использования с widgets

Используя параметр widgets , вы можете указать словарь значений для настройки класса компонентов конкретного поля ModelForm . Это тот же принцип работы, что и словарь внутреннего widgets класса Meta a ModelForm :

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title'),
...     widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20})})

Активация регионализации полей с помощью localized_fields

С помощью параметра localized_fields вы можете включить регионализацию полей формы.

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title', 'birth_date'),
...     localized_fields=('birth_date',))

Если localized_fields установлено специальное значение '__all__' , значения всех полей будут регионализированы.

Указание начальных значений

Как и в случае с обычными сгруппированными формами, можно предоставить исходные данные для сгруппированных форм, указав параметр initial при создании экземпляра класса сгруппированных форм модели, возвращаемого modelformset_factory() . Однако для форм, сгруппированных по модели, начальные значения применяются только к дополнительным формам, которые не связаны с экземплярами существующих объектов. Если длина initial превышает количество дополнительных форм, исходные лишние данные игнорируются. Если дополнительные формы с исходными данными не изменяются пользователем, они не будут проверены и сохранены.

Сохранение сгруппированных объектов формы

Как и в случае с одним ModelForm , вы можете сохранить данные как объект модели. Это делается методом save() сгруппированных форм:

# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)

# Assuming all is valid, save the data.
>>> instances = formset.save()

Метод save() возвращает экземпляры, которые были сохранены в базе данных. Если данные для конкретного экземпляра не были изменены в данных формы, экземпляр не будет сохранен в базе данных и не будет включен в возвращаемое значение ( instances в показанном примере -выше).

Когда поля отсутствуют в форме (например, из-за их исключения), эти поля не изменяются методом save() . Дополнительную информацию об этом ограничении, которое также действует для ModelForm обычных форм , можно найти в разделе Выбор полей для использования .

Переключитесь commit=False на получение незарегистрированных экземпляров шаблона:

# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
...     # do something with instance
...     instance.save()

Это позволяет вам определять дополнительные данные в экземплярах перед их сохранением в базе данных. Если ваши сгруппированные формы содержат поле ManyToManyField , вам также потребуется позвонить, formset.save_m2m() чтобы убедиться, что ссылки «многие ко многим» сохранены правильно.

После вызова save() сгруппированные формы модели будут иметь три новых атрибута, содержащих модификации формы:

models.BaseModelFormSet.changed_objects
models.BaseModelFormSet.deleted_objects
models.BaseModelFormSet.new_objects

Ограничение количества редактируемых объектов

Что касается обычных сгруппированных форм, вы можете использовать настройки max_num и extra для modelformset_factory() ограничения количества дополнительных форм отображения.

max_num не предотвращает отображение существующих объектов:

>>> Author.objects.order_by('name')
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']

Кроме того, extra=0 не препятствует созданию новых экземпляров модели, поскольку можно добавлять дополнительные формы с помощью JavaScript или отправлять дополнительные данные POST. Наборы форм еще не предоставляют функциональные возможности представления только для редактирования, которое предотвратило бы создание новых экземпляров.

Если значение max_num больше, чем количество существующих связанных объектов, extra к сгруппированным формам будет добавлено максимум дополнительных пустых форм, если общее количество форм не превышает max_num :

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100"><input type="hidden" name="form-0-id" value="1" id="id_form-0-id"></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100"><input type="hidden" name="form-1-id" value="3" id="id_form-1-id"></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100"><input type="hidden" name="form-2-id" value="2" id="id_form-2-id"></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100"><input type="hidden" name="form-3-id" id="id_form-3-id"></td></tr>

Значение max_num None (по умолчанию) устанавливает высокий предел количества отображаемых форм (1000). На практике это означает отсутствие ограничений.

Использование сгруппированных форм модели на виде

Сгруппированные шаблоны форм очень похожи на обычные сгруппированные формы. Скажем, мы хотим отображать сгруппированные формы для изменения экземпляров модели Author :

from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    if request.method == 'POST':
        formset = AuthorFormSet(request.POST, request.FILES)
        if formset.is_valid():
            formset.save()
            # do something.
    else:
        formset = AuthorFormSet()
    return render(request, 'manage_authors.html', {'formset': formset})

Как видите, логика представления сгруппированных форм модели принципиально не отличается от логики обычных сгруппированных форм. Единственное отличие состоит в том, что мы вызываем formset.save() для сохранения данных в базе данных (как описано выше в разделе « Сохранение объектов для сгруппированных форм» ).

Перегрузка clean() ModelFormSet

Что касается ModelForms метода clean() по ModelFormSet умолчанию, то никакие допустимые элементы в объединенной форме не нарушают уникальные ограничения вашей модели (либо unique , unique_together либо unique_for_date|month|year ). Если вы хотите перегрузить метод clean() из ModelFormSet и поддерживать эту проверку, вы должны вызвать метод clean родительского класса:

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super().clean()
        # example custom validation across forms in the formset
        for form in self.forms:
            # your custom formset validation
            ...

Также обратите внимание, что как только вы достигли этого шага, отдельные экземпляры шаблона для каждой формы уже были созданы. Таким образом, для изменения form.cleaned_data записанного значения недостаточно изменить значение. Если вы хотите изменить значение в ModelFormSet.clean() , вы должны изменить form.instance :

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super().clean()

        for form in self.forms:
            name = form.cleaned_data['name'].upper()
            form.cleaned_data['name'] = name
            # update the instance value.
            form.instance.name = name

С помощью набора пользовательских запросов

Как указывалось ранее, можно переопределить набор запросов, используемый по умолчанию сгруппированными модельными формами:

from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    if request.method == "POST":
        formset = AuthorFormSet(
            request.POST, request.FILES,
            queryset=Author.objects.filter(name__startswith='O'),
        )
        if formset.is_valid():
            formset.save()
            # Do something.
    else:
        formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
    return render(request, 'manage_authors.html', {'formset': formset})

Обратите внимание, что мы передаем параметр queryset в оба запроса POST и GET в этом примере.

Использование сгруппированных форм в шаблоне

Есть три способа отображать формы, сгруппированные в шаблоне Django.

Во-первых, вы можете делегировать большую часть работы сгруппированным формам:

<form method="post">
    {{ formset }}
</form>

Во-вторых, вы можете отображать сгруппированные формы вручную, но позволять каждой форме отображать себя:

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form }}
    {% endfor %}
</form>

Когда вы сами позаботитесь об отображении различных форм, постарайтесь также включить форму управления, как показано в примере выше. См. Документацию к форме управления .

В-третьих, вы можете вручную отображать каждое поле:

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {% for field in form %}
            {{ field.label_tag }} {{ field }}
        {% endfor %}
    {% endfor %}
</form>

Если вы выберете этот третий метод и не пропустите поля с помощью символа a , вы также должны включить поле первичного ключа. Например, если вы отображаете поля и шаблона:{% for %} name age

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form.id }}
        <ul>
            <li>{{ form.name }}</li>
            <li>{{ form.age }}</li>
        </ul>
    {% endfor %}
</form>

Посмотрите, как нам нужно явно отображать . Это необходимо для обеспечения правильного функционирования сгруппированных форм модели в случае отправки (в этом примере предполагается, что вызывается первичный ключ . Если вы явно указали первичный ключ модели иначе, чем , позаботьтесь о «дисплей).{{ form.id }} POST id id

Сгруппированные подчиненные формы

класс models.BaseInlineFormSet

Сгруппированные подчиненные формы - это небольшой уровень абстракции поверх сгруппированных форм модели. Они упрощают ситуацию, в которой манипулируют объектами, связанными через внешний ключ. Предположим, у нас есть эти две модели:

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)

Если вы хотите создать сгруппированные формы для редактирования книг, принадлежащих определенному автору, вы можете сделать следующее:

>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=('title',))
>>> author = Author.objects.get(name='Mike Royko')
>>> formset = BookFormSet(instance=author)

Префикс для BookFormSet IS 'book_set' ( <nom_modèle>_set ). Если внешний ключ Book червя Author имеет атрибут related_name , используется атрибут .

Заметка

inlineformset_factory() использует modelformset_factory() и определяет can_delete=True .

Методы перегрузки InlineFormSet

При переопределении сгруппированных методов subform ( InlineFormSet ) лучше наследовать от, BaseInlineFormSet а не от BaseModelFormSet .

Например, если вы хотите переопределить clean() :

from django.forms import BaseInlineFormSet

class CustomInlineFormSet(BaseInlineFormSet):
    def clean(self):
        super().clean()
        # example custom validation across forms in the formset
        for form in self.forms:
            # your custom formset validation
            ...

См. Также Перегрузка clean () ModelFormSet .

Затем при создании сгруппированных подчиненных форм передайте необязательный параметр formset :

>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=('title',),
...     formset=CustomInlineFormSet)
>>> author = Author.objects.get(name='Mike Royko')
>>> formset = BookFormSet(instance=author)

Более одного внешнего ключа для одной модели

Если ваша модель содержит более одного внешнего ключа для одной и той же модели, вам придется разрешать неоднозначность вручную, используя fk_name . Например, рассмотрим следующую модель:

class Friendship(models.Model):
    from_friend = models.ForeignKey(
        Friend,
        on_delete=models.CASCADE,
        related_name='from_friends',
    )
    to_friend = models.ForeignKey(
        Friend,
        on_delete=models.CASCADE,
        related_name='friends',
    )
    length_in_months = models.IntegerField()

Чтобы решить эту проблему можно установить fk_name в inlineformset_factory() :

>>> FriendshipFormSet = inlineformset_factory(Friend, Friendship, fk_name='from_friend',
...     fields=('to_friend', 'length_in_months'))

Использование сгруппированных подчиненных форм в представлении

Может возникнуть необходимость определить представление, которое позволяет пользователю изменять связанные объекты модели. Вот как мы можем это сделать:

def manage_books(request, author_id):
    author = Author.objects.get(pk=author_id)
    BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
    if request.method == "POST":
        formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
        if formset.is_valid():
            formset.save()
            # Do something. Should generally end with a redirect. For example:
            return HttpResponseRedirect(author.get_absolute_url())
    else:
        formset = BookInlineFormSet(instance=author)
    return render(request, 'manage_books.html', {'formset': formset})

Обратите внимание, как мы определяем параметр instance как в случае, так POST и в случае GET .

Определение компонентов для использования в подчиненной форме

inlineformset_factory использует modelformset_factory и передает большинство своих параметров в modelformset_factory . Это означает, что вы можете использовать параметр так widgets же, как и вы modelformset_factory . См. Раздел Определение компонентов формы для использования с виджетами выше.

Copyright ©2020 All rights reserved