forms组件、局部钩子、全局钩子
什么是forms组件
forms组件就是一个类,可以检测前端传来的数据,是否合法。 例如,前端传来的邮箱数据,判断邮件格式对不对,用户名中不能以什么开头,等等
forms组件的使用
1、使用语法
from django.shortcuts import render, HttpResponse from django import forms # 1.先写一个类,继承Form class MyForm(forms.Form): # 定义一个属性,可以用来校验字符串类型 # 限制最大长度是8,最小长度是3 name=forms.CharField(max_length=8,min_length=3) pwd=forms.CharField(max_length=8,min_length=3,required=True) # 校验是否是邮箱格式 email=forms.EmailField() # 2.在视图函数中使用MyForm来校验数据 # 实例化产生对象,传入要校验的数据(可以传字典字典,也可以不传) myform=MyForm(request.POST) # 3.校验,is_valid如果是true表示校验成功(满足myform里的条件),反之,校验失败 if myform.is_valid(): # myform.clean_data 表示校验通过的数据 print(myform.cleaned_data) return HttpResponse('校验成功') else: print(myform.cleaned_data) #校验失败的信息,myform.errors 可以当成一个字典,它是所有错误信息{name:[列表,]} # 每个字段.errors 是一个列表,表示每个字段的错误信息 print(myform.errors) return HttpResponse('校验失败')
方法总结:
- myform.clean_data 验证通过的数据
- myform.errors 错误数据的对象
- myform.errors.as_data 错误数据的信息
2、组件的参数
max_length # 代表该字段最长为多少 min_length # 代表该字段最短为多少 error_messages # 这是设置错误信息的属性 required # 默认值为True,意思是你传来的字段必须有它,没有的话校验失败 widget=widgets.TextInput() # 你在模板渲染的时候,就会渲染成Input框,type为text lable # lable='用户名' # 例子 pwd = forms.CharField(max_length=8, min_length=3, required=True, label='密码', error_messages={'max_length': '最长是8', 'min_length': '最短是3', 'required': '这个必须填'},
3、注意点
- MyForm实例化时,传入必须是字典,或者不传
- errors 调用这个方法,返回值是对象,你可以通过get取值
- 要校验的数据,字段可以多于MyForm类中的字段,但不能少,少相当于该字段没有数据
渲染模板
# form组件可以在视图函数中使用,也可以在模板中使用 # 视图层: def index(request): myform = Myform() return render(request,'index.html',local())
# 模板层 # 1.渲染方式一: <form action='' method='post'> 用户名:{{myform:name}} <br> <input type='submit' value = '提交'></input> </form> # 这里的{{myform:name}} 和你写input框是一样的效果,就是属性比input框多一点 # 2.渲染方式二(推荐使用): <form action='' method='post'> {% for foo in myform%} {{ foo.lable }} : {{ foo }} <br> <input type='submit' value = '提交'></input> </form> # 页面显示都是一样的,foo.lable不是用户名,是name,但是可以在创建Myform类时,在CharFiel中添加lable='用户名',这样就行了。 # 3.渲染方式三: <form action='' method='post'> {{ myform.as_p }} <input type='submit' value = '提交'></input> </form>
渲染错误信息
<form action='' method='post'> {% for foo in myform%} {{ foo.lable }} : {{ foo }} <span>{{foo.errors.0}}</span><br> <input type='submit' value = '提交'></input> </form>
局部钩子
1、什么是局部钩子
定义一个函数,名字叫:clean_字段名字,内部,取出该字段,进行校验,如果通过,将该字段返回,如果失败,抛异常(ValidationError)
2、定义局部钩子
# 函数名:clean_字段名字 def clean_name(self): # self:当前form对象 name = self.cleaned_data.get('name') if name.startswith('sb'): # 失败,抛异常,将异常信息以 {'name':value} 写入errors字典中 raise ValidationError('不能以sb开头') # 正常,把name返回到clean_data,将name写入clean_data字典中 return name
注意点:
- 校验失败,抛异常,将异常信息以 {'name':value} 写入 errors 字典中
- 校验成功,把name返回到clean_data,写入clean_data字典中
- 抛出异常的类型为ValidationError,
from django.core.exceptions import ValidationError
导入
全局钩子
1、什么是全局钩子
在写注册用户的时候,有输入密码,确认密码,可以进行布局钩子处理,处理完毕是不是在进行判断,判断他们是否相等,相等的话,就存到数据库中,不相等就抛个异常。
2、定义全局钩子
# 重写clean方法 def clean(self): # 程序能走到该函数,前面校验已经通过了,所以可以从cleaned_data中取出密码和确认密码 pwd=self.cleaned_data.get('pwd') re_pwd=self.cleaned_data.get('re_pwd') # 进行自己的校验 if pwd==re_pwd: # 通过,直接返回cleaned_data return self.cleaned_data else: # 失败,抛异常(ValidationError) raise ValidationError('两次密码不一致')
全局钩子注意点:
- 校验失败,抛异常,将异常信息以
{'__all__':[value,]}
写入 errors 字典中 - 校验成功,返回clean_data字典
- 抛出异常的类型为ValidationError,
from django.core.exceptions import ValidationError
导入
钩子错误信息渲染注意点:
-
局部钩子抛出的异常会添加到该字段中的错误信息中,获取错误信息:
前台:
for循环生成input框,{{ foo.errors.0 }}
-
全局钩子抛出的异常会添加到_all_中,获取错误信息:
后台:
myforms.errors.get('__all__')[0]
注意先判断myforms.errors.get('__all__')
是否存在
前台:{{ myforms.errors.__all__.0 }}
-
如果程序走到了局部钩子这一步,说明传的字典里的数据符合要求,此时就可以从clean_data中取数据,因为此时clean_data中的数据全符合要求,而且clean_data是一个字典
-
局部钩子,全局钩子所抛出异常的类型为ValidationError,以下导入
from django.core.exceptions import ValidationError
完整的forms组件校验
1、视图层
from django.shortcuts import render, HttpResponse, redirect # forms组件数据校验的功能 # 第一步:先要继承Form from django import forms from django.forms import widgets from django.core.exceptions import ValidationError # 写一个类 class MyForm(forms.Form): # 定义一个属性,可以用来校验字符串类型 # 限制最大长度是8,最小长度是3 name = forms.CharField(max_length=8, min_length=3, label='用户名' error_messages={'max_length': '最长是8', 'min_length': '最短是3', 'required': '这个必须填'}, widget=widgets.TextInput(attrs={'class': 'form-control'})) pwd = forms.CharField(max_length=8, min_length=3, required=True, label='密码', error_messages={'max_length': '最长是8', 'min_length': '最短是3', 'required': '这个必须填'}, widget=widgets.PasswordInput()) re_pwd = forms.CharField(max_length=8, min_length=3, required=True, label='确认密码', error_messages={'max_length': '最长是8', 'min_length': '最短是3', 'required': '这个必须填'}, widget=widgets.PasswordInput()) # 校验是否是邮箱格式 email = forms.EmailField(label='邮箱', error_messages={'required': '这个必须填', 'invalid': '不符合邮箱格式'}) # aa = forms.CharField(label='选择', error_messages={'required': '这个必须填', 'invalid': '不符合邮箱格式'},widget=widgets.CheckboxInput()) def clean_name(self): # self:当前form对象 name = self.cleaned_data.get('name') if name.startswith('sb'): # 失败,抛异常 raise ValidationError('不能以傻逼开头') # 正常,把name返回 return name def clean(self): pwd = self.cleaned_data.get('pwd') re_pwd = self.cleaned_data.get('re_pwd') if pwd == re_pwd: return self.cleaned_data else: raise ValidationError('两次密码不一致') def index_form(request): # 生成对象时(实例化),需要传入要校验的数据(字典) if request.method == 'GET': myform = MyForm() return render(request,'indxe2.html',locals()) elif request.method == 'POST': myform = MyForm(request.POST) if myform.is_valid(): # print(myform.cleaned_data) # 验证通过的数据 # models.User.objects.create(name='lqz',pwd='123',re_pwd='123) myform.cleaned_data.pop('re_pwd') models.User.objects.create(**myform.cleaned_data) return redirect('http://www.baidu.com') else: all_error = myform.errors.get('__all__') if all_error: all_error = all_error[0] # print(myform.errors.as_data) return render(request, 'indxe3.html', locals())
2、模板层
<form action="" method="post" novalidate> {% for foo in myform %} <p>{{ foo.label }}:{{ foo }} <span>{{ foo.errors.0 }}</span></p> {% endfor %} <input type="submit" value="提交"><span>{{ all_error }}</span> </form>
注意点:
-
局部钩子的错误信息:
前台:
for循环生成input框,{{ foo.errors.0 }}
-
全局钩子的错误信息:
后台:
myforms.errors.get('__all__')[0]
注意先判断myforms.errors.get('__all__')
是否存在
前台:{{ myforms.errors.__all__.0 }}
-
校验全部通过,创建数据时,从
clean_data
中获取数据,但是必须要将其中多于的数据pop掉,如clean_data.pop('r_pwd')
modelFrom
如果不用ModelForm,编辑的时候得显示之前的数据,还得挨个取一遍值,如果ModelForm,只需要加一个instance=obj(obj是要修改的数据库的一条数据的对象)就可以得到同样的效果 编辑数据一开始需要获取原始数据展示到页面上再让用户修改,这个就需要拿到该编辑数据的对象,传入modelform中及instance=obj这一句,用户修改完成后,相当于要基于上次的数据进行修改操作,需要传入两个参数一个是提交过来的数据request.POST一个就是修改的对象instance=obj,这样modelform才知道修改哪个对象数据 代码示例:
from django.shortcuts import render,HttpResponse,redirect from django.forms import ModelForm # Create your views here. from app01 import models def test(request): # model_form = models.Student model_form = models.Student.objects.all() return render(request,'test.html',{'model_form':model_form}) class StudentList(ModelForm): class Meta: model = models.Student #对应的Model中的类 fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段 exclude = None #排除的字段 labels = None #提示信息 help_texts = None #帮助提示信息 widgets = None #自定义插件 error_messages = None #自定义错误信息 #error_messages用法: error_messages = { 'name':{'required':"用户名不能为空",}, 'age':{'required':"年龄不能为空",}, } #widgets用法,比如把输入用户名的input框给为Textarea #首先得导入模块 from django.forms import widgets as wid #因为重名,所以起个别名 widgets = { "name":wid.Textarea } #labels,自定义在前端显示的名字 labels= { "name":"用户名" } def student(request): if request.method == 'GET': student_list = StudentList() return render(request,'student.html',{'student_list':student_list}) else: student_list = StudentList(request.POST) if student_list.is_valid(): student_list.save() return render(request,'student.html',{'student_list':student_list}) def student_edit(request,pk): obj = models.Student.objects.filter(pk=pk).first() if not obj: return redirect('test') if request.method == "GET": student_list = StudentList(instance=obj) return render(request,'student_edit.html',{'student_list':student_list}) else: student_list = StudentList(request.POST,instance=obj) if student_list.is_valid(): student_list.save() return render(request,'student_edit.html',{'student_list':student_list})
总结: 从上边可以看到ModelForm用起来是非常方便的,比如增加修改之类的操作。但是也带来额外不好的地方,model和form之间耦合了。如果不耦合的话,mf.save()方法也无法直接提交保存。 但是耦合的话使用场景通常局限用于小程序,写大程序就最好不用了。