Django——Ajax

Ajax操作(重点)


Ajax简介

'''
异步提交,局部刷新(类似github的注册页面)
	动态获取用户输入,实时与后端确认并实时展示的前端(局部刷新)
往后端发送请求的方式:
	1、浏览器输入url地址	 (GET请求)
	2、a标签的href属性跳转 	  (GET请求)
	3、form表单提交			(GET/POST请求)
	4、ajax				   (GET/POST请求)
'''
# Ajax简介
#	1、AJAX 不是新的编程语言,而是一种使用现有标准的新方法(比较装饰器)
#	2、AJAX最大的优点就是能够在不刷新页面的情况下与后端实时交换数据跟新页面局部内容
#	  (给用户感觉像是已经完成了请求响应过程)
#	3、这里我们只学习jQuery封装之后的版本,因此使用ajax是确保jquery已经导入了
#	4、除了jQuery还有其他的框架也能够实现ajax,原理一致,只是语法不同罢了


ajax简单使用

'''
页面上有三个input框
	在前两个框中输入数字 点击按钮 朝后端发送ajax请求
	后端计算出结果 再返回给前端动态展示的到第三个input框中
	(整个过程页面不准有刷新,也不能在前端计算)
	
ajax请求格式
$.ajax({
	url:'',   // 1.指定向哪个后端提交ajax请求,不填则默认向当前地址提交
	type:'',  // 2.指定请求提交方式:get/post  默认是get请求
	data:{}, // 3.要提交给后端的数据
	# dataType:'', //预期服务器返回的数据类型
	// 4.回调函数:当后端返回结果时会自动触发,args用来接收后端的返回结果
	success:function (args){}
})

针对后端返回字典类型数据:
	HttpResponse返回 (注意:HttpResponse只能返回字符串)
    	必须先将字典转成json格式才能返回,否则无法返回完整的字典
    JsonResponse返回,底层已经自动的帮你序列化成了json数据返回
针对前端接收字典类型数据:
	dataType //预期服务器返回的数据类型
	HttpResponse返回的json数据,回调函数不会自动帮我们反序列化
		需要我们自己手动来反序列化
			方法1:在ajax中设置dataType参数
			方法2:自己手动JSON.parse()来反序列化
	JsonResponse返回的json数据,回调函数会自动进行反序列化,拿到手的就是一个对象了
    
'''
# 后端
'''
# import json
from django.http import JsonResponse
def test_ajax(request):
    if request.method == 'POST':
        inp1 = request.POST.get('inp1')
        inp2 = request.POST.get('inp2')
        inp3 = int(inp1) + int(inp2)
        # return HttpResponse(inp3)
        # json_d = json.dumps({'sum': inp3}) # 将字典转成json格式数据
        # HttpResponse只能返回字符串
        # return HttpResponse(json_d)
        return JsonResponse(d)
    return render(request, 'test.html')
'''
# 前端
'''
<input id="inp1" type="text"> +
<input id="inp2" type="text"> =
<input id="inp3" type="text">
<input id="btn1" class="btn btn-dark" type="button" value="求和">
<script>
    $('#btn1').click(function () {
        // 朝后端发送ajax请求
        $.ajax({
            // 1.指定向哪个后端提交ajax请求,不填则默认向当前地址提交
            url: '',
            // 2.指定请求提交方式:get/post  默认是get请求
            type: 'post',
            // 3.要提交给后端的数据
            data:{'inp1':$('#inp1').val(), 'inp2':$('#inp2').val()},
            // 4.回调函数:当后端返回结果时会自动触发,args用来接收后端的返回结果
            // dataType:'json', //预期服务器返回的数据类型
            success:function (args) {
                // $('#inp3').val(args);
                // $('#inp3').val(JSON.parse(args).sum);
                // $('#inp3').val(args.sum);
            },
        })
    })
</script>
'''

前后端传输数据编码格式

'''前提:前后端数据传输时一定要保证编码格式与数据真正格式是一致的'''
# get请求数据的编码格式直接跟在了url后面
'''url?name=123&age=123'''

