API форм

Об этом документе

В этом документе рассматриваются детали API форм Django. Сначала вам следует прочитать introduction to working with forms.

Связанные и несвязанные формы

Экземпляр Form либо связан с набором данных, либо не связан.

  • Если он связан с набором данных, он способен проверить эти данные и вывести форму в виде HTML с отображением данных в HTML.
  • Если это unbound, он не может выполнить валидацию (потому что нет данных для валидации!), но он все еще может отобразить пустую форму как HTML.
class Form[исходный код]

Чтобы создать несвязанный экземпляр Form, инстанцируйте класс:

>>> f = ContactForm()

Чтобы привязать данные к форме, передайте их в виде словаря в качестве первого параметра в конструктор вашего класса Form:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)

В этом словаре ключами являются имена полей, которые соответствуют атрибутам в вашем классе Form. Значения - это данные, которые вы пытаетесь проверить. Обычно это строки, но нет требования, чтобы они были строками; тип передаваемых данных зависит от Field, как мы увидим через некоторое время.

Form.is_bound

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

>>> f = ContactForm()
>>> f.is_bound
False
>>> f = ContactForm({'subject': 'hello'})
>>> f.is_bound
True

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

>>> f = ContactForm({})
>>> f.is_bound
True

Если у вас есть связанный экземпляр Form и вы хотите как-то изменить данные, или если вы хотите связать несвязанный экземпляр Form с какими-то данными, создайте еще один экземпляр Form. Не существует способа изменить данные в экземпляре Form. После создания экземпляра Form следует считать его данные неизменяемыми, независимо от того, есть у него данные или нет.

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

Form.clean()

Реализуйте метод clean() на вашем Form, когда вам необходимо добавить пользовательскую валидацию для полей, которые являются взаимозависимыми. Пример использования см. в Очистка и проверка полей, которые зависят друг от друга.

Form.is_valid()

Основная задача объекта Form заключается в проверке данных. При наличии связанного экземпляра Form вызовите метод is_valid(), чтобы запустить проверку и вернуть булево значение, указывающее, были ли данные достоверными:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True

Давайте попробуем с недействительными данными. В данном случае subject пуст (ошибка, так как по умолчанию все поля обязательны для заполнения), а sender не является действительным адресом электронной почты:

