除非你计划建立一个只发布内容,不接受访问者输入的网站和应用程序,否则你需要理解和使用表单。
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引用,包括表单字段、表单小部件以及表单和字段验证。
7月 22, 2024