'''
可以发送post请求的方式:
	1、form表单
	2、ajax请求
	
前后端传输数据的编码格式:
	1、urlencoded
	2、formdata
	3、json
'''
# form表单post提交:
'''
1、默认提交的编码格式:urlencoded
	数据格式:user=yumi&pwd=123
django后端针对符合urlencoded编码格式的数据会自动解析封装到request.POST中
	
2、涉及文件提交的编码格式:form-data
	数据格式:不显示
那么针对普通数据还是解析封装到request.POST中,但是文件数据解析封装到了request.FILES中了

3、form表单无法提交json格式数据
'''

# ajax请求post提交:
'''
1、默认提交的编码格式:urlencoded
	数据格式:user=yumi&pwd=123&file=C%3A%5Cfakepath%5C2020-03-21-19-28-39_0.png
django后端针对符合urlencoded编码格式的数据会自动解析封装到request.POST中
'''

ajax发送json格式数据

'''
<script>
$('#btn').click(function(){
	$.alax({
		url:'',
		type:'post',
		data:JSON.stringify({'user': 'yumi', 'age': 21}),
		contentType:'applecation/json',
		success:function(args){}
	})
})
</script>
'''
# 提交方式:applecation/json
# 数据格式:{"user":"yumi","age":21}
json_byte = request.body
# json_str = json_byte.decode(json_byte)
# json.loads括号内如果传入了一个二进制格式的数据其内部会自动解码再反序列化
dic = json.loads(json_byte)
'''
注意:针对json数据,后端不会做任何处理,也就不会帮你解析封装到request.POST中
	而是变成了二进制的请求,此时你可以用request.body来获取二进制请求数据

request对象方法补充:request.is_ajax()判断请求是否是ajax请求,返回布尔值
'''

'''
ajax请求发送json格式数据时需要注意:
	1、要指定提交方式:设置contentType参数并且指定为'application/json'
	2、数据必须是真正的json格式数据:data:JSON.stringify({'user': 'yumi', 'age': 21})
	3、针对json格式数据django后端需要自己通过request.body来手动获取处理
'''

ajax发送文件

'''
$('#btn').click(function () {
	// 1 需要先利用FormData内置对象
    let formData = new FormData();
    // 2 添加普通的键值对
    formData.append('user', $('#inp1').val());
    formData.append('pwd', $('#inp2').val());
    // 3 添加文件对象
    formData.append('file', $('#f1')[0].files[0]); //先将jQuery对象转成标签对象再进一步获取文件数据
    // 4 将对象基于ajax发送给后端
    $.ajax({
        url:'',
        type:'post',
        data:formData, //直接将对象放data后面即可
        //ajax发送文件时必须要指定的两个参数:
        contentType:false, // 不需使用任何编码 django后端能够自动识别formdata对象
        processData:false, // 告诉浏览器不要对你的数据进行任何处理
        
        success:function (args) {
            {#alert(args)#}
        }
    })
})
'''
if request.is_ajax():
    if request.method == 'POST':
        print(request.POST)
        print(request.FILES)

'''
总结:
	1、利用FormData内置对象
		let formData = new FormData()
	2、针对普通键值对,直接往对象里添加即可
		formData.append('username',$('#d1').val());
	3、针对文件数据,需要先将文件对应的jQuery对象转成标签对象再通过files获取真正的文件数据再添加到formData对象中
	4、ajax发送文件时必须要指定两个关键性的参数
		contentType:false, // 不需使用任何编码
		processData:false, // 告诉浏览器不要对你的数据进行任何处理
	5、django后端自动识别formdata对象
		自动将内部的普通键值对解析封装到request.POST中
		自动将文件数据解析封装到request.FILES中
'''

ajax结合sweetalert实现删除按钮的二次确认操作

'''
知道如何CV代码
学会基于别人代码的基础之上做修改
研究别人代码中各个参数的意思,再照猫画虎的加些效果
'''
'''
$('.del').click(function () {
        let deleteBtn = $(this);
        swal({
                title: "真的要删除吗?",
                text: "请确认以备份数据",
                type: "warning",
                showCancelButton: true,
                confirmButtonClass: "btn-danger",
                confirmButtonText: "删除",
                cancelButtonText: "返回",
                closeOnConfirm: false,
                closeOnCancel: false,
                showLoaderOnConfirm: true //遇到ajax请求遇到延迟时的等待效果
            },
            function (isConfirm) {
                if (isConfirm) {
                    $.ajax({ // 朝后端发送ajax请求删除数据之后 再弹下面的提示框
                        url:'',
                        type:'post',
                        data:{'del_id':deleteBtn.attr('delete_id')},
                        success:function (args) {
                            swal("删除成功", "已删除", "success");
                            if (args.code === 1000) { // 判断响应状态码 然后做不同的处理
                                swal("删除成功", args.msg, "success");
                                // 删除成功后刷新页面的两种方式
                                // 1、直接刷新当前页面
                                // window.location.reload()
                                // 2、利用DOM操作,动态刷新
                                deleteBtn.parent().parent().remove();
                            } else {
                                swal("删除失败", '内部未知错误', "info");
                            }
                        }
                    })
                } else {
                    swal("删除失败", "取消删除", "error");
                }
            });
'''

django自带的序列化组件(drf铺垫)

# 需求:在前端给我获取到后端用户表里面所有的数据 并且要是列表套字典
'''只需将获取的数据重新组成一个个字典然后添加到列表中转成json格式数据传给前端即可'''
# 方法一:手动拼接字典
'''
from django.http import JsonResponse
def ab_serializers(request):
    queryset = models.User.objects.all()
    tmp_list = []
    for obj in  queryset:
        tmp = {
            'pk': obj.pk,
            'user': obj.user,
            'age': obj.age,
            'gender': obj.gender
        }
        tmp_list.append(tmp)
    return JsonResponse(tmp_list, safe=False)
'''

# 方法二:使用serializers模块
from django.core import serializers
'''
from django.core import serializers
def ab_ser(request):
    # 需求:在前端给我获取到后端用户表里面所有的数据 并且要是列表套字典
    queryset = models.User.objects.all()
    """会自动将数据序列化变成json格式的字符串 并且内部非常的全面"""
    res = serializers.serialize('json', queryset)
    return HttpResponse(res)
'''
"""
[
 {"pk": 1, "username": "jason", "age": 25, "gender": "male"}, 
 {"pk": 2, "username": "egon", "age": 31, "gender": "female"},
 {"pk": 3, "username": "kevin", "age": 32, "gender": "others"}, 
 {"pk": 4, "username": "tank", "age": 40, "gender": 4}
 ]
前后端分离的项目
    作为后端开发的你只需要写代码将数据处理好
    能够序列化返回给前端即可 
        再写一个接口文档 告诉前端每个字段代表的意思即可
        
[
{   "model": "app01.user", 
    "pk": 1, 
    "fields": {"username": "jason", "age": 25, "gender": 1}}, 
    
{   "model": "app01.user", 
    "pk": 2, 
    "fields": {"username": "egon", "age": 31, "gender": 2}}, 
    
{   "model": "app01.user", 
    "pk": 3, 
    "fields": {"username": "kevin", "age": 32, "gender": 3}},
     
{   "model": "app01.user", 
    "pk": 4, 
    "fields": {"username": "tank", "age": 40, "gender": 4}}
]
写接口就是利用序列化组件渲染数据然后写一个接口文档 该交代交代一下就完事
"""

批量插入

'''
批量插入数据的时候 使用orm给提供的bulk_create能够大大的减少操作时间
'''

def ab_pl(request):
    # 先给Book插入一万条数据
    # for i in range(10000):
    #     models.Book.objects.create(title='第%s本书'%i)
    # # 再将所有的数据查询并展示到前端页面
    book_queryset = models.Book.objects.all()

    # 批量插入
    # book_list = []
    # for i in range(100000):
    #     book_obj = models.Book(title='第%s本书'%i)
    #     book_list.append(book_obj)
    # models.Book.objects.bulk_create(book_list)
    """
    当你想要批量插入数据的时候 使用orm给你提供的bulk_create能够大大的减少操作时间
    :param request: 
    :return: 
    """
    return render(request,'ab_pl.html',locals())