>>> data = {'subject': '',
...         'message': 'Hi there',
...         'sender': 'invalid email address',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False
Form.errors

Обратитесь к атрибуту errors, чтобы получить словарь сообщений об ошибках:

>>> f.errors
{'sender': ['Enter a valid email address.'], 'subject': ['This field is required.']}

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

Вы можете получить доступ к errors без необходимости сначала вызывать is_valid(). Данные формы будут проверены при первом вызове is_valid() или обращении к errors.

Процедура валидации будет вызвана только один раз, независимо от того, сколько раз вы обращаетесь к errors или вызываете is_valid(). Это означает, что если валидация имеет побочные эффекты, то эти побочные эффекты будут вызваны только один раз.

Form.errors.as_data()

Возвращает dict, который отображает поля на их исходные ValidationError экземпляры.

>>> f.errors.as_data()
{'sender': [ValidationError(['Enter a valid email address.'])],
'subject': [ValidationError(['This field is required.'])]}

Используйте этот метод в любое время, когда вам нужно идентифицировать ошибку по ее code. Это позволяет, например, переписать сообщение об ошибке или написать пользовательскую логику в представлении при наличии данной ошибки. Его также можно использовать для сериализации ошибок в пользовательском формате (например, XML); например, as_json() зависит от as_data().

Необходимость метода as_data() обусловлена обратной совместимостью. Ранее экземпляры ValidationError терялись, как только их рендеренные сообщения об ошибках добавлялись в словарь Form.errors. В идеале Form.errors должен был хранить экземпляры ValidationError, а методы с префиксом as_ могли бы их рендерить, но пришлось сделать наоборот, чтобы не ломать код, ожидающий рендеринга сообщений об ошибках в Form.errors.

Form.errors.as_json(escape_html=False)

Возвращает ошибки, сериализованные в виде JSON.

>>> f.errors.as_json()
{"sender": [{"message": "Enter a valid email address.", "code": "invalid"}],
"subject": [{"message": "This field is required.", "code": "required"}]}

По умолчанию as_json() не экранирует свой вывод. Если вы используете его для чего-то вроде AJAX-запросов к форме представления, где клиент интерпретирует ответ и вставляет ошибки на страницу, вы захотите убедиться, что результаты на стороне клиента экранированы, чтобы избежать возможности межсайтовой скриптинг-атаки. Вы можете сделать это в JavaScript с помощью element.textContent = errorText или с помощью $(el).text(errorText) в jQuery (а не его функции .html()).

Если по какой-то причине вы не хотите использовать экранирование на стороне клиента, вы также можете установить escape_html=True, и сообщения об ошибках будут экранированы, чтобы вы могли использовать их непосредственно в HTML.

Form.errors.get_json_data(escape_html=False)

Возвращает ошибки в виде словаря, пригодного для сериализации в JSON. Form.errors.as_json() возвращает сериализованный JSON, в то время как это возвращает данные об ошибках до их сериализации.

Параметр escape_html ведет себя так, как описано в Form.errors.as_json().

Form.add_error(field, error)

Этот метод позволяет добавлять ошибки в определенные поля из метода Form.clean() или вообще извне формы, например, из представления.

Аргумент field является именем поля, к которому должны быть добавлены ошибки. Если его значение равно None, то ошибка будет рассматриваться как неполевая ошибка, возвращаемая Form.non_field_errors().

Аргумент error может быть строкой или, предпочтительно, экземпляром ValidationError. См. раздел Поднятие ValidationError о лучших практиках при определении ошибок формы.

Обратите внимание, что Form.add_error() автоматически удаляет соответствующее поле из cleaned_data.

Form.has_error(field, code=None)

Этот метод возвращает булево число, обозначающее, есть ли в поле ошибка с конкретной ошибкой code. Если code - None, то он вернет True, если поле вообще содержит какие-либо ошибки.

Для проверки ошибок, не связанных с полем, используйте NON_FIELD_ERRORS в качестве параметра field.

Form.non_field_errors()

Этот метод возвращает список ошибок из Form.errors, которые не связаны с конкретным полем. Сюда входят ValidationErrorошибки, возникающие в Form.clean() и ошибки, добавленные с помощью Form.add_error(None, "...").

Поведение несвязанных форм

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

>>> f = ContactForm()
>>> f.is_valid()
False
>>> f.errors
{}

Динамические начальные значения

Form.initial

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

Чтобы добиться этого, используйте аргумент initial для Form. Этот аргумент, если он задан, должен представлять собой словарь, отображающий имена полей на начальные значения. Включите только те поля, для которых вы указываете начальное значение; нет необходимости включать каждое поле в вашу форму. Например:

>>> f = ContactForm(initial={'subject': 'Hi there!'})

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

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

>>> from django import forms
>>> class CommentForm(forms.Form):
...     name = forms.CharField(initial='class')
...     url = forms.URLField()
...     comment = forms.CharField()
>>> f = CommentForm(initial={'name': 'instance'}, auto_id=False)
>>> print(f)
<tr><th>Name:</th><td><input type="text" name="name" value="instance" required></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" required></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" required></td></tr>
Form.get_initial_for_field(field, field_name)

Используйте get_initial_for_field() для получения начальных данных для поля формы. Он получает данные из Form.initial и Field.initial, в таком порядке, и оценивает любые вызываемые начальные значения.

Проверка того, какие данные формы были изменены

Form.has_changed()

Используйте метод has_changed() на вашем Form, когда вам нужно проверить, были ли данные формы изменены по сравнению с начальными данными.

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data, initial=data)
>>> f.has_changed()
False

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

>>> f = ContactForm(request.POST, initial=data)
>>> f.has_changed()

has_changed() будет True, если данные из request.POST отличаются от тех, что были предоставлены в initial или False в противном случае. Результат вычисляется путем вызова Field.has_changed() для каждого поля формы.

Form.changed_data

Атрибут changed_data возвращает список имен полей, значения которых в связанных данных формы (обычно request.POST) отличаются от того, что было предоставлено в initial. Он возвращает пустой список, если данные не отличаются.

>>> f = ContactForm(request.POST, initial=data)
>>> if f.has_changed():
...     print("The following fields changed: %s" % ", ".join(f.changed_data))
>>> f.changed_data
['subject', 'message']

Доступ к полям из формы

Form.fields

Вы можете получить доступ к полям экземпляра Form из его атрибута fields:

>>> for row in f.fields.values(): print(row)
...
<django.forms.fields.CharField object at 0x7ffaac632510>
<django.forms.fields.URLField object at 0x7ffaac632f90>
<django.forms.fields.CharField object at 0x7ffaac3aa050>
>>> f.fields['name']
<django.forms.fields.CharField object at 0x7ffaac6324d0>

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

>>> f.as_table().split('\n')[0]
'<tr><th>Name:</th><td><input name="name" type="text" value="instance" required></td></tr>'
>>> f.fields['name'].label = "Username"
>>> f.as_table().split('\n')[0]
'<tr><th>Username:</th><td><input name="name" type="text" value="instance" required></td></tr>'

Остерегайтесь изменять атрибут base_fields, поскольку это изменение повлияет на все последующие ContactForm экземпляры в рамках одного процесса Python:

>>> f.base_fields['name'].label = "Username"
>>> another_f = CommentForm(auto_id=False)
>>> another_f.as_table().split('\n')[0]
'<tr><th>Username:</th><td><input name="name" type="text" value="class" required></td></tr>'

Доступ к «чистым» данным

Form.cleaned_data

Каждое поле в классе Form отвечает не только за проверку данных, но и за их «очистку» - нормализацию до согласованного формата. Это хорошая особенность, поскольку она позволяет вводить данные для конкретного поля различными способами, всегда приводя к согласованному результату.

