Django 表单api

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

class Form

class AD_form(forms.Form):
    # author detail form
    name = forms.CharField(required=True, max_length=20)
    city = forms.CharField(required=True, max_length=30)

若要创建一个未绑定的表单实例,只需简单地实例化该类:
In [3]: f = AD_form()


若要绑定数据到表单,可以将数据以字典的形式传递给表单类的构造函数的第一个参数:
f = AD_form({'name':'bj', 'city':'beijing'})

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

Form.is_bound
如果运行时刻你需要区分绑定的表单和未绑定的表单,可以检查下表单is_bound 属性的值:
In [4]: f.is_bound
Out[4]: False

In [5]: f = AD_form({'name':'bj', 'city':'beijing'})

In [6]: f.is_bound
Out[6]: True

注意,传递一个空的字典将创建一个带有空数据的绑定的表单:
In [7]: f = AD_form({})

In [8]: f.is_bound
Out[8]: True

如果你有一个绑定的表单实例但是想改下数据,或者你想绑定一个未绑定的表单表单到某些数据,你需要创建另外一个表单实例。Form 实例的数据没有办法修改。表单实例一旦创建,你应该将它的数据视为不可变的,无论它有没有数据。
In [10]: f['name']='beijing'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-228d48ce594e> in <module>
----> 1 f['name']='beijing'

TypeError: 'AD_form' object does not support item assignment

使用表单来验证数据

Form.clean()
当你需要为相互依赖的字段添加自定义的验证时,你可以实现表单的clean()方法。示例用法参见Cleaning and validating fields that depend on each other。
 
Form.is_valid()  # 这里是方法,不是属性
表单对象的首要任务就是验证数据。对于绑定的表单实例,可以调用is_valid()方法来执行验证并返回一个表示数据是否合法的布尔值。
In [11]: f = AD_form({'name':'bj', 'city':'beijing'})

In [12]: f.is_valid()
Out[12]: True

让我们试下非法的数据。下面的情形中,city为空(默认所有字段都是必需的)
In [13]: f = AD_form({'name':'hb',})

In [14]: f.is_valid()
Out[14]: False

Form.errors
访问errors 属性可以获得错误信息的一个字典:
In [15]: f.errors
Out[15]: {'city': ['This field is required.']}

在这个字典中,键为字段的名称,值为表示错误信息的Unicode 字符串组成的列表。错误信息保存在列表中是因为字段可能有多个错误信息。
你可以在调用is_valid() 之前访问errors。表单的数据将在第一次调用is_valid() 或者访问errors 时验证。
验证只会调用一次,无论你访问errors 或者调用is_valid() 多少次。这意味着,如果验证过程有副作用,这些副作用将只触发一次。

