使用表单

关于此文档

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

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

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

HTML表单

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

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

以及它的 <input> 元素,窗体必须指定两个内容:

  • 在哪里? :应将与用户输入相对应的数据返回到的URL

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

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

它还告诉浏览器表单数据应发送到 <form>action 属性- /admin/ -并且应该使用 method 属性- post .

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

GET and POST

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

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

GET 相反,将提交的数据打包成一个字符串,并使用这个字符串组成一个URL。URL包含必须发送数据的地址,以及数据键和值。如果您在django文档中进行搜索,您可以看到这一点,该文档将生成表单的URL。 https://docs.djangoproject.com/search/?q=forms&release=1 .

GETPOST 通常用于不同的目的。

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

GET 也不适合密码表单,因为密码将出现在URL中,因此也会出现在浏览器历史记录和服务器日志中,所有这些都是以纯文本形式出现的。它既不适合大数据量,也不适合二进制数据,如图像。使用以下功能的Web应用程序 GET 对管理表单的请求是一个安全风险:攻击者很容易模仿表单的请求来访问系统的敏感部分。 POST ,再加上其他保护措施,如Django CSRF protection 提供对访问的更多控制。

另一方面, GET 适用于Web搜索表单之类的内容,因为表示 GET 请求可以很容易地进行书签、共享或重新提交。

Django 在形式上的作用

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

Django的表单功能可以简化和自动化这项工作的大部分,并且可以比大多数程序员自己编写的代码更安全地完成这项工作。

Django处理表格中涉及的三个不同部分的工作:

  • 准备和重新构造数据以准备渲染

  • 为数据创建HTML表单

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

它是 可能的 编写手动完成所有这些工作的代码,但Django可以为您处理所有这些工作。

Django形式

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

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

丹乔 Form

这个系统的核心部件是Django的 Form 类。与Django模型描述对象的逻辑结构、行为以及它的各个部分对我们的表示方式基本相同,一个 Form 类描述窗体并确定其工作方式和显示方式。

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

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

这会告诉浏览器将表单数据返回到URL /your-name/ ,使用 POST 方法。它将显示一个文本字段,标记为“您的姓名:”,以及一个标记为“确定”的按钮。如果模板上下文包含 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 . 这有两件事。它提出了一个 maxlength="100" 浅谈HTML <input> (因此,浏览器应防止用户首先输入的字符数超过该数目)。这也意味着当Django从浏览器接收到表单时,它将验证数据的长度。

A 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>

表单的所有字段及其属性都将从此处解包到HTML标记中。 {{{{ form }}}} 使用Django的模板语言。

表单和跨站点请求伪造保护

Django提供易于使用的 protection against Cross Site Request Forgeries . 通过提交表单时 POST 启用CSRF保护后,必须使用 csrf_token 模板标记,如前面的示例所示。但是,由于CSRF保护不直接绑定到模板中的表单,因此本文档中的以下示例中省略了此标记。

HTML5输入类型和浏览器验证

如果你的表格包括 URLField ,一个 EmailField 或任何整数字段类型,Django将使用 urlemailnumber HTML5输入类型。默认情况下,浏览器可以对这些字段应用自己的验证,这可能比Django的验证更严格。如果要禁用此行为,请设置 novalidate 属性 form 标记或指定字段上的其他小部件,如 TextInput .

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

这就是您开始所需要的全部内容,但是表单框架让您更容易掌握。一旦您了解了上面描述的流程的基础知识,就应该准备好了解表单系统的其他特性,并准备好进一步了解底层机器。

关于Django的更多信息 Form

所有窗体类都创建为 django.forms.Formdjango.forms.ModelForm . 你可以想到 ModelForm 作为 Form . FormModelForm 实际上从(私有)继承公共功能 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 ,A CharField . 在这种情况下,我们的表单有四个字段: subjectmessagesendercc_myself . CharFieldEmailFieldBooleanField 只有三种可用的字段类型;完整的列表可以在 表单字段 .

小部件

每个表单域都有一个对应的 Widget class ,然后对应于HTML表单小部件,如 <input type="text"> .

在大多数情况下,字段将有一个合理的默认小部件。例如,默认情况下, CharField 将有一个 TextInput 小部件,生成 <input type="text"> 在HTML中。如果你需要 <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 控制。你必须自己提供这些。

可重用表单模板

呈现表单时的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 了解更多详细信息。

可重复使用的字段组模板

New in Django 5.0.

每个字段都可以作为表单的一个属性使用 {{ 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" 表单样式。

可以通过设置来自定义默认模板 field_template_name 在您的项目级 FORM_RENDERER **

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)

手动呈现字段

对场渲染进行更精细的控制也是可能的。这很可能是在自定义字段模板中,以允许模板编写一次,然后对每个字段重复使用。但是,也可以从表单上的field属性直接访问它。例如:

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

表单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 }}

输出A <ul class="errorlist"> 包含与此字段对应的任何验证错误。您可以使用 {{% for error in field.errors %}} 循环。在这种情况下,循环中的每个对象都是一个包含错误消息的字符串。

{{ field.field }}

这个 Field 来自窗体类的实例 BoundField 包。你可以用它来访问 Field 属性,例如 {{{{ char_field.field.max_length }}}} .

{{ field.help_text }}

与字段关联的任何帮助文本。

{{ field.html_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>

{{ field.use_fieldset }}

此属性为 True 如果表单域的小部件包含多个输入,这些输入应该在语义上分组到 <fieldset> 使用一个 <legend> 以提高可访问性。模板中使用的示例:

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

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

进一步主题

这涵盖了基础知识,但是表单可以做得更多:

参见

The Forms Reference

包括完整的API引用,包括表单字段、表单小部件以及表单和字段验证。