Например, DateField нормализует входные данные в объект Python datetime.date. Независимо от того, передаете ли вы ему строку в формате '1994-07-15', объект datetime.date или ряд других форматов, DateField всегда нормализует ее в объект datetime.date, пока он действителен.

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

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}

Обратите внимание, что любое текстовое поле - такое как CharField или EmailField - всегда очищает вводимые данные в строку. Мы рассмотрим последствия кодирования позже в этом документе.

Если ваши данные не проходят валидацию, словарь cleaned_data содержит только валидные поля:

>>> data = {'subject': '',
...         'message': 'Hi there',
...         'sender': 'invalid email address',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there'}

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

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True,
...         'extra_field_1': 'foo',
...         'extra_field_2': 'bar',
...         'extra_field_3': 'baz'}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data # Doesn't contain extra_field_1, etc.
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}

Когда Form действителен, cleaned_data будет включать ключ и значение для всех своих полей, даже если данные не включали значение для некоторых необязательных полей. В этом примере словарь данных не включает значение для поля nick_name, но cleaned_data включает его, с пустым значением:

>>> from django import forms
>>> class OptionalPersonForm(forms.Form):
...     first_name = forms.CharField()
...     last_name = forms.CharField()
...     nick_name = forms.CharField(required=False)
>>> data = {'first_name': 'John', 'last_name': 'Lennon'}
>>> f = OptionalPersonForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'nick_name': '', 'first_name': 'John', 'last_name': 'Lennon'}

В этом примере значение cleaned_data для nick_name установлено в пустую строку, потому что nick_name - это CharField, а CharFields рассматривает пустые значения как пустую строку. Каждый тип поля знает, что такое «пустое» значение - например, для DateField это None вместо пустой строки. Для получения подробной информации о поведении каждого поля в этом случае, смотрите примечание «Пустое значение» для каждого поля в разделе «Встроенные классы Field» ниже.

Вы можете написать код для выполнения валидации для отдельных полей формы (на основе их названия) или для формы в целом (учитывая комбинации различных полей). Более подробная информация об этом содержится в Валидация форм и полей.

Вывод форм в формате HTML

Вторая задача объекта Form - отобразить себя как HTML. Для этого, print он:

>>> f = ContactForm()
>>> print(f)
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>

Если форма связана с данными, то HTML-вывод будет включать эти данные соответствующим образом. Например, если поле представлено символом <input type="text">, то данные будут находиться в атрибуте value. Если поле представлено символом <input type="checkbox">, то HTML-вывод будет включать checked, если это уместно:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> print(f)
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" value="hello" required></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" value="Hi there" required></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" value="foo@example.com" required></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" checked></td></tr>

Этот вывод по умолчанию представляет собой двухколоночную HTML-таблицу, с <tr> для каждого поля. Обратите внимание на следующее:

  • Для гибкости вывод не включает теги <table> и </table>, а также теги <form> и </form> или тег <input type="submit">. Это ваша работа, чтобы сделать это.
  • Каждый тип поля имеет HTML-представление по умолчанию. CharField представляется символом <input type="text">, а EmailField - <input type="email">. BooleanField(null=False) представляется символом <input type="checkbox">. Обратите внимание, что это всего лишь разумные значения по умолчанию; вы можете указать, какой HTML использовать для данного поля, используя виджеты, о которых мы расскажем в ближайшее время.
  • HTML name для каждого тега берется непосредственно из имени его атрибута в классе ContactForm.
  • Текстовая метка для каждого поля - например, 'Subject:', 'Message:' и 'Cc myself:' - генерируется из имени поля путем преобразования всех знаков подчеркивания в пробелы и перевода первой буквы в верхний регистр. Еще раз отметим, что это всего лишь разумные значения по умолчанию; вы также можете задать метки вручную.
  • Каждая текстовая метка окружена тегом HTML <label>, который указывает на соответствующее поле формы через его id. Его id, в свою очередь, формируется путем добавления 'id_' к имени поля. Атрибуты id и теги <label> включаются в вывод по умолчанию, чтобы следовать лучшим практикам, но вы можете изменить это поведение.
  • При выводе используется синтаксис HTML5, ориентированный на <!DOCTYPE html>. Например, он использует булевы атрибуты, такие как checked, а не стиль XHTML checked='checked'.

Хотя вывод <table> является стилем вывода по умолчанию, когда вы print форму, доступны и другие стили вывода. Каждый стиль доступен как метод на объекте формы, и каждый метод вывода возвращает строку.

as_p()

Form.as_p()

as_p() отображает форму как серию тегов <p>, каждый <p> содержит одно поле:

>>> f = ContactForm()
>>> f.as_p()
'<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p>\n<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></p>\n<p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" required></p>\n<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>'
>>> print(f.as_p())
<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p>
<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></p>
<p><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></p>
<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>

as_ul()

Form.as_ul()

