Django表单的基本使用

本文最后更新于:2 年前

若想搭建一个具备接收访问者输入功能的网站,必须理解表单,并在搭建网站时使用表单

Form表单概述

为什么有了模型,还要自己创建表单类呢?原因之一是模型中有一些字段你不需要用户从前端输入数据,或者需要用户额外输入一些非模型字段的数据。Form表单精确控制了这些行为,相当于你在用户HTML表单输入框和Django模型之间的中间件。

Form表单的功能

  • 自动生成HTML表单元素

  • 检查表单数据的合法性

  • 如果验证错误,重新显示表单(数据不会重置)

  • 数据类型转换(字符类型的数据转换成相应的Python类型)

定义表单的方式

Django 常以 HTML 文件作为模板文件,在 HTML 文件中定义表单是实现网站交互式工作的经典方式,此外Django 也支持利用 Form 类在 Python 文件中定义表单。

Form类的常用字段

字段的通用参数

表单数据来源

表单验证

clean() 方法

调用表单实例的 clean() 方法可以对表单绑定的数据进行验证,既根据定义表单类字段时为字段设置的约束条件,如 required、unique 验证表单字段是否存在空值或重复,也可以根据字段类型验证传入的数据类型是否匹配。

若表单验证成功,验证后的数据会被存储到表单实例的 cleaned_data 属性(cleaned_data属性为 dict 类型,该属性以表单字段名为键,以字段值为元素的值)中;若验证失败则抛出ValidationError异常。

在对表单进行处理之前先对表单数据进行验证,在程序中生成并使用验证后的数据有助于提高程序的健壮性

需要注意,表单验证完毕后,程序仍能从 request.POST 中访问到用户提交对的未验证的数据,但程序中最好使用存储在 cleaned_data中 已验证的数据。

is_valid() 方法

除了直接调用 clean() 方法对触发表单验证外,还可以调用表单实例的 is_valid() 方法,或访问表单实例的errors属性间接触发表单验证。

  • 若 is_valid() 返回True,表单数据通过验证,并被存储到表单的cleaned_data属性中。
  • 若 is_valid() 返回 False,说明表单数据未能通过验证。表单实例的errors属性在第一次被访问时可以触发表单验证。

实例化、处理和渲染表单

Django表单从定义到呈现经历的流程

在模板中渲染表单实例与渲染模型实例基本相同,但存在一些关键性的差异:模型实例若为空,模板对它进行的处理没有意义;但表单实例为空时,模板需要将其渲染为空表单,以便用户填充数据

基于以上差异,处理模型时,一般从数据库中获取实例;在代码中处理表单时,则需在视图中实例化表单。我们可以在视图中实例化一个空表单,也可以预先准备数据,实例化一个非空表单

1. 编写表单类

我们可以通过 Django 提供的 Form 类来自用生成页面上的表单,不再需要手动在HTML中编写,最终由框架将 Django 表单渲染成真正的HTML元素。

在当前 app 内新建一个forms.py文件(这个套路是Django的惯用手法,就像views.pymodels.py等等),然后输入下面的内容:

1
2
3
4
5
from django import forms

class AddForm(forms.Form):
num1 = forms.IntegerField(label="数字1",max_length=100)
num2 = forms.IntegerField(label="数字2",max_length=100)

说明:

  • 提前导入forms模块
  • 所有的表单类都要继承forms.Form类
  • label 用于设置说明标签,相当于 name 属性。
  • 每个表单字段都有自己的字段类型比如 CharField,它们分别对应一种HTML语言中的 <form> 元素中的表单元素。这一点和 Django 模型系统的设计非常相似。
  • max_length 限制最大长度为100。它同时起到两个作用,一是在浏览器页面限制用户输入不可超过100个字符,二是在后端服务器验证用户输入的长度不可超过100。

2. 视图处理

需要在视图中,实例化我们编写好的表单类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@csrf_exempt
def add_form(request):
# 如果form通过POST方法发送数据
if request.method == 'POST':
form = AddForm(request.POST)
# 验证数据是否合法
if form.is_valid():
print(form.changed_data)
num1 = int(form.cleaned_data['num1'])
num2 = int(form.cleaned_data['num2'])
# print(form.clean_data)
sum = num1 + num2
return render(request, 'addform.html', {"sum": sum})
else:
# 实例化空表单
form = AddForm
# 将空表单实例传入模板进行渲染
return render(request, 'addform.html', {"form": form})

处理表单时只会涉及 GET 和 POST 两种 HTTP 请求方式,以上视图被调用后首先判断请求方式,若接收了GET请求,则创建一个空的表单实例,将其放在模板上下文中进行渲染;若接收了POST请求,则使用POST请求中携带的数据实例化一个非空表单。

要点:

  • 对于 GET 方法请求页面时,返回空的表单,让用户可以填入数据;
  • 对于 POST 方法,接收表单数据,并验证;
  • 如果数据合法,按照正常业务逻辑继续执行下去;
  • 如果不合法,返回一个包含先前数据的表单给前端页面,方便用户修改。
  • 千万要注意最后一行return语句的缩进位置。
  • 每个Django表单的实例都有一个内置的 is_valid()方法,用来验证接收的数据是否合法。如果所有数据都合法,那么该方法将返回 True,并将所有的表单数据转存到它的一个叫做 cleaned_data的属性中,该属性是一个字典类型数据。若验证失败则抛出ValidationError异常。

3. 模板处理