自己写一个分页器(只需掌握分页器的推到思路即可)

"""
总数据100 每页展示10 需要10
总数据101 每页展示10 需要11
总数据99 每页展示10  需要10

在制作页码个数的时候 一般情况下都是奇数个		符合中国人对称美的标准
"""
'''分页器逻辑推导'''
# 当前页面
current_page = request.GET.get('page')
count_book = models.Book.objects.count()
# 每页数据量
per_page_num = 10
# 解压复制获取总页数以及多出的数据
count_page, more = divmod(count_book, per_page_num)
if more:
    # 多处数据存在则总页数+1
    count_page += 1
# 将前端传回来的字符串页面数转成整形,页面不存在则设置当前页为第一页
try:
    current_page = int(current_page)
    if current_page > count_page:
        current_page = 1
except Exception:
    current_page = 1
# 起始位置
start_page = (current_page - 1) * per_page_num
# 终止位置
end_page = current_page * per_page_num

page_html = ''
is_active = current_page
if current_page < 6:
    current_page = 6
for i in range(current_page-5, current_page+6):   # 限制页码显示个数为11
    if is_active == i: # 在后端写好html代码然后前端通关转义直接使用
        page_html += '<li class="page-item active"><a class="page-link" href="?page=%s">%s</a></li>' % (i, i)
    else:
        page_html += '<li class="page-item"><a class="page-link" href="?page=%s">%s</a></li>' % (i, i)

book_queryset = models.Book.objects.all()[start_page:end_page]
return render(request, 'ab_pl.html', locals())

'''
django有自带的分页器模块但是书写起来太过麻烦且功能太简单,因此推荐使用自定义分页器
对于上述分页器代码,只需要知道内部逻辑推导即可,
基于上述思路已经封装好了自定义分页器,之后有需要直接拷贝使用即可
'''

自定义分页器的使用

  • 自定义分页器封装代码
# 自定义分页器
class Pagination(object):
    def __init__(self, current_page, all_count, per_page_num=2, pager_count=7):
        """
        封装分页相关数据
        :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 example'>
                    <ul class='pagination'>
                ''')
        first_page = '<li class="page-item"><a class="page-link" href="?page=%s">首页</a></li>' % (1)
        page_html_list.append(first_page)

        if self.current_page <= 1:
            prev_page = '<li class="disabled page-item"><a class="page-link" href="#">上一页</a></li>'
        else:
            prev_page = '<li class="page-item"><a class="page-link" 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 page-item"><a class="page-link" href="?page=%s">%s</a></li>' % (i, i,)
            else:
                temp = '<li class="page-item"><a class="page-link" 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 page-item"><a class="page-link" href="#">下一页</a></li>'
        else:
            next_page = '<li class="page-item"><a class="page-link" href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
        page_html_list.append(next_page)

        last_page = '<li class="page-item"><a class="page-link" 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)

  • 自定义分页器使用——后端
 def get_book(request):
   book_list = models.Book.objects.all()
   current_page = request.GET.get("page",1)
   all_count = book_list.count()
   page_obj = Pagination(current_page=current_page,all_count=all_count,per_page_num=10)
   page_queryset = book_list[page_obj.start:page_obj.end]
   return render(request,'booklist.html',locals())

  • 自定义分页器使用——前端
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            {% for book in page_queryset %}
            <p>{{ book.title }}</p>
            {% endfor %}
            {{ page_obj.page_html|safe }}
        </div>
    </div>
</div>

forms组件


前戏

"""
写一个注册功能
	获取用户名和密码 利用form表单提交数据
	在后端判断用户名和密码是否符合一定的条件
		用户名中不能含有666
		密码不能少于三位
	
	如何符合条件需要你将提示信息展示到前端页面
"""
def ab_form(request):
    back_dic = {'username':'','password':''}
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if '666' in username:
            back_dic['username'] = '不能666'
        if len(password) < 4:
            back_dic['password'] = '密码不能少于4位'
    """
    无论是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.对不符合要求的数据进行前端提示					  	  展示提示信息

forms组件能够完成的事情
			1.渲染html代码
			2.校验数据
			3.展示提示信息