as_ul() отображает форму как серию тегов <li>, каждый <li> содержит одно поле. Он не включает <ul> или </ul>, так что вы можете указать любые HTML атрибуты на <ul> для гибкости:

>>> f = ContactForm()
>>> f.as_ul()
'<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></li>\n<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></li>\n<li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></li>\n<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></li>'
>>> print(f.as_ul())
<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></li>
<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></li>
<li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></li>
<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></li>

as_table()

Form.as_table()

Наконец, as_table() выводит форму в виде HTML <table>. Это точно так же, как и print. На самом деле, когда вы print объект формы, он вызывает свой as_table() метод за кулисами:

>>> f = ContactForm()
>>> f.as_table()
'<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr>\n<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr>\n<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr>\n<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>'
>>> print(f)
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>

Стилизация требуемых или ошибочных строк формы

Form.error_css_class
Form.required_css_class

Довольно часто требуется стилизовать строки формы и поля, которые являются обязательными или содержат ошибки. Например, вы можете выделить обязательные строки формы жирным шрифтом, а ошибки - красным.

Класс Form имеет пару крючков, которые можно использовать для добавления атрибутов class к необходимым строкам или к строкам с ошибками: установить атрибуты Form.error_css_class и/или Form.required_css_class:

from django import forms

class ContactForm(forms.Form):
    error_css_class = 'error'
    required_css_class = 'required'

    # ... and the rest of your fields here

После этого строкам будут присвоены классы "error" и/или "required", по мере необходимости. HTML будет выглядеть примерно так:

>>> f = ContactForm(data)
>>> print(f.as_table())
<tr class="required"><th><label class="required" for="id_subject">Subject:</label>    ...
<tr class="required"><th><label class="required" for="id_message">Message:</label>    ...
<tr class="required error"><th><label class="required" for="id_sender">Sender:</label>      ...
<tr><th><label for="id_cc_myself">Cc myself:<label> ...
>>> f['subject'].label_tag()
<label class="required" for="id_subject">Subject:</label>
>>> f['subject'].label_tag(attrs={'class': 'foo'})
<label for="id_subject" class="foo required">Subject:</label>

Настройка атрибутов HTML id и тегов <label> элементов формы

Form.auto_id

По умолчанию методы рендеринга формы включают:

  • Атрибуты HTML id на элементах формы.
  • Соответствующие теги <label> окружают метки. Тег HTML <label> указывает, какой текст метки связан с каким элементом формы. Это небольшое усовершенствование делает формы более удобными для использования и более доступными для вспомогательных устройств. Всегда полезно использовать теги <label>.

Значения атрибутов id генерируются путем добавления id_ к именам полей формы. Однако это поведение можно настроить, если вы хотите изменить соглашение id или полностью удалить атрибуты HTML id и теги <label>.

Используйте аргумент auto_id в конструкторе Form для управления поведением id и метки. Этот аргумент должен быть True, False или строкой.

Если auto_id равно False, то вывод формы не будет включать ни <label> тегов, ни id атрибутов:

>>> f = ContactForm(auto_id=False)
>>> print(f.as_table())
<tr><th>Subject:</th><td><input type="text" name="subject" maxlength="100" required></td></tr>
<tr><th>Message:</th><td><input type="text" name="message" required></td></tr>
<tr><th>Sender:</th><td><input type="email" name="sender" required></td></tr>
<tr><th>Cc myself:</th><td><input type="checkbox" name="cc_myself"></td></tr>
>>> print(f.as_ul())
<li>Subject: <input type="text" name="subject" maxlength="100" required></li>
<li>Message: <input type="text" name="message" required></li>
<li>Sender: <input type="email" name="sender" required></li>
<li>Cc myself: <input type="checkbox" name="cc_myself"></li>
>>> print(f.as_p())
<p>Subject: <input type="text" name="subject" maxlength="100" required></p>
<p>Message: <input type="text" name="message" required></p>
<p>Sender: <input type="email" name="sender" required></p>
<p>Cc myself: <input type="checkbox" name="cc_myself"></p>

Если auto_id установлено в True, то вывод формы будет включать <label> теги и будет использовать имя поля в качестве id для каждого поля формы:

>>> f = ContactForm(auto_id=True)
>>> print(f.as_table())
<tr><th><label for="subject">Subject:</label></th><td><input id="subject" type="text" name="subject" maxlength="100" required></td></tr>
<tr><th><label for="message">Message:</label></th><td><input type="text" name="message" id="message" required></td></tr>
<tr><th><label for="sender">Sender:</label></th><td><input type="email" name="sender" id="sender" required></td></tr>
<tr><th><label for="cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="cc_myself"></td></tr>
>>> print(f.as_ul())
<li><label for="subject">Subject:</label> <input id="subject" type="text" name="subject" maxlength="100" required></li>
<li><label for="message">Message:</label> <input type="text" name="message" id="message" required></li>
<li><label for="sender">Sender:</label> <input type="email" name="sender" id="sender" required></li>
<li><label for="cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="cc_myself"></li>
>>> print(f.as_p())
<p><label for="subject">Subject:</label> <input id="subject" type="text" name="subject" maxlength="100" required></p>
<p><label for="message">Message:</label> <input type="text" name="message" id="message" required></p>
<p><label for="sender">Sender:</label> <input type="email" name="sender" id="sender" required></p>
<p><label for="cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="cc_myself"></p>

