Skip to content

Latest commit

 

History

History
580 lines (370 loc) · 34.1 KB

使用表单.md

File metadata and controls

580 lines (370 loc) · 34.1 KB

原文:Working with forms


关于本文档

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

除非你打算建立除了发布内容什么也不做的网站和应用程序,并且不接受你的访问者的任何输入,否则你将需要理解和使用的表单。

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属性指定的HTTP机制 - post - 进行发送。

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

GETPOST

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

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

GET,相比之下,捆绑提交的数据并转换成字符串,然后使用它来组成一个URL。URL包含数据必须被发送到的地址,以及数据的键值。如果你对Django文档进行搜索,这将生成一个格式为https://docs.djangoproject.com/search/?q=forms&amp;release=1的URL,从中你可以看到生成的格式。

GET和POST通常用于不同的目的。

任何可以用来修改系统状态的请求,例如,对数据库进行修改的请求,都应该使用POSTGET应该只用于那些不影响系统状态的请求。

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

另一方面,GET适用于例如web搜索表单之类的东西,因为可以很容易的标记,共享或者重新提交表示GET请求的URL。

Django在表单中的角色

处理表单是一个复杂的事。想想Django的admin,几个不同类型的数据的众多的项可能需要准备以便在表单上显示,呈现为HTML,使用便捷的界面编辑,返回到服务器,验证和清理,然后保存或传递以作进一步处理。

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

Django处理包含在表单中的三个不同部分:

  • 准备和调整数据,使其为渲染做好准备。
  • 创建HTML表单数据
  • 接收和处理从客户端提交的表单和数据

也有可能编写代码手动做到这些,但是Django可以为你处理好这一切。

Django中的表单

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

在Web应用程序的背景下,“表单”可能指的是HTML的<form> ,或者Django生成它的Form,或当它被提交时返回的结构化数据,或者这些部件的端到端工作集。

Django的Form

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

类似于一个模型类的字段映射到数据字段,一个表单类的字段映射到HTML表单的<input>元素。(一个[ModelForm]https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/#django.forms.ModelForm "django.forms.ModelForm") 通过一个Form映射一个模型类的字段到HTML表单的<input>元素;这便是Django admin的基础。)

一个表单字段是它们自身的类;它们管理表单数据并在表单提交时进行验证。一个DateField和一个FileField处理完全不同类型的数据,并且完成不同的事情。

一个表单字段作为一个HTML“小工具”(一个用户界面机制)在浏览器中展示给用户。每一个字段类型都有一个适当的默认Widget 类,但是在需要的时候,可以对其进行覆盖。

实例化,处理和渲染表单

当在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/上。它会显示一个文本字段,标有“Your name:”和一个标有“OK”的按钮。如果模板上下文包含一个current_name变量,那它将被用于预填充your_name字段。

你需要一个渲染包含HTML表单的模板的视图,并且它可以适当的提供current_name字段。

当表单提交后,被发送到服务器的POST请求,将包含表单数据。

现在,你还需要对应于/your-name/ URL的一个视图,它将在请求中找到合适的键/值对,然后对其进行处理。

这是一个非常简单的表单。在实践中,表单可能包含数十个或数百个字段,其中有许多可能需要预先进行填充,我们可能期望用户在结束操作之前做完几次编辑提交循环。

我们可能需要在浏览器中出现一些验证,甚至在提交表单之前; 我们可能希望使用更复杂的字段,即允许用户进行一些诸如从日历中挑日期之类的操作。

在这一点上,是很容易让Django为我们做大部分的工作的。

在Django中构建一个表单

我们已经知道我们想要的HTML表单长啥样了。在Django中,我们的起始点是:

#forms.py
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定义了该字段最大允许的长度。它做了两件事。它将maxlength="100"放到HTML <input>中 (所以浏览器首先应该会阻止用户输入超过这个数量的字符)。它还意味着,当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">

注意,它并不包含<form>标签,也不包含提交按钮。我们必须自己在模板中提供这些。

视图

发送回Django网站的表单数据是由一个视图处理的,它通常是与发布表单相同的视图。这使我们能够重复使用一些相同的逻辑。

为了处理表单,我们需要为我们所希望发布的URL在视图中对它进行实例化。为我们所希望的要发布的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)这就是所谓的“绑定数据到表单中”(现在它是一个约束表单)。

我们调用表单的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自带一个易于使用的防止跨站请求伪造的保护。在启用CSRF保护的情况下,当通过POST提交一个表单,你必须像前面的示例所示使用csrf_token模板标签。然而,由于CSRF保护不直接依赖于模板中的表单,在这个文档中这个标签在下面例子中被删除。

HTML5输入类型和浏览器验证

如果你的表单包含了一个URLField字段,一个EmailField字段或者其他整型字段类型,Django将使用url, emailnumber HTML5输入类型。默认情况下,浏览器可以在这些字段应用它们自身的验证,它可能比Django验证更加严厉。如果你想要禁用此行为,那么在form标签上novalidate属性,或者在该字段上指定一个不同的布局,例如TextInput

现在,我们有了一个可工作的web表单,由DjangoForm所描述,通过视图进行处理,并作为一个HTML <form>进行渲染。

这就是你需要开始的所有东西,但表单框架在你的指尖上提出了更多东西。一旦你理解了上述过程的基础知识,应该就为了解表单系统的其他功能做了准备,并准备学习更多有关的基本机制。

关于Django的Form类的更多信息

所有的表单都是作为django.forms.Form的子类被创建的,包括[ModelForm]https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/)这个你在Django admin中遇到的类。

模型和表单

事实上,如果你的表单将被直接用于增加或编辑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, sendercc_myselfCharField, EmailFieldBooleanField只是可用字段类型中的三种;完整列表可以在Form字段找到。

小工具(Widget)

每一个表单字段都有一个对应的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将是一个布尔类型的值。同样的,诸如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 = ['[email protected]']
    if cc_myself:
        recipients.append(sender)

    send_mail(subject, message, sender, recipients)
    return HttpResponseRedirect('/thanks/')

小提示

关于从Django发送邮件的更多信息,见发送邮件

有些字段类型需要一些额外的处理。例如,使用表单上传的文件需要区别处理(they can be retrieved from 它们可以从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" /></p>
<p><label for="id_message">Message:</label>
    <input type="text" name="message" id="id_message" /></p>
<p><label for="id_sender">Sender:</label>
    <input type="email" name="sender" id="id_sender" /></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属性,这是由伴随着的标记标签所引用的。这在确保表单可被辅助技术,例如读屏软件,所访问起到了重要作用。你也可以自定义生成标签和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类,errorlist,它允许你为其外观设计样式。如果你想要更进一步自定义错误的显示,那么你可以通过遍历它们来进行:

{% 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>

Django 1.8中做了修改:

增加了上面例子中所描述的nonfield类。

Form API以获得更多关于错误,样式和在模板中使用表单属性的信息。

遍历表单字段

假如对于每个表单字段都使用相同的HTML,那么你可以通过使用{% for %}依次遍历每个字段以减少重复代码: loop:

{% 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 }} 字段值。例如,[email protected]

{{ 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 %}

如果你发现你经常这样做,那么可能要考虑创建一个自定义的包含标签

更多主题

这篇文档涵盖了基础点,但是表单可以做一堆更多的东西:

另见

表单参考 涵盖了完整的API参考,其中包括表单字段,表单控件,以及表单和字段验证。