Form.errors.as_data()
返回一个字典,它映射字段到原始的ValidationError 实例。
In [19]: f.errors.as_data()
Out[19]: {'city': [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)
In [20]: f.errors.as_json()
Out[20]: '{"city": [{"message": "This field is required.", "code": "required"}]}
返回JSON 序列化后的错误。
默认情况下,as_json() 不会转义它的输出。如果你正在使用AJAX 请求表单视图,而客户端会解析响应并将错误插入到页面中,你必须在客户端对结果进行转义以避免可能的跨站脚本攻击。使用一个JavaScript 库比如jQuery 来做这件事很简单 —— 只要使用$(el).text(errorText) 而不是.html() 就可以。
如果由于某种原因你不想使用客户端的转义,你还可以设置escape_html=True,这样错误消息将被转义而你可以直接在HTML 中使用它们。

Form.add_error(field, error)
In [22]: f.add_error('city','need some desc')

In [23]: f.errors
Out[23]: {'city': ['This field is required.', 'need some desc']}
这个方法允许在Form.clean() 方法内部或从表单的外部一起给字段添加错误信息;例如从一个视图中。
field 参数为字段的名称。如果值为None,error 将作为Form.non_field_errors() 返回的一个非字段错误。
error 参数可以是一个简单的字符串,或者最好是一个ValidationError 实例。引发ValidationError 中可以看到定义表单错误时的最佳实践。
注意,Form.add_error() 会自动删除cleaned_data 中的相关字段。
In [22]: f.add_error('city','need some desc')

In [23]: f.errors
Out[23]: {'city': ['This field is required.', 'need some desc']}

In [24]: f.cleaned_data
Out[24]: {'name': 'hb'}

Form.has_error(field, code=None)
In [30]: f.has_error('city',code='required')
Out[30]: True

这个方法返回一个布尔值,指示一个字段是否具有指定错误code 的错误。当code 为None 时,如果字段有任何错误它都将返回True。
若要检查非字段错误,使用NON_FIELD_ERRORS 作为field 参数。

Form.non_field_errors()
这个方法返回Form.errors 中不是与特定字段相关联的错误。它包含在Form.clean() 中引发的ValidationError 和使用Form.add_error(None, "...") 添加的错误。

未绑定表单的行为
验证没有绑定数据的表单是没有意义的,下面的例子展示了这种情况:
In [31]: f = AD_form()

In [32]: f.is_valid()
Out[32]: False

In [33]: f.errors
Out[33]: {}


动态的初始值
Form.initial
表单字段的初始值使用initial声明。例如,你可能希望使用当前会话的用户名填充name字段。
使用Form的initial参数可以实现。该参数是字段名到初始值的一个字典。只需要包含你期望给出初始值的字段;不需要包含表单中的所有字段。例如:

>>> 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)
<tr><th>Name:</th><td><input type="text" name="name" value="instance" /></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" /></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>

检查表单数据是否改变
Form.has_changed()
当你需要检查表单的数据是否从初始数据发生改变时,可以使用表单的has_changed() 方法。
In [47]: data={'name':'hb','city':'hubei'}

In [48]: f = AD_form(data, initial=data)

In [49]: f.has_changed()
Out[49]: False

In [50]: f = AD_form(data)

In [51]: f.has_changed()
Out[51]: True

当提交表单时,我们可以重新构建表单并提供初始值,这样可以实现比较:
>>> f = AD_form(request.POST, initial=data)
>>> f.has_changed()
如果request.POST 中的数据与initial 中的不同,has_changed() 将为True,否则为False。 计算的结果是通过调用表单每个字段的Field.has_changed() 得到的。

从表单中访问字段
Form.fields
你可以从表单实例的fields属性访问字段:
In [53]: f.fields
Out[53]: 
OrderedDict([('name', <django.forms.fields.CharField at 0x7f8d71acf358>),
             ('city', <django.forms.fields.CharField at 0x7f8d71acf6d8>)])

In [54]: f.fields['name']
Out[54]: <django.forms.fields.CharField at 0x7f8d71acf358>

访问“clean”的数据
Form.cleaned_data
表单类中的每个字段不仅负责验证数据,还负责“清洁”它们 —— 将它们转换为正确的格式。这是个非常好用的功能,因为它允许字段以多种方式输入数据,并总能得到一致的输出。
例如,DateField 将输入转换为Python 的 datetime.date 对象。无论你传递的是'1994-07-15' 格式的字符串、datetime.date 对象、还是其它格式的数字,DateField 将始终将它们转换成datetime.date 对象,只要它们是合法的。
一旦你创建一个表单实例并通过验证后,你就可以通过它的cleaned_data 属性访问清洁的数据:
In [55]: data={'name':'hb','city':'hubei'}

In [56]: f = AD_form(data)

In [57]: f.cleaned_data
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-57-0cf611a2e7f5> in <module>
----> 1 f.cleaned_data

AttributeError: 'AD_form' object has no attribute 'cleaned_data'

In [58]: f.is_valid()
Out[58]: True

In [59]: f.cleaned_data
Out[59]: {'name': 'hb', 'city': 'hubei'}

只有经过验证之后,都会有cleaned_data

如果你的数据没有 通过验证,cleaned_data 字典中只包含合法的字段:
In [60]: data={'name':'hb',}

In [61]: f = AD_form(data)

In [62]: f.is_valid()
Out[62]: False

In [63]: f.cleaned_data
Out[63]: {'name': 'hb'}

