Настройка интерфейса администратора

ModelForm

class ModelForm

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

По этой причине Django предоставляет вспомогательный класс, который позволит вам создать класс Form по имеющейся модели.

Например:

>>> from django.forms import ModelForm

# Create the form class.
>>> class ArticleForm(ModelForm):
...     class Meta:
...         model = Article

# 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 будет иметь поля для каждого поля модели. Каждому полю модели соответствует стандартное поле формы. Например, CharField поле модели будет представлено на форме как CharField, а ManyToManyField поле модели будет представлено как MultipleChoiceField. Ниже представлен полный список соответствия полей модели и формы:

Поле модели Поле формы
AutoField Не представлено на форме
BigIntegerField IntegerField с атрибутом min_value равным -9223372036854775808 и атрибутом max_value равным 9223372036854775807.
BooleanField BooleanField
CharField CharField с атрибутом max_length равным значению атрибута max_length модели
CommaSeparatedIntegerField CharField
DateField DateField
DateTimeField DateTimeField
DecimalField DecimalField
EmailField EmailField
FileField FileField
FilePathField CharField
FloatField FloatField
ForeignKey ModelChoiceField (см. далее)
ImageField ImageField
IntegerField IntegerField
IPAddressField IPAddressField
GenericIPAddressField GenericIPAddressField
ManyToManyField ModelMultipleChoiceField (см. далее)
NullBooleanField CharField
PhoneNumberField USPhoneNumberField (из модуля django.contrib.localflavor.us)
PositiveIntegerField IntegerField
PositiveSmallIntegerField IntegerField
SlugField SlugField
SmallIntegerField IntegerField
TextField CharField с widget=forms.Textarea
TimeField TimeField
URLField URLField с атрибутом verify_exists равным атрибутом verify_exists поля модели.
Добавлено в Django 1.2: The BigIntegerField is new in Django 1.2.

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

  • Поле ForeignKey модели представлено полем формы ModelChoiceField, которое является обычным ChoiceField, но с вариантами значений, полученными из QuerySet.
  • Поле ManyToManyField модели представлено полем формы ModelMultipleChoiceField, которое является обычным MultipleChoiceField`, но с вариантами значений, полученными из ``QuerySet.

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

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

В конце, следует отметить, что вы можете переопределить поле формы, используемое для определённого поля модели. Обратитесь к Overriding the default field types or widgets далее.

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

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

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 __unicode__(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

class BookForm(ModelForm):
    class Meta:
        model = Book

Для этих моделей показанные выше классы 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())

Метод is_valid() и список errors

При первом вызове метода is_valid() или обращении к атрибуту errors форма ModelForm выполняет валидацию полученный данных, как это описано в model validation. Побочным эффектом данного действия является очистка модели, которую вы передали в конструктор ModelForm. Например, вызов is_valid() для вашей формы преобразует все поля дат вашей модели в настоящие объекты даты.

Метод save()

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

# 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.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(instance=a)
>>> 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() вызывает исключение ValueError, если данные формы не прошли проверку, т.е. атрибут errors формы вычисляется в True.

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

Использование commit=False также полезно в случае, когда ваша модель имеет связь “многие-ко-многим” с другой моделью. Для такой модели, если метод save() вызван с аргументом 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 работает аналогично обычной Form. Например, метод is_valid() используется для проверки данных, метод is_multipart() используется для определения загрузки файла (в этом случае request.FILES должен быть передан форме) и так далее. Обратитесь к документу Привязка загруженных файлов к форме для получения подробностей.

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

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

  1. Установите editable=False для поля модели. В результате, каждая форма, созданная по модели с помощью ModelForm не будет включать в себя это поле.
  2. Используйте атрибут fields внутреннего класса ModelForm.Meta. Этот атрибут, если он указан, должен содержать список имён полей, которые должны отображаться на форме. Порядок определения полей в этом списке влияет на порядок полей на форме.
  3. Используйте атрибут exclude внутреннего класса ModelForm.Meta. Этот атрибут, если он указан, должен содержать список имён полей, которые не должны отображаться на форме.

Например, если требуется, чтобы форма для модели Author (определена выше) отображала только поля name и title, укажите атрибуты fields или exclude вот так:

class PartialAuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title')

class PartialAuthorForm(ModelForm):
    class Meta:
        model = Author
        exclude = ('birth_date',)

Так как модель Author содержит всего три поля: name ``title `` и ``birth_date, то вышеприведённые формы будут отображать одни и те же поля.

Примечание

Если вы указываете fields или exclude при создании формы через ModelForm, то поля, которые не определены в форме, не будут учитываться при вызове метода save(). Также, если вы вручную добавите в форму исключенные поля, то они не будут заполняться из экземпляра модели.

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

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

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

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

Обратитесь к разделу section on saving forms для подробностей по использованию save(commit=False).

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

Добавлено в Django 1.2: The widgets attribute is new in Django 1.2.

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

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

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

from django.forms import ModelForm, Textarea

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

Ещё раз напомним, что аргумент widgets принимает словарь с экземплярами (т.е., Textarea(...)) или классами (т.е., Textarea) виджетов.

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

Например, если надо использовать MyDateFormField для поля pub_date, вы можете сделать следующее:

class ArticleForm(ModelForm):
    pub_date = MyDateFormField()

    class Meta:
        model = Article

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

>>> class ArticleForm(ModelForm):
...     pub_date = DateField(label='Publication date')
...
...     class Meta:
...         model = Article

Примечание

При явном создании поля, Django предполагает, что вы будете определять поведение формы в целом. Следовательно, стандартные атрибуты модели (такие как max_length или required) не передаются полям формы. Если вам потребуется обеспечить поведение, определённое в модели, вам потребуется явно установить соответствующие аргументы при определении поля формы.

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

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

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

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

    class Meta:
        model = Article

Обратитесь к `документации на поля формы`</ref/forms/fields>`_ для получения дополнительной информации о полях и их аргументах.

