表单
Django 提供了一个丰富的框架来帮助创建表单和处理表单数据。
一、基础:
1.概览
Django提供了一系列的工具和库来帮助您构建表单来接收网站访客的输入,然后处理以及响应这些输入。
HTML表单
在HTML中,表单是在 … 中的一些元素,它允许访客做一些类似输入文本、选择选项、操作对象或空间等动作,然后发送这些信息到服务端。
一些表单界面元素(文本框或复选框)内置在HTML中。其他会更复杂些;像弹出日期选择或者允许您移动滑块或者操作控件,一般通过使用JavaScript,CSS以及HTML表单中的 元素来实现这些效果。
和它的元素 一样,表单必须指定两样东西:
何地:负责响应用户输入数据的URL地址
如何:数据请求使用的HTTP方法。
例如,Django admin的登录表单包含了一些 元素:用户名用 type=“text” ,密码用 type=“password” ,登录按钮用 type=“submit” 。它还包含一些用户看不到的隐藏文本字段,Django用它们来决定下一步行为。
它还告诉浏览器表单数据应该发往 的 action 属性指定的URL—— /admin/ ,并且应该使用它的 method 属性指定的HTTP方法—— post 。
当 元素被触发的时候,数据会发送到 /admin/ 。
GET 和 POST
处理表单时只会用到 GET 和 POST 两种HTTP方法。
Django的登录表单使用 POST 方法传输数据,在这个方法中浏览器会封装表单数据,为了传输会进行编码,然后发送到服务端并接收它的响应。
相比之下,GET 方法将提交的数据捆绑到一个字符串中,并用它来组成一个URL。该URL包含了数据要发送的地址以及一些键值对应的数据。如果您在Django文档中进行一次搜索,就会看到这点,它会生成一个形似 https://docs.djangoproject.com/search/?q=forms&release=1 的URL。
GET 和 POST 通常用于不同的目的。
任何可能用于更改系统状态的请求应该使用 POST —— 比如一个更改数据库的请求。GET 应该只被用于不会影响系统状态的请求。
GET 方法也不适合密码表单,因为密码会出现在URL中,于是也会出现在浏览器的历史记录以及服务器的日志中,而且都是以纯文本的形式。它也不适合处理大量的数据或者二进制数据,比如一张图片。在WEB应用的管理表单中使用 GET 请求具有安全隐患:攻击者很容易通过模拟请求来访问系统的敏感数据。 POST 方法通过与其他像Django的 CSRF protection 这样的保护措施配合使用,能对访问提供更多的控制。
另一方面, GET 方法适用于诸如网页搜索表单这样的内容,因为这类呈现为一个 GET 请求的URL很容易被存为书签、分享或重新提交。
构建一张表单
<form action="/your-name/" method="post">
<label for="your_name">Your name: </label>
<input id="your_name" type="text" name="your_name" value="{{ current_name }}">
<input type="submit" value="OK">
</form>
2.表单 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.is_bound
如果你需要在运行时区分绑定和未绑定的表单实例,请检查表单的 is_bound 属性的值:
>>> f = ContactForm()
>>> f.is_bound
False
>>> f = ContactForm({'subject': 'hello'})
>>> f.is_bound
True
3.内建字段
表单字段
class Field(**kwargs)
当你创建一个 Form 类时,最重要的部分是定义表单的字段。每个字段都有自定义的验证逻辑,以及其他一些钩子。
Field.clean(value)
虽然你使用 Field 类的主要方式是在 Form 类中,但你也可以将它们实例化并直接使用,以更好地了解它们的工作方式。每个 Field 实例都有一个 clean() 方法,它接收一个参数,并引发一个 django.core.exceptions.ValidationError 异常或返回干净的值:
>>> from django import forms
>>> f = forms.EmailField()
>>> f.clean('foo@example.com')
'foo@example.com'
>>> f.clean('invalid email address')
Traceback (most recent call last):
...
ValidationError: ['Enter a valid email address.']
4.内建部件
部件
部件是 Django 对 HTML 输入元素的表示。部件处理 HTML 的渲染,以及从对应于部件的 GET/POST 字典中提取数据。
内置部件生成的 HTML 使用 HTML5 语法,目标是 。例如,它使用布尔属性,如 checked 而不是 XHTML 风格的 checked=‘checked’。
为一个字段使用不同的部件,你可以在字段定义中使用 widget 参数。例如:
from django import forms
class CommentForm(forms.Form):
name = forms.CharField()
url = forms.URLField()
comment = forms.CharField(widget=forms.Textarea)
二、进阶:
1.针对模型的表单
从模型创建表单
ModelForm
class ModelForm
如果您正在构建一个数据库驱动的应用程序,那么您很有可能会用到与Django模型密切相关的表单。例如,您可能有一个 BlogComment 模型,并且您想创建一个让用户提交评论的表单。在这种情况下,在表单中定义字段类型是多余的,因为您已经在模型中定义了字段。
因此,Django 提供了一个辅助类让你可以从一个 Django 模型创建一个 Form 类。
例如:
>>> from django.forms import ModelForm
>>> from myapp.models import Article
# Create the form class.
>>> class ArticleForm(ModelForm):
... class Meta:
... model = Article
... fields = ['pub_date', 'headline', 'content', 'reporter']
# Creating a form to add an article.
>>> form = ArticleForm()
# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)
2.整合媒体
表单资源( Media 类)
要渲染一个有吸引力且易于使用的Web表单不仅仅需要HTML,还需要CSS样式表,如果您想使用多样的 “Web2.0” 组件,您还需要在每个页面上包含一些JavaScript。要在任何给定模板上准确组合CSS和JavaScript取决于此页面上使用的组件。
这是资源定义的来源。Django允许你将不同的文件(像样式表和脚本)与需要这些资源的表单和组件进行关联。例如,如果您想用日历来渲染DateFields,你可以自定义一个日历组件。然后这个组件可以与渲染日历所需的CSS和JavaScript相关联。在表单上使用日历组件时,Django能够识别所需的CSS和JavaScript文件,并提供表单中的文件名列表,合适地插入到你的网页中。
资源作为静态定义
定义资源最简单方法是静态定义。要使用这种方法,声明是一个内部的 Media 类。此内部类的属性定义了这个需求。
这有个例子:
from django import forms
class CalendarWidget(forms.TextInput):
class Media:
css = {
'all': ('pretty.css',)
}
js = ('animations.js', 'actions.js')
3.表单集
class BaseFormSet
formset是一个抽象层,它可以在同一页面上处理多个表单的。它最适合被比喻成网格数据。我们假设您有以下表单:
>>> from django import forms
>>> class ArticleForm(forms.Form):
... title = forms.CharField()
... pub_date = forms.DateField()
您可能想允许用户一次创建多篇文章。 要创建一个 ArticleForm 的formset,您可以这样做:
>>> from django.forms import formset_factory
>>> ArticleFormSet = formset_factory(ArticleForm)
你现在已经创建了一个名为 ArticleFormSet 的表单集。实例化表单集让你能够迭代表单集中的表单,并像常规表单一样显示它们:
>>> formset = ArticleFormSet()
>>> for form in formset:
... print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>
如你所见,它只显示一个空表单。显示的空表单数量由 额外 参数控制。默认情况下,formset_factory() 定义了一个额外表单;下面的例子将创建一个表单集类来显示两个空白表单:
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
遍历 formset 将按照它们创建的顺序渲染表单。你可以通过为 iter() 方法提供替代的实现来改变这个顺序。
表单集也可以被索引然后返回对应的表单。如果您已经覆盖了 iter ,则还需覆盖 getitem 让它具备匹配行为。
4.自定义验证
表单和字段验证
表单验证发生在清理数据的时候。如果你想自定义这个过程,有各种地方可以进行更改,每个地方都有不同的目的。在表单处理过程中会运行三种类型的清理方法。这些方法通常在调用表单上的 is_valid() 方法时执行。还有其他一些事情也可以触发清理和验证(访问 errors 属性或直接调用 full_clean()),但通常不需要。
一般来说,任何清理方法都可以在处理的数据出现问题时引发 ValidationError,将相关信息传递给 ValidationError 构造函数。 参见下文 关于引发 ``ValidationError` 的最佳实践。如果没有引发 ValidationError,该方法应该将清理后(规范化)的数据作为 Python 对象返回。
大多数验证可以使用 validators —— 可以重复使用的辅助功能来完成。验证器是函数(或可调用对象),它只接受一个参数,并在无效输入时引发 ValidationError。验证器在字段的 to_python 和 validate 方法被调用后运行。
为构造函数提供一个描述性错误 code:
Good
ValidationError(_('Invalid value'), code='invalid')
Bad
ValidationError(_('Invalid value'))
不要在信息中强行加入变量;使用占位符和构造函数的 params 参数:
Good
ValidationError(
_('Invalid value: %(value)s'),
params={'value': '42'},
)
Bad
ValidationError(_('Invalid value: %s') % value)
使用映射键代替位置格式化。这样可以在重写信息时,将变量按任何顺序排列或完全省略:
Good
ValidationError(
_('Invalid value: %(value)s'),
params={'value': '42'},
)
Bad
ValidationError(
_('Invalid value: %s'),
params=('42',),
)
用 gettext 包装信息,以启用翻译:
Good
ValidationError(_('Invalid value'))
Bad
ValidationError('Invalid value')
把它放在一起:
raise ValidationError(
_('Invalid value: %(value)s'),
code='invalid',
params={'value': '42'},
)