django 1.8 官方文档翻译:5-1-2 表单API

表单 API

关于这篇文档

这篇文档讲述Django 表单API 的详细细节。你应该先阅读表单简介

绑定的表单和未绑定的表单

表单要么是绑定的,要么是未绑定的

  • 如果是绑定的,那么它能够验证数据,并渲染表单及其数据成HTML。
  • 如果是未绑定的,那么它不能够完成验证(因为没有可验证的数据!),但是仍然能渲染空白的表单成HTML。

class Form

若要创建一个未绑定的表单实例,只需简单地实例化该类:

>>> f = ContactForm()

若要绑定数据到表单,可以将数据以字典的形式传递给表单类的构造函数的第一个参数:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': '[email protected]',
...         'cc_myself': True}
>>> f = ContactForm(data)

在这个字典中,键为字段的名称,它们对应于表单类中的属性。值为需要验证的数据。它们通常为字符串,但是没有强制要求必须是字符串;传递的数据类型取决于字段,我们稍后会看到。

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.``clean()

当你需要为相互依赖的字段添加自定义的验证时,你可以实现表单clean()方法。示例用法参见Cleaning and validating fields that depend on each other

Form.``is_valid()

表单对象的首要任务就是验证数据。对于绑定的表单实例,可以调用is_valid()方法来执行验证并返回一个表示数据是否合法的布尔值。

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': '[email protected]',
...         '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.']}

在这个字典中,键为字段的名称,值为表示错误信息的Unicode 字符串组成的列表。错误信息保存在列表中是因为字段可能有多个错误信息。

你可以在调用is_valid() 之前访问errors。表单的数据将在第一次调用is_valid() 或者访问errors 时验证。

验证将值调用一次,无论你访问errors 或者调用is_valid() 多少次。这意味着,如果验证过程有副作用,这些副作用将只触发一次。

Form.errors.``as_data()

New in Django 1.7.

返回一个字典,它映射字段到原始的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)

New in Django 1.7.

返回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 库比如jQuery 来做这件事很简单 —— 只要使用$(el).text(errorText) 而不是.html() 就可以。

如果由于某种原因你不想使用客户端的转义,你还可以设置escape_html=True,这样错误消息将被转义而你可以直接在HTML 中使用它们。

Form.``add_error(field, error)

New in Django 1.7.

这个方法允许在Form.clean() 方法内部或从表单的外部一起给字段添加错误信息;例如从一个视图中。

field 参数为字段的名称。如果值为None,error 将作为Form.non_field_errors() 返回的一个非字段错误。

error 参数可以是一个简单的字符串,或者最好是一个ValidationError 实例。引发ValidationError 中可以看到定义表单错误时的最佳实践。

注意,Form.add_error() 会自动删除cleaned_data 中的相关字段。

Form.``has_error(field, code=None)

New in Django 1.8.

这个方法返回一个布尔值,指示一个字段是否具有指定错误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字段。

使用Forminitial参数可以实现。该参数是字段名到初始值的一个字典。只需要包含你期望给出初始值的字段;不需要包含表单中的所有字段。例如:

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

这些值只显示在没有绑定的表单中,即使没有提供特定值它们也不会作为后备的值。

注意,如果字段有定义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)
Name: type="text" name="name" value="instance" /></td>>
Url: type="url" name="url" /></td>>
Comment: type="text" name="comment" /></td>>

检查表单数据是否改变

Form.``has_changed()

当你需要检查表单的数据是否从初始数据发生改变时,可以使用表单has_changed() 方法。

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': '[email protected]',
...         '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.``fields

你可以从表单实例的fields属性访问字段:

>>> for row in f.fields.values(): print(row)
...
0x7ffaac632510>
0x7ffaac632f90>
0x7ffaac3aa050>
>>> f.fields['name']
0x7ffaac6324d0>

可你可以修改表单实例的字段来改变字段在表单中的表示:

>>> f.as_table().split('\n')[0]
'<tr><th>Name:th><td><input name="name" type="text" value="instance" />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" />td>tr>'

注意不要改变base_fields 属性,因为一旦修改将影响同一个Python 进程中接下来所有的ContactForm 实例:

>>> f.base_fields['name'].label = "Username"
>>> another_f = CommentForm(auto_id=False)
>>> another_f.as_table().split('\n')[0]
'Username:'

访问“清洁”的数据

Form.``cleaned_data

表单类中的每个字段不仅负责验证数据,还负责“清洁”它们 —— 将它们转换为正确的格式。这是个非常好用的功能,因为它允许字段以多种方式输入数据,并总能得到一致的输出。

例如,DateField 将输入转换为Python 的 datetime.date 对象。无论你传递的是'1994-07-15' 格式的字符串、datetime.date 对象、还是其它格式的数字,DateField 将始终将它们转换成datetime.date 对象,只要它们是合法的。

一旦你创建一个表单实例并通过验证后,你就可以通过它的cleaned_data 属性访问清洁的数据:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': '[email protected]',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there', 'sender': '[email protected]', 'subject': 'hello'}

注意,文本字段 —— 例如,CharFieldEmailField —— 始终将输入转换为Unicode 字符串。我们将在这篇文档的后面将是编码的影响。

如果你的数据没有 通过验证,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 始终 包含表单中定义的字段,即使你在构建表单 时传递了额外的数据。在下面的例子中,我们传递一组额外的字段给ContactForm 构造函数,但是cleaned_data 将只包含表单的字段:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': '[email protected]',
...         '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': '[email protected]', 'subject': 'hello'}