cleaned_data 始终只 包含表单中定义的字段,即使你在构建表单 时传递了额外的数据。在下面的例子中,我们传递一组额外的字段给AD_form构造函数,但是cleaned_data 将只包含表单的字段:
In [64]: data={'name':'hb','job':'butcher'}

In [65]: f = AD_form(data)

In [66]: f.is_valid()
Out[66]: False

In [67]: f.cleaned_data
Out[67]: {'name': 'hb'}

当表单合法时,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_data 中nick_name 设置为一个空字符串,这是因为nick_name 是CharField而 CharField 将空值作为一个空字符串。每个字段都知道自己的“空”值 —— 例如,DateField 的空值是None 而不是一个空字符串。关于每个字段空值的完整细节,参见“内建的Field 类”一节中每个字段的“空值”提示。
你可以自己编写代码来对特定的字段(根据它们的名字)或者表单整体(考虑到不同字段的组合)进行验证。更多信息参见表单和字段验证。

输出表单为HTML
表单对象的第二个任务是将它渲染成HTML。很简单,print 它:
In [68]: data={'name':'hb','job':'butcher'}

In [69]: f = AD_form(data)

In [70]: print(f)
<tr><th><label for="id_name">Name:</label></th><td><input type="text" name="name" value="hb" maxlength="20" required id="id_name" /></td></tr>
<tr><th><label for="id_city">City:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="city" maxlength="
30" required id="id_city" /></td></tr>

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

默认的输出时具有两个列的HTML 表格,每个字段对应一个<tr>。注意事项:
    • 为了灵活性,输出不包含<table> 和</table>、<form> 和</form> 以及<input type="submit"> 标签。你需要添加它们。
    • 每个字段类型有一个默认的HTML 表示。CharField 表示为一个<input type="text">,EmailField 表示为一个<input type="email">。BooleanField 表示为一个<input type="checkbox">。注意,这些只是默认的表示;你可以使用Widget 指定字段使用哪种HTML,我们将稍后解释。
    • 每个标签的HTML name 直接从AD_form 类中获取。
    • 每个字段的文本标签 —— 通过将所有的下划线转换成空格并大写第一个字母生成。再次提醒,这些只是默认的表示;你可以手工指定标签。
    • 每个文本标签周围有一个HTML <label> 标签,它指向表单字段的id。这个id,是通过在字段名称前面加上'id_' 前缀生成。id 属性和<label> 标签默认包含在输出中,但你可以改变这一行为。
虽然print 表单时<table> 是默认的输出格式,但是还有其它格式可用。每个格式对应于表单对象的一个方法,每个方法都返回一个Unicode 对象。

as_p()
as_p() 渲染表单为一系列的<p> 标签,每个<p> 标签包含一个字段:
In [71]: data={'name':'hb','city':'hubei'}

In [72]: f = AD_form(data)

In [73]: f.as_p()
Out[73]: '<p><label for="id_name">Name:</label> <input type="text" name="name" value="hb" maxlength="20" required id="id_name" /></p>\n<p><label for="id
_city">City:</label> <input type="text" name="city" value="hubei" maxlength="30" required id="id_city" /></p>'

as_ul()
as_ul() 渲染表单为一系列的<li>标签,每个<li> 标签包含一个字段。它不包含<ul> 和</ul>,所以你可以自己指定<ul> 的任何HTML 属性:
In [74]: f.as_ul()
Out[74]: '<li><label for="id_name">Name:</label> <input type="text" name="name" value="hb" maxlength="20" required id="id_name" /></li>\n<li><label for=
"id_city">City:</label> <input type="text" name="city" value="hubei" maxlength="30" required id="id_city" /></li>'

as_table()
最后,as_table()输出表单为一个HTML <table>。它与print 完全相同。事实上,当你print 一个表单对象时,在后台调用的就是as_table() 方法:
In [75]: f.as_table()
Out[75]: '<tr><th><label for="id_name">Name:</label></th><td><input type="text" name="name" value="hb" maxlength="20" required id="id_name" /></td></tr>
\n<tr><th><label for="id_city">City:</label></th><td><input type="text" name="city" value="hubei" maxlength="30" required id="id_city" /></td></tr>'

