使用表单 — Django 文档
使用表单
除非您计划构建只发布内容并且不接受访问者输入的网站和应用程序,否则您将需要了解和使用表单。
Django 提供了一系列工具和库来帮助您构建表单以接受来自站点访问者的输入,然后处理和响应输入。
HTML 表单
在 HTML 中,表单是 <form>...</form>
中元素的集合,允许访问者执行诸如输入文本、选择选项、操作对象或控件等操作,然后将该信息发送回服务器。
其中一些表单界面元素 - 文本输入或复选框 - 相当简单并且内置于 HTML 本身。 其他的要复杂得多。 弹出日期选择器或允许您移动滑块或操作控件的界面通常会使用 JavaScript 和 CSS 以及 HTML 表单 <input>
元素来实现这些效果。
除了它的 <input>
元素,表单还必须指定两件事:
- where:用户输入对应的数据应该返回的URL
- how:数据应该返回的HTTP方法
例如,Django 管理员的登录表单包含多个 <input>
元素:type="text"
之一用于用户名,type="password"
之一用于密码,以及 type="submit"
用于“登录”按钮。 它还包含一些用户看不到的隐藏文本字段,Django 使用这些字段来确定下一步要做什么。
它还告诉浏览器应该将表单数据发送到 <form>
的 action
属性中指定的 URL - /admin/
- 并且应该使用 HTTP 发送method
属性指定的机制 - post
。
当<input type="submit" value="Log in">
元素被触发时,数据返回到/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 中,因此也会出现在浏览器历史记录和服务器日志中,所有这些都是纯文本的。 它既不适合大量数据,也不适合二进制数据,例如图像。 对管理表单使用 GET
请求的 Web 应用程序存在安全风险:攻击者很容易模仿表单的请求来访问系统的敏感部分。 POST
,再加上其他保护,如 Django 的 CSRF 保护 ,提供了对访问的更多控制。
另一方面,GET
适用于 Web 搜索表单之类的内容,因为表示 GET
请求的 URL 可以很容易地添加书签、共享或重新提交。
Django 在表单中的作用
处理表单是一项复杂的业务。 考虑 Django 的管理员,其中可能需要准备多种不同类型的大量数据项以在表单中显示、呈现为 HTML、使用方便的界面进行编辑、返回到服务器、验证和清理,然后保存或传递作进一步处理。
Django 的表单功能可以简化和自动化大部分工作,并且可以比大多数程序员在他们自己编写的代码中更安全地完成这项工作。
Django 处理表单所涉及工作的三个不同部分:
- 准备和重组数据以使其为渲染做好准备
- 为数据创建 HTML 表单
- 接收和处理客户提交的表格和数据
可能 编写代码来手动完成所有这些工作,但 Django 可以为您处理所有这些。
Django 中的表单
我们已经简要描述了 HTML 表单,但 HTML <form>
只是所需机制的一部分。
在 Web 应用程序的上下文中,“表单”可能指的是 HTML <form>
,或产生它的 Django Form,或在提交时返回的结构化数据,或到这些部分的端到端工作集合。
Django Form 类
这个组件系统的核心是 Django 的 Form 类。 与 Django 模型描述对象的逻辑结构、行为及其部分向我们表示的方式大致相同,Form 类描述一个表单并确定它如何工作和出现。
以类似于模型类的字段映射到数据库字段的方式,表单类的字段映射到 HTML 表单 <input>
元素。 (ModelForm 通过 Form 将模型类的字段映射到 HTML 表单 <input>
元素;这是 Django 管理员所基于的。)
表单的字段本身就是类; 他们管理表单数据并在提交表单时执行验证。 DateField 和 FileField 处理非常不同类型的数据,必须用它做不同的事情。
表单域在浏览器中以 HTML“小部件”(一种用户界面机制)的形式呈现给用户。 每个字段类型都有一个适当的默认 Widget 类 ,但可以根据需要覆盖这些。
实例化、处理和渲染表单
在 Django 中渲染对象时,我们通常:
- 在视图中获取它(例如从数据库中获取它)
- 将其传递给模板上下文
- 使用模板变量将其扩展为 HTML 标记
在模板中渲染表单涉及的工作与渲染任何其他类型的对象几乎相同,但存在一些关键差异。
在模型实例不包含数据的情况下,在模板中对其进行任何操作几乎没有用处。 另一方面,渲染一个未填充的表单是非常有意义的——这就是我们希望用户填充它时所做的。
因此,当我们在视图中处理模型实例时,我们通常会从数据库中检索它。 当我们处理一个表单时,我们通常在视图中实例化它。
当我们实例化一个表单时,我们可以选择将其留空或预先填充它,例如:
- 来自已保存模型实例的数据(如用于编辑的管理表单)
- 我们从其他来源整理的数据
- 从以前的 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/
。 它将显示一个标记为“您的姓名:”的文本字段和一个标记为“确定”的按钮。 如果模板上下文包含 current_name
变量,它将用于预填充 your_name
字段。
您需要一个视图来呈现包含 HTML 表单的模板,并且可以根据需要提供 current_name
字段。
当表单提交时,发送到服务器的 POST
请求将包含表单数据。
现在,您还需要一个与 /your-name/
URL 对应的视图,它将在请求中找到适当的键/值对,然后处理它们。
这是一个非常简单的表格。 在实践中,一个表单可能包含数十个或数百个字段,其中许多可能需要预先填充,我们可能希望用户在结束操作之前多次完成编辑-提交循环。
我们可能需要在浏览器中进行一些验证,甚至在提交表单之前; 我们可能想要使用更复杂的字段,允许用户执行诸如从日历中选择日期等操作。
在这一点上,让 Django 为我们完成大部分工作要容易得多。
在 Django 中构建表单
Form 类
我们已经知道我们希望我们的 HTML 表单看起来像什么。 我们在 Django 中的起点是这样的:
from django import forms
class NameForm(forms.Form):
your_name = forms.CharField(label='Your name', max_length=100)
这定义了一个具有单个字段 (your_name
) 的 Form 类。 我们为该字段应用了一个人性化的标签,它会在渲染时出现在 <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.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import NameForm
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
form = NameForm()
return render(request, 'name.html', {'form': form})
如果我们使用 GET
请求到达此视图,它将创建一个空表单实例并将其放置在要呈现的模板上下文中。 这是我们第一次访问 URL 时可以预料到的情况。
如果使用 POST
请求提交表单,则视图将再次创建一个表单实例并使用请求中的数据填充它: form = NameForm(request.POST)
这称为“将数据绑定到表单”(它现在是 绑定 形式)。
我们调用表单的is_valid()
方法; 如果它不是 True
,我们回到带有表单的模板。 这次表单不再是空的 (unbound),因此 HTML 表单将使用之前提交的数据填充,可以根据需要在其中进行编辑和更正。
如果 is_valid()
是 True
,我们现在可以在其 cleaned_data
属性中找到所有经过验证的表单数据。 我们可以使用这些数据来更新数据库或进行其他处理,然后再向浏览器发送 HTTP 重定向,告诉浏览器下一步要去哪里。
模板
我们不需要在我们的 name.html
模板中做太多事情。 最简单的例子是:
<form action="/your-name/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
Django 的模板语言将所有表单的字段及其属性从 模板:Form
解压缩为 HTML 标记。
表单和跨站点请求伪造保护
Django 附带了一个易于使用的 保护以防止跨站点请求伪造 。 当通过启用 CSRF 保护的 POST
提交表单时,您必须使用 :ttag:`csrf_token` 模板标签,如上例所示。 但是,由于 CSRF 保护不直接与模板中的表单相关联,因此本文档的以下示例中省略了此标记。
HTML5 输入类型和浏览器验证
如果您的表单包含 URLField、EmailField 或任何整数字段类型,Django 将使用 url
、email
和 number
] HTML5 输入类型。 默认情况下,浏览器可能会对这些字段应用自己的验证,这可能比 Django 的验证更严格。 如果您想禁用此行为,请在 form
标签上设置 novalidate 属性,或在该字段上指定不同的小部件,例如 TextInput。
我们现在有一个可用的 Web 表单,由 Django Form 描述,由视图处理,并呈现为 HTML <form>
。
这就是您开始所需的全部内容,但表单框架让您轻松掌握更多内容。 一旦您了解了上述流程的基础知识,您就应该准备好了解表单系统的其他功能,并准备好了解更多有关底层机制的知识。
更多关于 Django Form 类
所有表单类都被创建为 django.forms.Form 或 django.forms.ModelForm 的子类。 您可以将 ModelForm
视为 Form
的子类。 Form
和 ModelForm
实际上从(私有)BaseForm
类继承了通用功能,但是这个实现细节很少重要。
绑定和未绑定表单实例
绑定和未绑定形式之间的区别很重要:
- 未绑定的表单没有与之关联的数据。 当呈现给用户时,它将为空或包含默认值。
- 绑定表单已提交数据,因此可用于判断该数据是否有效。 如果呈现无效的绑定表单,它可以包含内联错误消息,告诉用户要更正哪些数据。
表单的 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。 在这种情况下,我们的表单有四个字段:subject
、message
、sender
和 cc_myself
。 CharField、EmailField 和 BooleanField 只是三种可用的字段类型; 完整列表可在 表单字段 中找到。
小工具
每个表单字段都有一个对应的 Widget 类,它又对应一个 HTML 表单小部件,例如 <input type="text">
。
在大多数情况下,该字段将有一个合理的默认小部件。 例如,默认情况下,CharField 将有一个 TextInput 小部件,它在 HTML 中生成一个 <input type="text">
。 如果您需要 <textarea>
,您可以在定义表单字段时指定适当的小部件,就像我们对 message
字段所做的那样。
现场数据
无论表单提交的数据是什么,一旦通过调用 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/')
某些字段类型需要一些额外的处理。 例如,使用表单上传的文件需要以不同方式处理(它们可以从 request.FILES
中检索,而不是从 request.POST
中检索)。 有关如何使用表单处理文件上传的详细信息,请参阅 将上传的文件绑定到表单 。
使用表单模板
要将表单放入模板中,您所需要做的就是将表单实例放入模板上下文中。 因此,如果您的表单在上下文中称为 form
,则 模板:Form
将适当地呈现其 <label>
和 <input>
元素。
表单渲染选项
附加模板家具
不要忘记表单的输出 不 包括周围的 <form>
标签,或表单的 submit
控件。 您必须自己提供这些。
<label>
/<input>
对还有其他输出选项:
模板:Form.as table
将它们呈现为包裹在<tr>
标签中的表格单元格模板:Form.as p
会将它们包裹在<p>
标签中模板:Form.as ul
会将它们包裹在<li>
标签中
请注意,您必须自己提供周围的 <table>
或 <ul>
元素。
这是我们的 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>
,由随附的标签标记引用。 这对于确保屏幕阅读器软件等辅助技术可以访问表单非常重要。 您还可以自定义标签和id的生成方式。
有关更多信息,请参阅 将表单输出为 HTML。
手动渲染字段
我们不必让 Django 解压表单的字段; 如果我们愿意,我们可以手动完成(例如,允许我们重新排序字段)。 每个字段都可用作使用 模板: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_tag() 生成完整的 <label>
元素。 例如:
<div class="fieldWrapper">
{{ form.subject.errors }}
{{ form.subject.label_tag }}
{{ form.subject }}
</div>
呈现表单错误消息
当然,这种灵活性的代价是更多的工作。 到目前为止,我们不必担心如何显示表单错误,因为这已经为我们处理好了。 在这个例子中,我们必须确保我们处理每个字段的任何错误以及整个表单的任何错误。 注意表单顶部的 模板:Form.non field errors
和模板查找每个字段的错误。
使用 模板:Form.name of field.errors
显示表单错误列表,呈现为无序列表。 这可能看起来像:
<ul class="errorlist">
<li>Sender is required.</li>
</ul>
该列表有一个 errorlist
的 CSS 类,允许您设置其外观样式。 如果你想进一步自定义错误的显示,你可以通过循环它们来实现:
{% if form.subject.errors %}
<ol>
{% for error in form.subject.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
非字段错误(和/或在使用 form.as_p()
等帮助程序时呈现在表单顶部的隐藏字段错误)将使用额外的 nonfield
类来呈现,以帮助将它们与特定于字段的错误。 例如,模板:Form.non field errors
看起来像:
<ul class="errorlist nonfield">
<li>Generic validation error</li>
</ul>
有关错误、样式和在模板中使用表单属性的更多信息,请参阅 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_suffix。 例如,默认的label_suffix
是一个冒号:<label for="id_email">Email address:</label>
模板:Field.id for label
将用于此字段的 ID(上例中的
id_email
)。 如果您手动构建标签,您可能需要使用它来代替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
如果表单字段是隐藏字段,则此属性为
True
,否则为False
。 它作为模板变量并不是特别有用,但在条件测试中可能很有用,例如:
{% if field.is_hidden %}
{# Do something special #}
{% endif %}
模板:Field.field
- 来自这个 BoundField 包装的表单类的 Field 实例。 您可以使用它来访问 Field 属性,例如
模板:Char field.field.max length
。
可重用的表单模板
如果您的站点在多个位置对表单使用相同的呈现逻辑,您可以通过将表单的循环保存在独立模板中并使用 :ttag:`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 %}
如果传递给模板的表单对象在上下文中具有不同的名称,您可以使用 :ttag:`include` 标签的 with
参数为其别名:
{% include "form_snippet.html" with form=comment_form %}
如果您发现自己经常这样做,您可以考虑创建一个自定义的 包含标签 。