表单合法时,cleaned_data 将包含所有字段的键和值,即使传递的数据不包含某些可选字段的值。在下面的例子中,传递的数据字典不包含nick_name 字段的值,但是cleaned_data 任然包含它,只是值为空:

>>> from django.forms import Form
>>> class OptionalPersonForm(Form):
...     first_name = CharField()
...     last_name = CharField()
...     nick_name = 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_datanick_name 设置为一个空字符串,这是因为nick_nameCharFieldCharField 将空值作为一个空字符串。每个字段都知道自己的“空”值 —— 例如,DateField 的空值是None 而不是一个空字符串。关于每个字段空值的完整细节,参见“内建的Field 类”一节中每个字段的“空值”提示。

你可以自己编写代码来对特定的字段(根据它们的名字)或者表单整体(考虑到不同字段的组合)进行验证。更多信息参见表单和字段验证

输出表单为HTML

表单对象的第二个任务是将它渲染成HTML。很简单,print 它:

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

如果表单是绑定的,输出的HTML 将包含数据。例如,如果字段是 的形式,其数据将位于value 属性中。如果字段是 的形式,HTML 将包含checked="checked"

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': '[email protected]',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> print(f)
 id="id_subject" type="text" name="subject" maxlength="100" value="hello" /></td>>
="id_message">Message: type="text" name="message" id="id_message" value="Hi there" /></td>>
="id_sender">Sender: type="email" name="sender" id="id_sender" value="[email protected]" /></td>>
="id_cc_myself">Cc myself:</label>>"checkbox" name="cc_myself" id="id_cc_myself" checked="checked" /></td>>

默认的输出时具有两个列的HTML 表格,每个字段对应一个。注意事项:

  • 为了灵活性,输出包含
     以及 标签。你需要添加它们。
  • 每个字段类型有一个默认的HTML 表示。CharField 表示为一个EmailField 表示为一个BooleanField 表示为一个。注意,这些只是默认的表示;你可以使用Widget 指定字段使用哪种HTML,我们将稍后解释。
  • 每个标签的HTML name 直接从ContactForm 类中获取。
  • 每个字段的文本标签 —— 例如'Subject:''Message:''Cc myself:' 通过将所有的下划线转换成空格并大写第一个字母生成。再次提醒,这些只是默认的表示;你可以手工指定标签。
  • 每个文本标签周围有一个HTML 标签,它指向表单字段的id。这个id,是通过在字段名称前面加上'id_' 前缀生成。id 属性和 标签默认包含在输出中,但你可以改变这一行为。

虽然print 表单时

是默认的输出格式,但是还有其它格式可用。每个格式对应于表单对象的一个方法,每个方法都返回一个Unicode 对象。

as_p()

Form.``as_p()

as_p() 渲染表单为一系列的

标签,每个

标签包含一个字段:

>>> f = ContactForm()
>>> f.as_p()
'<p><label for="id_subject">Subject:label> <input id="id_subject" type="text" name="subject" maxlength="100" />p>\n<p><label for="id_message">Message:label> <input type="text" name="message" id="id_message" />p>\n<p><label for="id_sender">Sender:label> <input type="text" name="sender" id="id_sender" />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" />p>
<p><label for="id_message">Message:label> <input type="text" name="message" id="id_message" />p>
<p><label for="id_sender">Sender:label> <input type="email" name="sender" id="id_sender" />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() 渲染表单为一系列的

  • 标签,每个
  • 标签包含一个字段。它包含
    ,所以你可以自己指定
      的任何HTML 属性:

      >>> f = ContactForm()
      >>> f.as_ul()
      '<li><label for="id_subject">Subject:label> <input id="id_subject" type="text" name="subject" maxlength="100" />li>\n<li><label for="id_message">Message:label> <input type="text" name="message" id="id_message" />li>\n<li><label for="id_sender">Sender:label> <input type="email" name="sender" id="id_sender" />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" />li>
      <li><label for="id_message">Message:label> <input type="text" name="message" id="id_message" />li>
      <li><label for="id_sender">Sender:label> <input type="email" name="sender" id="id_sender" />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

  • 。它与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" />td>tr>\n<tr><th><label for="id_message">Message:label>th><td><input type="text" name="message" id="id_message" />td>tr>\n<tr><th><label for="id_sender">Sender:label>th><td><input type="email" name="sender" id="id_sender" />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" />td>tr>
    <tr><th><label for="id_message">Message:label>th><td><input type="text" name="message" id="id_message" />td>tr>
    <tr><th><label for="id_sender">Sender:label>th><td><input type="email" name="sender" id="id_sender" />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

    将必填的表单行和有错误的表单行定义不同的样式特别常见。例如,你想将必填的表单行以粗体显示、将错误以红色显示。

    表单类具有一对钩子,可以使用它们来添加class 属性给必填的行或有错误的行:只需简单地设置Form.error_css_class 和/或 Form.required_css_class 属性:

    from django.forms import Form
    
    class ContactForm(Form):
        error_css_class = 'error'
        required_css_class = 'required'
    
        # ... and the rest of your fields here
    

    一旦你设置好,将根据需要设置行的"error" 和/或"required" CSS 类型。 其HTML 看上去将类似:

    >>> f = ContactForm(data)
    >>> print(f.as_table())
    
    "required">"required">"required error">
    ...
    ...
    ...