Если auto_id установлен в строку, содержащую символ формата '%s', то вывод формы будет включать теги <label> и генерировать атрибуты id на основе строки формата. Например, для строки формата 'field_%s', поле с именем subject получит id значение 'field_subject'. Продолжая наш пример:

>>> f = ContactForm(auto_id='id_for_%s')
>>> print(f.as_table())
<tr><th><label for="id_for_subject">Subject:</label></th><td><input id="id_for_subject" type="text" name="subject" maxlength="100" required></td></tr>
<tr><th><label for="id_for_message">Message:</label></th><td><input type="text" name="message" id="id_for_message" required></td></tr>
<tr><th><label for="id_for_sender">Sender:</label></th><td><input type="email" name="sender" id="id_for_sender" required></td></tr>
<tr><th><label for="id_for_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_for_cc_myself"></td></tr>
>>> print(f.as_ul())
<li><label for="id_for_subject">Subject:</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" required></li>
<li><label for="id_for_message">Message:</label> <input type="text" name="message" id="id_for_message" required></li>
<li><label for="id_for_sender">Sender:</label> <input type="email" name="sender" id="id_for_sender" required></li>
<li><label for="id_for_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself"></li>
>>> print(f.as_p())
<p><label for="id_for_subject">Subject:</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" required></p>
<p><label for="id_for_message">Message:</label> <input type="text" name="message" id="id_for_message" required></p>
<p><label for="id_for_sender">Sender:</label> <input type="email" name="sender" id="id_for_sender" required></p>
<p><label for="id_for_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself"></p>

Если auto_id установлено в любое другое истинное значение - например, в строку, которая не включает %s - то библиотека будет действовать так, как будто auto_id - это True.

По умолчанию auto_id устанавливается в строку 'id_%s'.

Form.label_suffix

Переводимая строка (по умолчанию двоеточие (:) на английском языке), которая будет добавлена после любого имени метки при отображении формы.

Можно настроить этот символ или полностью его пропустить, используя параметр label_suffix:

>>> f = ContactForm(auto_id='id_for_%s', label_suffix='')
>>> print(f.as_ul())
<li><label for="id_for_subject">Subject</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" required></li>
<li><label for="id_for_message">Message</label> <input type="text" name="message" id="id_for_message" required></li>
<li><label for="id_for_sender">Sender</label> <input type="email" name="sender" id="id_for_sender" required></li>
<li><label for="id_for_cc_myself">Cc myself</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself"></li>
>>> f = ContactForm(auto_id='id_for_%s', label_suffix=' ->')
>>> print(f.as_ul())
<li><label for="id_for_subject">Subject -></label> <input id="id_for_subject" type="text" name="subject" maxlength="100" required></li>
<li><label for="id_for_message">Message -></label> <input type="text" name="message" id="id_for_message" required></li>
<li><label for="id_for_sender">Sender -></label> <input type="email" name="sender" id="id_for_sender" required></li>
<li><label for="id_for_cc_myself">Cc myself -></label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself"></li>

Обратите внимание, что суффикс метки добавляется только в том случае, если последний символ метки не является знаком препинания (в английском языке это ., !, ? или :).

Поля также могут определять свои собственные label_suffix. Это будет иметь приоритет над Form.label_suffix. Суффикс также может быть переопределен во время выполнения с помощью параметра label_suffix к label_tag().

Form.use_required_attribute

Если установлено значение True (по умолчанию), обязательные поля формы будут иметь HTML-атрибут required.

Formsets инстанцируйте формы с помощью use_required_attribute=False, чтобы избежать неправильной валидации браузера при добавлении и удалении форм из набора форм.

Настройка отображения виджетов формы

Form.default_renderer

Определяет renderer, который будет использоваться для формы. По умолчанию установлено значение None, что означает использование рендеринга по умолчанию, заданного параметром FORM_RENDERER.

Вы можете задать это как атрибут класса при объявлении формы или использовать аргумент renderer для Form.__init__(). Например:

from django import forms

class MyForm(forms.Form):
    default_renderer = MyRenderer()

или:

form = MyForm(renderer=MyRenderer())

Примечания по заказу полевых работ

В ярлыках as_p(), as_ul() и as_table() поля отображаются в том порядке, в котором вы определили их в классе формы. Например, в примере ContactForm поля определены в порядке subject, message, sender, cc_myself. Чтобы изменить порядок вывода HTML, измените порядок, в котором эти поля перечислены в классе.

Существует еще несколько способов индивидуализации заказа:

Form.field_order