在 Django 的模板中,我们只需要按下面处理,就可以得到完整的HTML页面:

1
2
3
4
5
6
7
{% csrf_token %}
<form action="/users/addform/ " method="post">
{{form}}
<hr/>
<input type="submit" value="计算求和"/>
<h3>相加后的结果为:{{sum}}</h3>
</form>

要点:

  • <form>...</form> 标签要自己写;
  • 使用 POST 的方法时,必须添加 {% csrf_token %} 标签,用于处理csrf安全机制;
  • {{ form }} 代表Django为你生成其它所有的form标签元素,也就是我们上面做的事情;
  • 提交按钮需要手动添加!

4. 效果测试

表单模板

表单渲染格式

Django 在将表单字段渲染为 HTML 代码时不会为其添加标签,开发人员需要主动在模板中的表单外添加标签。若表单中的每个字段都需要添加标签,开发人员需遍历表单,逐个获取表单字段并添加标签,此项操作非常繁琐。

Django 提供了3个可选的表单渲染选项:as_tableas_pas_ul,开发人员可利用它们简捷地实现上述功能。

  • {{ form.as_table }} 将表单渲染成一个表格元素,每个输入框作为一个 <tr> 标签。
  • {{ form.as_p }} 将表单的每个输入框包裹在一个 <p> 标签内 tags。
  • {{ form.as_ul }} 将表单渲染成一个列表元素,每个输入框作为一个 <li> 标签。

注意:你要自己手动编写 <table><ul> 标签。

手动渲染表单字段

直接 {{ form }} 虽然好,啥都不用操心,但是往往并不是你想要的,比如你要使用CSS和JS,或者字段之间添加其他元素,这些都需要对表单内的 input 元素进行额外控制,那怎么办呢?配合模板标签手动渲染字段就可以了。

根据模型创建表单

如果需要构建一个由数据库驱动的应用程序,那么很可能会用到与 Django 模型密切相关的表单。例如我们在前面的章节中定义了商品模型类 Goods,并实现了基于表单类的商品管理,但为表单定义字段类型是多余的,因为表单可以直接使用已在模型中定义的字段。

自定义模型表单类

自定义的模型表单类时需要继承 ModelForm类,ModelForm定义在 django.forms 模块中。

1
2
3
4
5
6
7
8
9
10
11
12
from django.forms import ModelForm
from django import forms
from goods.models import Goods

class GoodForm(ModelForm):
# 自定义表单字段
# number = forms.IntegerField(label="数量")
class Meta:
# 对应模型类
model = Goods
# 对应模型类中字段
fields = ['name','weight','price','goodsdesc']

若在表单类中重新定义了模型中的字段,表单字段将会覆盖模型字段。

模型表单类的字段

在定义基于模型的表单时可以选择模型中的部分字段作为表单字段,或排除部分不需要在表单中出现的模型字段

无论使用哪一种方式选择字段,字段会按模型中定义的顺序在表单中出现,ManyToManyField 会排在最后。


基于模型生成表单类时,Django会按照表单中内部类 Meta 中的 fields 属性指定的顺序为每个指定的模型字段定义一个表单字段:

模型字段 表单字段
AutoField 不呈现在表单中
BooleanField 若该字段的参数null=True,该字段对应NullBooleanField,否则对应BooleanField。
CharField CharField 将 max_length 设置为模型字段的 max_length ,如果模型中设置了 null=True ,会将 empty_value 设置为 None 。
DateField DateField
DateTimeField DateTimeField
DecimalField DecimalField
EmailField EmailField
FileField FileField
ForeignKey ModelChoiceField
ImageField ImageField
IntegerField IntegerField
ManyToManyField ModelMultipleChoiceField
SmallIntegerField IntegerField
TextField CharField,且设置 widget=forms.Textarea

在利用模型定义表单类时,表单字段参数的取值会受到模型字段参数取值的影响:

使用模型表单类

模型表单类的使用与表单类基本相同,创建表单模型类GoodForm的实例。

1
form = GoodForm()		# 创建空表单

我们可以在实例化时为 GoodForm 传入模型实例,获得一个填充了数据的表单:

下面利用前面自定义的表单类,编写视图函数,修改商品信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用Django表单渲染商品信息到页面
@csrf_exempt
def form_goods(request):
# 如果form通过POST方法发送数据
if request.method == 'POST':
form = GoodForm(request.POST)
# 验证数据是否合法
if form.is_valid():
good = Goods.objects.filter(id=1)
# 修改商品信息
good.update(name = form.cleaned_data['name'], weight=form.cleaned_data['weight'],price=form.cleaned_data['price'],goodsdesc = form.cleaned_data['goodsdesc'])
return HttpResponseRedirect("/goods/detail/1/")
else:
# 实例化表单
good = Goods.objects.get(id=1)
form = GoodForm(instance=good)
# 将空表单实例传入模板进行渲染
return render(request, 'goodsform.html', {"form": form})

模板页面渲染表单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<form action="/goods/goodsform/" method="post">
<table>
{% for field in form %}
<tr>
<td>
{{field.label_tag}}
</td>
<td>
{{field}}
</td>
</tr>
{% endfor %}
</table>
<br>
<input type="submit" value="确认修改" />
</form>

配置路由:

1
2
re_path(r'^goods/detail/(?P<goods_id>\d+)/$', goods_views.detailview.as_view(), name="goodsdetail"),
path('goods/goodsform/', goods_views.form_goods),

最终页面测试效果: