Django-11:Forms组件
Forms组件
一、引子
需求:
-
书写一个具有注册功能的WEB页面,利用form表单提交数据,并在后端判断用户名和密码是否符合条件,如果不符合条件,需要将提示信息展示到前端页面上。
1.用户名汇总不能含有“孤儿”。
2.密码不能少于4位。
一、思路
- 前端页面上可利用行内标签的特性,用来占位。
- 定义一个字典,该字典的key为uname_error和passwd_error,默认value为空。
- 后端接收前端提交的数据,并进行判断,如不符合需求,则给key添加value
- 通过rander(,locals())以及模板语法,给span标签添加内容。
代码执行流程:
1、当第一次访问时,由于span标签没有内容,所以不在前端页面做展示。
2、第一次提交数据时,后端定义字典的key可能会有值。
3、如果数据不符合要求,则字典对应key的value则变成报错信息。
4、由于form表单提交之后,页面刷新,会再次发送GET请求。
5、而此时span标签就可以展示对应的报错信息。
二、代码
-
视图层
def ab_forms(request): # 该字典默认为空,对应的,初次访问页面的时候,是没有报错提示信息的。 back_dic = {'username':' ','password':' '} if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') if '孤儿' in username: back_dic['username'] = '违规用户名' if len(password) < 4: back_dic['password'] = '密码位数最小4位' return render(request,'ab_forms.html',locals())
-
模板层
<div class="container"> <div class="row"> <div class="col-lg-6"> <h1>注册</h1> <form action="" method="post"> <p> <label for="">用户名:<input type="text" class="form-control" name="username"></label> <span style="color: red">{{ back_dic.username }}</span> </p> <p> <label for="">密码:<input type="text" class="form-control" name="password"></label> <span style="color: red">{{ back_dic.password }}</span> </p> <p><input type="submit" class="btn btn-success"></p> </form> </div> </div> </div>
三、效果展示

根据现象进行总结:
1、再次刷新页面,提示信息仍然存在,因为dict中的value还是存在的,所以span标签仍然有数据
2、提交之后,由于页面刷新,所以之前输入的数据也都没有了,体验不是很好。
1.1 forms组件
上述案例,我们自己做的事情:
- 手动书写前端获取用户数据的html代码(渲染html代码)
- 后端对用户数据进行校验(校验数据)
- 对不符合要求的数据进行前端提示(展示提示信息)
forms组件可以帮我们做到的事情:
- 渲染html代码
- 校验数据
- 展示提示信息
附:
为什么数据校验非要去后端,不能在前端利用js直接完成呢?
- 前端的校验是弱不禁风的,可以直接修改,或者利用爬虫程序绕过前端页面直接朝后端提交数据。
- 购物网站场景下,选取了货物之后,会计算一个价格发送给后端,如果后端不做价格的校验,那么会造成很大损失。
二、forms组件使用
步骤:
- 视图层定义类,属性类似于模板层的字段。
- 表单提交的数据,会和对应的“字段”规则进行校验,通过与不通过的“字段”值都会封装到对应方法中。
- 有了错误提示信息之后,再次使用组件所提供的方法,来渲染标签。
一、视图层校验代码示例
'''
视图层
'''
from django import forms
class TestForm(forms.Form):
username = forms.CharField(min_length=3,max_length=8)
# username字符串类型最小3位最大8位
password = forms.CharField(min_length=3,max_length=8)
# password字符串类型最小3位最大8位
email = forms.EmailField()
# email字段必须符合邮箱格式 xxx@xx.com
二、如何校验
本小结主要介绍上面定义的TestForm类,如何发挥它的校验作用,由于是直接使用,所以并不是通过前后端交互的方式来展示效果,而是通过测试文件。
- 测试文件除了django内部的test.py文件以外,Pycharm内部也提供的有测试环境,点击右下角的<Python console>,本小节的代码,都在这里进行测试。
基本方法:
-
1、将待校验的数据传入
In[2]: from app01.views import * In[3]: forms_obj = TestForm({'username':'ly','password':'123','email':'123@'})
-
2、判断数据是否合法
In[4]: forms_obj.is_valid() Out[4]: False ''' 该方法只有在所有的数据全部合法的情况下才会返回True '''
-
3、查看所有校验通过的数据
In[5]: forms_obj.cleaned_data Out[5]: {'password': '123'} ''' 实例化的时候,只有密码符合了TestForm类中的对应规则 '''
-
4、查看所有不符合校验规则,以及不符合的原因
In[6]:forms_obj.errors Out[6]: {'username': ['Ensure this value has at least 3 characters (it has 2).'], 'email': ['Enter a valid email address.']} # 字典key的value,之所以是列表的形式,是因为报错的信息,可能不止一条,比如不满足长度的同时,还包含了特殊字符或敏感词。
注意:
- 校验数据的时候,默认情况下数据可以多传但是绝不可能少传。
校验原理:
- 待校验数据的key,与类中的字段名进行匹配,如果相同,则将对应的value与类中的字段规则做校验,如果合法就放到cleaned_data,如果不合法则放到errors。
- 在多传值的情况下,不管是cleaded_data还是errors,都不会放进去。
- 当类中字段没有设置可为空时,少传则会放到errors里面。
本小结介绍了,如何使用组件来校验组件是否符合要求,如何展示在前端(渲染)详情见2.1章节
复杂的校验规则(如正则)等,在后续章节中,会陆续介绍。
2.1 渲染标签的几种方式
本章节将分为三个小章节来介绍三种不同的渲染方式。
- 三种方式各有各的好,觉得封装程度太高,留给自己二次开发的空间太小,那么就采用其他方式。
2.1.1 as_p
第一种:
-
forms组件实例化的对象中,有一个方法名叫as_p,在模板语法中使用可以渲染出表单。
但是,没有提交按钮
'''
视图层
'''
from django import forms
class TestForm(forms.Form):
username = forms.CharField(min_length=3, max_length=8)
password = forms.CharField(min_length=3, max_length=8)
email = forms.EmailField()
def my_forms(request):
# 1.先产生一个空对象
forms_obj = TestForm()
# 2.直接将该空对象传递给html页面
return render(request,'test_forms.html',locals())
<!--模板层-->
<h1>注册</h1>
<form action="" method="post">
{{ forms_obj.as_p }}
</form>
效果如下图:

现象:
- 可以看到,as_p 最终是渲染出了P标签,然后里面包裹着label标签和input标签
'''
除了as_p以外,还有as_ul as_table 等
'''
as_ul : li标签
as_table : input框都在一行,类似于表头。
2.1.2 .属性
第二种:
- 组件类实例化的对象,可以通过 .属性的方式,来渲染出input标签
<h1>注册</h1>
<form action="" method="post">
{{ forms_obj.username }}
</form>
<!--
这里的username,为TestForm类中的,username属性。
-->
效果展示:

现象:
- 虽然可以渲染出标签,但是输入框的前面,没有数据了,用户看到了不知道这里应该填写什么。
解决办法:
'''
forms_obj.username可以拿到Input表单,而这个表单中又有一个名为label的属性,这个属性的值就是as_p中展示出来的值,默认格式为TestForm类中的属性名首字母大写。
'''
<form action="" method="post">
{{ forms_obj.username.label }} {{ forms_obj.username }}
# 提示信息 输入框
</form>
-
此时就会遇到一个问题,如果不想要Username,觉得在页面上不好看,想换成中文的“用户名”,该如何操作呢?
''' 如何修改? 自己设置label属性的值,不要默认的。 ''' # views.py class MyForm(forms.Form): username = forms.CharField(min_length=3,max_length=8,label='用户名')
效果如下图:
2.2.3 for循环
先来看看forms_obj循环出来的是什么?
<form action="" method="post">
{% for foo in forms_obj %}
<p>{{ foo }}</p>
{% endfor %}
</form>

