表单 API

关于此文档

本文档介绍了 Django 的表单 API 的具体细节。你应该先阅读 使用表单的介绍

绑定和非绑定表单

一个 Form 实例要么是 绑定 到一组数据,要么是 未绑定

  • 如果是 绑定 了一组数据,它就能够验证这些数据,并将表单渲染成 HTML,并在 HTML 中显示数据。

  • 如果是 未绑定,它就不能进行验证(因为没有数据可验证!),但它仍然可以将空白表单渲染为 HTML。

class Form[source]

要创建一个未绑定的 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()

当你必须为相互依赖的字段添加自定义验证时,在你的 Form 上实现一个 clean() 方法。参见 清理和验证相互依赖的字段 的用法示例。

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 或者使用 jQuery 的 $(el).text(errorText) (而不是它的 .html() 函数)来实现。

如果出于某些原因你不想使用客户端转义,你也可以设置 escape_html=True,错误信息将被转义,这样你就可以直接在 HTML 中使用它们。

Form.errors.get_json_data(escape_html=False)

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 的错误。如果 codeNone,如果字段包含任何错误,它将返回 True

要检查非字段错误,使用 NON_FIELD_ERRORS 作为 field 参数。

Form.non_field_errors()

这个方法从 Form.errors 中返回没有与特定字段关联的错误列表。这包括在 Form.clean() 中引发的 ValidationError 和使用 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,那么后者的 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)
<div>Name:<input type="text" name="name" value="instance" required></div>
<div>Url:<input type="url" name="url" required></div>
<div>Comment:<input type="text" name="comment" required></div>
Form.get_initial_for_field(field, field_name)

返回一个表单字段的初始数据。如果存在的话,它从 Form.initial 检索数据,否则尝试 Field.initial。可调用的值将被执行。

建议使用 BoundField.initial 而不是 get_initial_for_field(),因为 BoundField.initial 具有更简单的接口。而且,与 get_initial_for_field() 不同,BoundField.initial 缓存其值。这在处理返回值可能会变化的可调用对象时特别有用(例如 datetime.nowuuid.uuid4):

>>> import uuid
>>> class UUIDCommentForm(CommentForm):
...     identifier = forms.UUIDField(initial=uuid.uuid4)
...
>>> f = UUIDCommentForm()
>>> f.get_initial_for_field(f.fields["identifier"], "identifier")
UUID('972ca9e4-7bfe-4f5b-af7d-07b3aa306334')
>>> f.get_initial_for_field(f.fields["identifier"], "identifier")
UUID('1b411fab-844e-4dec-bd4f-e9b0495f04d0')
>>> # Using BoundField.initial, for comparison
>>> f["identifier"].initial
UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30')
>>> f["identifier"].initial
UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30')

检查哪些表格数据已经改变

Form.has_changed()

当你需要检查表单数据是否与初始数据发生变化时,请使用 has_changed() 方法。

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

如果来自 request.POST 的数据与 initial 中提供的数据不同,那么 has_changed() 将为 True,否则为 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

您可以通过表单实例的 fields 属性访问 Form 的字段:

>>> 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 实例的字段和 BoundField,以改变它在表单中的呈现方式:

>>> f.as_div().split("</div>")[0]
'<div><label for="id_subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="id_subject">'
>>> f["subject"].label = "Topic"
>>> f.as_div().split("</div>")[0]
'<div><label for="id_subject">Topic:</label><input type="text" name="subject" maxlength="100" required id="id_subject">'

小心不要修改 base_fields 属性,因为这种修改会影响到同一个 Python 进程中所有后续的 ContactForm 实例:

>>> f.base_fields["subject"].label_suffix = "?"
>>> another_f = ContactForm(auto_id=False)
>>> another_f.as_div().split("</div>")[0]
'<div><label for="id_subject">Subject?</label><input type="text" name="subject" maxlength="100" required id="id_subject">'

访问“干净的”数据

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

请注意,任何基于文本的字段——如 CharFieldEmailField——总是将输入清理成一个字符串。我们将在本文档后面介绍编码的含义。

如果你的数据验证不通过,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'}

在上面这个例子中,nick_namecleaned_data 值被设置为一个空字符串,因为 nick_nameCharField,而 CharField 将空值视为空字符串。每个字段类型都知道它的“空”值是什么——例如,对于 DateField,它是 None 而不是空字符串。关于每个字段在这种情况下的行为的全部细节,请参阅下面“内置 Field 类”一节中每个字段的“空值”说明。