Изменение порядка полей

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

Атрибут fields определяет набор полей модели, подлежащих отображению, и их порядок на форме. Например, рассмотрим эту модель:

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

поле author будет выведено первым. Если же надо первым вывести поле title, то определим ModelForm так:

>>> class BookForm(ModelForm):
...     class Meta:
...         model = Book
...         fields = ('title', 'author')

Переопределение метода clean()

Вы можете переопределить метод clean() модели для того, чтобы обеспечить дополнительную проверку. Всё это аналогично работе с обычной формой.

Учитывая это, модельные формы обладают двумя особенностями по сравнению с формами:

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

Экземпляр модельной формы, привязанный к объекту модели имеет атрибут self.instance, через который методы модельной формы имеют доступ к соответствующему экземпляру модели.

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

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

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

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

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

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

Здесь мы добавили метод из EnhancedArticleForm и изменили оригинальный ArticleForm.Meta, убрав одно поле.

Тем не менее, надо уточнить несколько моментов.

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

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

Взаимодействие с механизмами модели

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

Наборы модельных форм

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

>>> from django.forms.models import modelformset_factory
>>> AuthorFormSet = modelformset_factory(Author)

Этот код создаст набор форм, которые будут работать с данными модели 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="selected">---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select></td></tr>
<tr><th><label for="id_form-0-birth_date">Birth date:</label></th><td><input type="text" name="form-0-birth_date" id="id_form-0-birth_date" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></td></tr>

Примечание

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

Изменение выборки

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

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

Также вы можете унаследоваться от класса набора модельных форм и определить self.queryset в конструкторе, указав необходимые параметры выборки:

from django.forms.models import BaseModelFormSet

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

Теперь передадим ваш класс BaseAuthorFormSet в функцию фабрики:

>>> AuthorFormSet = modelformset_factory(Author, formset=BaseAuthorFormSet)

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

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

Управляйте списком отображаемых полей через атрибуты fields и exclude

По умолчанию набор модельных форм использует все поля модели, которые не отмечены как editable=False. Тем не менее, такое поведение может быть изменено на уровне набора форм:

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

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

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

Передача начальных значений

Аналогично набору обычных форм, есть возможность указать начальные данные для форм набора, передав параметр initial при создании экземпляра набора, возвращенного modelformset_factory. Тем не менее, в случае набора модельных форм, начальными значениями заполняются только пустые, т.е. новые, формы.

Сохранение объектов набора форм

Благодаря 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(). Подробнее об этом ограничении модельных форм написано в Using a subset of fields on the form.

Передайте 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() для того, чтобы обеспечить сохранение связей «многие-ко-многим».

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

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

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

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

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

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

>>> AuthorFormSet = modelformset_factory(Author, 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 (по умолчанию) снимет ограничение на количество отображаемых набором форм.

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

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

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

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

Переопределение clean() у model_formset

Подобно``Forms``, по умолчанию метод clean() набора модельных форм будет проверять все данные на нарушение ограничений уникальности, определённых в вашей модели (unique, unique_together или unique_for_date|month|year). Желая сохранить данный функционал при переопределении метода clean(), следует вызывать метод clean() базового класса:

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

Использование собственной выборки

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

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author)
    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_to_response("manage_authors.html", {
        "formset": formset,
    })

Следует отметить, что мы передаём аргумент queryset в обе ветки POST и GET в этом примере.

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

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

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

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

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

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

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

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

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

Если вы предпочтёте третий способ и не будете использовать {% for %} для итерации по полям, то вам понадобится вывести поле для первичного ключа. Рассмотрим случай, когда требуется вывести поля name и age модели:

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

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

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

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

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

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

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

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

Примечание

inlineformset_factory использует modelformset_factory и устанавливает can_delete=True.

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

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

class Friendship(models.Model):
    from_friend = models.ForeignKey(Friend)
    to_friend = models.ForeignKey(Friend)
    length_in_months = models.IntegerField()

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

>>> FriendshipFormSet = inlineformset_factory(Friend, Friendship, fk_name="from_friend")

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

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

def manage_books(request, author_id):
    author = Author.objects.get(pk=author_id)
    BookInlineFormSet = inlineformset_factory(Author, Book)
    if request.method == "POST":
        formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
        if formset.is_valid():
            formset.save()
            # Do something.
    else:
        formset = BookInlineFormSet(instance=author)
    return render_to_response("manage_books.html", {
        "formset": formset,
    })

Следует отметить, что мы передаём instance в обоих (POST и GET) случаях.