По умолчанию Form.field_order=None, что сохраняет порядок, в котором вы определили поля в классе формы. Если field_order является списком имен полей, то поля упорядочиваются так, как указано в списке, а оставшиеся поля добавляются в соответствии с порядком по умолчанию. Неизвестные имена полей в списке игнорируются. Это позволяет отключить поле в подклассе, установив его в None без необходимости переопределять порядок.

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

Form.order_fields(field_order)

Вы можете переставлять поля в любое время, используя order_fields() со списком имен полей, как в field_order.

Как отображаются ошибки

Если вы выводите связанный объект Form, то при выводе автоматически выполняется валидация формы, если она еще не выполнена, и в HTML-вывод включаются ошибки валидации в виде <ul class="errorlist"> рядом с полем. Конкретное расположение сообщений об ошибках зависит от используемого метода вывода:

>>> data = {'subject': '',
...         'message': 'Hi there',
...         'sender': 'invalid email address',
...         'cc_myself': True}
>>> f = ContactForm(data, auto_id=False)
>>> print(f.as_table())
<tr><th>Subject:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="subject" maxlength="100" required></td></tr>
<tr><th>Message:</th><td><input type="text" name="message" value="Hi there" required></td></tr>
<tr><th>Sender:</th><td><ul class="errorlist"><li>Enter a valid email address.</li></ul><input type="email" name="sender" value="invalid email address" required></td></tr>
<tr><th>Cc myself:</th><td><input checked type="checkbox" name="cc_myself"></td></tr>
>>> print(f.as_ul())
<li><ul class="errorlist"><li>This field is required.</li></ul>Subject: <input type="text" name="subject" maxlength="100" required></li>
<li>Message: <input type="text" name="message" value="Hi there" required></li>
<li><ul class="errorlist"><li>Enter a valid email address.</li></ul>Sender: <input type="email" name="sender" value="invalid email address" required></li>
<li>Cc myself: <input checked type="checkbox" name="cc_myself"></li>
>>> print(f.as_p())
<p><ul class="errorlist"><li>This field is required.</li></ul></p>
<p>Subject: <input type="text" name="subject" maxlength="100" required></p>
<p>Message: <input type="text" name="message" value="Hi there" required></p>
<p><ul class="errorlist"><li>Enter a valid email address.</li></ul></p>
<p>Sender: <input type="email" name="sender" value="invalid email address" required></p>
<p>Cc myself: <input checked type="checkbox" name="cc_myself"></p>

Настройка формата списка ошибок

По умолчанию формы используют django.forms.utils.ErrorList для форматирования ошибок валидации. Если вы хотите использовать альтернативный класс для отображения ошибок, вы можете передать его во время конструирования:

>>> from django.forms.utils import ErrorList
>>> class DivErrorList(ErrorList):
...     def __str__(self):
...         return self.as_divs()
...     def as_divs(self):
...         if not self: return ''
...         return '<div class="errorlist">%s</div>' % ''.join(['<div class="error">%s</div>' % e for e in self])
>>> f = ContactForm(data, auto_id=False, error_class=DivErrorList)
>>> f.as_p()
<div class="errorlist"><div class="error">This field is required.</div></div>
<p>Subject: <input type="text" name="subject" maxlength="100" required></p>
<p>Message: <input type="text" name="message" value="Hi there" required></p>
<div class="errorlist"><div class="error">Enter a valid email address.</div></div>
<p>Sender: <input type="email" name="sender" value="invalid email address" required></p>
<p>Cc myself: <input checked type="checkbox" name="cc_myself"></p>

Более детальный вывод

Методы as_p(), as_ul() и as_table() являются ярлыками - это не единственный способ отображения объекта формы.

class BoundField[исходный код]

Используется для отображения HTML или атрибутов доступа для одного поля экземпляра Form.

Метод __str__() этого объекта отображает HTML для данного поля.

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

>>> form = ContactForm()
>>> print(form['subject'])
<input id="id_subject" type="text" name="subject" maxlength="100" required>

Чтобы получить все объекты BoundField, выполните итерацию формы:

>>> form = ContactForm()
>>> for boundfield in form: print(boundfield)
<input id="id_subject" type="text" name="subject" maxlength="100" required>
<input type="text" name="message" id="id_message" required>
<input type="email" name="sender" id="id_sender" required>
<input type="checkbox" name="cc_myself" id="id_cc_myself">

Вывод для конкретного поля соответствует настройке auto_id объекта формы:

>>> f = ContactForm(auto_id=False)
>>> print(f['message'])
<input type="text" name="message" required>
>>> f = ContactForm(auto_id='id_%s')
>>> print(f['message'])
<input type="text" name="message" id="id_message" required>

Атрибуты BoundField

BoundField.auto_id

Атрибут HTML ID для данного BoundField. Возвращает пустую строку, если Form.auto_id является False.

BoundField.data

Это свойство возвращает данные для этого BoundField, извлеченные методом value_from_datadict() виджета, или None, если они не были переданы:

>>> unbound_form = ContactForm()
>>> print(unbound_form['subject'].data)
None
>>> bound_form = ContactForm(data={'subject': 'My Subject'})
>>> print(bound_form['subject'].data)
My Subject
BoundField.errors