你可以编写代码来对特定的表单字段(基于其名称)或整个表单(考虑各种字段的组合)进行验证。更多关于这方面的信息请参见 表单和字段验证

将表单输出为 HTML

一个 Form 对象的第二个任务是将自己呈现为 HTML。要做到这一点,只需将它打印出来:

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

如果表单与数据绑定,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)
<div><label for="id_subject">Subject:</label><input type="text" name="subject" value="hello" maxlength="100" required id="id_subject"></div>
<div><label for="id_message">Message:</label><input type="text" name="message" value="Hi there" required id="id_message"></div>
<div><label for="id_sender">Sender:</label><input type="email" name="sender" value="foo@example.com" required id="id_sender"></div>
<div><label for="id_cc_myself">Cc myself:</label><input type="checkbox" name="cc_myself" id="id_cc_myself" checked></div>

这个默认输出会使用 <div> 包装每个字段。请注意以下内容:

  • 为了灵活性,输出 不包括 <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'

虽然 <div> 输出是在打印表单时的默认输出样式,但你可以通过使用自己的表单模板来自定义输出,这可以在整个站点范围、每个表单或每个实例中进行设置。请参阅 可复用的表单模板

默认渲染

当你打印一个表单时,默认的渲染使用以下方法和属性。

template_name

Form.template_name

如果表单被转换为字符串,例如通过 print(form) 或在模板中通过 {{ form }},则渲染的模板名称。

默认情况下,它返回渲染器的 form_template_name 的值。你可以将其设置为一个字符串模板名称,以便在特定的表单类中进行覆盖。

render()

Form.render(template_name=None, context=None, renderer=None)

render 方法会被 __str__ 方法调用,同时也被 Form.as_div()Form.as_table()Form.as_p()Form.as_ul() 方法调用。所有参数都是可选的,默认值为:

通过传递 template_name,你可以自定义仅用于单个调用的模板。

get_context()

Form.get_context()

返回用于渲染表单的模板上下文。

可用的上下文:

  • form: 绑定表单。

  • fields: 所有绑定字段,除了隐藏字段。

  • hidden_fields: 所有隐藏的绑定字段。

  • errors: 所有与字段无关的或与隐藏字段有关的表单错误。

template_name_label

Form.template_name_label

用于渲染字段的 <label> 的模板,当调用 BoundField.label_tag()/legend_tag() 时使用。可以通过覆盖此属性来更改每个表单的模板,也可以通过覆盖默认模板来更一般地更改,参见 覆盖内置表单模板

输出样式

更改表单输出样式的推荐方法是设置自定义表单模板,可以是整个站点范围、每个表单或每个实例。有关示例,请参阅 可复用的表单模板

以下的辅助函数是为了向后兼容而提供的,它们是对 Form.render() 的代理,传递了特定的 template_name 值。

Note

在框架提供的模板和输出样式中,推荐使用默认的 as_div() 而不是 as_p()as_table()as_ul() 版本,因为该模板实现了 <fieldset><legend> 来组合相关的输入,对于使用屏幕阅读器的用户更容易导航。

每个辅助函数将一个表单方法与一个给出适当模板名称的属性配对。

as_div()

Form.template_name_div

as_div() 使用的模板。默认值:'django/forms/div.html'

Form.as_div()

as_div() 将表单呈现为一系列 <div> 元素,每个 <div> 包含一个字段,例如:

>>> f = ContactForm()
>>> f.as_div()

...生成的 HTML 如下:

<div>
<label for="id_subject">Subject:</label>
<input type="text" name="subject" maxlength="100" required id="id_subject">
</div>
<div>
<label for="id_message">Message:</label>
<input type="text" name="message" required id="id_message">
</div>
<div>
<label for="id_sender">Sender:</label>
<input type="email" name="sender" required id="id_sender">
</div>
<div>
<label for="id_cc_myself">Cc myself:</label>
<input type="checkbox" name="cc_myself" id="id_cc_myself">
</div>

as_p()

Form.template_name_p

as_p() 使用的模板。默认值为 'django/forms/p.html'

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.template_name_ul

as_ul() 使用的模板。默认值为 'django/forms/ul.html'

Form.as_ul()

as_ul() 将表单呈现为一系列 <li> 标签,每个 <li> 包含一个字段。它不包括 <ul></ul>,这样你可以在 <ul> 上指定任何HTML属性以增加灵活性:

