Skip to main content

使用表单

关于本文档

本文档介绍了Web表单的基础知识以及如何在Django中处理它们。有关表单API的特定区域的详细信息,请参阅 Forms API表单字段表单和字段验证

除非你打算构建只发布内容,不接受访问者的输入的网站和应用程序,否则你需要理解和使用表单。

Django提供了一系列工具和库,以帮助您构建表单以接受来自站点访问者的输入,然后处理和响应输入。

HTML表单

在HTML中,表单是 <form>...</form> 中的元素的集合,其允许访问者执行诸如输入文本,选择选项,操纵对象或控件等等之类的东西,然后将该信息发送回服务器。

其中一些形式的界面元素 - 文本输入或复选框 - 是相当简单,并内置到HTML本身。其他更复杂;一个弹出日期选择器或允许您移动滑块或操纵控件的界面通常会使用JavaScript和CSS以及HTML表单 <input> 元素来实现这些效果。

除了它的 <input> 元素,表单必须指定两件事:

  • where:应返回与用户输入相对应的数据的URL

  • how:数据应该返回的HTTP方法

例如,Django admin的登录表单包含几个 <input> 元素:type="text" 用于用户名,type="password" 用于密码之一,type="submit" 用于“登录”按钮。它还包含一些用户看不到的隐藏文本字段,Django用它们来确定下一步做什么。

它还告诉浏览器表单数据应发送到 <form>action 属性(/admin/)中指定的URL,并且应使用 method 属性(post)指定的HTTP机制发送。

<input type="submit" value="Log in"> 元素被触发时,数据返回到 /admin/

GETPOST

GETPOST 是处理表单时唯一使用的HTTP方法。

使用 POST 方法返回Django登录表单,其中浏览器捆绑表单数据,对其进行编码以进行传输,将其发送到服务器,然后接收回其响应。

相反,GET 将提交的数据捆绑成一个字符串,并使用它来组成一个URL。 URL包含必须发送数据的地址,以及数据键和值。如果您在Django文档中进行搜索,可以看到此操作,这将产生一个形式为 https://docs.djangoproject.com/search/?q=forms&release=1 的URL。

GETPOST 通常用于不同的目的。

任何可用于更改系统状态的请求(例如,在数据库中进行更改的请求)都应使用 POSTGET 应仅用于不影响系统状态的请求。

GET 也不适合使用密码表单,因为密码会显示在URL中,因此也会显示在浏览器历史记录和服务器日志中,都以纯文本格式显示。它既不适用于大量数据,也不适用于二进制数据,例如图像。使用 GET 请求管理表单的Web应用程序具有安全风险:攻击者可以很容易模仿表单的请求以访问系统的敏感部分。 POST,加上其他保护,如Django的 CSRF保护 提供更多的访问控制。

另一方面,GET 适合于诸如web搜索表单之类的东西,因为代表 GET 请求的URL可以容易地被加书签,共享或重新提交。

Django在形式中的作用

处理表格是一项复杂的业务。考虑Django的管理员,其中可能需要准备若干不同类型的多个数据项以便以表格形式显示,呈现为HTML,使用方便的界面编辑,返回到服务器,验证和清理,然后保存或传递用于进一步处理。

Django表单功能可以简化和自动化这项工作的大部分,也可以比大多数程序员在他们自己写的代码中更安全。

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

  • 准备和重组数据以使其准备好渲染

  • 为数据创建HTML表单

  • 接收和处理来自客户端的提交的表格和数据

它是 possible 编写代码,所有这些手动,但Django可以照顾你的所有。

Django中的表单

我们简要地描述了HTML表单,但是HTML <form> 只是所需机制的一部分。

在Web应用程序的上下文中,’form’可能指的是HTML <form>,或者生成它的Django Form,或者提交时返回的结构化数据,或者这些的端到端工作集合部分。

Django Form

这个系统的核心是Django的 Form 类。与Django模型描述对象的逻辑结构,它的行为,以及它的部分表示给我们的方式大致相同,Form 类描述了一个形式并确定它如何工作和出现。

以类似的方式,模型类的字段映射到数据库字段,表单类的字段映射到HTML表单 <input> 元素。 (ModelForm 通过 Form 将模型类的字段映射到 <input> 元素的HTML格式;这是Django管理员所基于的)。

表单的字段本身是类;他们管理表单数据并在提交表单时执行验证。 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>

这告诉浏览器使用 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.shortcuts import render
from django.http import HttpResponseRedirect

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 请求到达这个视图,它将创建一个空表单实例,并将其放置在要呈现的模板上下文中。这是我们第一次访问网址时可能发生的情况。

如果表单是使用 POST 请求提交的,视图将再次创建一个表单实例,并用请求中的数据填充它:form = NameForm(request.POST) 这被称为“绑定数据到表单”(它现在是一个 bound 表单)。

我们称之为表单的 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 提交表单时,必须像前面的示例一样使用 csrf_token 模板标记。但是,由于CSRF保护不直接绑定到模板中的表单,因此此标记在本文档的以下示例中省略。

HTML5输入类型和浏览器验证

如果您的表单包含 URLFieldEmailField 或任何整数字段类型,Django将使用 urlemailnumber HTML5输入类型。默认情况下,浏览器可以对这些字段应用自己的验证,这可能比Django的验证更严格。如果要禁用此行为,请在 form 标记上设置 novalidate 属性,或在字段上指定不同的窗口小部件(如 TextInput)。

我们现在有一个工作的Web窗体,由Django Form 描述,由视图处理,并呈现为HTML <form>

这是所有你需要开始,但表单框架提供了更多的在你的指尖。一旦你了解上述过程的基础知识,你应该准备好了解表格系统的其他功能,并准备好了解更多关于底层机械。

更多关于Django Form

所有表单类都创建为 django.forms.Form 的子类,包括您在Django管理中遇到的 ModelForm

模型和形式

事实上,如果您的表单将用于直接添加或编辑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_nameCharField。在这种情况下,我们的形式有四个字段:subjectmessagesendercc_myselfCharFieldEmailFieldBooleanField 只是三种可用的字段类型;一个完整的列表可以在 表单字段 找到。

窗口小部件

每个表单字段具有对应的 小部件类,其依次对应于诸如 <input type="text"> 的HTML表单小部件。

在大多数情况下,该字段将具有合理的默认窗口小部件。例如,默认情况下,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> 元素。

表单渲染选项

其他表单模板家具

不要忘记表单的输出 not 包括周围的 <form> 标签,或表单的 submit 控件。你必须自己提供这些。

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

  • {{ form.as_table }} 将它们作为包装在 <tr> 标记中的表单元格

  • {{ form.as_p }} 将它们包装在 <p> 标签中

  • {{ form.as_ul }} 将它们包装在 <li> 标签中

请注意,您必须自行提供周围的 <table><ul> 元素。

下面是 {{ form.as_p }} 对于 ContactForm 实例的输出:

<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>,由附带的标签标签引用。这对于确保表单能够被辅助技术(例如屏幕阅读器软件)访问是重要的。你也可以 定制生成标签和ids的方式

有关更多信息,请参阅 将表单输出为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 }}

参见

有关属性和方法的完整列表,请参阅 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 %}

此示例不处理隐藏字段中的任何错误。通常,隐藏字段中的错误是形式篡改的标志,因为正常形式的交互不会改变它们。但是,您也可以轻松地为这些表单错误插入一些错误显示。

可重用的表单模板

如果您的网站对多个地方的表单使用相同的呈现逻辑,则可以通过将表单的循环保存在独立模板中并使用 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