django(Ajax、自定义分页器、form组件)
一、Ajax
1 概述
异步提交
局部刷新
例子:github注册
动态获取用户名实时的跟后端确认并实时展示到前端(局部刷新)
朝后端发送请求的方式
1.浏览器地址栏直接输入url回车 GET请求
2.a标签href属性 GET请求
3.form表单 GET请求和POST请求
4.ajax GET请求和POST请求
AJAX 不是新的编程语言,而是一种使用现有标准的新方法
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。
(这一特点给用户的感受是在不知不觉中完成请求和响应过程)
Ajax只学jQuery封装之后的版本(不学原生,原生的复杂并且实际项目中也一般不用)
前端页面使用ajax的时候需要确保导入了jQuery
ps:并不只是jQuery能够实现ajax,其他的框架也可以,原理一样
前端代码
页面上有三个input框 在前两个框中输入数字,点击按钮,朝后端发送ajax请求 后端计算出结果,再返回给前端动态展示的到第三个input框中 (整个过程页面不准刷新,也不能在前端计算) <input type="text" id="d1">+ <input type="text" id="d2">= <input type="text" id="d3"> <p> <button id="btn">点我</button> </p> <script> // 先给按钮绑定一个点击事件 $('#btn').click(function () { // 朝后端发送ajax请求 $.ajax({ // 1.指定朝那个后端发送ajax请求,跟action三种书写方式一致 url:'',// 不写就是朝当前地址提交 // 2.请求方式 type:'post',// 不指定默认就是get 都是小写 // 3.数据 {#data:{'username':'jason','password':123},#} dataType:'JSON', data:{'i1':$('#d1').val(),'i2':$('#d2').val()}, // 4.回调函数:当后端给你返回结果的时候会自动触发,args接受后端的返回结果 success:function (args) { {#alert(args)// 通过DOM操作动态渲染到第三个input里面#} JSON.parse(args) $('#d3').val(args) } }) }) </script> ''' 针对后端如果是用HttpResonse返回的数据,回调函数不会自动帮你反序列化 如果后端直接用的是JsonResponse返回的数据,回调函数会自动帮你反序列化 HttpResponse解决方式 1.自己在前端利用JSON.parse() 2.在ajax里面配置一个参数 '''
views.py
def ab_ajax(request): if request.method == 'POST': print(request.POST) i1 = request.POST.get('i1') i2 = request.POST.get('i2') # 先转成整型再加 i3 = int(i1) + int(i2) print(i3) # d = {'code': 100, 'msg': 'lq'} return HttpResponse(json.dumps(i3)) return render(request, 'index.html')
在django部分,写的代码报错
1.pycharm窗口提示,前端console界面
2.仔细核对代码(单词写错写多)
3.浏览器缓存没有清除
4.端口号可能冲突,一直跑的是之前得项目
5.重启计算机
(多用百度)
# post请求数据的编码格式 get请求数据就是直接放在url后面的 url?username=lq&password=123 # 可以朝后端发送post请求的方式 1.form表单 2.ajax请求 # 前后端传输数据的编码格式 urlencoded formdata json # 研究form表单 1. 默认的数据编码格式是urlencoded,from表单没有enctype属性
<form action="" method="post">
<p>username: <input type="text" name="username" class="form-control"></p>
<p>password: <input type="text" name="password" class="form-control"></p>
<p>file: <input type="file" name="file"></p>
<input type="submit" class="btn btn-success">
<input type="button" class="btn btn-danger" value="按钮" id="d1">
</form>
数据格式:username=lq&password=123 django后端针对符合urlencoded编码格式的数据都会自动解析封装到request.POST中 username=lq&password=123 ----> request.POST 2.form表单可以通过enctype来修改编码格式--->formdata
<form action="" method="post" enctype="multipart/form-data">
<p>username: <input type="text" name="username" class="form-control"></p>
<p>password: <input type="text" name="password" class="form-control"></p>
<p>file: <input type="file" name="file"></p>
<input type="submit" class="btn btn-success">
<input type="button" class="btn btn-danger" value="按钮" id="d1">
</form>
如果你把编码格式改成formdata,那么针对普通的键值对还是解析到request.POST中
而将文件解析到request.FILES中 form表单是没有办法发送json格式数据的 # 研究ajax 默认的编码格式也是urlencoded 数据格式:username=lq&age=18 django后端针对符合urlencoded编码格式的数据都会自动解析封装到request.POST中 username=lq&password=123 ----> request.POST
''' 前后端传输数据的时候一定要确保编码格式跟数据真正的格式是一致的 {"username":"lq","age":18} 在request.POST里面肯定找不到 django针对json格式的数据,不会做任何的处理 request对象方法补充 request.is_ajax() 判断当前请求是否是ajax请求,返回布尔值 ''' # 前端 <script> $('#d1').click(function () { $.ajax({ url:'', type:'post', data:JSON.stringify({'username':'lq','age':18}), contentType:'application/json', // 指定编码格式,不指定默认是urlencoded格式 success:function (args) { } }) }) </script> # 后端 import json def ab_json(request): if request.is_ajax(): print(request.body) # b'{"username":"lq","age":18}' # 针对json格式数据需要自己手动处理 json_bytes=request.body # json_srt=json_bytes.decode('utf-8') # json_dict=json.loads(json_srt) # json补充知识点,json.loads括号内如果传入了一个二进制格式的数据那么内部自动解码再反序列化 json_dict=json.loads(json_bytes) # {'username': 'lq', 'age': 18} <class 'dict'> print(json_dict,type(json_dict)) return render(request, 'ab_json.html') # 总结 ajax发送json格式数据需要注意 1.contentType参数指定成:application/json 2.数据是真正的json格式数据 3.django后端不会帮你处理json格式数据需要你自己去request.body获取并处理
# ajax发送文件需要借助于js内置对象FormData # 前端 <script> // 点击按钮朝后端发送普通键值对和文件数据 $('#d4').on('click',function () { // 1.需要先利用FormData内置对象 let formDataObj=new FormData(); // 2.添加普通的键值对 formDataObj.append('username',$('#d1').val()); formDataObj.append('password',$('#d2').val()); // 3.添加文件对象 formDataObj.append('myfile',$('#d3')[0].files[0]) // jQuery对象转成js对象 // 4.将对象基于ajax发送给后端 $.ajax({ url:'', type:'post', data:formDataObj, // 直接将对象放在data后面即可 // ajax发送文件必须要指定的两个参数 contentType:false, // 不需要使用任何编码,django后端能够自动识别formdata对象 processData:false, // 告诉浏览器不要对数据进行任何处理 success:function (args) { } }) }) </script> # 后端 def ab_file(request): if request.is_ajax: if request.method=='POST': print(request.POST) print(request.FILES) return render(request,'ab_file.html') # 总结 ''' 1.需要利用内置对象FormData // 添加普通的键值对 formDataObj.append('username',$('#d1').val()); formDataObj.append('password',$('#d2').val()); // 添加文件对象 formDataObj.append('myfile',$('#d3')[0].files[0]) 2.ajax发送文件必须要指定的两个参数 contentType:false, // 不需要使用任何编码,django后端能够自动识别formdata对象 processData:false, // 告诉浏览器不要对数据进行任何处理 3.django后端能够直接识别到formdata对象并且能够将内部的普通键值对自动解析并封装到request.POST中 文件数据自动解析并封装到request.FILES中 '''
from django.core import serializers def ab_ser(request): user_queryset=models.User.objects.all() # [{},{},{},{}] # user_list=[] # for user_obj in user_queryset: # tmp={ # 'pk':user_obj.pk, # 'username':user_obj.username, # 'age':user_obj.age, # 'gender':user_obj.get_gender_display() # } # user_list.append(tmp) # return JsonResponse(user_list,safe=False) # return render(request,'ab_ser.html',locals()) # 序列化 res=serializers.serialize('json',user_queryset) # 会自动帮你将数据变成json格式的字符串,并且内部非常的全面 return HttpResponse(res) ''' [ {"pk": 1, "username": "lq", "age": 18, "gender": "male"}, {"pk": 2, "username": "zd", "age": 30, "gender": "female"}, {"pk": 3, "username": "cyz", "age": 6, "gender": "male"}, {"pk": 4, "username": "tank", "age": 33, "gender": "others"} ] 前后端分离的项目 作为后端开发的只需要写代码将数据处理好 能够序列化返回给前端即可 再写一个接口文档,告诉前端每个字段代表的意思即可 [ {"model": "app01.user", "pk": 1, "fields": {"username": "lq", "age": 18, "gender": 1}}, {"model": "app01.user", "pk": 2, "fields": {"username": "zd", "age": 30, "gender": 2}}, {"model": "app01.user", "pk": 3, "fields": {"username": "cyz", "age": 6, "gender": 1}}, {"model": "app01.user", "pk": 4, "fields": {"username": "tank", "age": 33, "gender": 3}} ] 写接口就是利用序列化组件渲染数据然后写一个接口文档 '''
在工作中写项目的时候,先找现成的模块,代码拷贝过来然后二次修改 # sweetalert针对中文可能会出现展示补全的现象 自己找到对应的标签书写css修改样式即可 # 也可以不结合sweetalert就用ajax和bom操作也能完成二次确认 $("#b55").click(function () { swal({ title: "你确定要删除吗?", text: "删除可就找不回来了哦!", type: "warning", showCancelButton: true, // 是否显示取消按钮 confirmButtonClass: "btn-danger", // 确认按钮的样式类 confirmButtonText: "删除", // 确认按钮文本 cancelButtonText: "取消", // 取消按钮文本 closeOnConfirm: false, // 点击确认按钮不关闭弹框 showLoaderOnConfirm: true // 显示正在删除的动画效果 }, function () { var deleteId = 2; $.ajax({ url: "/delete_book/", type: "post", data: {"id": deleteId}, success: function (data) { if (data.code === 0) { swal("删除成功!", "你可以准备跑路了!", "success"); } else { swal("删除失败", "你可以再尝试一下!", "error") } } }) }); })
9 更多详细参考
https://www.cnblogs.com/liuqingzheng/articles/9509764.html
二 分页器
频繁的走数据库操作的时候,效率会呈现指数型下降
用.bulk_create()批量插入时,就是插入一个列表,列表里是表生成的对象 ,不用
orm语句
def ab_pl(request): # 先给Book插入一万条数据 # for i in range(1000): # models.Book.objects.create(title='第{}本书'.format(i)) # 再将所有的数据查询并展示到前端页面 # book_queryset=models.Book.objects.all() # return render(request,'ab_pl.html',locals()) # 批量插入 book_list=[] for i in range(10000):
# 用的是生成对象,不要用orm语句,不要报错 book_obj=models.Book(title='第{}几本书'.format(i)) book_list.append(book_obj) models.Book.objects.bulk_create(book_list) ''' 想要批量插入数据的时候,使用orm给你提供的bulk_create能够大大的减少操作时间 ''' book_queryset=models.Book.objects.all() return render(request,'ab_pl.html',locals())
django中有自带的分页器模块,但是书写起来很麻烦并且功能太简单
所以我们自己想法写自定义分页器
推导过程代码无需掌握,只需要知道内部逻辑即可
基于上述思路,已经封装好的自定义分页器直接拷贝即可
''' django也有内置的分页器模块,但是功能较少代码繁琐不便于使用 所以我们自定义自己的分页器 1.queryset对象是直接切片操作的 2.用户到底要访问那一页,如何确定? url?page=1 current_page=request.GET.get('page',1) 获取到的数据都是字符串类型,需要注意类型转换 3.自己规定每页展示多少条数据 per_page_num=10 4.切片的起始位置和终止位置 start_page=(current-1)*per_page_num end_page=current_page*per_page_num 5.当前数据的总条数 book_queryset.count() 6.如何确定总共需要多少页才能展示完所有的数据 # 利用python内置函数divmod() page_count,more=divmod(all_count,per_page_num) if more: page_count+=1 7.前端模版语法时没有range功能的 # 前端代码不一定非要在前端书写,也可以在后端生成传递给页面 8.针对需要展示的页码需要你自己规划好到底展示多少页码 # 一般情况下页码的个数设计都是奇数(符合审美标准) 11个页码 当前页减5 当前页加6 可以给标签样式选中的页码高亮显示 9.针对页码小于6的情况,需要做处理,不能再减 '''
''' 当需要使用到非django内置的第三方功能或者组件代码的时候 我们一般情况下创建一个名为utils文件夹,在该文件夹内对模块进行功能性划分 utils可以在每个应用下创建,具体结合实际情况 到了后期封装代码的时候,不再局限于函数 还是尽量朝面向对象去封装 自定义的分页器是基于bootstrap样式来的,所以需要提前导入bootstrap bootstrap 版本 v3 jQuery 版本 3.0 '''
mypage.py
class Pagination(object): def __init__(self, current_page, all_count, per_page_num=10, pager_count=11): """ 封装分页相关数据 :param current_page: 当前页 :param all_count: 数据库中的数据总条数 :param per_page_num: 每页显示的数据条数 :param pager_count: 最多显示的页码个数 """ try: current_page = int(current_page) except Exception as e: current_page = 1 if current_page < 1: current_page = 1 self.current_page = current_page self.all_count = all_count self.per_page_num = per_page_num # 总页码 all_pager, tmp = divmod(all_count, per_page_num) if tmp: all_pager += 1 self.all_pager = all_pager self.pager_count = pager_count self.pager_count_half = int((pager_count - 1) / 2) @property def start(self): return (self.current_page - 1) * self.per_page_num @property def end(self): return self.current_page * self.per_page_num def page_html(self): # 如果总页码 < 11个: if self.all_pager <= self.pager_count: pager_start = 1 pager_end = self.all_pager + 1 # 总页码 > 11 else: # 当前页如果<=页面上最多显示11/2个页码 if self.current_page <= self.pager_count_half: pager_start = 1 pager_end = self.pager_count + 1 # 当前页大于5 else: # 页码翻到最后 if (self.current_page + self.pager_count_half) > self.all_pager: pager_end = self.all_pager + 1 pager_start = self.all_pager - self.pager_count + 1 else: pager_start = self.current_page - self.pager_count_half pager_end = self.current_page + self.pager_count_half + 1 page_html_list = [] # 添加前面的nav和ul标签 page_html_list.append(''' <nav aria-label='Page navigation>' <ul class='pagination'> ''') first_page = '<li><a href="?page=%s">首页</a></li>' % (1) page_html_list.append(first_page) if self.current_page <= 1: prev_page = '<li class="disabled"><a href="#">上一页</a></li>' else: prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,) page_html_list.append(prev_page) for i in range(pager_start, pager_end): if i == self.current_page: temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,) else: temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,) page_html_list.append(temp) if self.current_page >= self.all_pager: next_page = '<li class="disabled"><a href="#">下一页</a></li>' else: next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,) page_html_list.append(next_page) last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,) page_html_list.append(last_page) # 尾部添加标签 page_html_list.append(''' </nav> </ul> ''') return ''.join(page_html_list)
# 后端 from utils.mypage import Pagination def ab_page(request): book_queryset=models.Book.objects.all() # 惰性查询 current_page=request.GET.get('page',1) all_count=book_queryset.count() # 1.传值生成对象 page_obj=Pagination(current_page=current_page,all_count=all_count) # 2.直接对总数据进行切片操作 page_queryset=book_queryset[page_obj.start:page_obj.end] # 3.将page_queryset传递到页面,替换之前的book_queryset return render(request,'ab_page.html',locals()) # 前端 {% for book_obj in page_queryset %} <p>{{ book_obj.title }}</p> {% endfor %} {#利用自定义分页器直接显示分页器样式#} {{ page_obj.page_html|safe }}
4 跟多详细参考
https://www.cnblogs.com/liuqingzheng/articles/9509767.html
三 form组件
写一个注册功能
用form表单用户名不能有‘pc’
密码不少于3位
不满足条件的,要有提示信息
1、不要form组件实现注册数据校验和提示信息
# 后端 views.py def ab_form(request): back_dic = {'username': '', 'password': ''} if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') if 'pc' in username: back_dic['username'] = '不能含有这两个字母' if len(password) < 3: back_dic['password'] = '密码太短' ''' 无论是post请求还是get请求 页面都能够获取到字典,只不过get请求来的时候,字典值都是空 而post请求来之后,字典可能有值 ''' return render(request, 'ab_form.html', locals()) # 前端 <form action="" method="post"> <p>username: <input type="text" name="username"> <span style="color: red">{{ back_dic.username }}</span> </p> <p>password: <input type="text" name="password"> <span style="color: red">{{ back_dic.password }}</span> </p> <input type="submit" class="btn btn-info"> </form> ''' 总结 1.手动书写前端获取用户数据的html代码 渲染html代码 2.后端对用户数据进行校验 校验数据 3.对不符合要求的数据进行前端提示 展示提示信息 form组件 能够完成的事情 1.渲染html代码 2.校验数据 3.展示提示信息 为什么数据校验非要到后端,不能在前端利用js直接完成? 数据校验前端可有可无 但是后端必须要有 因为前端的校验不严谨,可以直接修改 或者利用爬虫程序绕过前端页面直接朝后端提交数据 '''
# views.py from django import forms class MyForm(forms.Form): # username字符串类型最小3位最大8位 username=forms.CharField(min_length=3,max_length=8) # password字符串类型最小3位最大8位 password=forms.CharField(min_length=3,max_length=8) # 字符串必须符合邮箱格式 xxx@xx.com email=forms.EmailField()
1.测试环境的准备,可以自己拷贝代码准备
2.pycharm里面已经准备了一个测试环境
python Console
from app01 import views # 1.将带校验的数据组织成字典的形式传入即可 form_obj=views.MyForm({'username':'lq','password':123,'email':321}) # 2.判断数据是否合法 注意该方法只有在所有的数据全部合法的情况下才会返回True form_obj.is_valid() False # 3.查看所有校验通过的数据 form_obj.cleaned_data {'password': '123'} # 4.查看所有不符合校验规则以及不符合的原因 form_obj.errors {'username': ['Ensure this value has at least 3 characters (it has 2).'], 'email': ['Enter a valid email address.']} # 5.校验数据只校验类中出现的字段,多传不影响,多传的字段直接忽略 # 6.校验数据,默认情况下,类里面所有的字段都必须传值 ''' 也意味着校验数据的时候,默认情况下数据可以多传但绝不可能少传 '''
forms组件只会自动帮你渲染获取用户输入的标签(input select radio checkbox)
不能帮你渲染提交按钮
# 后端 views.py class MyForm(forms.Form): # username字符串类型最小3位最大8位 username = forms.CharField(min_length=3, max_length=8,label='用户名') # password字符串类型最小3位最大8位 password = forms.CharField(min_length=3, max_length=8) # 字符串必须符合邮箱格式 xxx@xx.com email = forms.EmailField() def index(request): # 1.先产生一个空对象 form_obj = MyForm() # 2. 直接将该空对象传递给html页面 return render(request, 'index.html', locals()) # 前端 <form action="" method="post"> <p>第一种渲染方式:代码书写极少,封装程度太高,不便于后续的扩展,一般情况下只在本地测试使用</p> {{ form_obj.as_p }} {{ form_obj.as_ul }} {{ form_obj.as_table }} <p>第二种渲染方式:可扩展性很强,但是需要书写的代码太少,一般情况下不用</p> <p>{{ form_obj.username.label }}:{{ form_obj.username }}</p> <p>{{ form_obj.password.label }}:{{ form_obj.password }}</p> <p>{{ form_obj.email.label }}:{{ form_obj.email }}</p> <p>第三种渲染方式(推荐使用):代码书写简单,并且扩展性也高</p> {% for form in form_obj %} <p>{{ form.label }}:{{ form }}</p> {% endfor %} </form> ''' label属性默认展示的是类中定义的字段首字母大写的形式 也可以自己修改,直接给字段对象加label属性即可 username = forms.CharField(min_length=3, max_length=8,label='用户名') '''
浏览器会自动帮你校验数据,但是前端校验不严谨
如何让浏览器不做校验
# 前端 index.html # novalidate属性就让浏览器不做校验 <form action="" method="post" novalidate> {% for form in form_obj %} <p>{{ form.label }}:{{ form }}</p> <span style="color: red">{{ form.errors.0 }}</span> {% endfor %} <input type="submit" class="btn btn-info"> </form> # 后端 views.py class MyForm(forms.Form): # username字符串类型最小3位最大8位 username = forms.CharField(min_length=3, max_length=8, label='用户名', error_messages={ 'min_length':'用户名最少3位', 'max_length':'用户名最大8位', 'required':'用户名不能为空' }) # password字符串类型最小3位最大8位 password = forms.CharField(min_length=3, max_length=8, label='密码', error_messages={ 'min_length': '密码最少3位', 'max_length': '密码最大8位', 'required': '密码不能为空' } ) # 字符串必须符合邮箱格式 xxx@xx.com email = forms.EmailField(label='邮箱', error_messages={ 'invalid':'邮箱格式不正确', 'required':'邮箱不能为空' } ) def index(request): # 1.先产生一个空对象 form_obj = MyForm() if request.method=='POST': # 获取用户数据并且校验 ''' 1)数据获取繁琐 2)校验数据需要构造成字典的格式传入才行 ps:request.POST可以看成就是一个字典 ''' # 3) 校验数据,form_obj的名一定要跟空对象form_obj一样 # 错误数据才能留在浏览器上 form_obj=MyForm(request.POST) # 4) 判断数据是否合法 if form_obj.is_valid(): # 5) 如果合法,操作数据库存储数据 return HttpResponse('OK') # 6) 不合法,有错误 # 2. 直接将该空对象传递给html页面 return render(request, 'index.html', locals()) ''' 1.必备的条件,get请求和post传给html页面对象变量名必须一样,不合规的注册信息继续留在input框中,让注册者修改 2.forms组件当你的数据不合法的情况下,会保存你上次的数据,让你基于之前的结果进行修改 更加的人性化 针对错误的提示信息还可以自定制,类属性中error_messages字典中添加 '''
在特定的节点自动触发完成响应操作
钩子函数在forms组件中就类似于第二道关卡,能够让我们自定义校验规则
钩子函数写在类里面
在forms组件中有两类钩子
1.局部钩子
当你需要给单个字段增加校验规则的时候可以使用
2.全局钩子
当你需要给多个字段增加校验规则的时候可以使用
# 实际案例 # 1)校验用户名中不能含有666 只是校验username字段 局部钩子 # 2)校验密码和确认密码是否一致 password confirm_password两个字段 全局钩子 # 钩子函数,在类里面书写方法即可 # 局部钩子 views.py MyForm类下 def clean_username(self): # 获取到用户名 username=self.cleaned_data.get('username') if 'pc' in username: # 提示前端展示错误信息 self.add_error('username','不能含有pc字母') # 将钩子函数沟出来,再放回去 return username # 全局钩子 def clean(self): password=self.cleaned_data.get('password') confirm_password=self.cleaned_data.get('confirm_password') if not confirm_password==password: self.add_error('confirm_password','两次密码不一致') # 将钩子函数沟出来的数据再放回去 return self.cleaned_data
label 字段名 error_messages 自定义报错信息 initial 默认值 required 控制字段是否必填 widget 前端input框css样式属性控制 ''' 1.字段没有样式 2.针对不同类型的input如何修改 text password date radio checkbox ''' username = forms.CharField(min_length=3, max_length=8, label='用户名', initial='lq', required=False, error_messages={ 'min_length': '用户名最少3位', 'max_length': '用户名最大8位', 'required': '用户名不能为空' }, widget=forms.widgets.TextInput(attrs={'class': 'form-control c1 c2'}) ) # 多个样式属性值,直接空格隔开即可 # 第一道关卡里面还支持正则校验 phone = forms.CharField( validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头') ], )
# 单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() ) # 多选Select hobby = 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 hobby = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"),), label="爱好", initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple() )
9 forms组件源码(编程思想)
入口:form_obj.is_valid()
@property def errors(self): if self._errors is None: # 第二步 self.full_clean() return self._errors # 入口 def is_valid(self): return self.is_bound and not self.errors def full_clean(self): """ Cleans all of self.data and populates self._errors and self.cleaned_data. """ # 三步,建字典 self._errors = ErrorDict() if not self.is_bound: # Stop further processing. return self.cleaned_data = {} # If the form is permitted to be empty, and none of the form data has # changed from the initial data, short circuit any validation. if self.empty_permitted and not self.has_changed(): return # 第四步,顺序执行 # 局部钩子 self._clean_fields() # 全局钩子 self._clean_form() # form组件内部钩子,额外额校验执行, self._post_clean() # 局部钩子 def _clean_fields(self): # 第五步 # 对forms组件的每个字段进行循环取值 for name, field in self.fields.items(): # value_from_datadict() gets the data from the data dictionaries. # Each widget type knows how to retrieve its own data, because some # widgets split data over several HTML fields. if field.disabled: value = self.get_initial_for_field(field, name) else: value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) try: if isinstance(field, FileField): initial = self.get_initial_for_field(field, name) value = field.clean(value, initial) else: value = field.clean(value) self.cleaned_data[name] = value # 第六步,反射判断,局部钩子对应字段名 if hasattr(self, 'clean_%s' % name): # 取值执行 value = getattr(self, 'clean_%s' % name)() self.cleaned_data[name] = value except ValidationError as e: # 校验不同过反馈的错误信息 self.add_error(name, e) # 全局钩子,已经清洗过的数据 def _clean_form(self): try: cleaned_data = self.clean() except ValidationError as e: self.add_error(None, e) else: if cleaned_data is not None: self.cleaned_data = cleaned_data def _post_clean(self): """ An internal hook for performing additional cleaning after form cleaning is complete. Used for model validation in model forms. """ pass
10 更多详细参考
https://www.cnblogs.com/liuqingzheng/articles/9509775.html