使用表单

关于这个文件

本文档介绍了Web表单的基本知识以及如何在Django中处理它们。 有关表单API特定区域的更详细的信息,请参阅The Forms APIForm fieldsForm and field validation

除非您打算构建网站和应用程序,而这些网站和应用程序除了发布内容之外什么都不做,也不接受来自访问者的输入,您将需要了解和使用表单。

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

HTML表单

在HTML中,表单是<form>...</form>内的元素集合,它允许访问者执行输入文本,选择选项,操作对象或控件等操作等等,然后将这些信息发送回服务器。

其中一些表单界面元素(文本输入或复选框)非常简单,并且内置于HTML本身。 其他则更为复杂;一个弹出日期选择器的界面,或者允许您移动滑块或操作控件的界面通常使用JavaScript和CSS以及HTML表单<input>元素来实现这些效果。

<input>元素一样,表单必须指定两件事:

  • where: the URL to which the data corresponding to the user’s input should be returned
  • how: the HTTP method the data should be returned by

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

It also tells the browser that the form data should be sent to the URL specified in the <form>’s action attribute - /admin/ - and that it should be sent using the HTTP mechanism specified by the method attribute - post.

When the <input type="submit" value="Log in"> element is triggered, the data is returned to /admin/.

GETPOST

GET and POST are the only HTTP methods to use when dealing with forms.

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

GET, by contrast, bundles the submitted data into a string, and uses this to compose a URL. 该URL包含数据必须发送的地址以及数据键和值。 如果您在Django文档中进行搜索,可以看到这个动作,它将生成https://docs.djangoproject.com/search/?q=forms&release=1

GETPOST通常用于不同的目的。

任何可以用来改变系统状态的请求(例如,在数据库中进行更改的请求)都应该使用POST GET should be used only for requests that do not affect the state of the system.

GET也不适合密码形式,因为密码会出现在URL中,因此,在浏览器历史记录和服务器日志中也都会以纯文本显示。 也不适用于大量的数据,或二进制数据,如图像。 使用GET请求管理表单的Web应用程序存在安全风险:攻击者可能很容易模仿表单的请求来访问系统的敏感部分。 POST, coupled with other protections like Django’s CSRF protection offers more control over access.

On the other hand, GET is suitable for things like a web search form, because the URLs that represent a GET request can easily be bookmarked, shared, or resubmitted.

Django在形式中的角色

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

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

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

  • 准备和重组数据以使其可以呈现
  • 为数据创建HTML表单
  • 接收和处理客户提交的表格和数据

It is possible to write code that does all of this manually, but Django can take care of it all for you.

Django中的表单

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

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

Django Form

这个组件系统的核心是Django的Form类。 就像Django模型描述一个对象的逻辑结构,它的行为以及它的各个部分向我们呈现的方式一样,一个Form类描述一个表单并决定它是如何工作和出现的。

类似于模型类的字段映射到数据库字段,表单类的字段映射到HTML表单<input>元素。 (A ModelForm maps a model class’s fields to HTML form <input> elements via a Form; this is what the Django admin is based upon.)

表单的字段本身就是类;他们管理表单数据并在提交表单时执行验证。 一个DateField和一个FileField处理非常不同类型的数据,并且必须做不同的事情。

表单字段在浏览器中表示为HTML“小部件” - 一种用户界面机制。 每个字段类型都有一个合适的默认Widget class,但是这些可以根据需要重写。

实例化,处理和呈现表单

在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/ 它将显示一个标有“你的名字:”的文本字段和一个标记为“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)

这定义了一个带有单个字段的Form类(your_name)。 我们已经在该字段上应用了一个人性化的标签,这个标签在呈现时会出现在<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请求到达这个视图,它将创建一个空的表单实例,并将其放置在模板上下文中进行渲染。 这是我们第一次访问网址时预期会发生的事情。

If the form is submitted using a POST request, the view will once again create a form instance and populate it with data from the request: form = NameForm(request.POST) This is called “binding data to the form” (it is now a bound form).

我们称之为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's从{{ 表单 }}解压缩为HTML标记模板语言。

表格和跨站请求伪造保护

Django提供了一个易于使用的防止跨站请求伪造的protection against Cross Site Request Forgeries 当通过POST提交表单并启用了CSRF保护时,您必须使用csrf_token模板标记,如上例所示。 但是,由于CSRF保护不直接与模板中的表单绑定,因此本文档中的以下示例中省略了该标记。

HTML5输入类型和浏览器验证

If your form includes a URLField, an EmailField or any integer field type, Django will use the url, email and number HTML5 input types. 默认情况下,浏览器可以在这些字段上应用自己的验证,这可能比Django的验证更严格。 如果要禁用此行为,请在form标记上设置novalidate属性,或者在字段上指定一个不同的小部件,如TextInput

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