为什么数据校验非要去后端 不能在前端利用js直接完成呢?
	因为前端校验不安全,可以直接修改,爬虫程序也可以绕过前端页面直接朝后端提交数据
	
	购物网站	
		选取了货物之后 会计算一个价格发送给后端 如果后端不做价格的校验
		
		实际是获取到用户选择的所有商品的主键值
		然后在后端查询出所有商品的价格 再次计算一遍
		如果跟前端一致 那么完成支付如果不一致直接拒绝
"""

基本使用

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)
    # email字段必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField()

校验数据

"""
1.测试环境的准备 可以自己拷贝代码准备
2.其实在pycharm里面已经帮你准备一个测试环境
	python console
	
基本方法:
	1、is_valid():判断是否合法,只有所有都合法才返回true
	2、errors:查看所有不符合校验规则以及不符合的原因
	3、cleaned_data:查看所有校验通过的数据
"""
from app01 import views
# 1 将带校验的数据组织成字典的形式传入即可
form_obj = views.MyForm({'username':'jason','password':'123','email':'123'})
# 2 判断数据是否合法		注意该方法只有在所有的数据全部合法的情况下才会返回True
form_obj.is_valid()
False
# 3 查看所有校验通过的数据
form_obj.cleaned_data
{'username': 'jason', 'password': '123'}
# 4 查看所有不符合校验规则以及不符合的原因
form_obj.errors
{
  'email': ['Enter a valid email address.']
}
# 5 校验数据只校验类中出现的字段 多传不影响 多传的字段直接忽略
form_obj = views.MyForm({'username':'jason','password':'123','email':'123@qq.com','hobby':'study'})
form_obj.is_valid()
True
# 6 校验数据 默认情况下 类里面所有的字段都必须传值
form_obj = views.MyForm({'username':'jason','password':'123'})
form_obj.is_valid()
False
"""
也就意味着校验数据的时候 默认情况下数据可以多传但是绝不可能少传
"""

渲染标签

'''
forms组件只会自动帮我们渲染获取用户输入的标签(input、select、radio、checkbox)
无法渲染提交按钮
'''
def register(request):
    # 产生一个空对象
    form_obj = MyForms()
    # 直接将空对象传递给html页面
    return render(request,'register.html',locals())

# 渲染标签的三种方式
'''
方式一:
	优点:代码简洁
	缺点:封装程度太高,不利于扩展
	{{ form_obj.as_p }}
	{{ form_obj.as_ul }}
	{{ form_obj.as_table }}
'''
'''
方式二:
	优点:可扩展性强
	缺点:代码过于冗余
	<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>
'''
'''
方式三:
	优点:代码简洁,可扩展性也高,推荐使用
	{% for form in form_obj %}
		<p>{{ form.label }}:{{ form }}</p>
	{% endfor %}
	
label属性默认展示的是类中定义的字段首字母大写的形式
也可以自己修改 直接给字段对象加label属性即可
	 username = forms.CharField(min_length=3,max_length=8,label='用户名')
'''

展示信息

'''
使用forms组件时浏览器会自动帮我们校验数据,但是前端的校验太弱了
可以给form标签设置novalidate参数使浏览器不自动校验
forms组件当数据不合法的情况下,会保存上次的数据 让你基于之前的结果进行修改
更加的人性化
'''
def register(request):
    # 1、先产生空对象
    form_obj = MyForms()
    if request.method == 'POST':
        # 3、获取数据信息并校验
        """
        1、校验数据需要传入字典格式
        2、request.POST可以看成是一个字典
        3、保证get请求与post请求传给html页面的变量名必须一样
        """
        form_obj = MyForms(request.POST)
        # 4、判断数据是否合法 ps只有当类中数据全部合法才算合法
        if form_obj.is_valid():
            # 合法时的操作
            return HttpResponse('OK')
        
    # 2、将空对象传递给html页面
    return render(request,'register.html',locals())

{% for form in form_obj %}
        <p>{{ form.label }}:{{ form }}
            <span style="color: red">{{ form.errors.0 }}</span></p>
{% endfor %}

# 针对错误提示可以使用error_messages来自定义错误提示 (第一道防线)
'''
error_messages中需要记忆的几个参数
	1、'invalid': '邮箱不合法'   用于邮箱不合法时的错误提示
	2、'required': 'xxx不能为空' 用于提交信息为空时的错误提示
'''
# 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': "密码不能为空"})
    # email字段必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField(label='邮箱',
                             error_messages={
                                 'invalid':'邮箱格式不正确',
                                 'required': "邮箱不能为空"})

钩子函数(HOOK)

'''
钩子函数:在特定的节点自动触发完成相应操作
钩子函数在forms组件中就类似于第二道防线,能够让我们自定义校验规则

在forms组件中有两类钩子
	1.局部钩子
		案例:校验用户输入的用户名必须是字母数字下划线的组合
		当你需要给单个字段增加校验规则的时候可以使用
	2.全局钩子
		案例:校验用户两次密码输入是否一致
  		当你需要给多个字段增加校验规则的时候可以使用
'''
# 局部钩子
def clean_user(self):
    # 获取到用户名
    user = self.cleaned_data.get('user')
    obj = models.User.objects.filter(user=user).first()
    if obj:
        # 提示前端展示错误信息
        self.add_error('user', '用户名已存在')
    # 将钩子函数钩去出来数据再放回去
    return user
# 全局钩子
def clean(self):
    pwd = self.cleaned_data.get('pwd')
    pwd2 = self.cleaned_data.get('pwd2')
    if pwd != pwd2:
        self.add_error('pwd2', '两次密码不一致')
    # 将钩子函数钩出来数据再放回去
    return self.cleaned_data

其他参数及补充知识点

# 必须掌握的
'''
label	自定义字段名
error_messages	自定义报错信息(第一道防线)
initial		给字段设置默认值
required	控制字段是否必填
render_value=True   展示错误信息时让密码不重置
PasswordInput(render_value=True) 默认为False,展示错误信息重置密码
'''

# 修改字段样式
# EmailInput会将input的type属性由text修改成email
widget=forms.widgets.EmailInput(attrs={'class': 'form-control', 'placeholder': '邮箱', 'name': 'email'}),
# PasswordInput会将input的type属性由text修改成password
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control', 'placeholder': '密码', 'name': 'pwd'}),
# 多个属性值之间用空格分隔

# 第一道防线————正则校验
from django.core.validators import RegexValidator
validators=[
    RegexValidator(r'^1[3456789]\d{9}', '手机号格式错误')
]

其他类型渲染

    # 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()
    )

forms组件源码

'''forms组件内部原理'''
'''
通过手动抛出异常来展示提示信息(通过看源码知道的第二种展示信息的方法)
# 不推荐,应为过于繁琐,知道有这个方法即可
from django.core.exceptions import ValidationError
raise ValidationError('用户名已存在')
'''
# 切入点:forms_obj.is_valid()
    def is_valid(self):
        """
        Returns True if the form has no errors. Otherwise, False. If errors are
        being ignored, returns False.
        """
        return self.is_bound and not self.errors

# self.is_bound:只要你往组件传值(request.POST)那么self.is_bound就为True

# self.errors
	@property #(让被装饰的函数直接通过对象点属性的方式调用)
    def errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None: # 一定会触发,因为self._errors默认就是None
            self.full_clean()
        return self._errors
    
# self.full_clean()
    def full_clean(self):
        """
        Cleans all of self.data and populates self._errors and
        self.cleaned_data.
        """
        self._errors = ErrorDict() # 我们的errors就是从这里开始创建出来的
        if not self.is_bound:  # 只要传值了就不会触发
            return
        self.cleaned_data = {} # 我们的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
	    
        # forms组件的精髓就在下面三句代码里
        self._clean_fields() # 校验字段、局部钩子
        self._clean_form() # 全局钩子
        self._post_clean()
        
# self._clean_fields()
    def _clean_fields(self):
        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.
            ...
            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)

# self._clean_form() # 全局钩子
    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
posted @ 2020-06-02 16:53  群青-Xi  阅读(277)  评论(0编辑  收藏  举报