>>> 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.template_name_table

as_table() 使用的模板。默认值为 'django/forms/table.html'

Form.as_table()

as_table() 将表单呈现为 HTML 的 <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.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>
<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)
<div class="required"><label for="id_subject" class="required">Subject:</label> ...
<div class="required"><label for="id_message" class="required">Message:</label> ...
<div class="required"><label for="id_sender" class="required">Sender:</label> ...
<div><label for="id_cc_myself">Cc myself:</label> ...
>>> f["subject"].label_tag()
<label class="required" for="id_subject">Subject:</label>
>>> f["subject"].legend_tag()
<legend class="required" for="id_subject">Subject:</legend>
>>> f["subject"].label_tag(attrs={"class": "foo"})
<label for="id_subject" class="foo required">Subject:</label>
>>> f["subject"].legend_tag(attrs={"class": "foo"})
<legend for="id_subject" class="foo required">Subject:</legend>

设置表单元素的 HTML id 属性和 <label> 标签。

Form.auto_id

默认情况下,表单渲染方法包括:

  • 表单元素的 HTML id 属性。

  • 标签周围对应的 <label> 标签。HTML <label> 标签指定了哪个标签文本与哪个表单元素相关联。这个小小的改进使表单更加可用,也更容易被辅助设备访问。使用 <label> 标签总是一个好主意。

id 属性值是通过将 id_ 预置到表单字段名后生成的。 如果你想改变 id 惯例或完全删除 HTML id 属性和 <label> 标签,这种行为是可以设置的。

使用 Form 构造函数的 auto_id 参数来控制 id 和标签行为。这个参数必须是 TrueFalse 或一个字符串。

如果 auto_idFalse,那么表单输出将不包含 <label> 标签和 id 属性:

>>> f = ContactForm(auto_id=False)
>>> print(f)
<div>Subject:<input type="text" name="subject" maxlength="100" required></div>
<div>Message:<textarea name="message" cols="40" rows="10" required></textarea></div>
<div>Sender:<input type="email" name="sender" required></div>
<div>Cc myself:<input type="checkbox" name="cc_myself"></div>

如果 auto_id 设置为 True,那么表单输出将包含 <label> 标签,并且将使用字段名作为每个表单字段的 id

>>> f = ContactForm(auto_id=True)
>>> print(f)
<div><label for="subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="subject"></div>
<div><label for="message">Message:</label><textarea name="message" cols="40" rows="10" required id="message"></textarea></div>
<div><label for="sender">Sender:</label><input type="email" name="sender" required id="sender"></div>
<div><label for="cc_myself">Cc myself:</label><input type="checkbox" name="cc_myself" id="cc_myself"></div>

如果 auto_id 设置为包含格式字符 '%s' 的字符串,那么表单输出将包含 <label> 标签,并且将根据格式字符串生成 id 属性。例如,对于格式字符串 'field_%s',名为 subject 的字段将获得 id'field_subject'。继续我们的例子:

>>> f = ContactForm(auto_id="id_for_%s")
>>> print(f)
<div><label for="id_for_subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="id_for_subject"></div>
<div><label for="id_for_message">Message:</label><textarea name="message" cols="40" rows="10" required id="id_for_message"></textarea></div>
<div><label for="id_for_sender">Sender:</label><input type="email" name="sender" required id="id_for_sender"></div>
<div><label for="id_for_cc_myself">Cc myself:</label><input type="checkbox" name="cc_myself" id="id_for_cc_myself"></div>

如果 auto_id 被设置为任何其他的真值——比如一个不包含 %s 的字符串——那么该库就会像 auto_idTrue 一样。

默认情况下,auto_id 被设置为字符串 'id_%s'

Form.label_suffix

一个可翻译的字符串(英文默认为冒号(:)),将在渲染表格时附加在任何标签名称之后。

可以使用 label_suffix 参数自定义该字符,或完全省略它:

>>> f = ContactForm(auto_id="id_for_%s", label_suffix="")
>>> print(f)
<div><label for="id_for_subject">Subject</label><input type="text" name="subject" maxlength="100" required id="id_for_subject"></div>
<div><label for="id_for_message">Message</label><textarea name="message" cols="40" rows="10" required id="id_for_message"></textarea></div>
<div><label for="id_for_sender">Sender</label><input type="email" name="sender" required id="id_for_sender"></div>
<div><label for="id_for_cc_myself">Cc myself</label><input type="checkbox" name="cc_myself" id="id_for_cc_myself"></div>
>>> f = ContactForm(auto_id="id_for_%s", label_suffix=" ->")
>>> print(f)
<div><label for="id_for_subject">Subject -&gt;</label><input type="text" name="subject" maxlength="100" required id="id_for_subject"></div>
<div><label for="id_for_message">Message -&gt;</label><textarea name="message" cols="40" rows="10" required id="id_for_message"></textarea></div>
<div><label for="id_for_sender">Sender -&gt;</label><input type="email" name="sender" required id="id_for_sender"></div>
<div><label for="id_for_cc_myself">Cc myself -&gt;</label><input type="checkbox" name="cc_myself" id="id_for_cc_myself"></div>

请注意,只有当标签的最后一个字符不是标点符号时,才会加上标签后缀(在英文中,这些字符是 .!?:)。

字段还可以定义自己的 label_suffix。这将优先于 Form.label_suffix。后缀也可以在运行时使用 label_suffix 参数来覆盖 label_tag()/ legend_tag()

Form.use_required_attribute

当设置为 True (默认)时,必填表单字段将有 required HTML 属性。

表单集 实例化表单时使用 use_required_attribute=False 以避免从表单集中添加和删除表单时浏览器验证错误。

设置表单组件的渲染方式

Form.default_renderer

指定 渲染器 用于表单的渲染。默认值为 None,表示使用 FORM_RENDERER 设置中指定的默认渲染器。

你可以在声明你的表单时将其设置为一个类属性,或者使用 Form.__init__()renderer 参数。例如:

from django import forms


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

或者:

form = MyForm(renderer=MyRenderer())

字段顺序的注意事项

as_p()as_ul()as_table() 快捷方式中,字段是按照你在表单类中定义的顺序显示的。例如,在 ContactForm 的例子中,字段是按照 subjectmessagesendercc_myself 的顺序定义的。要调整 HTML 输出的顺序,改变这些字段在类中的排列顺序。

还有其他几种方式可以自定义顺序:

Form.field_order

默认情况下 Form.field_order=None,它保留了你在表单类中定义字段的顺序。如果 field_order 是一个字段名的列表,则字段按列表指定的顺序排列,其余字段按默认顺序追加。列表中未知的字段名将被忽略。这使得在子类中可以通过将字段设置为 None 来禁用字段,而不必重新定义排序。

你也可以使用 FormForm.field_order 参数来覆盖字段顺序。如果一个 Form 定义了 field_order并且 你在实例化 Form 时包含了 field_order,那么后者的 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)
<div>Subject:
  <ul class="errorlist"><li>This field is required.</li></ul>
  <input type="text" name="subject" maxlength="100" required aria-invalid="true">
</div>
<div>Message:
  <textarea name="message" cols="40" rows="10" required>Hi there</textarea>
</div>
<div>Sender:
  <ul class="errorlist"><li>Enter a valid email address.</li></ul>
  <input type="email" name="sender" value="invalid email address" required aria-invalid="true">
</div>
<div>Cc myself:
  <input type="checkbox" name="cc_myself" checked>
</div>

自定义错误列表格式

class ErrorList(initlist=None, error_class=None, renderer=None)[source]

默认情况下,表单使用 django.forms.utils.ErrorList 来格式化验证错误。ErrorList 是一个类似列表的对象,其中 initlist 是错误列表。此外,这个类有以下属性和方法。

error_class

渲染错误列表时要使用的 CSS 类。任何提供的类将被添加到默认的 errorlist 类中。

renderer

指定 渲染器 用于 ErrorList。默认为 None,即使用由 FORM_RENDER 配置指定的默认渲染器。

template_name

调用 __str__render() 时使用的模板名称。默认情况下,这是 'django/forms/errors/list/default.html',它是 'ul.html' 模板的代理。

template_name_text

调用 as_text() 时使用的模板名称。默认是 'django/forms/errors/list/text.html'。该模板将错误显示为一个列表,其中包含了一些要点。

template_name_ul

调用 as_ul() 时使用的模板名称。默认情况下,这是 'django/forms/errors/list/ul.html'。这个模板在 <li> 标签中渲染错误,并使用 error_class 所定义的 CSS 类来包装 <ul>

get_context()[source]

返回模板中渲染错误的上下文。

可用的上下文:

  • errors: 一个错误列表。

  • error_class:一个 CSS 类的字符串。

