使用表单

关于本页文档

本页文档介绍了Web表单的基本内容以及它们在Django中是如何处理的。想更详细的了解表单的API,参见 表单 API表单字段 ,以及 表单和字段验证

除非您计划搭建的网站和应用只发布内容且不接收访问者的输入,否则您就需要理解和使用表单。

Django提供了一系列的工具和库来帮助您构建表单来接收网站访客的输入,然后处理以及响应这些输入。

HTML表单

在HTML中,表单是在 <form>...</form> 中的一些元素,它允许访客做一些类似输入文本、选择选项、操作对象或空间等动作,然后发送这些信息到服务端。

一些表单界面元素(文本框或复选框)内置在HTML中。其他会更复杂些;像弹出日期选择或者允许您移动滑块或者操作控件,一般通过使用JavaScript,CSS以及HTML表单中的 <input> 元素来实现这些效果。

和它的元素 <input> 一样,表单必须指定两样东西:

  • 何地:负责响应用户输入数据的URL地址
  • 如何:数据请求使用的HTTP方法。

例如,Django admin的登录表单包含了一些 <input> 元素:用户名用 type="text" ,密码用 type="password" ,登录按钮用 type="submit" 。它还包含一些用户看不到的隐藏文本字段,Django用它们来决定下一步行为。

它还告诉浏览器表单数据应该发往 <form>action 属性指定的URL—— /admin/ ,并且应该使用它的 method 属性指定的HTTP方法—— post

<input type="submit" value="Log in"> 元素被触发的时候,数据会发送到 /admin/

GETPOST

处理表单时只会用到 GETPOST 两种HTTP方法。

Django的登录表单使用 POST 方法传输数据,在这个方法中浏览器会封装表单数据,为了传输会进行编码,然后发送到服务端并接收它的响应。

相比之下,GET 方法将提交的数据捆绑到一个字符串中,并用它来组成一个URL。该URL包含了数据要发送的地址以及一些键值对应的数据。如果您在Django文档中进行一次搜索,就会看到这点,它会生成一个形似 https://docs.djangoproject.com/search/?q=forms&release=1 的URL。

GETPOST 通常用于不同的目的。

任何可能用于更改系统状态的请求应该使用 POST —— 比如一个更改数据库的请求。GET 应该只被用于不会影响系统状态的请求。

GET 也不适合密码表格,因为密码会出现在 URL 中,因此也会出现在浏览器历史和服务器日志中,都是纯文本。它也不适合于大量的数据,或二进制数据,如图像。一个使用 GET 请求管理表单的网络应用程序是一个安全风险:攻击者很容易模仿表单的请求来获得对系统敏感部分的访问。POST,加上其他保护措施,如 Django 的 CSRF 保护,可以对访问进行更多控制。

另一方面, GET 方法适用于诸如网页搜索表单这样的内容,因为这类呈现为一个 GET 请求的URL很容易被存为书签、分享或重新提交。

Django在表单中的角色

处理表单是一件挺复杂的事情。想想看Django的admin,许多不同类型的数据可能需要在一张表单中准备显示,渲染成HTML,使用方便的界面进行编辑,传到服务器,验证和清理数据,然后保存或跳过进行下一步处理。

Django的表单功能可以简化和自动化上述工作的大部分内容,并且也能比大多数程序员自己编写代码去实现来的更安全些。

Django会处理涉及表单的三个不同部分:

  • 准备并重组数据,以便下一步的渲染
  • 为数据创建HTML 表单
  • 接收并处理客户端提交的表单及数据

可以 手动编写代码来实现,但Django 可以帮你完成所有这些工作。

Django 中的表单

我们已经简单的描述过了HTML 表单,但是一个HTML <form> 只是其所需的一部分。

在网络应用的上下文中,“表单”可能指的是那个HTML <form>,或者指产生它的 Django Form,或者指它提交时返回的结构化数据,或者指这些部分的端到端工作集合。

Django的 Form

Django表单系统的核心组件是 Form 类。它与Django模型描述对象的逻辑结构、行为以及它呈现给我们内容的形式的方式大致相同, Form 类描述一张表单并决定它如何工作及呈现。

类似于模型类的字段映射到数据库字段的方式,表单类的字段会映射到HTML表单的 <input> 元素。 ModelForm 通过 Form 映射模型类的字段到HTML表单的 <input> 元素,Django admin就基于此。

表单字段本身也是类;他们管理表单数据并在提交表单时执行验证。 DateFieldFileField 处理的数据类型差别很大,所以必须用来处理不同的字段。

在浏览器中,表单字段以HTML“控件”(用户界面的一个片段)的形式展现给我们。每个字段类型都有与之相匹配的 控件类 ,但必要时可以覆盖。

实例化、处理和渲染表单

在Django中渲染一个对象的时候,我们通常:

  1. 在视图中获取它(例如从数据库中取出)
  2. 将它传递给模板上下文
  3. 使用模板变量将它扩展为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>

这告诉浏览器将表单数据返回给URL /your-name/ ,并使用 POST 方法。它将显示一个标签为 "Your name:" 的文本字段,以及一个 "OK" 按钮。如果模板上下文包含一个 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收到浏览器传过来的表单时,对数据长度进行验证(也就是服务器端验证)。

A Form instance has an is_valid() method, which runs validation routines for all its fields. When this method is called, if all fields contain valid data, it will:

  • 返回 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模板语言从 {{ form }} 中被解包成HTML标记。

表格和跨站请求伪造保护