表单必填行和错误行的样式
    Form.error_css_class
    Form.required_css_class
将必填的表单行和有错误的表单行定义不同的样式特别常见。例如,你想将必填的表单行以粗体显示、将错误以红色显示。
表单类具有一对钩子,可以使用它们来添加class 属性给必填的行或有错误的行:只需简单地设置Form.error_css_class 和/或 Form.required_css_class 属性:
from django import forms

class AD_form(forms.Form):
    # author detail form
    error_css_class = 'error'
    required_css_class = 'required'
    name = forms.CharField(required=True, max_length=20)
    city = forms.CharField(required=True, max_length=30)
一旦你设置好,将根据需要设置行的"error" 和/或"required" CSS 类型。 其HTML 看上去将类似:
In [1]: from book.forms import *

In [2]: f=AD_form({'name':'bj','city':'beijing'})

In [3]: print(f.as_table())
<tr class="required"><th><label class="required" for="id_name">Name:</label></th><td><input type="text" name="name" value="bj" maxlength="20" required i
d="id_name" /></td></tr>
<tr class="required"><th><label class="required" for="id_city">City:</label></th><td><input type="text" name="city" value="beijing" maxlength="30" requi
red id="id_city" /></td></tr>

配置表单元素的HTML id 属性和 <label> 标签
Form.auto_id
默认情况下,表单的渲染方法包含:
    • 表单元素的HTML id 属性
    • 对应的<label> 标签。HTML <label> 标签指示标签文本关联的表单元素。这个小小的改进让表单在辅助设备上具有更高的可用性。使用<label> 标签始终是个好想法。
id 属性值通过在表单字段名称的前面加上id_ 生成。但是如果你想改变id 的生成方式或者完全删除 HTML id 属性和<label>标签,这个行为是可配置的。
 id 和label 的行为使用表单构造函数的auto_id 参数控制。这个参数必须为True、False 或者一个字符串。
如果auto_id 为False,那么表单的输出将不包含<label> 标签和id 属性:
In [4]: f=AD_form(auto_id=False)

In [5]: print(f)
<tr class="required"><th>Name:</th><td><input type="text" name="name" maxlength="20" required /></td></tr>
<tr class="required"><th>City:</th><td><input type="text" name="city" maxlength="30" required /></td></tr>

如果auto_id 设置为True,那么输出的表示将 包含<label> 标签并简单地使用字典名称作为每个表单字段的id:
In [11]: f=AD_form(auto_id=True)

In [12]: print(f)
<tr class="required"><th><label class="required" for="name">Name:</label></th><td><input type="text" name="name" maxlength="20" required id="name" /></t
d></tr>
<tr class="required"><th><label class="required" for="city">City:</label></th><td><input type="text" name="city" maxlength="30" required id="city" /></t
d></tr>

如果auto_id 设置为包含格式字符'%s' 的字符串,那么表单的输出将包含<label> 标签,并将根据格式字符串生成id 属性。例如,对于格式字符串'field_%s',名为subject 的字段的id 值将是'field_subject'。继续我们的例子:
In [13]: f=AD_form(auto_id='id_for_%s')

In [14]: print(f)
<tr class="required"><th><label class="required" for="id_for_name">Name:</label></th><td><input type="text" name="name" maxlength="20" required id="id_f
or_name" /></td></tr>
<tr class="required"><th><label class="required" for="id_for_city">City:</label></th><td><input type="text" name="city" maxlength="30" required id="id_f
or_city" /></td></tr>

字段的顺序
在as_p()、as_ul() 和as_table() 中,字段以表单类中定义的顺序显示。

错误如何显示
如果你渲染一个绑定的表单对象,渲染时将自动运行表单的验证,HTML 输出将在出错字段的附近以<ul class="errorlist"> 形式包含验证的错误。错误信息的位置与你使用的输出方法有关:
In [15]: f=AD_form({'name':'bj',})

In [16]: print(f.as_table())
<tr class="required"><th><label class="required" for="id_name">Name:</label></th><td><input type="text" name="name" value="bj" maxlength="20" required i
d="id_name" /></td></tr>
<tr class="error required"><th><label class="required" for="id_city">City:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><i
nput type="text" name="city" maxlength="30" required id="id_city" /></td></tr>

