55.1 ajax && 多对对表关系创建的三种方式
1. 多对多表关系 的三种创建方式
建议使用半自动,可以扩展第三张表字段 还可以使用orm正反向查询
多对多三种创建方式 1.全自动(较为常用) class Book(models.Model): title = models.CharField(max_length=32) authors = models.ManyToManyField(to='Author') # orm就会自动帮你创建第三张表 class Author(models.Model): name = models.CharField(max_length=32) """ 好处:第三张表自动创建 不足之处:第三张表无法扩展额外的字段 """ 2.纯手动(了解) class Book(models.Model): title = models.CharField(max_length=32) class Author(models.Model): name = models.CharField(max_length=32) class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author') create_time = models.DateField(auto_now_add=True) """ 好处在于第三表可以扩展额外的字段 不足之处:orm查询的时候会带来不便 """ 3.半自动(推荐) class Book(models.Model): title = models.CharField(max_length=32) authors = models.ManyToManyField(to='Author',through='Book2Author',through_fields=('book','author')) class Author(models.Model): name = models.CharField(max_length=32) books = models.ManyToManyField(to='Author',through='Book2Author',through_fields=('author','book')) class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author') create_time = models.DateField(auto_now_add=True) """ 好处在于第三步可以扩展任意的额外字段 还可以利用orm 正反向查询 不足之处:无法利用 add set remove clear 虽然无法使用了 但是你还可以自己直接操作第三表
2.Ajax (基于js的功能模块,后面学的是基于JQ的Ajax) 异步提交 局部刷新
1.Ajax介绍
Ajax 异步提交 同步与异步 同步 任务提交之后原地等待任务的返回结果 进程表现上来说 阻塞 异步 任务提交之后不需要原地等待返回结果 直接执行下一行代码 进程表现上来说 非阻塞 任务的结果肯定是需要的 是通过异步回调机制 callback() 局部刷新 与后端进行交互的方式 1.浏览器窗口输入url回车 GET 2.a标签href属性填写url点击 GET 3.form表单 GET/POST 4.Ajax GET/POST Ajax并不是一门新的语言 它其实就是基于js写的一个功能模块而已 由于原生js书写ajax较为繁琐 所以我们直接学jQuery封装号的ajax模块操作
2.Ajax 基本用法
初识ajax 案例:页面上有三个input框 一个按钮 用户在前两个框中输入数字 点击按钮保证页面不刷新的情况下将数据发到后端做计算 将计算好的结果再发给前端展示到第三个input框中 ajax基本语法结构 // ajax基本语法 $.ajax({ // 1.到底朝哪个后端提交数据 url:'', // 控制数据的提交路径 有三种写法 跟form表单的action属性一致 // 2.指定当前请求方式 type:'post', // 3.提交的数据 data:{'i1':$('#i1').val(),'i2':$('#i2').val()}, // 4.ajax是异步提交 所以需要给一个回调函数来处理返回的结果 success:function (data) { // data就是异步提交的返回结果 // 将异步回调的结果通过DOM操作渲染到第三个input框中 $('#i3').val(data) } })
解释:url 三种提交方式与form表单的action相同 不写向当前打开的页面提交,写‘/*’向系统某个页面提交,全路径 向某个网站提交
分析:点击按钮 就把第前两个数的和渲染到第三个框中
步骤:按钮绑定点击事件,点击按钮就向后端发送Ajax 请求,请求后返回数据渲染到第三个框中
前端代码 有发送数据和接收数据的两个过程 默认格式是urlencoded格式 username=jason&password=‘123’ django会自动解析到request.POST中
body> <input type="text" id="i1"> + <input type="text" id="i2"> = <input type="text" id="i3"> <button id="d1">按钮</button> <script> $('#d1').click(function () { // 获取两个框里面内容 朝后端提交异步请求 // ajax基本语法 $.ajax({ // 1.到底朝哪个后端提交数据 url:'', // 控制数据的提交路径 有三种写法 跟form表单的action属性一致 // 2.指定当前请求方式 type:'post', // 3.提交的数据 data:{'i1':$('#i1').val(),'i2':$('#i2').val()}, // 4.ajax是异步提交 所以需要给一个回调函数来处理返回的结果 success:function (data) { // data就是异步提交的返回结果 // 将异步回调的结果通过DOM操作渲染到第三个input框中 $('#i3').val(data) } })
views层
# Create your views here. def index(request): if request.method == 'POST': i1 = request.POST.get('i1') i2 = request.POST.get('i2') # i1 和 i2 是字符串类型 需要先做类型转换 i3 = int(i1) + int(i2) return HttpResponse(i3) return render(request,'index.html')
3.Content-Type参数
这里可以扩展
1.Ajax上传文件(前端 ,views, models) models 存file文件字段
FileField(Field)
upload_to
=
'指定文件路径'
upload_to
=
'指定文件路径'
2.前端发送json数据FormData application/json
后台响应json数据(JsonResonse)或serializers drf会有更好的方法
3. form表单上传文件前后端type=‘file’
content·-Type: 前后端传输数据的三种编码格式(urlencoded,formdata,application/json)
form表单 不能发送application/json 格式数据 普通的键值对(urlencoded) 大字典封装到request.POST中, 文件(formdata) 封装到request.FILES中
form表单 默认是urlencoded编码格式传输数据 urlencoded数据格式 username=jason&password=123 django后端针对该格式的数据 会自动解析并帮你打包到request.POST中 formdata数据格式 django后端针对符合urlencoded编码格式数据(普通键值对)还是统一解析到request.POST中 而针对formdata文件数据就会自动解析放到request.FILES中
Ajax 提交数据
默认urlencode 可以发送application/json 格式的数据 django不作任何处理 直接放到request.body里面 二进制见图解
文件 内置对象FormData
ajax提交 ajax默认的也是urlencoded编码格式 前后端数据交互 编码格式与数据格式一定要一致 不能骗人家!!! application/json django后端针对json格式数据 并不会做任何的处理 而是直接放在request.body中 $('#d2').on('click',function () { $.ajax({ url:'', type:'post', // 修改content-Type参数 contentType:'application/json', data:JSON.stringify({'username':'jason','password':123}), // 将数据序列化成json格式字符串 success:function (data) { alert(data) } }) }) ajax发送文件(******) 内置对象FormData 即发普通键值对也发文件 // ajax发送文件数据 需要借助于内置对象 $('#d3').click(function () { // 1 需要先生成一个内置对象 var myFormData = new FormData(); // 2 传普通键值对 当普通键值对较多的时候 我们可以利用for循环来添加 myFormData.append('username','jason'); myFormData.append('password',123); // 3 传文件 myFormData.append('myfile',$('#i1')[0].files[0]); // 获取input框内部用户上传的文件对象 // 发送ajax请求 $.ajax({ url:'', type:'post', data:myFormData, // 发送formdata对象需要指定两个关键性的参数 processData:false, // 让浏览器不要对你的数据进行任何的操作 contentType:false, // 不要使用任何编码格式 对象formdata自带编码格式并且django能够识别该对象 success:function (data) { alert(data) } }) })
解释:
Ajax 发送 application/json 数据格式 数据
request.body 是二进制数据 需要自己处理
注:json.loads可以直接将二进制数据解码成字典格式,可以不用decode
form表单发送文件
前端使用type=‘file’ 后端 request.FILES.get('filename') 即可获取文件对象
非form表单上传文件
前端 使用内置FormData对象(可以上传键值对也可以上传文件) 结合Ajax(异步请求) 看上面代码
后端获取: request.POST获取普通键值对 request.Files 文件
4.django序列化组件(了解) 后面用别的
serializers.serialize('json',user_querset) 对比 Jsonresponse(user_dict) 可以直接放字典
模块:serializers 作用把后端的queryset 对象序列化成json格式传给前端
from app01 import models
from django.core import serializers
def ab_se(request): user_queryset = models.Userinfo.objects.all() #自己写[{},{}] 序列化成json格式传给前端 # user_list = [] # for user_obj in user_queryset: # user_list.append({ # 'username':user_obj.username, # 'password':user_obj.password, # 'gender':user_obj.get_gender_display(), # }) # res = json.dumps(user_list)
#基于 serializers 模块写 res = serializers.serialize('json',user_queryset) # return render(request,'ab_se.html',locals()) return HttpResponse(res)
手写的
serializers 模块
5.批量插入数据 内置方法 bulk_create
def ab_bc(request): # 先插入1000条件数据 特别慢 # for i in range(1,1001): # models.Book.objects.create(title='第%s本书'%i) # book_list = []
#批量插入 指数级增长 特别快 for i in range(1,10001): book_list.append(models.Book(title='新的%s书'%i)) models.Book.objects.bulk_create(book_list) # 批量插入数据的方式
book_queryset = models.Book.objects.all()
return render(request,'ab_bc.html',locals())
解释
6.自定义分页器和基于封装好的分页器
自定义分页器(了解)
def ab_bc(request): # 先插入1000条件数据 # for i in range(1,1001): # models.Book.objects.create(title='第%s本书'%i) # book_list = [] # for i in range(1,10001): # book_list.append(models.Book(title='新的%s书'%i)) # models.Book.objects.bulk_create(book_list) # 批量插入数据的方式 book_queryset = models.Book.objects.all() # current_page = request.GET.get('page',1) # 当page没有值的时候默认展示第一页的的内容 # # 数据类型转换 # try: # current_page = int(current_page) # except BaseException as e: # current_page = 1 # # # 每页展示多少条数据 # per_page_num = 10 # # 定义其实位置和终止位置 # page_start = (current_page - 1) * per_page_num # page_end = current_page * per_page_num # # 数据总条数 # all_count = book_queryset.count() # # 求到底需要多少页 # page_count, more = divmod(all_count,per_page_num) # if more: # 如果有余数总页数加1 # page_count += 1 # # # 后端渲染前端代码 # html = '' # xxx = current_page # if current_page < 6: # xxx = 6 # for i in range(xxx-5,xxx+6): # if i == current_page: # html += '<li class="active"><a href="?page=%s">%s</a></li>'%(i,i) # else: # html += '<li><a href="?page=%s">%s</a></li>' % (i, i) # book_queryset = book_queryset[start_page:end_page] current_page = request.GET.get('page', 1) all_count = book_queryset.count() """ per_page_num = 10 current_page page_start page_end 1 0 10 2 10 20 3 20 30 4 30 40 per_page_num = 5 current_page start_page end_page 1 0 5 2 5 10 3 10 15 4 15 20 page_start = (current_page -1 ) * per_page_num page_end = current_page * per_page_num 总数据是1000条 每页展示10条 需要几页100 总数据是1001条 每页展示10条 需要几页101 总数据是999条 每页展示10条 需要几页100 """
基于写好的分页器的使用
1.新建一个分页器
代码
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: 最多显示的页码个数 用法: queryset = model.objects.all() page_obj = Pagination(current_page,all_count) page_data = queryset[page_obj.start:page_obj.end] 获取数据用page_data而不再使用原始的queryset 获取前端分页样式用page_obj.page_html """ 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)
views层
from app01.utils.mypage import Pagination def ab_bc(request): book_queryset = models.Book.objects.all() current_page = request.GET.get('page', 1) # 不输入 默认返回1 all_count = book_queryset.count() # 1 现生成一个自定义分页器类对象
page_obj = Pagination(current_page=current_page,all_count=all_count,pager_count=9) # 2 针对真实的queryset数据进行切片操作 page_queryset = book_queryset[page_obj.start:page_obj.end] return render(request,'ab_bc.html',locals())
前端:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> </head> <body> {% for book_obj in page_queryset %} <p>{{ book_obj.title }}</p> {% endfor %} {{ page_obj.page_html|safe }} </body> </html>
7.Ajax 结合sweetalert实现删除二次确认 (删除按钮绑定点击事件)
思路 删除按钮绑定点击事件 把sweetalert代码 copy进去 然后结合Ajax发送删除请求
https://github.com/lipis/bootstrap-sweetalert 基于JQ写的
前端代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> {% load static %} <link rel="stylesheet" href="{% static 'dist/sweetalert.css' %}"> <script src="{% static 'dist/sweetalert.min.js' %}"></script> <style> div.sweet-alert h2 { padding-top: 10px; } </style> </head> <body> <div class="container"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <h2 class="text-center">数据展示</h2> <table class="table-hover table table-striped"> <thead> <tr> <th>主键</th> <th>用户名</th> <th>密码</th> <th>性别</th> <th>操作</th> </tr> </thead> <tbody> {% for user_obj in user_queryset %} <tr> <td>{{ user_obj.pk }}</td> <td>{{ user_obj.username }}</td> <td>{{ user_obj.password }}</td> <td>{{ user_obj.get_gender_display }}</td> <td> <a href="#" class="btn btn-primary btn-xs">编辑</a> <a href="#" class="btn btn-danger btn-xs cancel" data_id="{{ user_obj.pk }}">删除</a> </td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> <script> $('.cancel').click(function () { var $aEle = $(this); swal({ title: "你确定要删吗?", text: "你如果删了,你可要准备跑路啊!", type: "warning", showCancelButton: true, confirmButtonClass: "btn-danger", confirmButtonText: "是的,老子就要删!", cancelButtonText: "惹不起惹不起!", closeOnConfirm: false, closeOnCancel: false, showLoaderOnConfirm: true }, function (isConfirm) { if (isConfirm) { // 发送ajax请求 $.ajax({ url:'', type:'post', data:{'delete_id':$aEle.attr("data_id")}, # 自定义的class属性 success:function (data) { // 回调函数会自动将二进制的json格式数据 解码并反序列成js中的数据类型 if (data.code == 1000){ swal("删了!", "你准备跑路吧!", "success"); // 方式1 {#window.location.reload()#} // 方式2 DOM操作动态修改 $aEle.parent().parent().remove() // 将标签直接移除 }else{ swal('发生了未知的错误', "error"); } } }); } else { swal("怂笔", "你成功的刷新我对你的认知", "error"); } }); }) </script> </body> </html>
后端代码views
def show_user(request): """ 前后端如果是通过ajax进行交互 那么交互的媒介一般情况下都是一个字典 """ if request.method == 'POST': time.sleep(3) back_dic = {"code":1000,'msg':''} delete_id = request.POST.get('delete_id') models.Userinfo.objects.filter(pk=delete_id).delete() back_dic['msg'] = '删除成功,准备跑路!!!' return JsonResponse(back_dic) user_queryset = models.Userinfo.objects.all() return render(request,'show_user.html',locals())