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

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. Вот полный список преобразований:

Поле модели Поле формы
AutoField Не представлено в форме
BigAutoField Не представлено в форме
BigIntegerField IntegerFieldс min_valueустановленным значением -9223372036854775808 и max_valueзначением 9223372036854775807.
BinaryField CharField, Если editableзадано значение Trueна поле модели, в противном случае не представлены в виде.
BooleanField BooleanField, или NullBooleanFieldесли null=True.
CharField CharFieldwith max_lengthустановлено в поле модели max_lengthи empty_value установлено в Noneif 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 (см. ниже)
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

Есть два основных этапа проверки ModelForm:

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

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

Modelvalidation ( 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 fieldmodel field

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

Вы можете переопределить сообщения об ошибках, 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 ).

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

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

    from django.forms import ModelForm
    
    class AuthorForm(ModelForm):
        class Meta:
            model = Author
            fields = '__all__'
    
  2. Установите 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 %}nameage

<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 }}POSTidid

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

класс 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>_setBookForeignKeyAuthorrelated_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. См. Указание виджетов для использования в форме с виджетами выше.

Copyright ©2021 All rights reserved