Django与form组件
Forms组件:
是Django提供的用于数据校验和模板渲染的组件, 在Django使用该组件可以便捷的帮助我们来完成很多事情;下面会对forms组件的每一个功能进行详细的介绍和实际的使用方法
一、校验字段功能
校验字段功能:
该功能可以帮助我们将前端传回来的数据进行快速的和数据库中的数据格式要求进行比对, 完成校验, 一般在用户注册和登录时使用的较多,下面我们是使用forms完成校验字段功能的步骤:
forms组件功能之一:
在项目中创建一个py文件
1 写一个类继承Form
2 在类中写属性,写的属性,就是要校验的字段
3 使用:生成一个你写的类的对象myform,把要校验的数据(字典),传到对象中:MyForm(字典)
4 myform.is_valid() 是True表示所有字段都通过校验
数据多?数据少?数据多可以,少不行(required控制):这里的数据多少指的是: 前端传回来的数据和设置要校验的数据个数的多少
5 myform.cleaned_data:是一个字典,所有通过校验的数据放在里面
6 myform.errors:是一个字典,所有的错误字段的信息
在模板中:
{{myform.name}}
{%for item in myform%}
{{item.label}}:{{item}}
{%endfor%}
myform.as_table/as_p/as_ul(不推荐使用)
校验字段的时候传入数据比定义的字段多/少的处理方式
前端传进来的字段比定义的字段多的情况:
已经定义校验的字段会正常进行校验, 多传进来的字段不会进行校验, 此时is_valid()的值为True
前端传进来的字段比定义的字段少的情况:
已经定义校验的字段会正常进行校验, 少传的字段会返回一个该字段必填的错误信息(该字段是否必填有required=True/False来控制), 此时is_valued()的值为False
注: 校验字段时,只有定义的全部字段都校验通过的时候,is_valid()的值才会为True, 同时也只有在该判断之后clean_data中才会有值可取
实例:注册用户来讲解一下forms组件的校验字段功能。
模型:models.py
class UserInfo(models.Model): name=models.CharField(max_length=32) pwd=models.CharField(max_length=32) email=models.EmailField()
模板文件:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="" method="post"> {% csrf_token %} <div> <label for="user">用户名</label> <p><input type="text" name="name" id="name"></p> </div> <div> <label for="pwd">密码</label> <p><input type="password" name="pwd" id="pwd"></p> </div> <div> <label for="r_pwd">确认密码</label> <p><input type="password" name="r_pwd" id="r_pwd"></p> </div> <div> <label for="email">邮箱</label> <p><input type="text" name="email" id="email"></p> </div> <input type="submit"> </form> </body> </html>
视图函数:
# 导入forms组件 from django.forms import widgets from django import forms wid_01=widgets.TextInput(attrs={"class":"form-control"}) wid_02=widgets.PasswordInput(attrs={"class":"form-control"}) # 创建一个类继承form.Form # 对要校验的字段进行设置: 最长、最短等限制 class UserForm(forms.Form): name=forms.CharField(max_length=32,min_length=4,widget=wid_01) pwd=forms.CharField(max_length=32,min_length=3,widget=wid_02) r_pwd=forms.CharField(max_length=32,min_length=3,widget=wid_02) email=forms.EmailField(widget=wid_01) tel=forms.CharField(max_length=32,min_length=8,min_length=3,widget=wid_01) def register(request): if request.method=="POST": # 这一步是校验数据的具体执行, 将POST字典中的数据使用之前基于forms定义好的类进行校验 form=UserForm(request.POST) # 判断所有的字段是否都校验通过 if form.is_valid(): print(form.cleaned_data) # 所有干净的字段以及对应的值 else: print(form.cleaned_data) #只有检验通过的字段数据信息 print(form.errors) # ErrorDict : {"校验错误的字段":["错误信息",]} print(form.errors.get("name")) # ErrorList ["错误信息",] return HttpResponse("OK") form=UserForm() return render(request,"register.html",locals())
二、渲染标签功能
form组件拥有渲染标签的功能, 可以为我们自动生成相应的标签到页面中,
前提是:要先定义好一个校验字段的类,如:MyForm, 然后将该类实例化产生的空对象,(如: form = MyForm())传到前端, 在前端使用模板语言进行处理
渲染方式1(推荐)
使用模板语言将相关内容渲染到页面中, 会自动生成输入框, 详情可以看forms组件的源码, 在这里是将一些标签和数据做成字符串,通过模板语言将其'打印' 出来放到这里的
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <h3>注册页面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form action="" method="post"> {% csrf_token %} <div> <label for="">用户名</label> {{ form.name }} </div> <div> <label for="">密码</label> {{ form.pwd }} </div> <div> <label for="">确认密码</label> {{ form.r_pwd }} </div> <div> <label for=""> 邮箱</label> {{ form.email }} </div> <input type="submit" class="btn btn-default pull-right"> </form> </div> </div> </div> </body> </html>
渲染方式2(推荐)
使用for循环传进来的form对象, 如果只是for循环, 输入框前面是没有内容的, 也就是没有这个输入框是要输入什么内容的提示, 这个时候就要用到循环出来的对象的lable属性了, 但是这样循环出来的lable值是默认的定义字段时的英文名字, 要想改成中文, 需要在定义校验字段的时候, 在后边增加lable属性, 指定了之后再使用这种方法,循环取到的值就是指定的中文了.例如:
email=forms.EmailField(label='邮箱',error_messages={'invalid':'邮箱格式不合法','required':'这个字段必填'})
-forms组件的渲染错误信息
在模板中:
在模板中可以这样取值, 是因为前端的模板语言和jquery
{{ foo.errors.0 }}
forms使用bootstrap样式
widget=widgets.EmailInput(attrs={'class':'form-control'}))
<form action="" method="post"> {% csrf_token %} {% for field in form %} <div> <label for="">{{ field.label }}</label> {{ field }} </div> {% endfor %} <input type="submit" class="btn btn-default pull-right"> </form>
渲染方式3
共有几种模式可选:
-
form.as_p : 将内容放到p标签中
-
form.as_table : 将内容渲染到表格中, 要注意这种需要将该语句放到<table></table>标签中间
-
form.as_ul : 将内容渲染到列表中
代码写的越少, 可控性就越低, 所以进行模板渲染的时候不推荐使用这种方式进行渲染
<form action="" method="post"> {% csrf_token %} {{ form.as_p }} <input type="submit" class="btn btn-default pull-right"> </form>
三、渲染错误信息功能
校验的错误信息放在errors字典中, 形式: ErrorDict : {"校验错误的字段":["错误信息",]}
之所以字典中value是一个列表形式,是因为一个字段可能设置了多个校验限制,因为errors是个字典, 所以可以使用get方法从中进行取值.然后将错误信息反映到前端, 也正因为value是一个列表形式, 所以在前端取值的时候要将具体的值取出来渲染到页面中,这也是下面出现field.errors.0 的原因
视图函数:
def register(request): if request.method=="POST": form=UserForm(request.POST) if form.is_valid(): print(form.cleaned_data) # 所有干净的字段以及对应的值 else: print(form.cleaned_data) # print(form.errors) # ErrorDict : {"校验错误的字段":["错误信息",]} print(form.errors.get("name")) # ErrorList ["错误信息",] return render(request,"register.html",locals()) form=UserForm() return render(request,"register.html",locals())
模板html文件接收错误信息:
form action="" method="post" novalidate> {% csrf_token %} {% for field in form %} <div> <label for="">{{ field.label }}</label> {{ field }} <span class="pull-right" style="color: red">{{ field.errors.0 }}</span> </div> {% endfor %} <input type="submit" class="btn btn-default"> </form>
四、forms
组件的参数配置
各个参数的说明:
-
max_length: 限制要校验字段的最大长度
-
min_length: 限制要校验字段的最小长度
-
error_messages: 控制错误信息的值, 比如max_length校验失败时, 在errors字典中 ErrorDict : {"校验错误的字段":["错误信息",]}会有一个''max_length'':['太长'] 这样一个信息,value的值是一个列表,是因为在校验的时候可以设置多个校验限制
-
widget: 为标签引入css样式
详解widget:
导入:from django.forms import widgets
wid_01=widgets.TextInput(attrs={"class":"form-control"}) wid_02=widgets.PasswordInput(attrs={"class":"form-control"})
widgets后面会有很多内置的css样式, 比如上面两种,一个是指定type=text 一个是type=password 的input框,
当然wiget的功能还不止于此, 它还可以在后边的参数中给该标签添加全局属性, 添加方式如上所示, 这样我们就可以通过这种方法同时借助于Bootstrap来添加我们想要的样式了
required: 控制该字段是否必填
invalid: 格式不合法
class Ret(Form): name = forms.CharField(max_length=10, min_length=2, label='用户名', error_messages={'required': '该字段不能为空', 'invalid': '格式错误', 'max_length': '太长', 'min_length': '太短'}, widget=widgets.TextInput(attrs={'class':'form-control'})) pwd = forms.CharField(max_length=10, min_length=2, widget=widgets.PasswordInput(attrs={'class':'form-control'})) email = forms.EmailField(label='邮箱', error_messages={'required': '该字段不能为空', 'invalid': '格式错误'})
五、全局钩子和局部钩子
在介绍全局钩子和局部钩子的时候, 我们先引入一个概念: 面向切面编程(AOP)
什么是面向切面编程:
面向切面编程简单来说就是, 程序在正常运行过程中, 切到其他地方执行了一个函数,然后又回来继续执行
什么是全局钩子和局部钩子?
我们在这里要介绍的全局钩子个局部钩子就和这种编程方式很类似, 我们可以理解为在程序的某一处放了一个钩子, 在程序运行到该处的时候, 会自动被钩过去执行一些代码, 然后才能回来. 下面我们就对这两种方式进行详细的介绍
局部钩子
-
使用场景: 是在系统已有的校验方式的基础上再自定义一些自己所需要的校验方式
-
出现背景: 局部钩子是针对某一个字段的二次校验而设定的
-
执行时间: 在系统对每个字段完成校验之后会进行一个判断, 判断是否定义了钩子,如果定义了就会执行局部钩子指定的校验方式,并会更新经过二次校验的clean_data
-
固定用法: 定义的时候必须使用clean_字段名(self), 进行定义,不得更改,这里的self是用于校验的类实例化出来的对象
-
从clean_data中获取已经被字段检验阶段校验过的数据做逻辑处理
-
如果不满足自定义的校验格式, 要抛一个ValidationError('异常信息'),这个异常信息会自动添加到errors字典中,形式是{'校验的字段名':['异常信息']}
-
如果满足自定义的校验, 要将二次校验过的字段返回出去,保证程序后续的正常运行
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError def clean_name(self): val=self.cleaned_data.get("name") ret=UserInfo.objects.filter(name=val) if not ret: return val else: raise ValidationError("该用户已注册!") def clean_tel(self): val=self.cleaned_data.get("tel") if len(val)==11: return val else: raise ValidationError("手机号格式错误")
全局钩子
-
使用场景: 是在已有的校验方式的基础上(包括系统已有的校验和自定义的局部钩子校验)再自定义一些自己所需要的校验方式
-
出现背景: 局部钩子是针对某一个字段的二次校验而设定的, 而全局钩子是针对多个字段的二次校验设定的, 比如用来校验用户连词两次输入的密码是否一致的场景
-
执行时间: 在系统校验和局部钩子的校验都完成后,系统判断时候有全局钩子校验, 如果有,就在此时执行,并返回相应的数据(更新后的clean_data或者异常信息)
-
固定用法: 定义的时候必须使用clean(self), 进行定义,不得更改,这里的self是用于校验的类实例化出来的对象
-
从clean_data中获取已经被字段检验阶段校验过的数据做逻辑处理
-
如果不满足自定义的校验格式, 要抛一个ValidationError('异常信息'),这个异常信息会自动添加到errors字典中,形式是{'校验的字段名':['异常信息']}, 取值的时候必须使用myform.errors.get(''__all__'')才能取出来
-
如果满足自定义的校验, 要将原来的clean_data完整的返回出去,保证程序后续的正常运行
def clean(self): pwd=self.cleaned_data.get('pwd') r_pwd=self.cleaned_data.get('r_pwd') if pwd and r_pwd: if pwd==r_pwd: return self.cleaned_data else: raise ValidationError('两次密码不一致') else: return self.cleaned_data
1 读的入口是:
form.is_valid()--->self.errors(BaseForm类)---》self.full_clean()(BaseForm类)--》
-self._clean_fields(局部数据校验)和self._clean_form(全局数据校验)
2 self._clean_fields(BaseForm类)
for name, field in self.fields.items():
try:
# 字段自己的校验(最大值,最小值,是不是邮箱格式)
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name): # 反射判断有没有clean_字段名
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
3 self._clean_form(BaseForm类) 全局钩子
try:
cleaned_data = self.clean() # self.clean执行的是自己类的clean方法
except ValidationError as e:
self.add_error(None, e)
面向切面编程(AOP OOP:面向对象编程)