Django5.1(130)—— 表单 API一(API参考)
表单 API一
关于此文档
本文档介绍了 Django 的表单 API 的具体细节。
绑定和非绑定表单
一个 Form 实例要么是 绑定 到一组数据,要么是 未绑定。
- 如果是 绑定 了一组数据,它就能够验证这些数据,并将表单渲染成 HTML,并在 HTML 中显示数据。
- 如果是 未绑定,它就不能进行验证(因为没有数据可验证!),但它仍然可以将空白表单渲染为 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
()
当你必须为相互依赖的字段添加自定义验证时,在你的 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
的实例。关于定义表单错误的最佳做法。
注意,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 = CommentForm(auto_id=False)
>>> 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'}
在上面这个例子中,nick_name
的 cleaned_data
值被设置为一个空字符串,因为 nick_name
是 CharField
,而 CharField
将空值视为空字符串。每个字段类型都知道它的“空”值是什么——例如,对于 DateField
,它是 None
而不是空字符串。关于每个字段在这种情况下的行为的全部细节,请参阅下面“内置 Field
类”一节中每个字段的“空值”说明。
你可以编写代码来对特定的表单字段(基于其名称)或整个表单(考虑各种字段的组合)进行验证。