render(template_name=None, context=None, renderer=None)

渲染方法被 __str__ 以及 as_ul() 方法所调用。

所有参数都是可选的,将默认为:

as_text()

使用由 template_name_text 定义的模板渲染错误列表。

as_ul()

使用由 template_name_ul 定义的模板渲染错误列表。

如果你想自定义错误的渲染,这可以通过覆盖 template_name 属性来实现,或者更普遍地通过覆盖默认模板来实现,也可以参见 覆盖内置表单模板

更精细的输出

as_p()as_ul()as_table() 方法都是快捷方式——它们并不是显示表单对象的唯一方式。

class BoundField[source]

用于显示 Form 实例的单个字段的 HTML 或访问属性。

该对象的 __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[source]

BoundField 的 HTML ID 属性。如果 Form.auto_idFalse,则返回一个空字符串。

BoundField.data[source]

该属性返回由 widget 的 value_from_datadict() 方法提取的这个 BoundField 的数据,如果没有提供数据则返回 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[source]

一个 类似列表的对象,在打印时显示为 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 aria-invalid="true">
>>> 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)
''

当渲染带有错误的字段时,将在字段的小部件上设置 aria-invalid="true",以向屏幕阅读器用户指示存在错误。

Changed in Django 5.0:

当字段存在错误时,将添加 aria-invalid="true"

BoundField.field

这个 Field 封装的表单类中的表单 BoundField 实例。

BoundField.form

这个 Form 实例与这个 BoundField 绑定。

BoundField.help_text

字段的 help_text

BoundField.html_name

部件的 HTML name 属性中使用的名称。它考虑到了 prefix 的形式。

BoundField.id_for_label[source]

使用这个属性来渲染该字段的 ID。例如,如果你在模板中手动构造一个 <label> (尽管 label_tag()/legend_tag() 会为你执行此操作):

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

默认情况下,这将是字段的名称,前缀为 id_ (上面的例子为 "id_my_field")。你可以通过设置 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.initial[source]

使用 BoundField.initial 来检索一个表单字段的初始数据。如果存在,它从 Form.initial 检索数据,否则尝试 Field.initial。值为可调用对象将被执行。参见 初始表单值 获取更多的例子。

BoundField.initial 缓存其返回值,这在处理可调用对象的情况下特别有用,其返回值可能会改变(例如,datetime.nowuuid.uuid4):

>>> from datetime import datetime
>>> class DatedCommentForm(CommentForm):
...     created = forms.DateTimeField(initial=datetime.now)
...
>>> f = DatedCommentForm()
>>> f["created"].initial
datetime.datetime(2021, 7, 27, 9, 5, 54)
>>> f["created"].initial
datetime.datetime(2021, 7, 27, 9, 5, 54)

建议使用 BoundField.initial 而不是 get_initial_for_field()

BoundField.is_hidden[source]

如果这个 BoundField 的部件被隐藏,返回 True

BoundField.label

字段的 label。这在 label_tag()/legend_tag() 中使用。

BoundField.name

该字段在表单中的名称:

>>> f = ContactForm()
>>> print(f["subject"].name)
subject
>>> print(f["message"].name)
message
BoundField.template_name[source]
New in Django 5.0.

使用 BoundField.as_field_group() 渲染的模板的名称。

如果设置了 template_name,则返回该值,否则返回 field_template_name 的值的属性。

BoundField.use_fieldset[source]

返回该 BoundField widget 的 use_fieldset 属性的值。

BoundField.widget_type[source]

返回封装字段 widget 的类名的小写形式,去除任何尾随的 inputwidget。这可以在构建依赖于 widget 类型的表单时使用。例如:

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

BoundField 方法

BoundField.as_field_group()
New in Django 5.0.

使用默认值使用 BoundField.render() 渲染字段,渲染 BoundField,包括其标签、帮助文本和错误,使用模板的 template_name (如果设置)或者使用 field_template_name

BoundField.as_hidden(attrs=None, **kwargs)[source]

返回将其表示为 <input type="hidden"> 的 HTML 字符串。

**kwargs 传递给 as_widget()

这个方法主要在内部使用。你应该使用部件来代替。

BoundField.as_widget(widget=None, attrs=None, only_initial=False)[source]

通过渲染通过的部件来渲染该字段,并添加作为 attrs 传递的任何 HTML 属性。 如果没有指定部件,那么将使用该字段的默认部件。

