除非你计划建立一个只发布内容,不接受访问者输入的网站和应用程序,否则你需要理解和使用表单。
Django提供了一系列工具和库,帮助您构建表单以接受网站访问者的输入,然后处理和响应输入。
在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¶GET 和 POST 是处理表单时唯一要使用的HTTP方法。
Django的登录表单返回时使用 POST 方法,其中浏览器将表单数据打包,对其进行编码以便传输,将其发送到服务器,然后接收其响应。
GET 相反,将提交的数据打包成一个字符串,并使用这个字符串组成一个URL。URL包含必须发送数据的地址,以及数据键和值。如果您在django文档中进行搜索,您可以看到这一点,该文档将生成表单的URL。 https://docs.djangoproject.com/search/?q=forms&release=1 .
GET 和 POST 通常用于不同的目的。
任何可用于更改系统状态的请求(例如,对数据库进行更改的请求)都应使用 POST . GET 应仅用于不影响系统状态的请求。
GET 也不适合密码表单,因为密码将出现在URL中,因此也会出现在浏览器历史记录和服务器日志中,所有这些都是以纯文本形式出现的。它既不适合大数据量,也不适合二进制数据,如图像。使用以下功能的Web应用程序 GET 对管理表单的请求是一个安全风险:攻击者很容易模仿表单的请求来访问系统的敏感部分。 POST ,再加上其他保护措施,如Django CSRF protection 提供对访问的更多控制。
另一方面, GET 适用于Web搜索表单之类的内容,因为表示 GET 请求可以很容易地进行书签、共享或重新提交。
处理表单是一项复杂的业务。考虑Django的管理,其中可能需要准备多个不同类型的数据项以表单形式显示、呈现为HTML、使用方便的界面编辑、返回服务器、验证和清理,然后保存或传递以进行进一步处理。
Django的表单功能可以简化和自动化这项工作的大部分,并且可以比大多数程序员自己编写的代码更安全地完成这项工作。
Django处理表格中涉及的三个不同部分的工作:
准备和重新构造数据以准备渲染
为数据创建HTML表单
接收和处理客户提交的表格和数据
它是 可能的 编写手动完成所有这些工作的代码,但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中渲染对象时,我们通常:
在视图中获取它(例如,从数据库中获取它)
将其传递到模板上下文
使用模板变量将其扩展为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 为我们做大部分工作要容易得多。
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 或任何integer字段类型,Django将使用 url , email 和 number HTML5输入类型。默认情况下,浏览器可以对这些字段应用自己的验证,这可能比Django的验证更严格。如果您想禁用此行为,请设置 novalidate 属性 form 标签,或在字段上指定不同的小部件,例如 TextInput 。
我们现在有一个正在工作的web表单,由Django描述 Form ,由视图处理,并呈现为HTML <form> .
这就是您开始所需要的全部内容,但是表单框架让您更容易掌握。一旦您了解了上面描述的流程的基础知识,就应该准备好了解表单系统的其他特性,并准备好进一步了解底层机器。
Form 班¶所有窗体类都创建为 django.forms.Form 或 django.forms.ModelForm . 你可以想到 ModelForm 作为 Form . Form 和 ModelForm 实际上从(私有)继承公共功能 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 . 在这种情况下,我们的表单有四个字段: subject , message , sender 和 cc_myself . CharField , EmailField 和 BooleanField 只有三种可用的字段类型;完整的列表可以在 表单字段 .
每个表单域都有一个对应的 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 将是布尔值。同样,如 IntegerField 和 FloatField 将值转换为python int 和 float 分别。
以下是如何在处理此表单的视图中处理表单数据:
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 了解更多详细信息。
每个字段都可以作为表单的一个属性使用 {{ 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 }}输出 <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 .
这涵盖了基础知识,但是表单可以做得更多:
参见
包括完整的API引用,包括表单字段、表单小部件以及表单和字段验证。
5月 28, 2025