list-like object, который при печати отображается как HTML <ul class="errorlist">:

>>> data = {'subject': 'hi', 'message': '', 'sender': '', 'cc_myself': ''}
>>> f = ContactForm(data, auto_id=False)
>>> print(f['message'])
<input type="text" name="message" required>
>>> f['message'].errors
['This field is required.']
>>> print(f['message'].errors)
<ul class="errorlist"><li>This field is required.</li></ul>
>>> f['subject'].errors
[]
>>> print(f['subject'].errors)

>>> str(f['subject'].errors)
''
BoundField.field

Экземпляр формы Field из класса формы, в который обернут этот BoundField.

BoundField.form

Экземпляр Form, к которому привязан данный BoundField.

BoundField.help_text

Значение help_text поля.

BoundField.html_name

Имя, которое будет использоваться в HTML атрибуте виджета name. При этом учитывается форма prefix.

BoundField.id_for_label

Используйте это свойство для отображения ID этого поля. Например, если вы вручную создаете <label> в своем шаблоне (несмотря на то, что label_tag() сделает это за вас):

<label for="{{ form.my_field.id_for_label }}">...</label>{{ my_field }}

По умолчанию это будет имя поля с префиксом id_id_my_field» для примера выше). Вы можете изменить ID, установив attrs в виджете поля. Например, объявив поле следующим образом:

my_field = forms.CharField(widget=forms.TextInput(attrs={'id': 'myFIELD'}))

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

<label for="myFIELD">...</label><input id="myFIELD" type="text" name="my_field" required>
BoundField.is_hidden

Возвращает True, если виджет этого BoundField скрыт.

BoundField.label

label> поля. Это используется в label_tag().

BoundField.name

Имя этого поля в форме:

>>> f = ContactForm()
>>> print(f['subject'].name)
subject
>>> print(f['message'].name)
message
BoundField.widget_type
New in Django 3.1.

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

{% for field in form %}
    {% if field.widget_type == 'checkbox' %}
        # render one way
    {% else %}
        # render another way
    {% endif %}
{% endfor %}

Методы BoundField

BoundField.as_hidden(attrs=None, **kwargs)[исходный код]

Возвращает строку HTML для представления этого как <input type="hidden">.

**kwargs передаются в as_widget().

Этот метод используется в основном для внутренних целей. Вместо него следует использовать виджет.

BoundField.as_widget(widget=None, attrs=None, only_initial=False)[исходный код]

Рендерит поле путем рендеринга переданного виджета, добавляя любые HTML-атрибуты, переданные как attrs. Если виджет не указан, то будет использован виджет поля по умолчанию.

only_initial используется внутренними модулями Django и не должен быть установлен явно.

BoundField.css_classes(extra_classes=None)[исходный код]

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

>>> f = ContactForm(data={'message': ''})
>>> f['message'].css_classes()
'required'

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

>>> f = ContactForm(data={'message': ''})
>>> f['message'].css_classes('foo bar')
'foo bar required'
BoundField.label_tag(contents=None, attrs=None, label_suffix=None)[исходный код]

Чтобы отдельно отобразить тег label поля формы, можно вызвать его метод label_tag():

>>> f = ContactForm(data={'message': ''})
>>> print(f['message'].label_tag())
<label for="id_message">Message:</label>

Вы можете предоставить параметр contents, который заменит автоматически сгенерированный тег label. Словарь attrs может содержать дополнительные атрибуты для тега <label>.

Генерируемый HTML содержит суффикс label_suffix формы (двоеточие, по умолчанию) или, если задан, суффикс label_suffix текущего поля. Необязательный параметр label_suffix позволяет отменить любой ранее установленный суффикс. Например, вы можете использовать пустую строку, чтобы скрыть метку для выбранных полей. Если вам нужно сделать это в шаблоне, вы можете написать пользовательский фильтр, позволяющий передавать параметры в label_tag.

BoundField.value()[исходный код]

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

>>> initial = {'subject': 'welcome'}
>>> unbound_form = ContactForm(initial=initial)
>>> bound_form = ContactForm(data={'subject': 'hi'}, initial=initial)
>>> print(unbound_form['subject'].value())
welcome
>>> print(bound_form['subject'].value())
hi

Настройка BoundField

Если вам необходимо получить доступ к дополнительной информации о поле формы в шаблоне и использование подкласса Field недостаточно, рассмотрите также возможность настройки BoundField.

Поле пользовательской формы может переопределять get_bound_field():

Field.get_bound_field(form, field_name)[исходный код]

Принимает экземпляр Form и имя поля. Возвращаемое значение будет использоваться при обращении к полю в шаблоне. Скорее всего, это будет экземпляр подкласса BoundField.

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

class GPSCoordinatesBoundField(BoundField):
    @property
    def country(self):
        """
        Return the country the coordinates lie in or None if it can't be
        determined.
        """
        value = self.value()
        if value:
            return get_country_from_coordinates(value)
        else:
            return None

