Django 系列博客(十六)
django 系列博客(十六)
前言
本篇博客介绍 django 的 forms 组件。
基本属性介绍
创建 forms 类时,主要涉及到字段和插件,字段用于对用户请求数据的验证,插件用于自动生成 html。
form 类内置字段介绍
field required=true, 是否允许为空 widget=none, html插件 label=none, 用于生成label标签或显示内容 initial=none, 初始值 help_text='', 帮助信息(在标签旁边显示) error_messages=none, 错误信息 {'required': '不能为空', 'invalid': '格式错误'} show_hidden_initial=false, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直) validators=[], 自定义验证规则 localize=false, 是否支持本地化 disabled=false, 是否可以编辑 label_suffix=none label内容后缀 charfield(field) max_length=none, 最大长度 min_length=none, 最小长度 strip=true 是否移除用户输入空白 integerfield(field) max_value=none, 最大值 min_value=none, 最小值 floatfield(integerfield) ... decimalfield(integerfield) max_value=none, 最大值 min_value=none, 最小值 max_digits=none, 总长度 decimal_places=none, 小数位长度 basetemporalfield(field) input_formats=none 时间格式化 datefield(basetemporalfield) 格式:2015-09-01 timefield(basetemporalfield) 格式:11:12 datetimefield(basetemporalfield)格式:2015-09-01 11:12 durationfield(field) 时间间隔:%d %h:%m:%s.%f ... regexfield(charfield) regex, 自定制正则表达式 max_length=none, 最大长度 min_length=none, 最小长度 error_message=none, 忽略,错误信息使用 error_messages={'invalid': '...'} emailfield(charfield) ... filefield(field) allow_empty_file=false 是否允许空文件 imagefield(filefield) ... 注:需要pil模块,pip3 install pillow 以上两个字典使用时,需要注意两点: - form表单中 enctype="multipart/form-data" - view函数中 obj = myform(request.post, request.files) urlfield(field) ... booleanfield(field) ... nullbooleanfield(booleanfield) ... choicefield(field) ... choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),) required=true, 是否必填 widget=none, 插件,默认select插件 label=none, label内容 initial=none, 初始值 help_text='', 帮助提示 modelchoicefield(choicefield) ... django.forms.models.modelchoicefield queryset, # 查询数据库中的数据 empty_label="---------", # 默认空显示内容 to_field_name=none, # html中value的值对应的字段 limit_choices_to=none # modelform中对queryset二次筛选 modelmultiplechoicefield(modelchoicefield) ... django.forms.models.modelmultiplechoicefield typedchoicefield(choicefield) coerce = lambda val: val 对选中的值进行一次转换 empty_value= '' 空值的默认值 multiplechoicefield(choicefield) ... typedmultiplechoicefield(multiplechoicefield) coerce = lambda val: val 对选中的每一个值进行一次转换 empty_value= '' 空值的默认值 combofield(field) fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式 fields.combofield(fields=[fields.charfield(max_length=20), fields.emailfield(),]) multivaluefield(field) ps: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合multiwidget使用 splitdatetimefield(multivaluefield) input_date_formats=none, 格式列表:['%y--%m--%d', '%m%d/%y', '%m/%d/%y'] input_time_formats=none 格式列表:['%h:%m:%s', '%h:%m:%s.%f', '%h:%m'] filepathfield(choicefield) 文件选项,目录下文件显示在页面中 path, 文件夹路径 match=none, 正则匹配 recursive=false, 递归下面的文件夹 allow_files=true, 允许文件 allow_folders=false, 允许文件夹 required=true, widget=none, label=none, initial=none, help_text='' genericipaddressfield protocol='both', both,ipv4,ipv6支持的ip格式 unpack_ipv4=false 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, ps:protocol必须为both才能启用 slugfield(charfield) 数字,字母,下划线,减号(连字符) ... uuidfield(charfield) uuid类型
内直插件
textinput(input) numberinput(textinput) emailinput(textinput) urlinput(textinput) passwordinput(textinput) hiddeninput(textinput) textarea(widget) dateinput(datetimebaseinput) datetimeinput(datetimebaseinput) timeinput(datetimebaseinput) checkboxinput select nullbooleanselect selectmultiple radioselect checkboxselectmultiple fileinput clearablefileinput multiplehiddeninput splitdatetimewidget splithiddendatetimewidget selectdatewidget
校验字段功能
在没有使用 forms 组件时,如果需要对数据进行校验,比如注册时,对用户名或者密码有位数限制等,我们需要首先获取数据然后手动进行判断,那么有了 forms 组件,这些事统统可以不用手动进行判断了,只需要把限制条件作为参数传进去那么就会自动进行判断。
前端代码
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>title</title> </head> <body> <form action="" method="post" novalidate> <p>用户名: <input type="text" name="name"></p> <p>密码: <input type="password" name="pwd"></p> <p>邮箱: <input type="email" name="email"></p> <input type="submit" value="提交"> </form> </body> </html>
后端代码
from django import forms # 继承form这个类 class myform(forms.form): # 限制name这个字段最长为8,最短为3 name=forms.charfield(min_length=3,max_length=8,label='用户名',required=true,error_messages={'min_length':'至少为3','max_length':'最长为8,您太长了'}) pwd=forms.charfield(min_length=3,max_length=8,label='密码',error_messages={'min_length':'至少为3','max_length':'最长为8,您太长了'}) email=forms.emailfield(label='邮箱',error_messages={'invalid':'邮箱格式不合法','required':'这个字段必填'}) def register(request): if request.method=='get': # 生成一个空的form对象 myform=myform() return render(request,'register.html',locals()) else: # 生成对象,传参,传字典,要校验数据的字典 # myform=myform(request.post) # 自己写要校验的字典,数据多了,多的是不校验的,但是cleaned_data中也没有多出来的数据 # dic={'name':'lqz','pwd':'123','email':'22@qq.com','xx':'xxx'} # dic={'name':'lqz','pwd':'123','email':'22'} dic={'name':'lqzfgsdfgsdf','pwd':'1','email':'5555'} myform = myform(dic) # 所有字典都校验通过,它就是true的 if myform.is_valid(): # 取出校验通过的数据 clean_data=myform.cleaned_data print(clean_data) # models.userinfo.objects.create(**clean_data) else: # 所有的错误信息 # 只要是校验通过的值,都在cleaned_data中放着 print(myform.cleaned_data) print(myform.errors.as_data()) # 字典类型 print(type(myform.errors)) print(myform.errors.get('name')) from django.forms.utils import errordict return httpresponse('ok')
渲染标签功能
- 渲染方式一
<h1>forms的模板渲染之一(推荐)</h1> <form action="" method="post" novalidate> <p>用户名: {{ myform.name }}</p> <p>密码: {{ myform.pwd }}</p> <p>邮箱: {{ myform.email }}</p> <input type="submit" value="提交"> </form>
- 渲染方式二
<h1>forms的模板渲染之二(推荐)</h1> <form action="" method="post" novalidate> {% for item in myform %} <p>{{ item.label }}:{{ item }}</p> {% endfor %} <input type="submit" value="提交"> </form>
- 渲染方式三
<h1>forms的模板渲染之三</h1> <form action="" method="post" novalidate> {# <table>#} {# {{ myform.as_table }}#} {# </table>#} {{ myform.as_p }} <input type="submit" value="提交"> </form>
渲染错误信息功能
前端代码
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <script src="/static/jquery-3.3.1.js"></script> <title>注册</title> </head> <body> <div class="row"> <div class="col-md-6 col-md-offset-3"> <form action="" method="post" novalidate> {% for foo in myform %} <p>{{ foo.label }}:{{ foo }} <span class="pull-right">{{ foo.errors.0 }}</span></p> {% endfor %} <input type="submit" value="提交"><span>{{error_all.0 }}</span> </form> <hr> <form action="" method="post" novalidate> <p>用户名:{{ myform.name }} <span>{{ myform.errors.name }}</span></p> <p>用户名:{{ myform.pwd }} <span>{{ myform.errors.pwd }}</span></p> <p>用户名:{{ myform.email }} <span>{{ myform.errors.email }}</span></p> <input type="submit" value="提交"> </form> </div> </div> </body> </html>
后端代码
from app01.myform import regform def register(request): if request.method == 'get': myform = regform() else: myform = regform(request.post) if myform.is_valid(): # 存数据库,保存这个人 print(myform.cleaned_data) else: # 校验不通过,这里面也可能有值 # 总错误信息{'name':['太长了',],'pwd':['太短了']} # print(myform.cleaned_data) # 总错误 # myform.glo_err=myform.errors.get('__all__') error_all = myform.errors.get('__all__') # print() # 每一个的错误信息 # for item in myform: # # ['太长了',] # # print(item.errors[0]) # # print(type(item.errors)) # from django.forms.utils import errorlist # # print(item.errors.as_data()) # print(item.errors) # # print(type(item.errors)) return render(request, 'register.html', locals())
组件的参数设置
class regform(forms.form): name = forms.charfield(max_length=8, min_length=3, label='用户名', error_messages={'max_length': '太长了', 'min_length': '太短了', 'required': '该项不能为空' }, widget=widgets.textinput(attrs={'class': 'form-control'}) ) pwd = forms.charfield(max_length=8, min_length=3, label='密码', error_messages={'max_length': '太长了', 'min_length': '太短了', 'required': '该项不能为空' }, widget=widgets.passwordinput(attrs={'class': 'form-control'}) ) re_pwd = forms.charfield(max_length=8, min_length=3, label='确认密码', error_messages={'max_length': '太长了', 'min_length': '太短了', 'required': '该项不能为空' }, widget=widgets.passwordinput(attrs={'class': 'form-control'}) ) email = forms.emailfield(label='邮箱', error_messages={'invalid': '格式不正确', 'required': '该项不能为空' }, widget=widgets.emailinput(attrs={'class': 'form-control'}) )
局部钩子
继承自 form类的校验类,可以对类中的每个字段进行校验,校验通过后的数据会放在 clean_data中,没通过的放在errors中,可以通过字段名取值,那么校验通过的字段如果还需要进行其他的判断,比如是否有敏感词,这些单单依靠字段的校验是完成不了的。所以 django 提供了局部钩子函数,当每个字段进行校验完成后会自动调用该钩子函数。看源码就很清楚了。
首先,所有的数据校验都是调用了 form 对象的 is_valid()方法进行的。所以进入is_valid()方法。
self.is_bound
为 true,所以进入self.errors
方法,为什么没加括号呢,因为被property
装饰了,变成了属性。
self._errors
为none
,进入full_clean()
方法。
在这个方法里,首先定义了cleaned_data
为一个空字典,然后就是下面的三个方法:
- _clean_fields():见名思义,这个方法是用来校验字段的,就是继承自 form 中类里面定义的字段。
光看源码不看例子很难理解,
class myform(forms.form): name = forms.charfield(min_length=3, max_length=8, label='用户名', required=true, error_messages={'min_length': '用户名长度至少为三位', 'max_length': '用户名长度最长为八位', }) pwd = forms.charfield(min_length=3, max_length=8, label='密码', required=true, error_messages={'min_length': '用户名长度至少为三位', 'max_length': '用户名长度最长为八位', }) email = forms.emailfield(label='邮箱', error_messages={'invalid': '邮箱格式不合法', 'required': '该字段必填'})
在self.fields
中放的是一个个键值对,比如在上面的myform
里面的字段name
和后面的charfield
类的一个对象的对应关系,那么在self.fields
里面就有三个对应关系,也就是有三个键值对。
往下走,字典的items()
方法,把myform
里面的字段值赋给了name
,把字段指向的类对象赋给了field
,之后进行判断,if field.disable:
,这个判断是判断类对象的某个属性值,可以知道在定义字段时,并没有定义这个值,那么这个值是否有默认值,进入forms.charfield
中查看,
可以看出该类中没有,那么去父类中查看也就是field
:
找到了,默认为 false, 所以说if field.disables:
为 false, 就走到了下一步:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
这句看不懂,接着往下走:
首先,咱们定义的都不是filefield
字段,所以走value=field.clean(value)
:
因为是字段的方法,所以找forms.charfield
,在field
类中找到:
这里面有点绕,我先把顺序写出来:
首先要知道value
到底是什么:
在上面说过调用了field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
方法,是字段的方法,
在charfield
中没找到widget
,去field
中寻找,
可以看到widget=textinput
,所以肯定是调用了textinput
的value_from_datadict
方法,
没有,去input
,
还是没有,去widget
,
找到,返回了data.get(name)
,很显然这是字典的取值方法,但是data
是什么呢?
data
是个querydict
对象,找到name
是什么,就可以知道value
是什么了。
可以知道name
是通过self.add_prefix(name)
获取的,进入:
是个三元表达式,关键在于self.prefix
的值是多少。这是个对象属性,很显然去类中找:
所以这个三元表达式的值就是myform
中定义的字段名,所以通过data.get(name)
取得值就是上传的值,比如musibii
。
那么执行field.clean(value)
相当于把musibii
传进去执行,
在field.clean
方法中:
- to_python('musibii'):因为
charfield
中定义了to_python
方法,所以进入自己的方法;
musibii
不是none,空字符串、空字典、空元祖,所以进入forece_text('musibii')
方法:
别看这么多,其实就进行了一步判断:if issubclass(type('musibii'), six.text_type):
因为这个判断为true
,
所以直接把musibii
返回。然后判断if self.strip:
因为该参数默认为true
,所以value=value.strip()
,默认是通过空格进行分割的,走完后后进入self.valitate('musibii')
,
直接退出进入run_validators('musibii')
,
在这个方法里面,主要是进行了字段的一些限制校验,比如长度等,musibii
没有错误,返回musibii
,然后把该数据加到self.cleaned_data
中,表示这个数据是成功通过校验的。
这句代码是反射用法,判断self
是否具有clean_name
属性,(name 变量的值为 name),因为没有,所以第一个字段的校验结束,进行第二个字段的校验。
如果我们在myform
类中定义了clean_name
或者clean_pwd
或者clean_email
方法,如果在_clean_fields
方法中通过校验就会执行自定义的校验方法,这就是局部钩子。
class regform(forms.form): name = forms.charfield(max_length=8, min_length=3, label='用户名', error_messages={'max_length': '太长了', 'min_length': '太短了', 'required': '该项不能为空' }, widget=widgets.textinput(attrs={'class': 'form-control'}) ) pwd = forms.charfield(max_length=8, min_length=3, label='密码', error_messages={'max_length': '太长了', 'min_length': '太短了', 'required': '该项不能为空' }, widget=widgets.passwordinput(attrs={'class': 'form-control'}) ) re_pwd = forms.charfield(max_length=8, min_length=3, label='确认密码', error_messages={'max_length': '太长了', 'min_length': '太短了', 'required': '该项不能为空' }, widget=widgets.passwordinput(attrs={'class': 'form-control'}) ) email = forms.emailfield(label='邮箱', error_messages={'invalid': '格式不正确', 'required': '该项不能为空' }, widget=widgets.emailinput(attrs={'class': 'form-control'}) ) # clean_字段名字 def clean_name(self): # 从cleaned_data中取出字段的值 name = self.cleaned_data.get('name') # # 校验是否以sb开头 if name.startswith('sb'): raise validationerror('不能以sb开头') else: return name # user = models.userinfo.objects.filter(name=name).first() # if user: # # 用户已经存在,校验不通过,抛一个异常 # raise validationerror('该用户已经存在') # # else: # # 校验通过,返回要校验的数据 # return name # def clean_pwd(self): # pass
在这里,如果name
字段校验通过就会执行clean_name
这个方法进行进一步的校验。
全局钩子
当_clean_fields
运行结束后会执行_clean_form
方法,
如果myform
中定义了clean
方法的话就会执行该方法:
def clean(self): pwd=self.cleaned_data.get('pwd') re_pwd=self.cleaned_data.get('re_pwd') if pwd==re_pwd: # 正确,返回self.cleaned_data return none else: # 校验失败,抛异常 raise validationerror('两次密码不一致')
比如要判断两次密码是否一致就可以定义一个全局钩子函数。
应用
模板层
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <script src="/static/jquery-3.3.1.js"></script> <title>注册</title> </head> <body> <div class="row"> <div class="col-md-6 col-md-offset-3"> <form action="" method="post" novalidate> {% for foo in myform %} <p>{{ foo.label }}:{{ foo }} <span class="pull-right">{{ foo.errors.0 }}</span></p> {% endfor %} <input type="submit" value="提交"><span>{{error_all.0 }}</span> </form> </div> </div> </body> </html>
视图层
def register(request): if request.method == 'get': myform = regform() else: myform = regform(request.post) if myform.is_valid(): # 存数据库,保存这个人 print(myform.cleaned_data) else: # 校验不通过,这里面也可能有值 # 总错误信息{'name':['太长了',],'pwd':['太短了']} # print(myform.cleaned_data) # 总错误 # myform.glo_err=myform.errors.get('__all__') error_all = myform.errors.get('__all__') # print() # 每一个的错误信息 # for item in myform: # # ['太长了',] # # print(item.errors[0]) # # print(type(item.errors)) # from django.forms.utils import errorlist # # print(item.errors.as_data()) # print(item.errors) # # print(type(item.errors)) return render(request, 'register.html', locals())
全局钩子产生的错误会放到myform.errors[__all__]中
下一篇: JS实现验证码倒计时的注册页面
推荐阅读
-
LNMP系列教程之 SSL安装WordPress博客(程序下载与安装)
-
Django系列---使用MySql数据库
-
《Dotnet9》系列之建站-中文站最好WordPress主题,自媒体,博客,企业,商城主题一网打尽
-
Flutter 即学即用系列博客——04 Flutter UI 初窥
-
Python使用Django实现博客系统完整版
-
Flutter 即学即用系列博客——01 环境搭建
-
SLAM+语音机器人DIY系列:(八)高阶拓展——2.centos7下部署Django(nginx+uwsgi+django+python3)
-
Django+Bootstrap+Mysql 搭建个人博客 (六)
-
ASP系列讲座(十六)访问数据库
-
基于django的个人博客网站建立(六)