使用表单¶
除非您计划构建的网站和应用程序只发布内容,并且不接受访客的输入,否则您需要了解和使用表单。
Django 提供了一系列工具和库来帮助您构建表单以接受网站访客的输入,然后处理和响应输入。
HTML 表单¶
在 HTML 中,表单是 <form>...</form>
内的元素集合,允许访问者执行诸如输入文本、选择选项、操作对象或控件等操作,然后将这些信息发送回服务器。
其中一些表单界面元素(文本输入或复选框)内置于 HTML 本身。其他一些则更为复杂;弹出日期选择器或允许您移动滑块或操作控件的界面通常会同时使用 JavaScript 和 CSS 以及 HTML 表单 <input>
元素来实现这些效果。
除了 <input>
元素外,表单还必须指定两件事:
位置:应将与用户输入相对应的数据返回到的 URL
方式:应返回数据的 HTTP 方法
例如,Django 管理员的登录表单包含多个 <input>
元素:一个 type="text"
用于用户名,一个 type="password"
用于密码,一个 type="submit"
用于“登录”按钮。它还包含一些用户看不到的隐藏文本字段,Django 使用这些字段来确定下一步的操作。
它还告诉浏览器表单数据应发送到 <form>
的 action
属性(/admin/
)中指定的 URL,并且应使用 method
属性(post
)指定的 HTTP 机制发送。
当 <input type="submit" value="Log in">
元素被触发时,数据将返回到 /admin/
。
GET
和 POST
¶
处理表单时,只有 GET
和 POST
两种 HTTP 方法可用。
Django 的登录表单使用 POST
方法返回,浏览器将表单数据捆绑在一起,对其进行编码以进行传输,将其发送到服务器,然后接收回其响应。
GET
相反,它将提交的数据捆绑到一个字符串中,并使用它来构成一个 URL。URL 包含必须发送数据的位置以及数据键和值。如果您在 Django 文档中进行搜索,您会看到这一点,它会生成一个类似 https://docs.djangopy.cn/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中,我们的起点是:
forms.py
¶from django import forms
class NameForm(forms.Form):
your_name = forms.CharField(label="Your name", max_length=100)
这定义了一个具有单个字段(your_name
)的Form
类。我们已将人性化的标签应用于该字段,该标签将在呈现时显示在<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的视图中实例化它:
views.py
¶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
,我们将使用表单返回模板。这次表单不再为空(未绑定),因此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的模板语言将所有表单的字段及其属性解包到HTML标记中,来自{{ form }}
。
表单和跨站点请求伪造保护
Django附带易于使用的针对跨站点请求伪造的保护。当通过启用CSRF保护的POST
提交表单时,必须使用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
类继承了公共功能,但此实现细节很少重要。
模型和表单
事实上,如果您的表单将用于直接添加或编辑Django模型,ModelForm可以为您节省大量时间、精力和代码,因为它将根据Model
类构建表单以及相应的字段及其属性。
绑定和未绑定表单实例¶
绑定和未绑定表单之间的区别很重要
未绑定表单没有任何与其关联的数据。当呈现给用户时,它将为空或包含默认值。
绑定表单具有提交的数据,因此可用于判断该数据是否有效。如果呈现无效的绑定表单,它可以包含内联错误消息,告知用户需要更正哪些数据。
表单的is_bound
属性将告诉您表单是否绑定了数据。
关于字段的更多信息¶
考虑一个比我们上面最小的示例更有用的表单,我们可以使用它来实现个人网站上的“联系我”功能:
forms.py
¶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
只是三种可用的字段类型;完整的列表可以在表单字段中找到。
小部件¶
每个表单字段都有一个对应的小部件类,它又对应于一个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
。
以下是处理此表单的视图中如何处理表单数据的方法
views.py
¶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发送电子邮件的更多信息,请参阅发送电子邮件。
某些字段类型需要一些额外的处理。例如,使用表单上传的文件需要不同的处理方式(它们可以从request.FILES
而不是request.POST
中检索)。有关如何使用表单处理文件上传的详细信息,请参阅将上传的文件绑定到表单。
使用表单模板¶
要将表单放入模板中,只需将表单实例放入模板上下文即可。因此,如果您的表单在上下文中被称为form
,则{{ form }}
将适当地呈现其<label>
和<input>
元素。
附加表单模板组件
请记住,表单的输出_不_包括周围的<form>
标签或表单的submit
控件。您必须自己提供这些。
可重用表单模板¶
呈现表单时的HTML输出本身是通过模板生成的。您可以通过创建合适的模板文件并设置自定义FORM_RENDERER
来使用该form_template_name
在站点范围内。您还可以通过覆盖表单的template_name
属性使用自定义模板呈现表单,或者通过将模板名称直接传递给Form.render()
来自定义每个表单。
下面的示例将导致{{ form }}
被渲染为form_snippet.html
模板的输出。
在您的模板中
# In your template:
{{ form }}
# In form_snippet.html:
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
然后,您可以配置FORM_RENDERER
设置
settings.py
¶from django.forms.renderers import TemplatesSetting
class CustomFormRenderer(TemplatesSetting):
form_template_name = "form_snippet.html"
FORM_RENDERER = "project.settings.CustomFormRenderer"
……或单个表单
class MyForm(forms.Form):
template_name = "form_snippet.html"
...
……或单个表单实例的渲染,将模板名称传递给Form.render()
。这是一个在视图中使用的示例
def index(request):
form = MyForm()
rendered_form = form.render("form_snippet.html")
context = {"form": rendered_form}
return render(request, "index.html", context)
有关更多详细信息,请参阅将表单输出为HTML。
可重用字段组模板¶
每个字段都可以作为表单的属性使用,在模板中使用{{ form.name_of_field }}
。字段具有一个as_field_group()
方法,该方法将字段的相关元素作为组呈现,包括其标签、小部件、错误和帮助文本。
这允许编写通用的模板来按所需布局排列字段元素。例如
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.subject.as_field_group }}
</div>
<div class="fieldWrapper">
{{ form.message.as_field_group }}
</div>
<div class="fieldWrapper">
{{ form.sender.as_field_group }}
</div>
<div class="fieldWrapper">
{{ form.cc_myself.as_field_group }}
</div>
默认情况下,Django 使用"django/forms/field.html"
模板,该模板设计用于与默认的"django/forms/div.html"
表单样式一起使用。
可以通过在项目级别的FORM_RENDERER
中设置field_template_name
来自定义默认模板
from django.forms.renderers import TemplatesSetting
class CustomFormRenderer(TemplatesSetting):
field_template_name = "field_snippet.html"
……或单个字段
class MyForm(forms.Form):
subject = forms.CharField(template_name="my_custom_template.html")
...
……或通过调用BoundField.render()
并提供模板名称,在每次请求的基础上。
def index(request):
form = ContactForm()
subject = form["subject"]
context = {"subject": subject.render("my_custom_template.html")}
return render(request, "index.html", context)
手动呈现字段¶
还可以对字段渲染进行更细粒度的控制。这可能是在自定义字段模板中,以允许模板编写一次并对每个字段重复使用。但是,它也可以直接从表单上的字段属性访问。例如
{{ 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>
呈现表单错误消息¶
这种灵活性的代价是一些额外的工作。到目前为止,我们不必担心如何显示表单错误,因为这已经为我们处理好了。在这个示例中,我们必须确保处理每个字段的任何错误以及整个表单的任何错误。请注意表单顶部的{{ 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>
有关错误、样式和在模板中使用表单属性的更多信息,请参阅 表单 API。
循环遍历表单字段¶
如果每个表单字段都使用相同的 HTML,则可以通过使用 {% for %}
循环依次遍历每个字段来减少重复代码。
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<p class="help" id="{{ field.auto_id }}_helptext">
{{ field.help_text|safe }}
</p>
{% endif %}
</div>
{% endfor %}
{{ field }}
的有用属性包括:
{{ field.errors }}
输出一个包含与该字段对应的任何验证错误的
<ul class="errorlist">
。你可以使用{% for error in field.errors %}
循环来自定义错误的显示方式。在这种情况下,循环中的每个对象都是包含错误消息的字符串。{{ field.field }}
来自表单类的
Field
实例,该BoundField
包装了它。你可以用它来访问Field
属性,例如{{ char_field.field.max_length }}
。{{ field.help_text }}
与字段关联的任何帮助文本。
{{ field.html_name }}
将在输入元素的 name 字段中使用的字段名称。如果已设置,则会考虑表单前缀。
{{ field.id_for_label }}
将用于此字段的 ID(上例中的
id_email
)。如果你正在手动构建标签,你可能希望使用它来代替label_tag
。例如,如果你有一些内联 JavaScript 并想要避免硬编码字段的 ID,它也很有用。{{ field.is_hidden }}
如果表单字段是隐藏字段,则此属性为
True
,否则为False
。作为模板变量,它并不是特别有用,但在条件测试中可能很有用,例如:
{% if field.is_hidden %}
{# Do something special #}
{% endif %}
{{ field.label }}
字段的标签,例如
Email address
。{{ field.label_tag }}
用适当的 HTML
<label>
标签包装的字段标签。这包括表单的label_suffix
。例如,默认的label_suffix
是冒号。<label for="id_email">Email address:</label>
{{ field.legend_tag }}
类似于
field.label_tag
,但使用<legend>
标签代替<label>
,用于包含在<fieldset>
中的多个输入的 widget。{{ field.use_fieldset }}
如果表单字段的 widget 包含多个输入,这些输入应该在语义上分组到一个
<fieldset>
中,并带有<legend>
以提高可访问性,则此属性为True
。模板中的示例用法:
{% if field.use_fieldset %}
<fieldset>
{% if field.label %}{{ field.legend_tag }}{% endif %}
{% else %}
{% if field.label %}{{ field.label_tag }}{% endif %}
{% endif %}
{{ field }}
{% if field.use_fieldset %}</fieldset>{% endif %}
{{ field.value }}
字段的值,例如
someone@example.com
。
另请参阅:
有关属性和方法的完整列表,请参阅 BoundField
。
其他主题¶
这涵盖了基础知识,但表单还可以做更多的事情:
另请参阅:
- 表单参考
涵盖完整的 API 参考,包括表单字段、表单小部件以及表单和字段验证。