class GPSCoordinatesField(Field):
    def get_bound_field(self, form, field_name):
        return GPSCoordinatesBoundField(form, self, field_name)

Теперь вы можете получить доступ к стране в шаблоне с помощью {{ form.coordinates.country }}.

Привязка загруженных файлов к форме

Работа с формами, имеющими поля FileField и ImageField, немного сложнее, чем с обычной формой.

Во-первых, чтобы загрузить файлы, вам нужно убедиться, что ваш элемент <form> правильно определяет enctype как "multipart/form-data":

<form enctype="multipart/form-data" method="post" action="/foo/">

Во-вторых, когда вы используете форму, вам необходимо связать данные файла. Файловые данные обрабатываются отдельно от обычных данных формы, поэтому, если ваша форма содержит FileField и ImageField, вам необходимо указать второй аргумент при привязке формы. Таким образом, если мы расширим нашу ContactForm и добавим в нее ImageField, называемую mugshot, нам нужно будет связать данные файла, содержащего изображение кружки:

# Bound form with an image field
>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', <file data>)}
>>> f = ContactFormWithMugshot(data, file_data)

На практике вы обычно указываете request.FILES в качестве источника данных файла (точно так же, как вы используете request.POST в качестве источника данных формы):

# Bound form with an image field, data from the request
>>> f = ContactFormWithMugshot(request.POST, request.FILES)

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

# Unbound form with an image field
>>> f = ContactFormWithMugshot()

Тестирование многочастных форм

Form.is_multipart()

Если вы пишете многоразовые представления или шаблоны, вы можете не знать заранее, является ли ваша форма многочастной или нет. Метод is_multipart() сообщает вам, требует ли форма многочастной кодировки для отправки:

>>> f = ContactFormWithMugshot()
>>> f.is_multipart()
True

Вот пример того, как это можно использовать в шаблоне:

{% if form.is_multipart %}
    <form enctype="multipart/form-data" method="post" action="/foo/">
{% else %}
    <form method="post" action="/foo/">
{% endif %}
{{ form }}
</form>

Подклассификация форм

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

Когда вы создаете подкласс пользовательского класса Form, полученный подкласс будет включать все поля родительского класса (классов), а затем поля, которые вы определите в подклассе.

В этом примере ContactFormWithPriority содержит все поля из ContactForm, плюс дополнительное поле priority. Поля ContactForm упорядочены в первую очередь:

>>> class ContactFormWithPriority(ContactForm):
...     priority = forms.CharField()
>>> f = ContactFormWithPriority(auto_id=False)
>>> print(f.as_ul())
<li>Subject: <input type="text" name="subject" maxlength="100" required></li>
<li>Message: <input type="text" name="message" required></li>
<li>Sender: <input type="email" name="sender" required></li>
<li>Cc myself: <input type="checkbox" name="cc_myself"></li>
<li>Priority: <input type="text" name="priority" required></li>

Можно подклассифицировать несколько форм, рассматривая формы как миксины. В этом примере BeatleForm является подклассом PersonForm и InstrumentForm (в таком порядке), и его список полей включает поля из родительских классов:

>>> from django import forms
>>> class PersonForm(forms.Form):
...     first_name = forms.CharField()
...     last_name = forms.CharField()
>>> class InstrumentForm(forms.Form):
...     instrument = forms.CharField()
>>> class BeatleForm(InstrumentForm, PersonForm):
...     haircut_type = forms.CharField()
>>> b = BeatleForm(auto_id=False)
>>> print(b.as_ul())
<li>First name: <input type="text" name="first_name" required></li>
<li>Last name: <input type="text" name="last_name" required></li>
<li>Instrument: <input type="text" name="instrument" required></li>
<li>Haircut type: <input type="text" name="haircut_type" required></li>

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

>>> from django import forms

>>> class ParentForm(forms.Form):
...     name = forms.CharField()
...     age = forms.IntegerField()

>>> class ChildForm(ParentForm):
...     name = None

>>> list(ChildForm().fields)
['age']

Префиксы для форм

Form.prefix

Вы можете разместить несколько форм Django внутри одного тега <form>. Чтобы дать каждой Form свое собственное пространство имен, используйте аргумент prefix ключевое слово:

>>> mother = PersonForm(prefix="mother")
>>> father = PersonForm(prefix="father")
>>> print(mother.as_ul())
<li><label for="id_mother-first_name">First name:</label> <input type="text" name="mother-first_name" id="id_mother-first_name" required></li>
<li><label for="id_mother-last_name">Last name:</label> <input type="text" name="mother-last_name" id="id_mother-last_name" required></li>
>>> print(father.as_ul())
<li><label for="id_father-first_name">First name:</label> <input type="text" name="father-first_name" id="id_father-first_name" required></li>
<li><label for="id_father-last_name">Last name:</label> <input type="text" name="father-last_name" id="id_father-last_name" required></li>

Префикс также может быть указан на форме class:

>>> class PersonForm(forms.Form):
...     ...
...     prefix = 'person'
Вернуться на верх