In [17]: print(f.as_ul())
<li class="required"><label class="required" for="id_name">Name:</label> <input type="text" name="name" value="bj" maxlength="20" required id="id_name"
/></li>
<li class="error required"><ul class="errorlist"><li>This field is required.</li></ul><label class="required" for="id_city">City:</label> <input type="t
ext" name="city" maxlength="30" required id="id_city" /></li>

绑定上传的文件到表单
处理带有FileField 和ImageField 字段的表单比普通的表单要稍微复杂一点。
首先,为了上传文件,你需要确保你的<form> 元素正确定义enctype 为"multipart/form-data":
<form enctype="multipart/form-data" method="post" action="/foo/">
其次,当你使用表单时,你需要绑定文件数据。文件数据的处理与普通的表单数据是分开的,所以如果表单包含FileField 和ImageField,绑定表单时你需要指定第二个参数。所以,如果我们扩展ContactForm 并包含一个名为mugshot 的ImageField,我们需要绑定包含mugshot 图片的文件数据:
# Bound form with an image field
>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': '[email protected]',
...         'cc_myself': True}
>>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', <file data>)}
>>> f = ContactFormWithMugshot(data, file_data)
实际上,你一般将使用request.FILES 作为文件数据的源(和使用request.POST 作为表单数据的源一样):
# Bound form with an image field, data from the request
>>> f = ContactFormWithMugshot(request.POST, request.FILES)
构造一个未绑定的表单和往常一样 —— 将表单数据和文件数据同时省略:
# Unbound form with an image field
>>> f = ContactFormWithMugshot()

测试multipart 表单
    Form.is_multipart()
如果你正在编写可重用的视图或模板,你可能事先不知道你的表单是否是一个multipart 表单。is_multipart() 方法告诉你表单提交时是否要求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>

子类化表单
如果你有多个表单类共享相同的字段,你可以使用子类化来减少冗余。
当你子类化一个自定义的表单类时,生成的子类将包含父类中的所有字段,以及在子类中定义的字段。
在下面的例子中,Pri_AD_form 包含AD_form 中的所有字段,以及另外一个字段job。排在前面的是AD_form 中的字段:
from django import forms


class AD_form(forms.Form):
    # author detail form
    name = forms.CharField(required=True, max_length=20)
    city = forms.CharField(required=True, max_length=30)


class Pri_AD_form(AD_form):
    job = forms.CharField(required=True, )
In [2]: from book.forms import *

In [3]: f=Pri_AD_form()

In [4]: print(f)
<tr><th><label for="id_name">Name:</label></th><td><input type="text" name="name" maxlength="20" required id="id_name" /></td></tr>
<tr><th><label for="id_city">City:</label></th><td><input type="text" name="city" maxlength="30" required id="id_city" /></td></tr>
<tr><th><label for="id_job">Job:</label></th><td><input type="text" name="job" required id="id_job" /></td></tr>

可以子类化多个表单,将表单作为“mix-ins”。在下面的例子中,BeatleForm 子类化PersonForm 和 InstrumentForm ,所以它的字段列表包含两个父类的所有字段:
>>> from django.forms import Form
>>> class PersonForm(Form):
...     first_name = CharField()
...     last_name = CharField()
>>> class InstrumentForm(Form):
...     instrument = CharField()
>>> class BeatleForm(PersonForm, InstrumentForm):
...     haircut_type = CharField()
>>> b = BeatleForm(auto_id=False)
>>> print(b.as_ul())
<li>First name: <input type="text" name="first_name" /></li>
<li>Last name: <input type="text" name="last_name" /></li>
<li>Instrument: <input type="text" name="instrument" /></li>
<li>Haircut type: <input type="text" name="haircut_type" /></li>
New in Django 1.7. 
    • 在子类中,可以通过设置名字为None 来删除从父类中继承的字段。例如:
>>> from django import forms

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

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

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

上一篇:Django Ajax使用

下一篇:Django cookie session