现象:
-
可以看到foo在每次循环中,就等同于forms_obj.username、forms_obj.password、forms_obj.email。
-
那么我们想要获取到输入框前面的文本提示数据时,只需要foo.label即可。
因为:
forms_obj.xxx = foo = input框
forms_obj.xxx.label = 提示信息所以:
foo.label = forms_obj.xxx.label = 提示信息
验证效果:
<form action="" method="post">
{% for foo in forms_obj %}
<p>{{ foo.label }}:{{ foo }}</p>
{% endfor %}
</form>
<img src="https://s2.loli.net/2023/02/18/iGT45oeVkn7FOL1.png" alt="image-20211223135633264" style="zoom:67%;" />
2.2 展示提示信息
在前面,介绍了如何定义“校验类”(TestForm)、一些常用的方法is_valid()、cleaned_data、errors,以及as_p等其他方式来渲染标签。
那么就差最后一步了,如何将提示信息展示在页面上,例:不符合邮箱格式、用户名不符合长度等。
本章节代码依旧和2、2.1章节一致.
from django import forms class TestForm(forms.Form): username = forms.CharField(min_length=3, max_length=8) password = forms.CharField(min_length=3, max_length=8) email = forms.EmailField() def forms_test(request): forms_obj = TestForm() return render(request,'forms_test.html',locals())
模板文件,由于组件并不会渲染出提交按钮,和form表单,所以需要进行补充:
<h1>注册</h1> <form action="" method="post" novalidate> <!--#novalidate取消浏览器自动校验--> {% for foo in forms_obj %} <p>{{ foo.label }}:{{ foo }}</p> {% endfor %} <button type="submit" class="btn btn-success">提交</button> </form>
渲染错误信息:
'''
2.0章节,我们知道了数据校验的格式为:TestForm({字典格式}) TestForm为自定义字段校验的类名
所以我们进行校验时代码是这样的 ↓↓↓
'''
forms_obj = TestForm()
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
email = request.POST.get('email')
forms_obj = MyForm({'username':username,'password':password,'email':email})
'''
是三个表单还好,可如果表单比较多,例如:调查问卷、在线考试测评这种。
那就会很麻烦。
'''
仔细考虑下:
1.request.POST.get() 这个get我们还在那里见过?dict.get()?
对,就是字典,所以我们就可以将request.POST 看做成字典
2.MyForm({}) 这里刚好需要的就是字典,那么不就可以 MyForm(request.POST)
3.如果将request.POST 当做字典进行数据校验时,校验类中的字段名,必须和前端HTML标签中表单的name属性相同。
解决办法:
- request.POST.get()这个get还在那里见过?dict.get()就是字典,所以我们可以把request.POST这个querydict对象看成是字典对象,随后作为参数,用于实例化form_obj对象。
最终代码为:
def forms_test(request):
# 首先 生成空对象
forms_obj = MyForm()
if request.method == 'POST':
# 校验数据
forms_obj = TestForm(requests.POST)
# 校验结果如何合法,则操作数据库
if forms_obj.is_valid():
# 输入的数据合法,保存数据库,代码略,此处为了实验方便,只是返回字符串。
return HttpResponse('OK,注册成功')
return render(request,'ab_forms.html',locals())
'''
foo为每个表单的输入框
.errors可以获取到所有错误信息的列表
如果不取索引为0的数据,那么在列表上渲染出来的格式就为ur>li,而非span标签
所以,为了不让它自动转换,这里可以errors.0,取索引第一位。
'''
<form action="" method="post" novalidate> #novalidate取消浏览器自动校验
{% for foo in forms_obj %}
<p>
{{ foo.label }}:{{ foo }}
<!--错误提示信息-->
<span style="color: red">{{ foo.errors.0 }}</span>
</p>
{% endfor %}
<input type="submit" class="btn btn-success">
</form>
展示效果:

