Working with forms使用表格
除非你打算建立只发布内容的网站和应用程序,而不接受访问者的意见,你将需要了解和使用表单。
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 attribute-post
指定的HTTP
机制发送表单数据。
当<input type =“submit”value =“Log in”>
元素被触发时,数据将返回到/ admin /
。
GET和POST
GET
和POST
是处理表单时唯一使用的HTTP
方法。
使用POST
方法返回Django
的登录表单,其中浏览器将表单数据打包,对其进行传输,将其发送到服务器,然后接收其响应。
相比之下,GET
将提交的数据捆绑成一个字符串,并使用它来构成一个URL。 URL包含必须发送数据的地址以及数据键和值。如果您在Django文档中进行搜索,则可以看到此操作,该文档将生成https://docs.djangoproject.com/search/?q=forms&release=1
格式的URL
。
GET
和POST
通常用于不同的目的。
可用于更改系统状态的任何请求(例如,在数据库中进行更改的请求)都应使用POST
。GET
只能用于不影响系统状态的请求。
GET
也不适用于密码表单,因为密码将显示在URL
中,因此也将显示在浏览器历史记录和服务器日志中,均以纯文本形式显示。也不适用于大量数据,也不适用于二进制数据,如图像。对于管理表单使用GET
请求的Web
应用程序是一个安全风险:攻击者可以轻松地模仿表单的请求,以访问系统的敏感部分。POST
以及Django
的CSRF
保护等其他保护功能可以更好地控制访问。
另一方面,GET
适合于网页搜索表单,因为代表GET
请求的网址很容易被加入书签,共享或重新提交。
Django在表单中的角色
处理表格是一项复杂的业务。 考虑Django
的管理员,其中可能需要准备几种不同类型的数据,以便以HTML
形式显示,使用方便的界面编辑,返回到服务器,验证和清理,然后保存或传递 进一步处理。
Django
的表单功能可以简化和自动化这项工作的大部分,并且还可以比大多数程序员能够在自己编写的代码中做得更安全。
Django
处理涉及表单的三个不同部分的工作:
- 准备和重组数据,使其准备渲染
- 为数据创建HTML表单
- 接收和处理客户提交的表格和数据
可以手动编写所有这些代码,但Django
可以为您处理所有这些代码。
Django
中的表单
我们简要描述了HTML
表单,但HTML <form>
只是所需机器的一部分。
在Web应用程序的上下文中,“form”
可以指代HTML <form>
,或者指向生成它的Django表单,或者提交给返回的结构化数据或者端到端的工作集合的这些部分。
Django
表单类
这个组件系统的核心是Django
的Form
类。以与Django
模型描述对象的逻辑结构,其行为以及其部件对我们的方式相同的方式,Form
类描述一个表单并确定它的工作原理和出现方式。
以类似于模型类的字段映射到数据库字段的方式,表单类的字段映射到HTML
表单<input>
元素。 (ModelForm通过Form将模型类的字段映射到HTML表单<input>
元素;这是Django管理员基于的)。
表格的领域本身就是课程;他们管理表单数据,并在提交表单时执行验证。 DateField
和FileField
处理非常不同种类的数据,并且必须与其做不同的事情。
在浏览器中将表单字段表示为HTML“widget”
- 一个用户界面机器。每个字段类型都有一个适当的默认Widget
类,但是这些类可以根据需要重写。
实例化,处理和呈现表单
在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>
这告诉浏览器使用POST
方法将表单数据返回到URL / your-name /
。它将显示标有“您的姓名”的文本字段和标有“确定”的按钮。如果模板上下文包含一个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)
这定义了一个具有单个字段(your_name)
的Form
类。 我们已经在该领域应用了一个人性化的标签,当它被渲染时,它将显示在<label>
中(尽管在这种情况下,我们指定的标签实际上是相同的,如果我们忽略它,将自动生成) 。
该字段的最大允许长度由max_length
定义。 这有两件事情。 它在HTML <input>
上放置一个maxlength =“100”
(所以浏览器应该防止用户首先输入超过该数量的字符)。 这也意味着当Django
从浏览器接收到表单时,它将验证数据的长度。
一个Form
实例有一个is_valid()
方法,它运行所有字段的验证例程。 当调用此方法时,如果所有字段都包含有效数据,它将:
- 返回True
- 将表单的数据放在clean_data属性中。
整个表单,当第一次呈现时,将如下所示:
<label for="your_name">Your name: </label>
<input id="your_name" type="text" name="your_name" maxlength="100" required />
请注意,它不包括<form>
标签或提交按钮。 我们必须在模板中提供自己。
The view
发送回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请求到达这个视图,它将创建一个空的表单实例,并将其放在要渲染的模板上下文中。 这是我们第一次访问网址时可能会发生的情况。
如果使用POST请求提交表单,该视图将再次创建一个表单实例,并使用请求中的数据填充它:form = NameForm(request.POST)
这称为“将数据绑定到表单”(现在是 一个绑定的形式)。
我们调用窗体的is_valid()
方法; 如果不是True
,我们将返回到表单的模板。 这一次,表单不再是空的(未绑定),所以HTML
表单将填充以前提交的数据,在那里可以根据需要进行编辑和更正。
如果is_valid()
为True
,我们现在可以在其clean_data
属性中找到所有验证的表单数据。 在将HTTP重定向发送到浏览器之前,我们可以使用这些数据更新数据库或进行其他处理,告诉它下一步要去哪里。
模板
我们不需要在我们的name.html
模板中做很多。 最简单的例子是:
<form action="/your-name/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
所有表单的字段及其属性将由Django
的模板语言从{{form}}
解压缩成HTML标记。
表单和跨站点请求伪造保护
Django提供易于使用的保护,防止跨站点请求伪造。通过POST提交表单通过启用CSRF保护,您必须使用csrf_token模板标签,如上例所示。但是,由于CSRF保护不直接与模板中的表单相关联,因此本文档中的以下示例将省略此标记。
HTML5输入类型和浏览器验证
如果您的表单包含URLField,EmailField或任何整数字段类型,Django将使用URL,电子邮件和数字的HTML5输入类型.默认情况下,浏览器可以对这些字段应用自己的验证,这可能比Django的验证更严格。如果要禁用此行为,请在表单标签上设置novalidate属性,或在字段上指定其他小部件,如TextInput。
我们现在有一个工作的Web表单,由Django表单描述,由视图处理,并呈现为HTML <form>
。
这就是您需要开始的所有工作,但是窗体框架可以让您更加方便。 一旦您了解上述过程的基础知识,您应该准备好了解表单系统的其他功能,并准备更多地了解底层机械。
更多关于Django表单类
所有表单类都创建为django.forms.Form
的子类,包括您在Django
管理员中遇到的ModelForm
。
模型和形式
事实上,如果您的表单将用于直接添加或编辑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,sender和cc_myself。 CharField,EmailField和BooleanField只是三种可用的字段类型; 在表单字段中可以找到完整的列表。
Widget小部件
每个表单字段都有一个相应的Widget类,它们又对应于HTML表单窗口小部件,例如<input type =“text”>
。
在大多数情况下,该字段将具有明智的默认小部件。 例如,默认情况下,CharField
将有一个TextInput
小部件,它在HTML中产生一个<input type =“text”>
。 如果您需要<textarea>
,则可以在定义表单字段时指定相应的小部件,就像我们对消息字段所做的那样。
Field data
无论使用表单提交的数据,一旦通过调用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/')
某些字段类型需要一些额外的处理。 例如,使用表单上传的文件需要进行不同的处理(可以从request.FILES
而不是request.POST
中检索)。 有关如何使用表单处理文件上传的详细信息,请参阅将上传的文件绑定到表单。
使用表单模板
所有您需要做的是将表单变成模板是将表单实例放入模板上下文中。 因此,如果您的表单在上下文中被称为表单,{{form}}
将适当地呈现其<label>
和<input>
元素。
Form rendering options表单呈现选项
Additional form template furniture附加表格模板家具
不要忘记,表单的输出不包括周围的
<form>
标签,或表单的提交控件。 你必须自己提供这些。
对于<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" 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>
,由附带的标签标签引用。 这对于确保窗体可以访问辅助技术(如屏幕阅读器软件)非常重要。 您还可以自定义生成标签和ids的方式。
手动渲染字段
我们不必让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_tag()
生成完整的<label>
元素。 例如:
<div class="fieldWrapper">
{{ form.subject.errors }}
{{ form.subject.label_tag }}
{{ form.subject }}
</div>
Rendering form error messages呈现表单错误消息
当然,这种灵活性的价格更多的是工作。 到目前为止,我们不用担心如何显示表单错误,因为这是为我们照顾的。 在这个例子中,我们必须确保我们处理每个字段的任何错误以及整个表单的任何错误。 在表单顶部注明{{form.non_field_errors}}
,并对每个字段的错误进行模板查找。
使用{{form.name_of_field.errors}}
显示表单错误列表,呈现为无序列表。 这可能看起来像:
<ul class="errorlist">
<li>Sender is required.</li>
</ul>
该列表具有CSS类的错误列表,以允许您对其外观进行风格化。 如果您想进一步自定义错误的显示,您可以通过循环来进行此操作:
{% if form.subject.errors %}
<ol>
{% for error in form.subject.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
将使用非额外类的非场来呈现非字段错误(
和/
或在使用form.as_p())
时在窗体顶部呈现的隐藏字段错误,以帮助将其与场特定错误区分开来。 例如,{{form.non_field_errors}}
将如下所示:
<ul class="errorlist nonfield">
<li>Generic validation error</li>
</ul>
Looping over the form’s fields
Reusable form templates