这就是你需要开始的一切,但是表单框架在你的指尖上放了很多东西。 一旦你了解了上述过程的基础知识,就应该准备好理解表单系统的其他特性,并准备好了解更多关于基础机制的知识。

更多关于Django的Form

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

模型和形式

事实上,如果您的表单将被用来直接添加或编辑Django模型,那么ModelForm可以为您节省大量的时间,精力和代码,因为它将构建一个表单以及来自Model类的相应字段及其属性。

绑定和未绑定的表单实例

The distinction between Bound and unbound forms 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)

我们之前的表单使用了一个字段,即your_name,一个CharField 在这种情况下,我们的表单有四个字段:subjectmessagesendercc_myself CharField, EmailField and BooleanField are just three of the available field types; a full list can be found in Form fields.

窗口小部件¶ T0>

每个表单字段都有一个对应的Widget class,它依次对应于一个HTML表单控件,如&lt; input type =“text”&gt; T4> T2>。

在大多数情况下,该领域将有一个合理的默认小部件。 For example, by default, a CharField will have a TextInput widget, that produces an <input type="text"> in the HTML. 如果您需要使用<textarea>,则在定义表单字段时将指定相应的窗口小部件,就像我们为message字段所做的那样。

字段数据

无论使用表单提交的数据是什么,一旦通过调用is_valid()(和is_valid())已经成功验证了返回的True已验证的表单数据将位于form.cleaned_data字典中。 这些数据已经很好地转换成Python类型。

注意

您仍然可以直接从request.POST访问未经验证的数据,但验证的数据更好。

In the contact form example above, cc_myself will be a boolean value. 同样,诸如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发送电子邮件的更多信息,请参阅Sending email

一些字段类型需要一些额外的处理。 例如,使用表单上传的文件需要以不同的方式处理(它们可以从request.FILES而不是request.POST中检索到)。 有关如何使用表单处理文件上传的详细信息,请参见Binding uploaded files to a form

使用表单模板

您只需将表单实例放置到模板上下文中即可。 So if your form is called form in the context, {{ form }} will render its <label> and <input> elements appropriately.

表单渲染选项

额外的表格模板家具

不要忘记,表单的输出不包含周围的<form>标记或表单的submit控件。 你将不得不自己提供这些。

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

  • {{ form.as_table }} will render them as table cells wrapped in <tr> tags
  • {{ 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>,由相应的标签标签引用。 这对于确保表单可以通过屏幕阅读器软件等辅助技术很重要。 您也可以customize the way in which labels and ids are generated

有关详细信息,请参阅Outputting forms as 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>

该列表有一个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>

有关错误,样式和使用模板中的表单属性的更多信息,请参阅The Forms API

遍历表单的字段

If you’re using the same HTML for each of your form fields, you can reduce duplicate code by looping through each field in turn using a {% 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.label }}
该字段的标签,例如电子邮件 地址
{{ field.label_tag }}

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

<label for="id_email">Email address:</label>
{{ field.id_for_label }}
将用于此字段的标识(上例中的id_email)。 如果您正在手动构建标签,则可能需要使用它来代替label_tag 例如,如果您有一些内嵌JavaScript并希望避免对字段的ID进行硬编码,这也很有用。
{{ field.value }}
该字段的值。 someone@example.com
{{ field.html_name }}
将在输入元素的名称字段中使用的字段的名称。 如果已经设置,则将这个表单前缀考虑在内。
{{ field.help_text }}
任何与该字段关联的帮助文本。
{{ field.errors }}
Outputs a <ul class="errorlist"> containing any validation errors corresponding to this field. 您可以在 字段中使用{% 错误 .errors tt> %}循环。 在这种情况下,循环中的每个对象都是包含错误消息的简单字符串。
{{ field.is_hidden }}
如果表单字段是隐藏字段,则该属性为True,否则为False 它作为一个模板变量并不是特别有用,但是在条件测试中可能是有用的,例如:
{% if field.is_hidden %}
   {# Do something special #}
{% endif %}
{{ field.field }}
来自表示这个BoundField包装的表单类的Field实例。 You can use it to access Field attributes, e.g. {{ char_field.field.max_length }}.

也可以看看

有关属性和方法的完整列表,请参见BoundField

循环隐藏和可见的字段

如果你是在模板中手动布局表单,而不是依靠Django的默认表单布局,你可能需要处理&lt; input type =“hidden”&gt ;字段与非隐藏字段不同。 例如,由于隐藏字段不显示任何内容,因此将错误消息“放在”字段旁边可能会导致用户混淆,因此应该以不同的方式处理这些字段的错误。

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