Django自带一个简单易用的 跨站请求伪造防护 。当通过 POST 方法提交一张启用了CSRF防护的表单时,您必须使用上例中这样的模板标签 csrf_token 。但是,由于CSRF防护在模板中没有与表单直接绑定,因此这个标签在本页文档之后的示例中都将被忽略。

HTML5输入类型和浏览器验证

如果您的表单包含 URLFieldEmailField 或者其他整数字段类型,Django将使用 urlemailnumber HTML5输入类型。默认情况下,浏览器可能会在这些字段上应用他们自己的验证,这也许比Django的验证更加严格。如果您想禁用这个行为,请在 form 标签上设置 novalidate 属性,或者在字段上指定一个不同的控件,比如 TextInput

现在我们有了一个可以工作的web表单,它通过一张Django Form 描述,由一个视图来处理并渲染成一个HTML <form>

以上是您入门需要了解的所有内容,但是表单框架提供了更多垂手可得的内容。一旦您理解了上述过程的基础知识,您应该再了解下表单系统的其他功能,然后学习更多的底层机制。

详解Django Form

所有表单类都作为 django.forms.Form 或者 django.forms.ModelForm 的子类来创建。您可以把 ModelForm 想象成 Form 的子类。实际上 FormModelForm 从(私有) BaseForm 类继承了通用功能,但是这个实现细节不怎么重要。

模型和表单

实际上,如果您的表单是要直接用来添加或编辑Django模型,用 ModelForm ,可以省时、省力、省代码,因为它会根据 Model 类构建一张对应字段及其属性的表单。

绑定的和未绑定的表单实例

The distinction between 绑定和非绑定表单 is important:

  • 未绑定的表单没有与其关联的数据。当渲染给用户的时候,它会是空的或者包含默认值。
  • 绑定的表单拥有已提交的数据,因此可以用来判断数据是否合法。如果渲染了一张非法的绑定的表单,它将包含内联的错误信息,告知用户要纠正哪些数据。

表单的 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)

我们之前的表单只用了一个 CharField 类型的字段 your_name 。在这个例子中,我们的表单有四个字段: subjectmessagesendercc_myself 。只用到三种可用的字段类型: CharFieldEmailFieldBooleanField ;完整的字段类型清单请参看 表单字段

控件

每个表单字段都有一个相对应的 控件类 ,这个控件类又有对应的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 会被转化成一个布尔值。同样的,字段 IntegerFieldFloatField 的值分别会被转化为Python的 intfloat 类型。

下面例举了如何在视图中处理表单数据:

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 以获取更多详细信息。

Changed in Django 4.1:

在表单渲染器上设置默认的 form_template_name 的功能已添加。

表单渲染选项

对于 <label> / <input> 对,还有其他输出选项:

  • {{ form.as_div }} 将以 <div> 标签包裹呈现它们。
  • {{ 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_<field-name> 这样的ID属性,它被附带的label标签引用。这对于确保表单可供屏幕阅读软件这样的辅助技术访问非常重要。您还可以 自定义Label和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> 元素还可以使用 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>

该列表有一个CSS class errorlist ,允许您自定义其样式。如果你想进一步自定义错误信息的显示,您可以通过遍历它们来实现:

{% if form.subject.errors %}
    <ol>
    {% for error in form.subject.errors %}
        <li><strong>{{ error|escape }}</strong></li>
    {% endfor %}
    </ol>
{% endif %}

非字段验证错误信息(或者通过使用像 form.as_p() 这样的辅助方法渲染产生在表单顶部的隐藏错误信息)渲染后会额外带上一个class 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">{{ field.help_text|safe }}</p>
        {% endif %}
    </div>
{% endfor %}

Useful attributes on {{ field }} include:

{{ 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,您可能要用这个来替换 label_tag 。例如,如果你有一些内嵌的JavaScript并且想要避免硬编码字段的ID,这也很有用。
{{ field.is_hidden }}
如果是隐藏字段,这个属性为 True ,否则为 False 。它作为模板变量没多大作用,但可用于条件测试,例如:
{% if field.is_hidden %}
   {# Do something special #}
{% endif %}
{{ field.label }}
字段的label,比如 Email address
{{ field.label_tag }}

字段的标签将包装在适当的 HTML <label> 标签中。这包括表单的 label_suffix。例如,默认的 label_suffix 是冒号:

<label for="id_email">Email address:</label>

{{ field.legend_tag }}

New in Django 4.1.

类似于 field.label_tag,但在多个输入被包装在 <fieldset> 中的小部件上使用 <legend> 标签代替 <label>

{{ field.use_fieldset }}

New in Django 4.1.

如果表单字段的小部件包含应该在 <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

遍历隐藏字段和可见字段

如果您在手动布置模板中的表单,而不是依靠Django的默认表单布局,您可能希望将 <input type="hidden"> 字段与非隐藏字段区别开来。例如,因为隐藏字段不显示任何内容,将错误消息“放到”该字段旁边可能会导致用户混淆——所以这些字段的错误应该以不同的方式处理。

Django在表单上提供了两种方法,允许您独立地遍历隐藏和可见的字段: hidden_fields()visible_fields() 。以下是使用这两种方法对之前示例的修改:

{# Include the hidden fields #}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{# Include the visible fields #}
{% for field in form.visible_fields %}
    <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }} {{ field }}
    </div>
{% endfor %}

这个示例没有处理隐藏字段中的任何错误信息。通常,隐藏字段中的错误象征着表单被篡改,因为正常的表单交互不会去改变它们。但是,您也可以轻松地为这些表单错误插入一些错误信息显示出来。

Back to Top