only_initial 是 Django 内部使用的,不应该明确设置。

BoundField.css_classes(extra_classes=None)[source]

当你使用 Django 的渲染快捷方式时,CSS 类用于指示必填表单字段或包含错误的字段。如果你手动渲染一个表单,你可以使用 css_classes 方法访问这些 CSS 类:

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

如果你想要提供一些额外的类,除了可能需要的错误和必填类,你可以将这些类作为参数提供:

>>> f = ContactForm(data={"message": ""})
>>> f["message"].css_classes("foo bar")
'foo bar required'
BoundField.get_context()[source]
New in Django 5.0.

返回用于渲染字段的模板上下文。可用的上下文是 field,它是绑定字段的实例。

BoundField.label_tag(contents=None, attrs=None, label_suffix=None, tag=None)[source]

使用 Form.template_name_label 指定的模板,为表单字段渲染一个标签。

可用的上下文:

  • field:这个 BoundField 的实例。

  • contents:默认是 BoundField.labelForm.label_suffix (或者 Field.label_suffix, 如果设置的话)的连接字符串。这可以被 contentslabel_suffix 参数所覆盖。

  • attrs:一个包含 forForm.required_css_classiddictid 是由字段的部件 attrsBoundField.auto_id 产生的。其他的属性可以由 attrs 参数提供。

  • use_tag: 一个布尔值,如果标签具有 id,则为 True。如果为 False,默认模板将省略 tag

  • tag: 可选的字符串,用于自定义标签,默认为 label

Tip

在你的模板中 fieldBoundField 的实例。因此 field.field 访问 BoundField.field 是你声明的字段,例如 forms.CharField

要单独渲染表单字段的标签标签,可以调用其 label_tag() 方法:

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

如果你想自定义渲染,这可以通过覆盖 Form.template_name_label 属性来实现,或者更普遍地通过覆盖默认模板来实现,也可以参见 覆盖内置表单模板

BoundField.legend_tag(contents=None, attrs=None, label_suffix=None)[source]

使用 tag='legend' 调用 label_tag() 以使用 <legend> 标签呈现标签。这在呈现单选按钮和多选框小部件时非常有用,其中 <legend> 可能比 <label> 更合适。

BoundField.render(template_name=None, context=None, renderer=None)
New in Django 5.0.

渲染方法由 as_field_group 调用。所有参数都是可选的,并且默认为:

通过传递 template_name,你可以自定义仅用于单个调用的模板。

BoundField.value()[source]

使用这个方法来呈现该字段的原始值,就像它由一个 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)[source]

取一个 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 }} 访问国家。

将上传的文件绑定到表单中

处理有 FileFieldImageField 字段的表单比普通表单要复杂一些。

首先,为了上传文件,你需要确保你的 <form> 元素正确地将 enctype 定义为 "multipart/form-data"

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

其次,当你使用表单时,你需要绑定文件数据。文件数据与普通表单数据分开处理,所以当你的表单包含一个 FileFieldImageField 时,你需要在绑定表单时指定第二个参数。因此,如果我们扩展我们的 ContactForm 以包括一个名为 mugshotImageField,我们需要绑定包含头像图片的文件数据:

# 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", b"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 的所有字段,以及一个额外的字段 priorityContactForm 的字段首先排列:

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

可以子类化多个表单,将表单视为混合类。在这个例子中,BeatleForm 分别子类化了 PersonFormInstrumentForm (按顺序),它的字段列表包括父类的字段:

>>> 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)
<div>First name:<input type="text" name="first_name" required></div>
<div>Last name:<input type="text" name="last_name" required></div>
<div>Instrument:<input type="text" name="instrument" required></div>
<div>Haircut type:<input type="text" name="haircut_type" required></div>

可以通过在子类上将继承自父类的 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)
<div><label for="id_mother-first_name">First name:</label><input type="text" name="mother-first_name" required id="id_mother-first_name"></div>
<div><label for="id_mother-last_name">Last name:</label><input type="text" name="mother-last_name" required id="id_mother-last_name"></div>
>>> print(father)
<div><label for="id_father-first_name">First name:</label><input type="text" name="father-first_name" required id="id_father-first_name"></div>
<div><label for="id_father-last_name">Last name:</label><input type="text" name="father-last_name" required id="id_father-last_name"></div>

命名空间也可以在表单类上指定:

>>> class PersonForm(forms.Form):
...     ...
...     prefix = "person"
...
Back to Top