除非你打算建立只做出发布内容的网站和应用程序,并且不接受访问者的意见,否则您将需要了解和使用表单。
Django 提供广泛的工具和库来帮助你构建表单来接收网站访问者的输入,然后处理以及响应输入。
在HTML中,表单的作用是收集标签中的内容,<form>...</form>
中间可以由访问者添加类似于文本,选择,或者一些控制模块等等.然后这些内容将会被送到服务端
某些表单的元素 —— 文本输入和复选框 —— 非常简单而且内建于HTML 本身。 其他的复杂得多;弹出日期选择器或允许您移动滑块或操纵控件的界面通常将使用JavaScript和CSS以及HTML表单<input>
元素来实现这些效果。
与<input>
元素一样,一个表单必须指定两样东西:
例如,Django Admin 站点的登录表单包含几个type="text"
元素:<input>
用于用户名,type="password"
用于密码,type="submit"
用于“Log in" 按钮。 它还包含一些用户看不到的隐藏的文本字段,Django 使用它们来决定下一步的行为。
它还告诉浏览器表单数据应该发往action
的<form>
属性指定的URL —— /admin/
,而且应该使用post
属性指定的HTTP 方法 —— method
。
当触发<input type="submit" value="Log in">
元素时,数据将发送给/admin/
。
GET
和POST
¶处理表单时候只会用到POST
和 GET
方法。
Django 的登录表单使用POST
方法,在这个方法中浏览器组合表单数据、对它们进行编码以用于传输、将它们发送到服务器然后接收它的响应。
相反,GET
组合提交的数据为一个字符串,然后使用它来生成一个URL。 这个URL 将包含数据发送的地址以及数据的键和值。 如果你在Django 文档中做一次搜索,你会立即看到这点,此时将生成一个https://docs.djangoproject.com/search/?q=forms&release=1
形式的URL。
POST
和GET
用于不同的目的。
用于改变系统状态的请求 —— 例如,给数据库带来变化的请求 —— 应该使用POST
。 GET
只应该用于不会影响系统状态的请求。
GET
还不适合密码表单,因为密码将出现在URL 中,以及浏览器的历史和服务器的日志中,而且都是以普通的文本格式。 它还不适合数据量大的表单和二进制数据,例如一张图片。 使用GET
请求作为管理站点的表单具有安全隐患:攻击者很容易模拟表单请求来取得系统的敏感数据。
POST
,如果与其它的保护措施结合将对访问提供更多的控制,例如Django 的CSRF protection。
另一个方面,GET
适合网页搜索这样的表单,因为这种表示一个GET
请求的URL 可以很容易地作为书签、分享和重新提交。
处理表单是一件很复杂的事情。 考虑一下Django 的Admin 站点,不同类型的大量数据项需要在一个表单中准备好、渲染成HTML、使用一个方便的界面编辑、返回给服务器、验证并清除,然后保存或者向后继续处理。
Django 的表单功能可以简化并自动化大部分这些工作,而且还可以比大部分程序员自己所编写的代码更安全。
Django 会处理表单工作中的三个显著不同的部分:
可以手工编写代码来实现,但是Django 可以帮你完成所有这些工作。
我们已经简短讲述HTML 表单,但是HTML的<form>
只是其机制的一部分。
在一个Web 应用中,"表单"可能指HTML <form>
、或者生成它的Django 的Form
、或者提交时发送的结构化数据、或者这些部分的总和。
Form
类¶表单系统的核心部分是Django 的Form
类。 Django 的模型描述一个对象的逻辑结构、行为以及展现给我们的方式,与此类似,Form
类描述一个表单并决定它如何工作和展现。
就像模型类的属性映射到数据库的字段一样,表单类的字段会映射到HTML 的<input>
表单的元素。 (ModelForm
通过一个Form
映射模型类的字段到HTML 表单的<input>
元素;Django 的Admin 站点就是基于这个)。
一个表单的字段本身就是类;他们管理表单数据,并在提交表单时执行验证。 DateField
和FileField
处理的数据类型差别很大,必须完成不同的事情。
表单字段在浏览器中呈现给用户的是一个HTML 的“widget” —— 用户界面的一个片段。 每个字段类型都有一个合适的默认Widget class,需要时可以覆盖。
在Django 中渲染一个对象时,我们通常:
除了几个关键点不同之外,在模板中渲染表单和渲染其它类型的对象几乎一样。
在模型实例不包含数据的情况下,在模板中对它做处理很少有什么用处。 但是渲染一个未填充的表单却非常有意义 —— 我们希望用户去填充它。
所以当我们在视图中处理模型实例时,我们一般从数据库中获取它。 当我们处理表单时,我们一般在视图中实例化它。
当我们实例化表单时,我们可以选择让它为空还是预先填充它,例如使用:
获取HTML表单数据是最有趣的,因为这样做可以让用户不仅可以阅读网站,还可以将信息发送回来。
假设您想在您的网站上创建一个简单的表单,以获取用户的名字。 你需要类似这样的模板:
<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>
这告诉浏览器使用POST
方法将表单数据返回到URL / your-name /
。 它将显示一个标签为"Your name:"的文本字段,和一个"OK"按钮。 如果模板上下文包含current_name
变量,则将用于预填your_name
字段。
您将需要一个视图来渲染包含HTML表单的模板,并且可以根据需要提供current_name
字段。
当表单提交时,发往服务器的POST
请求将包含表单数据。
现在你还需要一个对应/your-name/
URL 的视图,它在请求中找到正确的键/值对,然后处理它们。
这是一个非常简单的表单。 实际应用中,一个表单可能包含几十上百个字段,其中大部分需要预填充,而且我们预料到用户将来回编辑-提交几次才能完成操作。
即使在提交表单之前,我们也可能需要在浏览器中进行一些验证。我们可能想要使用更复杂的字段,这样可以让用户做一些事情,例如从日历中选择日期等等。
这个时候,让Django 来为我们完成大部分工作是很容易的。
Form
类¶我们已经计划好了我们的 HTML 表单应该呈现的样子。 在Django 中,我们的起始点是这里:
from django import forms
class NameForm(forms.Form):
your_name = forms.CharField(label='Your name', max_length=100)
它定义一个Form
类,只带有一个字段(your_name
)。 我们已经对这个字段使用一个人性化的标签,当渲染时它将出现在<label>
中(在这个例子中,即使我们省略它,我们指定的label
还是会自动生成)。
字段允许的最大长度通过max_length
定义。 它完成两件事情。 首先,它在HTML 的<input>
上放置一个maxlength="100"
(这样浏览器将在第一时间阻止用户输入多于这个数目的字符)。 它还意味着当Django 收到浏览器发送过来的表单时,它将验证数据的长度。
Form
的实例具有一个is_valid()
方法,它为所有的字段运行验证的程序。 当调用这个方法时,如果所有的字段都包含合法的数据,它将:
True
cleaned_data
属性中。完整的表单,第一次渲染时,看上去将像:
<label for="your_name">Your name: </label>
<input id="your_name" type="text" name="your_name" maxlength="100" required />
注意它不包含 <form>
标签和提交按钮。
我们必须自己在模板中提供它们。
发送回Django网站的表单数据由视图处理,通常是发布表单的相同视图。 这允许我们重用一些相同的逻辑。
要操作一个通过URL发布的表单,我们要在视图中实例化它。
from django.shortcuts import render
from django.http import HttpResponseRedirect
from .forms import NameForm
def get_name(request):
# 如果这是一个POST请求,我们就需要处理表单数据
if request.method == 'POST':
# 创建一个表单实例,并且使用表单数据填充request请求:
form = NameForm(request.POST)
# 检查数据有效性:
if form.is_valid():
# 在需要时,可以在form.cleaned_date中处理数据
# ...
# 重定向到一个新的URL:
return HttpResponseRedirect('/thanks/')
# 如果是GET或者其它请求方法,我们将创建一个空的表单。
else:
form = NameForm()
return render(request, 'name.html', {'form': form})
如果访问视图的是一个GET
请求,它将创建一个空的表单实例并将它放置到要渲染的模板的上下文中。 这是我们在第一次访问该URL 时预期发生的情况。
如果使用POST
请求提交表单,该视图将再次创建一个表单实例,并使用请求中的数据填充表单:形式 = NameForm(request.POST)
这被称为“将数据绑定到表单”(现在是绑定的形式)。
我们调用窗体的is_valid()
方法;如果不是True
,我们返回到表单的模板。 这时表单不再为空(未绑定),所以HTML 表单将用之前提交的数据填充,然后可以根据要求编辑并改正它。
如果True
为is_valid()
,我们将能够在cleaned_data
属性中找到所有合法的表单数据。 在发送HTTP 重定向给浏览器告诉它下一步的去向之前,我们可以用这个数据来更新数据库或者做其它处理。
我们不需要在name.html
模板中做很多工作。 最简单的例子是:
<form action="/your-name/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
Django会根据模型类的字段和属性,在HTML中自动生成对应表单标签和标签属性。生成的标签会被放置到{{ form }}
所在的位置。
表单和跨站请求伪造的防护
Django 原生支持一个简单易用的protection against Cross Site Request
Forgeries。 当提交一个启用CSRF 防护的POST
表单时,你必须使用上面例子中的csrf_token
模板标签。 然而,因为CSRF 防护在模板中不是与表单直接捆绑在一起的,这个标签在这篇文档的以下示例中将省略。
HTML5 输入类型和浏览器验证
如果你的表单包含URLField
、EmailField
或其它整数字段类型,Django 将使用number
、url
和 email
这样的HTML5 输入类型。 默认情况下,浏览器可能会对这些字段进行它们自身的验证,这些验证可能比Django 的验证更严格。 如果你想禁用这个行为,请设置form
标签的novalidate 属性,或者指定一个不同的字段,如TextInput
。
现在我们有了一个可以工作的网页表单,它通过Django Form
描述、通过视图处理并渲染成一个HTML <form>
。
这是你入门所需要知道的所有内容,但是表单框架为了便利提供了更多的内容。 一旦你理解了上面描述的基本处理过程,你应该可以理解表单系统的其它功能并准备好学习更多的底层机制。
Form
类的更多内容¶所有的表单类都作为django.forms.Form
的子类创建,包括你在Django 管理站点中遇到的ModelForm。
模型和表单
实际上,如果你的表单打算直接用来添加和编辑Django 的模型,ModelForm 可以节省你的许多时间、精力和代码,因为它将根据Model
类构建一个表单以及适当的字段和属性。
Bound and unbound forms 之间的区别非常重要:
表单的is_bound
属性将告诉你一个表单是否具有绑定的数据。
考虑一个比我们上面的最小例子更有用的形式,我们可以用它来在个人网站上实现“联系我”功能:
from django import forms
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField(widget=forms.Textarea)
sender = forms.EmailField()
cc_myself = forms.BooleanField(required=False)
我们前面的表单只使用一个字段your_name
,它是一个CharField
。 在这个例子中,我们的表单具有四个字段:message
、subject
、sender
和cc_myself
。 CharField
,EmailField
和BooleanField
只是三种可用的字段类型;在Form fields中可以找到完整列表。
每个表单字段都有一个对应的Widget class,它对应一个HTML 表单Widget,例如<input type="text">
。
在大部分情况下,字段都具有一个合理的默认Widget。 例如,默认情况下,CharField
具有一个TextInput
Widget,它在HTML 中生成一个<input type="text">
。 如果你需要message
,在定义表单字段时你应该指定一个合适的Widget,例如我们定义的<textarea>
字段。
不管表单提交的是什么数据,一旦通过调用is_valid()
成功验证(is_valid()
返回True
),验证后的表单数据将位于form.cleaned_data
字典中。 这些数据已经为你转换好为Python 的类型。
注
此时,你依然可以从request.POST
中直接访问到未验证的数据,但是访问验证后的数据更好一些。
在上面的联系表单示例中,cc_myself
将是一个布尔值。
类似地,IntegerField
和FloatField
字段分别将值转换为Python 的int
和float
。
下面是在视图中如何处理表单数据:
from django.core.mail import send_mail
if form.is_valid():
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
sender = form.cleaned_data['sender']
cc_myself = form.cleaned_data['cc_myself']
recipients = ['info@example.com']
if cc_myself:
recipients.append(sender)
send_mail(subject, message, sender, recipients)
return HttpResponseRedirect('/thanks/')
提示
关于Django 中如何发送邮件的更多信息,请参见Sending email。
有些字段类型需要一些额外的处理。 例如,使用表单上传的文件需要不同地处理(它们可以从request.POST
获取,而不是request.FILES
)。 如何使用表单处理文件上传的更多细节,请参见Binding uploaded files to a form。
你需要做的就是将表单实例放进模板的context。 如果你的表单在Context 中叫做form
,那么 {{ form }}
将正确地渲染它的<input>
和 <label>
元素。
表单模板的额外标签
不要忘记,表单的输出不 包含submit
标签,和表单的<form>
按钮。 你必须自己提供它们。
对于<input>
/<label>
对,还有几个输出选项:
{{ form.as_table }}
以表格的形式将它们渲染在<tr>
标签中{{ form.as_p }}
将它们渲染在<p>
标签中{{ form.as_ul }}
将它们渲染在<li>
标签中注意,你必须自己提供<ul>
或<table>
元素。
下面是我们的ContactForm
实例的输出{{ form.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>
<textarea name="message" id="id_message" required></textarea></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>
注意,每个表单字段具有一个ID 属性并设置为id_<field-name>
,它被一起的label 标签引用。 它对于确保屏幕阅读软件这类的辅助计算非常重要。
你还可以customize the way in which labels and ids are generated。
更多信息参见 Outputting forms as HTML。
在上面的例子中,Django会根据{{ form.as_p }}自动生成表单,我们也可以自己来具体编写表单的HTML;(例如,允许我们重新排序字段)。 每个字段都是表单的一个属性,可以使用{{ form.name_of_field }}
访问,并将在Django 模板中正确地渲染。 像这样:
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.subject.errors }}
<label for="{{ form.subject.id_for_label }}">Email subject:</label>
{{ form.subject }}
</div>
<div class="fieldWrapper">
{{ form.message.errors }}
<label for="{{ form.message.id_for_label }}">Your message:</label>
{{ form.message }}
</div>
<div class="fieldWrapper">
{{ form.sender.errors }}
<label for="{{ form.sender.id_for_label }}">Your email address:</label>
{{ form.sender }}
</div>
<div class="fieldWrapper">
{{ form.cc_myself.errors }}
<label for="{{ form.cc_myself.id_for_label }}">CC yourself?</label>
{{ form.cc_myself }}
</div>
完整的<label>
元素还可以使用label_tag()
生成。 像这样:
<div class="fieldWrapper">
{{ form.subject.errors }}
{{ form.subject.label_tag }}
{{ form.subject }}
</div>
当然,这个便利性的代价是更多的工作。 直到现在,我们没有担心如何展示错误信息,因为Django 已经帮我们处理好。
在下面的例子中,我们将自己处理每个字段的错误和表单整体的各种错误。 注意,表单和模板顶部的{{ form.non_field_errors }}
查找每个字段的错误。
使用{{ form.name_of_field.errors }}
显示表单错误的一个清单,并渲染成一个ul。 看上去可能像:
<ul class="errorlist">
<li>Sender is required.</li>
</ul>
这个ul 有一个errorlist
CSS 类型,你可以用它来定义外观。
如果你希望进一步自定义错误信息的显示,你可以迭代它们来实现:
{% if form.subject.errors %}
<ol>
{% for error in form.subject.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
非字段错误(以及使用nonfield
时渲染的隐藏字段错误)将渲染成一个额外的CSS 类型form.as_p()
以助于和字段错误信息区分。 例如,{{ form.non_field_errors }}
看上去会像:
<ul class="errorlist nonfield">
<li>Generic validation error</li>
</ul>
参见The Forms API 以获得关于错误、样式以及在模板中使用表单属性的更多内容。
如果你为你的表单使用相同的HTML,你可以使用{% for %}
循环迭代每个字段来减少重复的代码:
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
{{ field }}
中有用的属性包括:
{{ field.label }}
Email address
。{{ field.label_tag }}
包含在HTML <label>
标签中的字段Label。 它包含表单的label_suffix
。 例如,默认的label_suffix
是一个冒号:
<label for="id_email">Email address:</label>
{{ field.id_for_label }}
id_email
)。 如果你正在手工构造label,你可能想使用它代替label_tag
。 如果你有一些内嵌的JavaScript 并且想避免硬编码字段的ID,这也是有用的。{{ field.value }}
someone@example.com
。{{ field.html_name }}
{{ field.help_text }}
{{ field.errors }}
<ul class="errorlist">
,包含这个字段的验证错误信息。 你可以使用{% for error in field.errors %}
自定义错误的显示。 这种情况下,循环中的每个对象只是一个包含错误信息的简单字符串。{{ field.is_hidden }}
False
,否则为True
。 作为模板变量,它不是很有用处,但是可以用于条件测试,例如:{% if field.is_hidden %}
{# Do something special #}
{% endif %}
{{ field.field }}
Field
实例,通过BoundField
封装。 您可以使用它来访问Field
属性,例如{{ char_field.field.max_length }}
。请参见
有关属性和方法的完整列表,请参见BoundField
。
如果你的网站在多个地方对表单使用相同的渲染逻辑,你可以保存表单的循环到一个单独的模板中来减少重复,然后在其它模板中使用include
标签来重用它:
# In your form template:
{% include "form_snippet.html" %}
# In form_snippet.html:
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
如果传递到模板上下文中的表单对象具有一个不同的名称,你可以使用include
标签的with
参数来对它起个别名:
{% include "form_snippet.html" with form=comment_form %}
如果你发现自己经常这样做,你可能需要考虑一下创建一个自定义的 inclusion tag。
2017年9月6日