Класс ModelForm
Если вы создаете приложение, управляемое базой данных, скорее всего, у вас будут формы, которые тесно связаны с моделями Django. Например, у вас может быть модель BlogComment, и вы хотите создать форму, которая позволяет людям отправлять комментарии. В этом случае было бы излишним определять типы полей в вашей форме, потому что вы уже определили поля в своей модели.
По этой причине Django предоставляет вспомогательный класс, который позволяет создавать класс Form из модели Django.
Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<span class="gp">>>> </span><span class="kn">from</span> <span class="nn">django.forms</span> <span class="k">import</span> <span class="n">ModelForm</span> <span class="gp">>>> </span><span class="kn">from</span> <span class="nn">myapp.models</span> <span class="k">import</span> <span class="n">Article</span> <span class="go"># Create the form class.</span> <span class="gp">>>> </span><span class="k">class</span> <span class="nc">ArticleForm</span><span class="p">(</span><span class="n">ModelForm</span><span class="p">):</span> <span class="gp">... </span> <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span> <span class="gp">... </span> <span class="n">model</span> <span class="o">=</span> <span class="n">Article</span> <span class="gp">... </span> <span class="n">fields</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'pub_date'</span><span class="p">,</span> <span class="s1">'headline'</span><span class="p">,</span> <span class="s1">'content'</span><span class="p">,</span> <span class="s1">'reporter'</span><span class="p">]</span> <span class="go"># Creating a form to add an article.</span> <span class="gp">>>> </span><span class="n">form</span> <span class="o">=</span> <span class="n">ArticleForm</span><span class="p">()</span> <span class="go"># Creating a form to change an existing article.</span> <span class="gp">>>> </span><span class="n">article</span> <span class="o">=</span> <span class="n">Article</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">pk</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span> <span class="gp">>>> </span><span class="n">form</span> <span class="o">=</span> <span class="n">ArticleForm</span><span class="p">(</span><span class="n">instance</span><span class="o">=</span><span class="n">article</span><span class="p">)</span> |
Типы полей
Сгенерированный класс Form будет иметь поле формы для каждого указанного поля модели в порядке, указанном в атрибуте fields.
Каждое поле модели имеет соответствующее поле формы по умолчанию. Например, CharField на модели представлен как CharField на форме. Модель ManyToManyField представлена в виде MultipleChoiceField. Вот полный список конверсий:
Как и следовало ожидать, типы полей модели ForeignKey и ManyToManyField являются особыми случаями:
ForeignKey представлен django.forms.ModelChoiceField, который является ChoiceField, чьи выборы являются моделью QuerySet.
ManyToManyField представлен django.forms.ModelMultipleChoiceField, который представляет собой MultipleChoiceField, выбором которого является модель QuerySet.
Кроме того, каждое сгенерированное поле формы имеет атрибуты, установленные следующим образом:
Если поле модели имеет пустое значение = True, то для поля формы требуется значение False. В противном случае требуется = True.
Метка поля формы установлена в verbose_name поля модели с первым заглавным символом.
Поле help_text поля формы имеет значение help_text поля модели.
Если для поля модели установлено значение «Выбор», для виджета поля формы будет установлено значение «Выбор», при этом варианты выбора будут выбраны из выбора поля модели. Варианты, как правило, включают пустой выбор, который выбран по умолчанию. Если поле является обязательным, это заставляет пользователя сделать выбор. Пустой выбор не будет включен, если поле модели имеет пустое значение = False и явное значение по умолчанию (вместо этого будет изначально выбрано значение по умолчанию).
Наконец, обратите внимание, что вы можете переопределить поле формы, используемое для данного поля модели. См. Переопределение полей по умолчанию ниже.
Полный пример
Рассмотрим набор моделей:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<strong>from</strong> <strong>django.forms</strong> <strong>import</strong> ModelForm TITLE_CHOICES = [ ('MR', 'Mr.'), ('MRS', 'Mrs.'), ('MS', 'Ms.'), ] <strong>class</strong> <strong>Author</strong>(models.Model): name = models.CharField(max_length=100) title = models.CharField(max_length=3, choices=TITLE_CHOICES) birth_date = models.DateField(blank=<strong>True</strong>, null=<strong>True</strong>) <strong>def</strong> __str__(self): <strong>return</strong> self.name <strong>class</strong> <strong>Book</strong>(models.Model): name = models.CharField(max_length=100) authors = models.ManyToManyField(Author) <strong>class</strong> <strong>AuthorForm</strong>(ModelForm): <strong>class</strong> <strong>Meta</strong>: model = Author fields = ['name', 'title', 'birth_date'] <strong>class</strong> <strong>BookForm</strong>(ModelForm): <strong>class</strong> <strong>Meta</strong>: model = Book fields = ['name', 'authors'] |
С этими моделями подклассы ModelForm выше были бы примерно эквивалентны этому (единственное отличие – метод save (), который мы обсудим чуть позже.):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<strong>from</strong> <strong>django</strong> <strong>import</strong> forms <strong>class</strong> <strong>AuthorForm</strong>(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=<strong>False</strong>) <strong>class</strong> <strong>BookForm</strong>(forms.Form): name = forms.CharField(max_length=100) authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all()) |
Проверка по ModelForm
Существует два основных этапа проверки ModelForm:
- Проверка формы
- Проверка экземпляра модели
Как и при обычной проверке формы, проверка формы модели запускается неявно при вызове is_valid () или при обращении к атрибуту ошибок, а также явно при вызове full_clean (), хотя обычно вы не будете использовать последний метод на практике.
Проверка модели (Model.full_clean ()) запускается на этапе проверки формы, сразу после вызова метода clean ().
Предупреждение
Процесс очистки изменяет экземпляр модели, передаваемый конструктору ModelForm, различными способами. Например, любые поля даты в модели преобразуются в объекты фактической даты. Неудачная проверка может оставить базовый экземпляр модели в несогласованном состоянии, и поэтому его не рекомендуется использовать повторно.
Переопределение метода clean ()
Вы можете переопределить метод clean () в форме модели, чтобы обеспечить дополнительную проверку так же, как в обычной форме.
Экземпляр формы модели, присоединенный к объекту модели, будет содержать атрибут экземпляра, который дает его методам доступ к этому конкретному экземпляру модели.
Предупреждение
Метод ModelForm.clean () устанавливает флаг, который позволяет шагу проверки модели проверять уникальность полей модели, помеченных как уникальные, unique_together или unique_for_date | month | year.
Если вы хотите переопределить метод clean () и сохранить эту проверку, вы должны вызвать метод clean () родительского класса.
Взаимодействие с валидацией модели¶
Как часть процесса проверки, ModelForm вызовет метод clean () для каждого поля вашей модели, у которого есть соответствующее поле в вашей форме. Если вы исключили какие-либо поля модели, проверка не будет выполняться для этих полей. См. Документацию проверки формы для получения дополнительной информации о том, как работают очистка и проверка полей.
Метод clean () модели будет вызван перед выполнением любых проверок уникальности. См. Проверка объектов для получения дополнительной информации о хуке модели clean ().
Соображения относительно модели error_messages
Сообщения об ошибках, определенные на уровне поля формы или на мета-уровне формы, всегда имеют приоритет над сообщениями об ошибках, определенными на уровне поля модели.
Сообщения об ошибках, определенные в полях модели, используются только в том случае, если ошибка ValidationError вызывается на этапе проверки модели, а соответствующие сообщения об ошибках не определены на уровне формы.
Вы можете переопределить сообщения об ошибках от NON_FIELD_ERRORS, полученные при проверке модели, добавив ключ NON_FIELD_ERRORS в словарь error_messages внутреннего мета-класса ModelForm:
1 2 3 4 5 6 7 8 9 10 |
<strong>from</strong> <strong>django.core.exceptions</strong> <strong>import</strong> NON_FIELD_ERRORS <strong>from</strong> <strong>django.forms</strong> <strong>import</strong> ModelForm <strong>class</strong> <strong>ArticleForm</strong>(ModelForm): <strong>class</strong> <strong>Meta</strong>: error_messages = { NON_FIELD_ERRORS: { 'unique_together': "<strong>%(model_name)s</strong>'s <strong>%(field_labels)s</strong> are not unique.", } } |
Метод Save
Каждый ModelForm также имеет метод save (). Этот метод создает и сохраняет объект базы данных из данных, привязанных к форме. Подкласс ModelForm может принять существующий экземпляр модели в качестве экземпляра аргумента ключевого слова; если это указано, save () обновит этот экземпляр. Если он не указан, save () создаст новый экземпляр указанной модели:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<strong>>>> </strong><strong>from</strong> <strong>myapp.models</strong> <strong>import</strong> Article <strong>>>> </strong><strong>from</strong> <strong>myapp.forms</strong> <strong>import</strong> ArticleForm # Create a form instance from POST data. <strong>>>> </strong>f = ArticleForm(request.POST) # Save a new Article object from the form's data. <strong>>>> </strong>new_article = f.save() # Create a form to edit an existing Article, but use # POST data to populate the form. <strong>>>> </strong>a = Article.objects.get(pk=1) <strong>>>> </strong>f = ArticleForm(request.POST, instance=a) <strong>>>> </strong>f.save() |
Обратите внимание, что если форма не была проверена, вызов save () сделает это путем проверки form.errors. Ошибка ValueError возникает, если данные в форме не проверяются, т. Е. Если form.errors имеет значение True.
Если необязательное поле не появляется в данных формы, результирующий экземпляр модели использует поле модели по умолчанию, если оно есть, для этого поля. Это поведение не применяется к полям, которые используют CheckboxInput, CheckboxSelectMultiple или SelectMultiple (или любой пользовательский виджет, метод value_omited_from_data () которого всегда возвращает False), поскольку непроверенный флажок и невыбранный не появляются в данных 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 (), чтобы сохранить данные формы многие-ко-многим. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<em># Create a form instance with POST data.</em> >>> f = AuthorForm(request.POST) <em># Create, but don't save the new author instance.</em> >>> new_author = f.save(commit=<strong>False</strong>) <em># Modify the author in some way.</em> >>> new_author.some_field = 'some_value' <em># Save the new instance.</em> >>> new_author.save() <em># Now, save the many-to-many data for the form.</em> >>> f.save_m2m() |
Вызов save_m2m () требуется только в том случае, если вы используете save (commit = False). Когда вы используете save () в форме, все данные, включая данные многие-ко-многим, сохраняются без необходимости каких-либо дополнительных вызовов методов. Например:
1 2 3 4 5 6 |
<span class="c1"># Create a form instance with POST data.</span> <span class="o">>>></span> <span class="n">a</span> <span class="o">=</span> <span class="n">Author</span><span class="p">()</span> <span class="o">>>></span> <span class="n">f</span> <span class="o">=</span> <span class="n">AuthorForm</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">POST</span><span class="p">,</span> <span class="n">instance</span><span class="o">=</span><span class="n">a</span><span class="p">)</span> <span class="c1"># Create and save the new author instance. There's no need to do anything else.</span> <span class="o">>>></span> <span class="n">new_author</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">save</span><span class="p">()</span> |
Помимо методов save () и save_m2m (), ModelForm работает точно так же, как и любые другие формы. Например, метод is_valid () используется для проверки правильности, метод is_multipart () используется для определения, требует ли форма многокомпонентной загрузки файла (и, следовательно, должен ли request.FILES передаваться в форму) и т.l. См. Привязка загруженные файлы в форму для получения дополнительной информации.
Выбор полей для использования
Настоятельно рекомендуется явно указать все поля, которые должны редактироваться в форме, используя атрибут fields. Невыполнение этого требования может легко привести к проблемам с безопасностью, когда форма неожиданно позволяет пользователю устанавливать определенные поля, особенно когда новые поля добавляются в модель. В зависимости от способа отображения формы проблема может даже не отображаться на веб-странице.
Альтернативный подход заключается в автоматическом включении всех полей или внесении в черный список только некоторых. Известно, что этот фундаментальный подход гораздо менее безопасен и привел к серьезным атакам на крупных веб-сайтах (например, GitHub).
Однако для случаев, когда вы можете гарантировать, что эти проблемы с безопасностью неприменимы к вам, существуют два ярлыка:
Установите для атрибута fields специальное значение «__all__», чтобы указать, что должны использоваться все поля в модели. Например:
1 2 3 4 5 6 |
<span class="kn">from</span> <span class="nn">django.forms</span> <span class="k">import</span> <span class="n">ModelForm</span> <span class="k">class</span> <span class="nc">AuthorForm</span><span class="p">(</span><span class="n">ModelForm</span><span class="p">):</span> <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span> <span class="n">model</span> <span class="o">=</span> <span class="n">Author</span> <span class="n">fields</span> <span class="o">=</span> <span class="s1">'__all__'</span> |
Установите для атрибута exclude внутреннего мета-класса ModelForm список полей, которые необходимо исключить из формы.
1 2 3 4 |
<span class="k">class</span> <span class="nc">PartialAuthorForm</span><span class="p">(</span><span class="n">ModelForm</span><span class="p">):</span> <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span> <span class="n">model</span> <span class="o">=</span> <span class="n">Author</span> <span class="n">exclude</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'title'</span><span class="p">]</span> |
Поскольку модель Author имеет 3 поля name, title и birth_date, это приведет к тому, что поля name и birth_date будут присутствовать в форме.
Если какой-либо из них используется, порядок отображения полей в форме будет соответствовать порядку, заданному полями в модели, при этом экземпляры ManyToManyField будут появляться последними.
Кроме того, Django применяет следующее правило: если вы установите editable = False в поле модели, любая форма, созданная из модели через ModelForm, не будет включать это поле.
Любые поля, не включенные в форму вышеуказанной логикой, не будут установлены методом save () формы. Кроме того, если вы вручную добавите исключенные поля обратно в форму, они не будут инициализированы из экземпляра модели.
Django предотвратит любую попытку сохранить неполную модель, поэтому, если модель не позволяет пустым пропущенным полям оставаться пустыми и не предоставляет значение по умолчанию для пропущенных полей, любая попытка сохранить () ModelForm с пропущенными полями потерпит неудачу , Чтобы избежать этого сбоя, вы должны создать свою модель с начальными значениями для отсутствующих, но обязательных полей.
Кроме того, вы можете использовать save (commit = False) и вручную установить любые дополнительные обязательные поля.
Переопределение полей по умолчанию
Типы полей по умолчанию, как описано в таблице типов полей выше, являются разумными значениями по умолчанию. Если в вашей модели есть DateField, скорее всего, вы хотите, чтобы он отображался в форме DateField. Но ModelForm дает вам гибкость в изменении поля формы для данной модели.
Чтобы указать пользовательский виджет для поля, используйте атрибут widgets внутреннего класса Meta. Это должен быть словарь, отображающий имена полей в классы или экземпляры виджетов.
Например, если вы хотите, чтобы CharField для атрибута имени Author был представлен вместо его <input type = “text”> по умолчанию, вы можете переопределить виджет поля:</p>
1 2 3 4 5 6 7 8 9 10 |
<span class="kn">from</span> <span class="nn">django.forms</span> <span class="k">import</span> <span class="n">ModelForm</span><span class="p">,</span> <span class="n">Textarea</span> <span class="kn">from</span> <span class="nn">myapp.models</span> <span class="k">import</span> <span class="n">Author</span> <span class="k">class</span> <span class="nc">AuthorForm</span><span class="p">(</span><span class="n">ModelForm</span><span class="p">):</span> <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span> <span class="n">model</span> <span class="o">=</span> <span class="n">Author</span> <span class="n">fields</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'name'</span><span class="p">,</span> <span class="s1">'title'</span><span class="p">,</span> <span class="s1">'birth_date'</span><span class="p">)</span> <span class="n">widgets</span> <span class="o">=</span> <span class="p">{</span> <span class="s1">'name'</span><span class="p">:</span> <span class="n">Textarea</span><span class="p">(</span><span class="n">attrs</span><span class="o">=</span><span class="p">{</span><span class="s1">'cols'</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="s1">'rows'</span><span class="p">:</span> <span class="mi">20</span><span class="p">}),</span> <span class="p">}</span> |
Словарь виджетов принимает либо экземпляры виджетов (например, Textarea (…)), либо классы (например, Textarea). Обратите внимание, что словарь виджетов игнорируется для поля модели с непустым атрибутом выбора. В этом случае вы должны переопределить поле формы, чтобы использовать другой виджет.
Точно так же вы можете указать атрибуты label, help_texts и error_messages внутреннего мета-класса, если хотите дополнительно настроить поле.
Например, если вы хотите настроить формулировку всех строк, обращенных к пользователю, для поля имени:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<span class="kn">from</span> <span class="nn">django.utils.translation</span> <span class="k">import</span> <span class="n">gettext_lazy</span> <span class="k">as</span> <span class="n">_</span> <span class="k">class</span> <span class="nc">AuthorForm</span><span class="p">(</span><span class="n">ModelForm</span><span class="p">):</span> <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span> <span class="n">model</span> <span class="o">=</span> <span class="n">Author</span> <span class="n">fields</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'name'</span><span class="p">,</span> <span class="s1">'title'</span><span class="p">,</span> <span class="s1">'birth_date'</span><span class="p">)</span> <span class="n">labels</span> <span class="o">=</span> <span class="p">{</span> <span class="s1">'name'</span><span class="p">:</span> <span class="n">_</span><span class="p">(</span><span class="s1">'Writer'</span><span class="p">),</span> <span class="p">}</span> <span class="n">help_texts</span> <span class="o">=</span> <span class="p">{</span> <span class="s1">'name'</span><span class="p">:</span> <span class="n">_</span><span class="p">(</span><span class="s1">'Some useful help text.'</span><span class="p">),</span> <span class="p">}</span> <span class="n">error_messages</span> <span class="o">=</span> <span class="p">{</span> <span class="s1">'name'</span><span class="p">:</span> <span class="p">{</span> <span class="s1">'max_length'</span><span class="p">:</span> <span class="n">_</span><span class="p">(</span><span class="s2">"This writer's name is too long."</span><span class="p">),</span> <span class="p">},</span> <span class="p">}</span> |
Вы также можете указать field_classes, чтобы настроить тип полей, созданных в форме.
Например, если вы хотите использовать MySlugFormField для поля slug, вы можете сделать следующее:
1 2 3 4 5 6 7 8 9 10 |
<span class="kn">from</span> <span class="nn">django.forms</span> <span class="k">import</span> <span class="n">ModelForm</span> <span class="kn">from</span> <span class="nn">myapp.models</span> <span class="k">import</span> <span class="n">Article</span> <span class="k">class</span> <span class="nc">ArticleForm</span><span class="p">(</span><span class="n">ModelForm</span><span class="p">):</span> <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span> <span class="n">model</span> <span class="o">=</span> <span class="n">Article</span> <span class="n">fields</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'pub_date'</span><span class="p">,</span> <span class="s1">'headline'</span><span class="p">,</span> <span class="s1">'content'</span><span class="p">,</span> <span class="s1">'reporter'</span><span class="p">,</span> <span class="s1">'slug'</span><span class="p">]</span> <span class="n">field_classes</span> <span class="o">=</span> <span class="p">{</span> <span class="s1">'slug'</span><span class="p">:</span> <span class="n">MySlugFormField</span><span class="p">,</span> <span class="p">}</span> |
Наконец, если вы хотите получить полный контроль над полем – включая его тип, валидаторы, обязательные и т. Д. – вы можете сделать это, декларативно указав поля, как в обычной форме.
Если вы хотите указать валидаторы поля, вы можете сделать это путем декларативного определения поля и установки его параметра validators:
1 2 3 4 5 6 7 8 |
<span class="kn">from</span> <span class="nn">myapp.models</span> <span class="k">import</span> <span class="n">Article</span> <span class="k">class</span> <span class="nc">ArticleForm</span><span class="p">(</span><span class="n">ModelForm</span><span class="p">):</span> <span class="n">slug</span> <span class="o">=</span> <span class="n">CharField</span><span class="p">(</span><span class="n">validators</span><span class="o">=</span><span class="p">[</span><span class="n">validate_slug</span><span class="p">])</span> <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span> <span class="n">model</span> <span class="o">=</span> <span class="n">Article</span> <span class="n">fields</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'pub_date'</span><span class="p">,</span> <span class="s1">'headline'</span><span class="p">,</span> <span class="s1">'content'</span><span class="p">,</span> <span class="s1">'reporter'</span><span class="p">,</span> <span class="s1">'slug'</span><span class="p">]</span> |
Когда вы явно создаете экземпляр поля формы, подобного этому, важно понимать, как связаны между собой ModelForm и обычная форма.
ModelForm – это обычная форма, которая может автоматически генерировать определенные поля. Поля, которые генерируются автоматически, зависят от содержимого класса Meta и от того, какие поля уже были определены декларативно. По сути, ModelForm генерирует только те поля, которые отсутствуют в форме, или, другими словами, поля, которые не были определены декларативно.
Поля, определенные декларативно, остаются как есть, поэтому любые настройки, сделанные для мета-атрибутов, таких как виджеты, метки, help_texts или error_messages, игнорируются; это относится только к полям, которые генерируются автоматически.
Аналогично, поля, определенные декларативно, не рисуют свои атрибуты, такие как max_length или требуемые из соответствующей модели. Если вы хотите сохранить поведение, указанное в модели, вы должны явно установить соответствующие аргументы при объявлении поля формы.
Например, если модель Article выглядит следующим образом:
1 2 3 4 5 6 7 |
<span class="n">headline</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">200</span><span class="p">,</span> <span class="n">null</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">help_text</span><span class="o">=</span><span class="s1">'Use puns liberally'</span><span class="p">,</span> <span class="p">)</span> <span class="n">content</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">()</span> |
и вы хотите выполнить некоторую пользовательскую проверку заголовка, сохраняя пустые значения и значения help_text, как указано, вы можете определить ArticleForm следующим образом:
1 2 3 4 5 6 7 8 9 |
<span class="n">headline</span> <span class="o">=</span> <span class="n">MyFormField</span><span class="p">(</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">200</span><span class="p">,</span> <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">help_text</span><span class="o">=</span><span class="s1">'Use puns liberally'</span><span class="p">,</span> <span class="p">)</span> <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span> <span class="n">model</span> <span class="o">=</span> <span class="n">Article</span> <span class="n">fields</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'headline'</span><span class="p">,</span> <span class="s1">'content'</span><span class="p">]</span> |
Вы должны убедиться, что тип поля формы может быть использован для установки содержимого соответствующего поля модели. Когда они несовместимы, вы получите ValueError, так как неявное преобразование не происходит.
Включение локализации полей
По умолчанию поля в ModelForm не локализуют свои данные. Чтобы включить локализацию для полей, вы можете использовать атрибут localized_fields в классе Meta.
1 2 3 4 5 6 |
<strong>>>> </strong><strong>from</strong> <strong>django.forms</strong> <strong>import</strong> ModelForm <strong>>>> </strong><strong>from</strong> <strong>myapp.models</strong> <strong>import</strong> Author <strong>>>> </strong><strong>class</strong> <strong>AuthorForm</strong>(ModelForm): <strong>... </strong> <strong>class</strong> <strong>Meta</strong>: <strong>... </strong> model = Author <strong>... </strong> localized_fields = ('birth_date',) |
С помощью базовых форм вы можете расширять и повторно использовать ModelForms, наследуя их. Это полезно, если вам нужно объявить дополнительные поля или дополнительные методы в родительском классе для использования в ряде форм, полученных из моделей. Например, используя предыдущий класс ArticleForm:
1 2 3 |
<strong>>>> </strong><strong>class</strong> <strong>EnhancedArticleForm</strong>(ArticleForm): <strong>... </strong> <strong>def</strong> clean_pub_date(self): <strong>... </strong> . |
Это создает форму, которая ведет себя идентично ArticleForm, за исключением некоторой дополнительной проверки и очистки для поля pub_date.
Вы также можете создать подкласс Meta внутреннего класса, если хотите изменить списки Meta.fields или Meta.exclude:
1 2 3 |
<strong>>>> </strong><strong>class</strong> <strong>RestrictedArticleForm</strong>(EnhancedArticleForm): <strong>... </strong> <strong>class</strong> <strong>Meta</strong>(ArticleForm.Meta): <strong>... </strong> exclude = ('body',) |
Это добавляет дополнительный метод из EnhancedArticleForm и изменяет исходный ArticleForm.Meta для удаления одного поля.
Однако следует отметить несколько вещей.
Применяются нормальные правила разрешения имен Python. Если у вас есть несколько базовых классов, которые объявляют внутренний класс Meta, будет использоваться только первый. Это означает, что мета ребенка, если она существует, в противном случае мета первого родителя и т. Д.
Можно наследовать как от Form, так и от ModelForm одновременно, однако вы должны убедиться, что ModelForm появляется первым в MRO. Это связано с тем, что эти классы зависят от разных метаклассов, а класс может иметь только один метакласс.
Можно декларативно удалить Поле, унаследованное от родительского класса, установив имя None в подклассе.
Вы можете использовать эту технику только для отказа от поля, определенного декларативно родительским классом; это не помешает метаклассу ModelForm генерировать поле по умолчанию. Чтобы отказаться от использования полей по умолчанию, см. Выбор полей для использования.
Предоставление начальных значений
Как и в случае с обычными формами, можно указать начальные данные для форм, указав начальный параметр при создании экземпляра формы. Исходные значения, предоставленные таким способом, будут переопределять как начальные значения из поля формы, так и значения из присоединенного экземпляра модели. Например:
1 2 3 4 5 6 |
<strong>>>> </strong>article = Article.objects.get(pk=1) <strong>>>> </strong>article.headline 'My headline' <strong>>>> </strong>form = ArticleForm(initial={'headline': 'Initial headline'}, instance=article) <strong>>>> </strong>form['headline'].value() 'Initial headline' |
функция ModelForm factory
Вы можете создавать формы из заданной модели, используя автономную функцию modelform_factory () вместо использования определения класса. Это может быть более удобно, если у вас не так много настроек:
1 2 3 |
<span class="gp">>>> </span><span class="kn">from</span> <span class="nn">django.forms</span> <span class="k">import</span> <span class="n">modelform_factory</span> <span class="gp">>>> </span><span class="kn">from</span> <span class="nn">myapp.models</span> <span class="k">import</span> <span class="n">Book</span> <span class="gp">>>> </span><span class="n">BookForm</span> <span class="o">=</span> <span class="n">modelform_factory</span><span class="p">(</span><span class="n">Book</span><span class="p">,</span> <span class="n">fields</span><span class="o">=</span><span class="p">(</span><span class="s2">"author"</span><span class="p">,</span> <span class="s2">"title"</span><span class="p">))</span> |
Это также можно использовать для внесения изменений в существующие формы, например, указав виджеты, которые будут использоваться для данного поля:
1 2 3 |
<span class="gp">>>> </span><span class="kn">from</span> <span class="nn">django.forms</span> <span class="k">import</span> <span class="n">Textarea</span> <span class="gp">>>> </span><span class="n">Form</span> <span class="o">=</span> <span class="n">modelform_factory</span><span class="p">(</span><span class="n">Book</span><span class="p">,</span> <span class="n">form</span><span class="o">=</span><span class="n">BookForm</span><span class="p">,</span> <span class="gp">... </span> <span class="n">widgets</span><span class="o">=</span><span class="p">{</span><span class="s2">"title"</span><span class="p">:</span> <span class="n">Textarea</span><span class="p">()})</span> |
Model formsets¶
класс моделей BaseModelFormSet¶
Как и обычные наборы форм, Django предоставляет несколько расширенных классов наборов форм, чтобы сделать работу с моделями Django более удобной. Давайте снова используем модель Author сверху:
1 2 3 |
<span class="gp">>>> </span><span class="kn">from</span> <span class="nn">django.forms</span> <span class="k">import</span> <span class="n">modelformset_factory</span> <span class="gp">>>> </span><span class="kn">from</span> <span class="nn">myapp.models</span> <span class="k">import</span> <span class="n">Author</span> <span class="gp">>>> </span><span class="n">AuthorFormSet</span> <span class="o">=</span> <span class="n">modelformset_factory</span><span class="p">(</span><span class="n">Author</span><span class="p">,</span> <span class="n">fields</span><span class="o">=</span><span class="p">(</span><span class="s1">'name'</span><span class="p">,</span> <span class="s1">'title'</span><span class="p">))</span> |
Использование полей ограничивает набор форм для использования только заданных полей. В качестве альтернативы, вы можете выбрать подход «отказаться», указав, какие поля исключить:
1 |
<span class="gp">>>> </span><span class="n">AuthorFormSet</span> <span class="o">=</span> <span class="n">modelformset_factory</span><span class="p">(</span><span class="n">Author</span><span class="p">,</span> <span class="n">exclude</span><span class="o">=</span><span class="p">(</span><span class="s1">'birth_date'</span><span class="p">,))</span> |
Это создаст набор форм, способный работать с данными, связанными с моделью Author. Он работает как обычный набор форм:
1 2 3 4 5 6 7 8 9 10 |
<span class="gp">>>> </span><span class="n">formset</span> <span class="o">=</span> <span class="n">AuthorFormSet</span><span class="p">()</span> <span class="gp">>>> </span><span class="nb">print</span><span class="p">(</span><span class="n">formset</span><span class="p">)</span> <span class="go"><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"></span> <span class="go"><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></span> <span class="go"><tr><th><label for="id_form-0-title">Title:</label></th><td><select name="form-0-title" id="id_form-0-title"></span> <span class="go"><option value="" selected>---------</option></span> <span class="go"><option value="MR">Mr.</option></span> <span class="go"><option value="MRS">Mrs.</option></span> <span class="go"><option value="MS">Ms.</option></span> <span class="go"></select><input type="hidden" name="form-0-id" id="id_form-0-id"></td></tr></span> |
modelformset_factory () использует formset_factory () для генерации наборов форм. Это означает, что набор форм модели является расширением базового набора форм, который знает, как взаимодействовать с конкретной моделью.
При использовании наследования нескольких таблиц формы, сгенерированные фабрикой наборов форм, будут содержать поле родительской ссылки (по умолчанию <parent_model_name> _ptr) вместо поля id.
Изменение набора запросов
По умолчанию, когда вы создаете набор форм из модели, набор форм будет использовать набор запросов, который включает все объекты в модели (например, Author.objects.all ()). Вы можете переопределить это поведение, используя аргумент queryset:
1 |
<span class="gp">>>> </span><span class="n">formset</span> <span class="o">=</span> <span class="n">AuthorFormSet</span><span class="p">(</span><span class="n">queryset</span><span class="o">=</span><span class="n">Author</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">name__startswith</span><span class="o">=</span><span class="s1">'O'</span><span class="p">))</span> |
Кроме того, вы можете создать подкласс, который устанавливает self.queryset в __init__:
1 2 3 4 5 6 7 |
<span class="kn">from</span> <span class="nn">django.forms</span> <span class="k">import</span> <span class="n">BaseModelFormSet</span> <span class="kn">from</span> <span class="nn">myapp.models</span> <span class="k">import</span> <span class="n">Author</span> <span class="k">class</span> <span class="nc">BaseAuthorFormSet</span><span class="p">(</span><span class="n">BaseModelFormSet</span><span class="p">):</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">queryset</span> <span class="o">=</span> <span class="n">Author</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">name__startswith</span><span class="o">=</span><span class="s1">'O'</span><span class="p">)</span> |
Затем передайте свой класс BaseAuthorFormSet заводской функции:
1 2 |
<span class="gp">>>> </span><span class="n">AuthorFormSet</span> <span class="o">=</span> <span class="n">modelformset_factory</span><span class="p">(</span> <span class="gp">... </span> <span class="n">Author</span><span class="p">,</span> <span class="n">fields</span><span class="o">=</span><span class="p">(</span><span class="s1">'name'</span><span class="p">,</span> <span class="s1">'title'</span><span class="p">),</span> <span class="n">formset</span><span class="o">=</span><span class="n">BaseAuthorFormSet</span><span class="p">)</span> |
Если вы хотите вернуть набор форм, который не включает какие-либо ранее существующие экземпляры модели, вы можете указать пустой QuerySet:
1 |
<span class="gp">>>> </span><span class="n">AuthorFormSet</span><span class="p">(</span><span class="n">queryset</span><span class="o">=</span><span class="n">Author</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">none</span><span class="p">())</span> |
Изменение наборов форм
По умолчанию, когда вы используете modelformset_factory, форма модели будет создана с использованием modelform_factory (). Часто бывает полезно указать пользовательскую форму модели. Например, вы можете создать пользовательскую форму модели, которая имеет пользовательскую проверку:
1 2 3 4 5 6 7 |
<strong>class</strong> <strong>Meta</strong>: model = Author fields = ('name', 'title') <strong>def</strong> clean_name(self): <em># custom validation for the name field</em> ... |
Затем передайте форму вашей модели заводской функции:
1 |
AuthorFormSet = modelformset_factory(Author, form=AuthorForm) |
Не всегда необходимо определять пользовательскую форму модели. Функция modelformset_factory имеет несколько аргументов, которые передаются в modelform_factory, которые описаны ниже.
Указание виджетов для использования в форме с виджетами
Используя параметр widgets, вы можете указать словарь значений, чтобы настроить класс виджетов ModelForm для определенного поля. Это работает так же, как работает словарь виджетов во внутреннем Meta-классе ModelForm:
1 2 3 |
<strong>>>> </strong>AuthorFormSet = modelformset_factory( <strong>... </strong> Author, fields=('name', 'title'), <strong>... </strong> widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20})}) |
Включение локализации для полей с localized_fields
Используя параметр localized_fields, вы можете включить локализацию для полей в форме.
1 2 3 |
<strong>>>> </strong>AuthorFormSet = modelformset_factory( <strong>... </strong> Author, fields=('name', 'title', 'birth_date'), <strong>... </strong> localized_fields=('birth_date',)) |
Если для localized_fields установлено специальное значение ‘__all__’, все поля будут локализованы.
Предоставление начальных значений
Как и в случае с обычными наборами форм, можно указать начальные данные для форм в наборе форм, указав начальный параметр при создании экземпляра класса formset модели, возвращаемого modelformset_factory (). Однако в случае наборов форм модели начальные значения применяются только к дополнительным формам, которые не привязаны к существующему экземпляру модели. Если длина начальных значений превышает количество дополнительных форм, избыточные начальные данные игнорируются. Если дополнительные формы с исходными данными не изменены пользователем, они не будут проверены или сохранены.
Сохранение объектов в форме
Как и в случае с ModelForm, вы можете сохранить данные как объект модели. Это делается с помощью метода save () формы:
1 2 3 4 5 |
<span class="c1"># Create a formset instance with POST data.</span> <span class="o">>>></span> <span class="n">formset</span> <span class="o">=</span> <span class="n">AuthorFormSet</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">POST</span><span class="p">)</span> <span class="c1"># Assuming all is valid, save the data.</span> <span class="o">>>></span> <span class="n">instances</span> <span class="o">=</span> <span class="n">formset</span><span class="o">.</span><span class="n">save</span><span class="p">()</span> |
Метод save () возвращает экземпляры, которые были сохранены в базе данных. Если данные данного экземпляра не изменились в связанных данных, экземпляр не будет сохранен в базе данных и не будет включен в возвращаемое значение (экземпляры в приведенном выше примере).
Если в форме отсутствуют поля (например, потому что они были исключены), эти поля не будут установлены методом save (). Вы можете найти больше информации об этом ограничении, которое также относится к обычным ModelForms, в разделе Выбор полей для использования.
Передайте commit = False, чтобы вернуть несохраненные экземпляры модели:
1 2 3 4 5 |
<span class="c1"># don't save to the database</span> <span class="o">>>></span> <span class="n">instances</span> <span class="o">=</span> <span class="n">formset</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">commit</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span> <span class="o">>>></span> <span class="k">for</span> <span class="n">instance</span> <span class="ow">in</span> <span class="n">instances</span><span class="p">:</span> <span class="o">...</span> <span class="c1"># do something with instance</span> <span class="o">...</span> <span class="n">instance</span><span class="o">.</span><span class="n">save</span><span class="p">()</span> |
Это дает вам возможность прикреплять данные к экземплярам перед их сохранением в базе данных. Если ваш набор форм содержит ManyToManyField, вам также необходимо вызвать formset.save_m2m (), чтобы обеспечить правильное сохранение отношений «многие ко многим».
После вызова save () у вашего модельного набора форм будет три новых атрибута, содержащих изменения в наборе форм:
models.BaseModelFormSet.changed_objects¶
models.BaseModelFormSet.deleted_objects¶
models.BaseModelFormSet.new_objects¶
Ограничение количества редактируемых объектов
Как и в случае с обычными наборами форм, вы можете использовать max_num и дополнительные параметры для modelformset_factory (), чтобы ограничить количество отображаемых дополнительных форм.
max_num не препятствует отображению существующих объектов:
1 2 3 4 5 6 7 |
<strong>>>> </strong>Author.objects.order_by('name') <QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]> <strong>>>> </strong>AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=1) <strong>>>> </strong>formset = AuthorFormSet(queryset=Author.objects.order_by('name')) <strong>>>> </strong>[x.name <strong>for</strong> x <strong>in</strong> formset.get_queryset()] ['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman'] |
Кроме того, extra = 0 не препятствует созданию новых экземпляров модели, поскольку вы можете добавлять дополнительные формы с помощью JavaScript или отправлять дополнительные данные POST. Наборы форм еще не предоставляют функциональность для представления «только для редактирования», которое предотвращает создание новых экземпляров.
Если значение max_num больше, чем количество существующих связанных объектов, в набор форм будет добавлено до дополнительных дополнительных пустых форм, если общее число форм не превышает max_num:
1 2 3 4 5 6 7 8 |
<strong>>>> </strong>AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=4, extra=2) <strong>>>> </strong>formset = AuthorFormSet(queryset=Author.objects.order_by('name')) <strong>>>> </strong><strong>for</strong> form <strong>in</strong> formset: <strong>... </strong> 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<strong>from</strong> <strong>django.forms</strong> <strong>import</strong> modelformset_factory <strong>from</strong> <strong>django.shortcuts</strong> <strong>import</strong> render <strong>from</strong> <strong>myapp.models</strong> <strong>import</strong> Author <strong>def</strong> manage_authors(request): AuthorFormSet = modelformset_factory(Author, fields=('name', 'title')) <strong>if</strong> request.method == 'POST': formset = AuthorFormSet(request.POST, request.FILES) <strong>if</strong> formset.is_valid(): formset.save() <em># do something.</em> <strong>else</strong>: formset = AuthorFormSet() <strong>return</strong> render(request, 'manage_authors.html', {'formset': formset}) |
Как видите, логика представления модельного набора форм не сильно отличается от логики «нормального» набора форм. Единственное отличие состоит в том, что мы вызываем formset.save () для сохранения данных в базе данных. (Это было описано выше, в разделе «Сохранение объектов в форме».)
Переопределение clean () в ModelFormSet
Как и в случае с ModelForms, по умолчанию метод clean () ModelFormSet проверит, что ни один из элементов в наборе форм не нарушает ограничения уникальности вашей модели (unique, unique_together или unique_for_date | month | year). Если вы хотите переопределить метод clean () в ModelFormSet и сохранить эту проверку, вы должны вызвать метод clean родительского класса:
1 2 3 4 5 6 7 8 9 |
<strong>from</strong> <strong>django.forms</strong> <strong>import</strong> BaseModelFormSet <strong>class</strong> <strong>MyModelFormSet</strong>(BaseModelFormSet): <strong>def</strong> clean(self): super().clean() <em># example custom validation across forms in the formset</em> <strong>for</strong> form <strong>in</strong> self.forms: <em># your custom formset validation</em> ... |
Также обратите внимание, что к тому времени, когда вы достигнете этого шага, отдельные экземпляры модели уже созданы для каждой формы. Изменение значения в form.cleaned_data недостаточно для воздействия на сохраненное значение. Если вы хотите изменить значение в ModelFormSet.clean (), вы должны изменить form.instance:
1 2 3 4 5 6 7 8 9 10 11 |
<strong>from</strong> <strong>django.forms</strong> <strong>import</strong> BaseModelFormSet <strong>class</strong> <strong>MyModelFormSet</strong>(BaseModelFormSet): <strong>def</strong> clean(self): super().clean() <strong>for</strong> form <strong>in</strong> self.forms: name = form.cleaned_data['name'].upper() form.cleaned_data['name'] = name <em># update the instance value.</em> form.instance.name = name |
Использование пользовательского набора запросов
Как указывалось ранее, вы можете переопределить набор запросов по умолчанию, используемый набором форм модели:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<strong>from</strong> <strong>django.forms</strong> <strong>import</strong> modelformset_factory <strong>from</strong> <strong>django.shortcuts</strong> <strong>import</strong> render <strong>from</strong> <strong>myapp.models</strong> <strong>import</strong> Author <strong>def</strong> manage_authors(request): AuthorFormSet = modelformset_factory(Author, fields=('name', 'title')) <strong>if</strong> request.method == "POST": formset = AuthorFormSet( request.POST, request.FILES, queryset=Author.objects.filter(name__startswith='O'), ) <strong>if</strong> formset.is_valid(): formset.save() <em># Do something.</em> <strong>else</strong>: formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O')) <strong>return</strong> render(request, 'manage_authors.html', {'formset': formset}) |
Обратите внимание, что мы передаем аргумент queryset в обоих случаях POST и GET в этом примере.
FormSet в шаблонах
Существует три способа визуализации набора форм в шаблоне Django.
Во-первых, вы можете позволить formset выполнять большую часть работы:
1 2 3 |
<<strong>form</strong> method="post"> {{ formset }} </<strong>form</strong>> |
Во-вторых, вы можете вручную визуализировать набор форм, но пусть форма справится сама с собой:
1 2 3 4 5 6 |
<span class="p"><</span><span class="nt">form</span> <span class="na">method</span><span class="o">=</span><span class="s">"post"</span><span class="p">></span> <span class="cp">{{</span> <span class="nv">formset.management_form</span> <span class="cp">}}</span> <span class="cp">{%</span> <span class="k">for</span> <span class="nv">form</span> <span class="k">in</span> <span class="nv">formset</span> <span class="cp">%}</span> <span class="cp">{{</span> <span class="nv">form</span> <span class="cp">}}</span> <span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span> <span class="p"></</span><span class="nt">form</span><span class="p">></span> |
Когда вы вручную визуализируете формы, не забудьте отобразить форму управления, как показано выше. Смотрите документацию формы управления.
В-третьих, вы можете вручную визуализировать каждое поле:
1 2 3 4 5 6 7 8 |
<span class="nt">form</span> <span class="na">method</span><span class="o">=</span><span class="s">"post"</span><span class="p">></span> <span class="cp">{{</span> <span class="nv">formset.management_form</span> <span class="cp">}}</span> <span class="cp">{%</span> <span class="k">for</span> <span class="nv">form</span> <span class="k">in</span> <span class="nv">formset</span> <span class="cp">%}</span> <span class="cp">{%</span> <span class="k">for</span> <span class="nv">field</span> <span class="k">in</span> <span class="nv">form</span> <span class="cp">%}</span> <span class="cp">{{</span> <span class="nv">field.label_tag</span> <span class="cp">}}</span> <span class="cp">{{</span> <span class="nv">field</span> <span class="cp">}}</span> <span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span> <span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span> <span class="p"></</span><span class="nt">form</span><span class="p">></span> |
Если вы решите использовать этот третий метод и не будете перебирать поля с циклом {% for%}, вам потребуется отобразить поле первичного ключа. Например, если вы рендерили поля имени и возраста модели:
1 2 3 4 5 6 7 8 9 10 |
<span class="p"><</span><span class="nt">form</span> <span class="na">method</span><span class="o">=</span><span class="s">"post"</span><span class="p">></span> <span class="cp">{{</span> <span class="nv">formset.management_form</span> <span class="cp">}}</span> <span class="cp">{%</span> <span class="k">for</span> <span class="nv">form</span> <span class="k">in</span> <span class="nv">formset</span> <span class="cp">%}</span> <span class="cp">{{</span> <span class="nv">form.id</span> <span class="cp">}}</span> <span class="p"><</span><span class="nt">ul</span><span class="p">></span> <span class="p"><</span><span class="nt">li</span><span class="p">></span><span class="cp">{{</span> <span class="nv">form.name</span> <span class="cp">}}</span><span class="p"></</span><span class="nt">li</span><span class="p">></span> <span class="p"><</span><span class="nt">li</span><span class="p">></span><span class="cp">{{</span> <span class="nv">form.age</span> <span class="cp">}}</span><span class="p"></</span><span class="nt">li</span><span class="p">></span> <span class="p"></</span><span class="nt">ul</span><span class="p">></span> <span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span> <span class="p"></</span><span class="nt">form</span><span class="p">></span> |
Обратите внимание, как нам нужно явно визуализировать {{form.id}}. Это гарантирует, что набор форм модели, в случае POST, будет работать правильно. (В этом примере предполагается использование первичного ключа с именем id. Если вы явно определили свой собственный первичный ключ, который не называется id, убедитесь, что он отображается.)
Встроенные формы
класс моделей. BaseInlineFormSet¶
Встроенные наборы форм – это небольшой слой абстракции поверх шаблонных наборов форм. Это упрощает работу со связанными объектами через внешний ключ. Предположим, у вас есть эти две модели:
1 2 3 4 5 6 7 8 |
<strong>from</strong> <strong>django.db</strong> <strong>import</strong> models <strong>class</strong> <strong>Author</strong>(models.Model): name = models.CharField(max_length=100) <strong>class</strong> <strong>Book</strong>(models.Model): author = models.ForeignKey(Author, on_delete=models.CASCADE) title = models.CharField(max_length=100) |
Если вы хотите создать набор форм, который позволяет редактировать книги, принадлежащие конкретному автору, вы можете сделать это:
1 2 3 4 |
<strong>>>> </strong><strong>from</strong> <strong>django.forms</strong> <strong>import</strong> inlineformset_factory <strong>>>> </strong>BookFormSet = inlineformset_factory(Author, Book, fields=('title',)) <strong>>>> </strong>author = Author.objects.get(name='Mike Royko') <strong>>>> </strong>formset = BookFormSet(instance=author) |
Префикс BookFormSet – «book_set» (<имя модели> _set). Если для параметра ForeignKey to Author в Book есть значение related_name, оно используется вместо него.
inlineformset_factory () использует modelformset_factory () и помечает can_delete = True.
Переопределение методов в InlineFormSet
При переопределении методов в InlineFormSet, вы должны создавать подкласс BaseInlineFormSet, а не BaseModelFormSet.
Например, если вы хотите переопределить clean ():
1 2 3 4 5 6 7 8 9 |
<strong>from</strong> <strong>django.forms</strong> <strong>import</strong> BaseInlineFormSet <strong>class</strong> <strong>CustomInlineFormSet</strong>(BaseInlineFormSet): <strong>def</strong> clean(self): super().clean() <em># example custom validation across forms in the formset</em> <strong>for</strong> form <strong>in</strong> self.forms: <em># your custom formset validation</em> ... |
Затем, когда вы создаете свой встроенный набор форм, передайте дополнительный набор форм:
1 2 3 4 5 |
<span class="gp">>>> </span><span class="kn">from</span> <span class="nn">django.forms</span> <span class="kn">import</span> <span class="n">inlineformset_factory</span> <span class="gp">>>> </span><span class="n">BookFormSet</span> <span class="o">=</span> <span class="n">inlineformset_factory</span><span class="p">(</span><span class="n">Author</span><span class="p">,</span> <span class="n">Book</span><span class="p">,</span> <span class="n">fields</span><span class="o">=</span><span class="p">(</span><span class="s1">'title'</span><span class="p">,),</span> <span class="gp">... </span> <span class="n">formset</span><span class="o">=</span><span class="n">CustomInlineFormSet</span><span class="p">)</span> <span class="gp">>>> </span><span class="n">author</span> <span class="o">=</span> <span class="n">Author</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Mike Royko'</span><span class="p">)</span> <span class="gp">>>> </span><span class="n">formset</span> <span class="o">=</span> <span class="n">BookFormSet</span><span class="p">(</span><span class="n">instance</span><span class="o">=</span><span class="n">author</span><span class="p">)</span> |
Более одного внешнего ключа для одной модели
Если ваша модель содержит более одного внешнего ключа для одной и той же модели, вам необходимо устранить неоднозначность вручную, используя fk_name. Например, рассмотрим следующую модель:
1 2 3 4 5 6 7 8 9 10 11 12 |
<span class="k">class</span> <span class="nc">Friendship</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span> <span class="n">from_friend</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ForeignKey</span><span class="p">(</span> <span class="n">Friend</span><span class="p">,</span> <span class="n">on_delete</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">CASCADE</span><span class="p">,</span> <span class="n">related_name</span><span class="o">=</span><span class="s1">'from_friends'</span><span class="p">,</span> <span class="p">)</span> <span class="n">to_friend</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ForeignKey</span><span class="p">(</span> <span class="n">Friend</span><span class="p">,</span> <span class="n">on_delete</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">CASCADE</span><span class="p">,</span> <span class="n">related_name</span><span class="o">=</span><span class="s1">'friends'</span><span class="p">,</span> <span class="p">)</span> <span class="n">length_in_months</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">IntegerField</span><span class="p">()</span> |
Чтобы решить эту проблему, вы можете использовать fk_name для inlineformset_factory ():
1 2 |
<span class="gp">>>> </span><span class="n">FriendshipFormSet</span> <span class="o">=</span> <span class="n">inlineformset_factory</span><span class="p">(</span><span class="n">Friend</span><span class="p">,</span> <span class="n">Friendship</span><span class="p">,</span> <span class="n">fk_name</span><span class="o">=</span><span class="s1">'from_friend'</span><span class="p">,</span> <span class="gp">... </span> <span class="n">fields</span><span class="o">=</span><span class="p">(</span><span class="s1">'to_friend'</span><span class="p">,</span> <span class="s1">'length_in_months'</span><span class="p">))</span> |
Использование встроенного набора форм в представлении
Возможно, вы захотите предоставить представление, позволяющее пользователю редактировать связанные объекты модели. Вот как вы можете это сделать:
1 2 3 4 5 6 7 8 9 10 11 12 |
<strong>def</strong> manage_books(request, author_id): author = Author.objects.get(pk=author_id) BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',)) <strong>if</strong> request.method == "POST": formset = BookInlineFormSet(request.POST, request.FILES, instance=author) <strong>if</strong> formset.is_valid(): formset.save() <em># Do something. Should generally end with a redirect. For example:</em> <strong>return</strong> HttpResponseRedirect(author.get_absolute_url()) <strong>else</strong>: formset = BookInlineFormSet(instance=author) <strong>return</strong> render(request, 'manage_books.html', {'formset': formset}) |
Обратите внимание, как мы передаем экземпляр в случаях POST и GET.
Указание виджетов для использования во встроенной форме
inlineformset_factory использует modelformset_factory и передает большинство своих аргументов для modelformset_factory. Это означает, что вы можете использовать параметр widgets так же, как и передать его в modelformset_factory. См. Определение виджетов для использования в форме с виджетами выше.