表单 API¶
关于此文档
本文档介绍了 Django 的表单 API 的具体细节。你应该先阅读 使用表单的介绍。
绑定和非绑定表单¶
一个 Form
实例要么是 绑定 到一组数据,要么是 未绑定。
如果是 绑定 了一组数据,它就能够验证这些数据,并将表单渲染成 HTML,并在 HTML 中显示数据。
如果是 未绑定,它就不能进行验证(因为没有数据可验证!),但它仍然可以将空白表单渲染为 HTML。
要创建一个未绑定的 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
的错误。如果 code
是 None
,如果字段包含任何错误,它将返回 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.now
或 uuid.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'}
请注意,任何基于文本的字段——如 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'}
In this above example, the cleaned_data
value for nick_name
is set to an
empty string, because nick_name
is CharField
, and CharField
s treat
empty values as an empty string. Each field type knows what its "blank" value
is -- e.g., for DateField
, it's None
instead of the empty string. For
full details on each field's behavior in this case, see the "Empty value" note
for each field in the 内置 Field 类 section below.
你可以编写代码来对特定的表单字段(基于其名称)或整个表单(考虑各种字段的组合)进行验证。更多关于这方面的信息请参见 表单和字段验证。
将表单输出为 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
:Form.template_name
context
:由Form.get_context()
返回的值renderer
: 由Form.default_renderer
返回的值
通过传递 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>
You may further modify the rendering of form rows by using a custom BoundField.
设置表单组件的渲染方式¶
- 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
的例子中,字段是按照 subject
、message
、sender
、cc_myself
的顺序定义的。要调整 HTML 输出的顺序,改变这些字段在类中的排列顺序。
还有其他几种方式可以自定义顺序:
- Form.field_order¶
默认情况下 Form.field_order=None
,它保留了你在表单类中定义字段的顺序。如果 field_order
是一个字段名的列表,则字段按列表指定的顺序排列,其余字段按默认顺序追加。列表中未知的字段名将被忽略。这使得在子类中可以通过将字段设置为 None
来禁用字段,而不必重新定义排序。
你也可以使用 Form
的 Form.field_order
参数来覆盖字段顺序。如果一个 Form
定义了 field_order
,并且 你在实例化 Form
时包含了 field_order
,那么后者的 field_order
将具有优先权。
- Form.order_fields(field_order)¶
你可以在任何时候使用 order_fields()
对字段进行重新排列,字段名列表如 field_order
。
如何显示错误¶
If you render a bound Form
object, the act of rendering will automatically
run the form's validation if it hasn't already happened, and the HTML output
will include the validation errors as a <ul class="errorlist">
.
The following:
>>> data = {
... "subject": "",
... "message": "Hi there",
... "sender": "invalid email address",
... "cc_myself": True,
... }
>>> ContactForm(data).as_div()
...生成的 HTML 如下:
<div>
<label for="id_subject">Subject:</label>
<ul class="errorlist" id="id_subject_error"><li>This field is required.</li></ul>
<input type="text" name="subject" maxlength="100" required aria-invalid="true" aria-describedby="id_subject_error" id="id_subject">
</div>
<div>
<label for="id_message">Message:</label>
<textarea name="message" cols="40" rows="10" required id="id_message">Hi there</textarea>
</div>
<div>
<label for="id_sender">Sender:</label>
<ul class="errorlist" id="id_sender_error"><li>Enter a valid email address.</li></ul>
<input type="email" name="sender" value="invalid email address" maxlength="320" required aria-invalid="true" aria-describedby="id_sender_error" 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>
Django's default form templates will associate validation errors with their
input by using the aria-describedby
HTML attribute when the field has an
auto_id
and a custom aria-describedby
is not provided. If a custom
aria-describedby
is set when defining the widget this will override the
default value.
If the widget is rendered in a <fieldset>
then aria-describedby
is
added to this element, otherwise it is added to the widget's HTML element (e.g.
<input>
).
aria-describedby
was added to associate errors with its input.
自定义错误列表格式¶
- class ErrorList(initlist=None, error_class=None, renderer=None, field_id=None)[source]¶
默认情况下,表单使用
django.forms.utils.ErrorList
来格式化验证错误。ErrorList
是一个类似列表的对象,其中initlist
是错误列表。此外,这个类有以下属性和方法。Changed in Django 5.2:The
field_id
argument was added.- error_class¶
渲染错误列表时要使用的 CSS 类。任何提供的类将被添加到默认的
errorlist
类中。
- field_id¶
- New in Django 5.2.
An
id
for the field for which the errors relate. This allows an HTMLid
attribute to be added in the error template and is useful to associate the errors with the field. The default template uses the formatid="{{ field_id }}_error"
and a value is provided byForm.add_error()
using the field'sauto_id
.
- 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>
。
- render(template_name=None, context=None, renderer=None)¶
渲染方法被
__str__
以及as_ul()
方法所调用。所有参数都是可选的,将默认为:
template_name
:由template_name
返回的值。context
: 由get_context()
返回的值renderer
: 由renderer
返回的值
- 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。You can use
Form.bound_field_class
andField.bound_field_class
to specify a differentBoundField
class per form or per field, respectively.See 自定义 BoundField for examples of overriding a
BoundField
.
要检索单个 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.aria_describedby[source]¶
- New in Django 5.2.
Returns an
aria-describedby
reference to associate a field with its help text and errors. ReturnsNone
ifaria-describedby
is set inWidget.attrs
to preserve the user defined attribute when rendering the form.
- BoundField.auto_id[source]¶
BoundField
的 HTML ID 属性。如果Form.auto_id
是False
,则返回一个空字符串。
- 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"
,以向屏幕阅读器用户指示存在错误。
- BoundField.field¶
这个
Field
封装的表单类中的表单BoundField
实例。
- BoundField.form¶
这个
Form
实例与这个BoundField
绑定。
- 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.now
或uuid.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
的部件被隐藏,返回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]¶
使用
BoundField.as_field_group()
渲染的模板的名称。如果设置了
template_name
,则返回该值,否则返回field_template_name
的值的属性。
BoundField
方法¶
- BoundField.as_field_group()¶
使用默认值使用
BoundField.render()
渲染字段,渲染BoundField
,包括其标签、帮助文本和错误,使用模板的template_name
(如果设置)或者使用field_template_name
。
返回将其表示为
<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.label_tag(contents=None, attrs=None, label_suffix=None, tag=None)[source]¶
使用
Form.template_name_label
指定的模板,为表单字段渲染一个标签。可用的上下文:
field
:这个BoundField
的实例。contents
:默认是BoundField.label
和Form.label_suffix
(或者Field.label_suffix
, 如果设置的话)的连接字符串。这可以被contents
和label_suffix
参数所覆盖。attrs
:一个包含for
、Form.required_css_class
和id
的dict
。id
是由字段的部件attrs
或BoundField.auto_id
产生的。其他的属性可以由attrs
参数提供。use_tag
: 一个布尔值,如果标签具有id
,则为True
。如果为False
,默认模板将省略tag
。tag
: 可选的字符串,用于自定义标签,默认为label
。
Tip
在你的模板中
field
是BoundField
的实例。因此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)¶
渲染方法由
as_field_group
调用。所有参数都是可选的,并且默认为:template_name
:BoundField.template_name
context
:由BoundField.get_context()
返回的值。renderer
: 由Form.default_renderer
返回的值
通过传递
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
¶
- Form.bound_field_class¶
Define a custom BoundField
class to use when rendering
the form. This takes precedence over the project-level
BaseRenderer.bound_field_class
(along with a custom
FORM_RENDERER
), but can be overridden by the field-level
Field.bound_field_class
.
If not defined as a class variable, bound_field_class
can be set via the
bound_field_class
argument in the Form
or Field
constructor.
For compatibility reasons, a custom form field can still override
Field.get_bound_field()
to use a custom class, though any of the
previous options are preferred.
You may want to use a custom BoundField
if you need to access some
additional information about a form field in a template and using a subclass of
Field
isn't sufficient.
For example, if you have a GPSCoordinatesField
, and want to be able to
access additional information about the coordinates in a template, this could
be implemented as follows:
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):
bound_field_class = GPSCoordinatesBoundField
现在你可以在模板中使用 {{form.coordinates.country }}
访问国家。
You may also want to customize the default form field template rendering. For
example, you can override BoundField.label_tag()
to add a custom class:
class StyledLabelBoundField(BoundField):
def label_tag(self, contents=None, attrs=None, label_suffix=None, tag=None):
attrs = attrs or {}
attrs["class"] = "wide"
return super().label_tag(contents, attrs, label_suffix, tag)
class UserForm(forms.Form):
bound_field_class = StyledLabelBoundField
name = CharField()
This would update the default form rendering:
>>> f = UserForm()
>>> print(f["name"].label_tag)
<label for="id_name" class="wide">Name:</label>
To add a CSS class to the wrapping HTML element of all fields, a BoundField
can be overridden to return a different collection of CSS classes:
class WrappedBoundField(BoundField):
def css_classes(self, extra_classes=None):
parent_css_classes = super().css_classes(extra_classes)
return f"field-class {parent_css_classes}".strip()
class UserForm(forms.Form):
bound_field_class = WrappedBoundField
name = CharField()
This would update the form rendering as follows:
>>> f = UserForm()
>>> print(f)
<div class="field-class"><label for="id_name">Name:</label><input type="text" name="name" required id="id_name"></div>
Alternatively, to override the BoundField
class at the project level,
BaseRenderer.bound_field_class
can be defined on a custom
FORM_RENDERER
:
from django.forms.renderers import DjangoTemplates
from .forms import CustomBoundField
class CustomRenderer(DjangoTemplates):
bound_field_class = CustomBoundField
FORM_RENDERER = "mysite.renderers.CustomRenderer"
将上传的文件绑定到表单中¶
处理有 FileField
和 ImageField
字段的表单比普通表单要复杂一些。
首先,为了上传文件,你需要确保你的 <form>
元素正确地将 enctype
定义为 "multipart/form-data"
:
<form enctype="multipart/form-data" method="post" action="/foo/">
其次,当你使用表单时,你需要绑定文件数据。文件数据与普通表单数据分开处理,所以当你的表单包含一个 FileField
和 ImageField
时,你需要在绑定表单时指定第二个参数。因此,如果我们扩展我们的 ContactForm 以包括一个名为 mugshot
的 ImageField
,我们需要绑定包含头像图片的文件数据:
# 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
的所有字段,以及一个额外的字段 priority
。ContactForm
的字段首先排列:
>>> 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
分别子类化了 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)
<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"
...