Spec-Zone.ru › Django 5.2

Создание форм из моделей

ModelForm

class ModelForm [source]

Если вы разрабатываете приложение, работающее с базой данных, скорее всего, у вас будут формы, которые тесно связаны с моделями 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. Вот полный список преобразований:

Модельное поле

Поле формы

AutoField

В форме не представлено

BigAutoField

В форме не представлено

BigIntegerField

IntegerField с min_value, установленным в -9223372036854775808, и max_value, установленным в 9223372036854775807.

BinaryField

CharField, если editable для поля модели установлено в True, в противном случае в форме не представлено.

BooleanField

BooleanField или NullBooleanField, если null=True.

CharField

CharField с max_length, установленным в значение max_length поля модели, и empty_value, установленным в None, если 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

JSONField

JSONField

ManyToManyField

ModelMultipleChoiceField (см. ниже)

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

Существует два основных этапа, вовлечённых в валидацию 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() модели.

Учёт сообщений об ошибках модели

Сообщения об ошибках, определённые на уровне form field или на уровне метаданных формы, всегда имеют приоритет над сообщениями об ошибках, определёнными на уровне model field.

Сообщения об ошибках, определённые на уровне model fields, используются только тогда, когда возникает исключение ValidationError во время шага валидации модели и соответствующие сообщения об ошибках не определены на уровне формы.

Вы можете переопределить сообщения об ошибках, возникших из-за NON_FIELD_ERRORS, вызванной валидацией модели, добавив ключ NON_FIELD_ERRORS в словарь error_messages внутреннего класса 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), так как неотмеченный флажок и невыбранный <select multiple> не отображаются в данных отправки HTML-формы. Если вам необходима поведенческая настройка для таких полей в API, используйте пользовательское поле формы или виджет.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Например:

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

    Поскольку у модели Author есть 3 поля name, title и birth_date, это приведёт к включению полей name и birth_date в форму.

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

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

Примечание

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

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

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

В качестве альтернативы, вы можете использовать save(commit=False) и вручную установить любые дополнительные необходимые поля:

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

Подробности использования save(commit=False) см. в разделе сохранение форм.

Переопределение стандартных полей

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

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

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

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


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

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

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

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

from django.utils.translation import gettext_lazy as _


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

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

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

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


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

или:

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


def formfield_for_dbfield(db_field, **kwargs):
    if db_field.name == "slug":
        return MySlugFormField()
    return db_field.formfield(**kwargs)


class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ["pub_date", "headline", "content", "reporter", "slug"]
        formfield_callback = formfield_for_dbfield

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

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

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


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

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

Примечание

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

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

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

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

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

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

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

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

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

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

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

Включение локализации полей

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

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

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

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

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

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

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

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

>>> class RestrictedArticleForm(EnhancedArticleForm):
...     class Meta(ArticleForm.Meta):
...         exclude = ["body"]
...

Это добавляет дополнительный метод из EnhancedArticleForm и изменяет исходный ArticleForm.Meta, чтобы удалить одно поле.

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

  • Применяются обычные правила разрешения имён Python. Если у вас есть несколько базовых классов, которые объявляют внутренний класс Meta, будет использоваться только первый. Это означает, что будет использоваться Meta дочернего класса, если он существует, иначе — Meta первого родительского класса и т. д.
  • Возможна одновременная наследование от Form и ModelForm, однако вы должны убедиться, что ModelForm появляется первой в 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. См. документацию по Выбор используемых полей.

… или включить локализации для конкретных полей:

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

Наборы форм модели

class models.BaseModelFormSet

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

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

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

>>> AuthorFormSet = modelformset_factory(Author, exclude=["birth_date"])

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

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

Примечание

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

Примечание

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

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

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

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

В качестве альтернативы вы можете создать подкласс, устанавливающий self.queryset в __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
... )

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

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

Изменение формы

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

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

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

Затем передайте вашу форму модели в функцию-фабрику:

AuthorFormSet = modelformset_factory(Author, form=AuthorForm)

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

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

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

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

Включение локализации для полей с localized_fields

Используя параметр localized_fields, вы можете включить локализация для полей в форме.

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

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

Предоставление начальных значений

Как и в обычных наборах форм, можно указать начальные данные для форм в наборе форм, указав параметр initial при создании класса набора форм модели, возвращаемого modelformset_factory(). Однако для наборов форм модели начальные значения применяются только к дополнительным формам, которые не привязаны к существующему экземпляру модели. Если длина 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

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

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

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

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

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

Также extra=0 не предотвращает создание новых экземпляров моделей, так как вы можете добавить дополнительные формы с помощью JavaScript или отправить дополнительные данные POST. См. Предотвращение создания новых объектов для получения более подробной информации по этому вопросу.

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

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

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

Предотвращение создания новых объектов

Используя параметр edit_only, вы можете предотвратить создание новых объектов:

>>> AuthorFormSet = modelformset_factory(
...     Author,
...     fields=["name", "title"],
...     edit_only=True,
... )

Здесь набор форм будет только редактировать существующие Author экземпляры. Никакие другие объекты не будут созданы или отредактированы.

Использование набора форм модели в представлении

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

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


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

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

Переопределение clean() в ModelFormSet

Так же, как и с ModelForms, по умолчанию метод clean() набора форм модели будет проверять, что ни один из элементов набора форм не нарушает уникальные ограничения вашей модели (либо 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 уже были созданы отдельные экземпляры модели. Изменение значения в form.cleaned_data недостаточно для изменения сохранённого значения. Если вы хотите изменить значение в ModelFormSet.clean(), необходимо изменить form.instance:

from django.forms import BaseModelFormSet


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

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

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

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

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


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

Обратите внимание, что мы передаём аргумент queryset в обоих случаях POST и GET в этом примере.

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

Существует три способа отобразить набор форм в шаблоне Django.

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

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

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

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

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

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

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

Если вы выберете этот третий метод и не будете итерировать по полям с помощью цикла {% for %}, вам нужно будет отобразить поле первичного ключа. Например, если вы отображали поля name и 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, убедитесь, что он отображается.)

Встроенные наборы форм

class models.BaseInlineFormSet

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

from django.db import models


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


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

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

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

BookFormSet’s префикс — 'book_set' (<model name>_set ). Если префикс Book’s ForeignKey к Author имеет related_name, то он используется вместо этого.

Примечание

inlineformset_factory() использует modelformset_factory() и помечает can_delete=True.

См. также

Вручную отображаемые can_delete и can_order.

Переопределение методов в 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() в наборе форм модели.

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

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

Несколько внешних ключей к одной и той же модели

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

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

Для решения этой проблемы вы можете использовать fk_name для inlineformset_factory():

>>> FriendshipFormSet = inlineformset_factory(
...     Friend, Friendship, fk_name="from_friend", fields=["to_friend", "length_in_months"]
... )

Использование встроенного набора форм в представлении

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

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

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

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

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

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.2/topics/forms/modelforms/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API