2.2.1 取消浏览器校验
浏览器会根据我们的HTML标签,对表单进行简单的校验,例如当“用户名”输入框的长度太短,那么浏览器将无法提交,这样我们的错误信息就看不到效果,所以需要需要浏览器自动校验。
具体操作:
- 表单后跟上novalidate属性。
<form action="" method="post" novalidate>
<!--
novalidate 不校验
-->
2.2.2 提示信息自定义
在2.2章节中,实现了错误信息提示,但是却是英文的,给用户的体验不好,所以我们需要自定义提示信息。
自定义提示信息:
-
对于继承forms.Form类(如案例中的TestForm)的属性,添加error_messages属性,示例如下:
class TestForm(forms.Form): username = forms.CharField(min_length=3,max_length=8,label='用户名',error_messages={ 'min_length':'用户名最少3位', 'max_length':'用户名最多8位', 'required':'用户名不能为空', }) password = forms.CharField(min_length=3,max_length=8,label='密码',error_messages={ 'min_length':'密码最小3位', 'max_length':'密码最大8位', 'required':'密码不能为空', }) email = forms.EmailField(label='邮箱',error_messages={ 'invalid':'邮箱格式不正确', 'required':'邮箱不可为空', })
required:为空时自定义报错,如“用户名不能为空”。
min_length:最短长度,反义为max_length。
invalid:针对于邮箱格式问题,如:“邮箱格式不正确”。
默认为必填,选填以及身份证校验、手机号校验等,需要利用正则校验的,会在后续章节介绍。
三、钩子函数(HOOK)
在二章节中,可以发现只是简单的校验,并没有针对表单数据去做详细的校验,这个时候就需要利用钩子函数来进行处理
在forms组件中有两类钩子:
- 局部钩子:对单个字段的数据进行校验(如:用户名内是否含有敏感词。)
- 全局钩子:对多个字段的数据进行校验(如:密码的二次确认。)
局部钩子:
-
格式: clean_字段
from django import forms class TestForm(forms.Form): username = forms.CharField(....略....) ''' 其他字段略 ''' def clean_username(self): username = self.cleaned_data.get('username') if '嘿嘿嘿' in username: # 展示错误信息,格式:add_error(字段,错误信息) self.add_error('username','携带敏感、低俗词汇') return username
钩子函数书写在类的内部,如果是局部钩子那么格式就为clean_字段,例如clean_username。
钩子函数的原理就是,通过forms.CharField校验之后,form_obj对象自动执行钩子函数,随后可以获取数据,并对其进行各种校验,如正则等。
随后通过add_error函数,给对应的字段添加错误信息,如self.add_error(‘username’,‘携带敏感、低俗词汇’)
最后一步,将**“钩”过来的数据“返回”**。
最后一步的return是必须的,因为分析源码内是有变量接收的,所以必须要return。
全局钩子:
-
格式:clean
from django import forms class TestForm(forms.Form): password = forms.CharField(....略....) confirm_password = forms.CharField(....略....) def clean(self): password = self.cleaned_data.get('password') confirm_password = self.cleaned_data.get('confirm_password') if password != confirm_password: self.add_error('confirm_password','二次密码不一致') return self.cleaned_data
全局钩子直接clean即可,但是钩子函数结束时,由于拿的是多个字段的数据,所以这里直接return self.cleaned_data即可。
3.1 案例1-用户名校验
案例:用户名中不可以出现’嘿嘿嘿’
-
from django import forms class MyForm(forms.Form): username = forms.CharField(min_length=3, max_length=8, label='用户名', error_messages={ 'min_length': '用户名最少3位', 'max_length': '用户名最多8位', 'required': '用户名不能为空', }) def clean_username(self): # 获取到用户名 username_dict = self.cleaned_data.get('username') if '嘿嘿嘿' in username: # 展示错误信息,add_error(字段,错误信息) self.add_error('username','携带敏感、低俗词汇') # 将钩子函数钩去出来数据再放回去 return username
由于是做用户名校验,针对单个字段,所以选用局部钩子,函数名为clean_username。
然后cleaned_data获取对应字段的数据,格式为字典,然后有通过内置方法get(),取值。
随后通过add_error(field,error)对指定的字段,添加错误信息。
最后,将“钩”出来的数据,再“放”回去,所以要记得return。
附:
在获取数据时,为什么使用的是cleaned_data而不是errors呢,因为钩子函数是在第一道校验完成之后才会进入后续校验的,所以肯定是符合前面的校验,自然数据就都在cleaned_data里。
3.2 案例2-确认密码
案例:注册页面密码的二次确认
-
表单中有两个密码输入框,第二个作为二次确认,当两个内的数据完全一致时才可以提交。
from django import forms class TestForm(forms.Form): ''' 基本的中文自定义报错,与基础校验代码略。 ''' username = forms.CharField(.....略) password = forms.CharField(.....略) confirm_password = forms.CharField(.....略) email = forms.EmailField(.....略) def clean(self): password = self.cleaned_data.get('password') confirm_password = self.cleaned_data.get('confirm_password') if password != confirm_password: # 添加错误信息 self.add_error('confirm_password','两次输入的密码不一致') # 全局钩子需要返回所有字段的数据。 return self.cleaned_data
由于是密码字段和确认密码这两个字段做校验,所以采用全局钩子,函数名直接为clean
四、forms渲染标签属性
在上文中,提到的有label属性、error_messages属性,那么本小结将介绍其他属性,如:表单默认值、input表单的类型是text还是passwd、等等等…
label、error_messages
-
作用:渲染表单时,修改提示性信息,不使用默认的字段名首字母大写。 后者则用于自定义报错信息。
from django import forms class TestForm(forms.Form): username = forms.CharField( # input框的提示信息(字段名) label = '用户名', # 自定义报错信息 eorros_messahes = { 'min_length': '用户名最少3位', 'max_length': '用户名最多8位', 'required': '用户名不能为空', }, )
initial、required
-
作用:initial设置默认值, required用于设置是否为空,False为允许为空。
from django import forms class TestForm(forms.Form): username = forms.CharField( initial = '张三', # 默认值,默认为“张三” required = False, # False允许为空,默认为True表示不允许为空 )
widget
-
作用:修改input表单的type属性的值,默认是text,当我们需要修改为file、password时,就需要利用widget去进行修改。
另外,当我们的表单想要添加样式的时候,也需要使用到widget
''' 修改type属性值 ''' from django import forms class TestForm(forms.Form): username = forms.CharField(min_length=3, max_length=8, label='用户名', error_messages={ 'min_length': '用户名最少3位', 'max_length': '用户名最多8位', 'required': '用户名不能为空', }, widget=forms.widgets.TextInput(),) password = forms.CharField(min_length=3, max_length=8, label='密码', error_messages={ 'min_length': '密码最小3位', 'max_length': '密码最大8位', 'required': '密码不能为空', }, widget=forms.widgets.PasswordInput(),)
修改之后,这样password所属的表单,在输入时就不是明文的了。
当我们需要继承比如BootStrap样式的时候,就需要使用attrs方法。
具体格式为:forms.CharField( widget = forms.widgets.类型Input(attrs{‘class’:‘类1 类2 类3’}) ),多个类直接空格隔开。
''' 修改样式 ''' class TestForm(forms.Form): username = forms.CharField(min_length=3, max_length=8, label='用户名', error_messages={ 'min_length': '用户名最少3位', 'max_length': '用户名最多8位', 'required': '用户名不能为空', }, #指定样式 widget=forms.widgets.TextInput(attrs={'class':'form-control'}),) password = forms.CharField(min_length=3, max_length=8, label='密码', error_messages={ 'min_length': '密码最小3位', 'max_length': '密码最大8位', 'required': '密码不能为空', }, #指定样式 widget=forms.widgets.PasswordInput(attrs={'class':'form-control'}),)
五、正则校验
上文中,我们实现了对数据长度进行简单校验,以及利用钩子函数对各个表单的内容进行更加详细的判断,正则校验可以在钩子函数内导入re模块,进行判断,但是本章节将要介绍的是,如何不使用钩子函数,直接就可以完成正则校验。
格式:
-
validators=[ RegexValidator(‘正则表达式’,‘错误提示’) ,RegexValidator(),RegexValidator()… ]
一个字段,可以通过多个正则表达式的校验,如果不通过,则显示指定的报错信息。
需求:校验手机号,并且还要是130开头的
'''
需求:校验手机号,并且还要是130开头的
'''
from django import forms
# 导入模块
from django.core.validators import RegexValidator
class MyForm(forms.Form):
username = forms.CharField(min_length=3,max_length=8,label='用户名',)
phone = forms.CharField(
validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^130[0-9]+$', '必须是130开头的11位号码')
]
)
正则表达式尽量不要自己书写,可以在一些正则在线测试网站上,使用自动生成好的规则即可。
六、其他类型渲染
前面几个章节的代码中,永远都是forms.CharField(),如:
class MyForm(forms.Form): username = forms.CharField(min_length=3,max_length=8,label='用户名',) password = forms.CharField(widget=forms.widgets.PasswordInput(),)
所以本章节就是来介绍下其他标签,如radio、select等。
# radio
gender = forms.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=forms.widgets.RadioSelect()
)
# select
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
# 多选
hobby1 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
# 单选checkbox
keep = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
# 多选checkbox
hobby2 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)