Django框架之多对多表关系创建与Ajax
Django框架之多对多表关系创建与Ajax
1、表关系创建多对多三种创建方式
1、全自动(较为常用)
Django ORM会自动帮你创建第三张表只需定义两张表类使用多对多外键连接
class Book(models.Model): name = models.CharField(max_length=32) price = models.CharField(max_length=32) authors = models.ManyToManyField(to='Author') # 会自动帮你创建第三张表 class Author(models.Model): name = models.CharField(max_length=32) addr = models.CharField(max_length=32)
好处:第三张表自动创建
不足:第三张表无法扩展额外的字段
2、存手动
第三张表自己手动创建,外键连接两张表,可以扩展额外的字段,但是不能用ORM查询语句,无正向反向的概念了
class Book(models.Model): name = models.CharField(max_length=32) price = models.CharField(max_length=32) class Author(models.Model): name = models.CharField(max_length=32) addr = models.CharField(max_length=32) class Book_Author(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): name = models.CharField(max_length=32) price = models.CharField(max_length=32) # 多对多的外键字段需要再指定两个关键字参数through='第三张表',through_fields = ('本表名','另一种关系表名') authors = models.ManyToManyField(to="Author", through="Book_Author", through_fields=('book', 'author')) class Author(models.Model): name = models.CharField(max_length=32) addr = models.CharField(max_length=32) # 多对多的外键字段需要再指定两个关键字参数through='第三张表',through_fields = ('本表名','另一种关系表名')
# books = models.ManyToManyField(to="Book", through='Book_Author', through_fields=('author', 'book')) # 不需要两张表同时写
class Book_Author(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
1、Ajax简介
Ajax不是一门新的语言,它其实就是基于js写的一个功能模块
异步提交,局部刷新(最大的优点):不加载整个页面的情况下,可以跟服务器交换数据并更新部分网页内容,在不知不觉的过程中完成了请求响应过程
应用:在用户输入关键字的时候,会自动去数据库中进行查询和进行校验操作,最后返回出结果,整个过程页面没有刷新,只是刷新局部的位置
2、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) } })
3、Content-Type前后端传输数据编码格式
1、三种数据编码格式:
-
application/urlencoded
前端代码以 username=jason&password=123格式展示
Django后端会针对该种格式的数据会自动解析打包到request.POST中
-
multipart/form-data
django后端针对符合urlencoded编码格式数据(普通键值对)还是统一解析到request.POST中
后端会针对formdata数据格式的文件数据会自动解析到request.FILES中
-
application/json
前后的数据交互编码格式一定要一样,是json格式的发送要进行转码
Django后端针对json格式数据,不会做任何的处理,而是放进request.body中需要自己手动进行转换,先解码,再反序列化(json.loads能够自动解码并序列化)
json_bytes = request.body json_dict = json.loads(json_bytes) # 拿到真实的字典数据
提交数据两种方式:form表单和Ajax
2、form表单
1、默认是urlencoded编码格式传输数据
前端以username=Jason&password=123展示,Django后端会自动解析打包到request.POST中,而针对文件数据只能获取文件名,并不能真正获取文件数据
2、可以修改为formdata数据格式
指定enctype="multipart/form-data"
<form action="" method="post" enctype="multipart/form-data">
django后端针对符合urlencoded编码格式数据(普通键值对)还是统一解析到request.POST中,会针对formdata数据格式的文件数据会自动解析到request.FILES中
3、Ajax提交
Ajax默认也是urlencoded编码格式,也可以修改提交数据格式为json数据格式
4、Ajax发送json格式数据及发送文件
1、Ajax发送json数据格式数据
可以修改提交的数据格式为:contentType:'application/json'
$('#d1').on('click', function () { $.ajax({ url:'', type:'post', // 修改contentType参数 contentType:'application/json', // 改成json格式的传输,需要将数据序列化成json格式 data:JSON.stringify({'username':'tank','password':123}), success:function (data) { alert(data) } }) })
后端需要自己处理数据,将拿到的数据解码再反序列化才能拿到真实字典数据
# 自己处理json格式数据 json_bytes = request.body # 先解码 # json_str = json_bytes.decode('utf-8') # 再反序列化 # json_dict = json.loads(json_str) # 扩展 json.loads能够自动解码并序列化 json_dict = json.loads(json_bytes) print(json_dict,type(json_dict))
2、Ajax发送文件
内置对象FormData:既可以发普通键值对数据也可以发文件数据
Ajax发送文件数据需要借助于内置对象
1、首先需要生成一个内置对象
var myFormData = new FormData
2、向生成的对象中添加数据
3、发送Ajax请求
// 需要先生成一个内置对象 var myformdata = new FormData(); // 向内置对象中添加普通键值对 myformdata.append('username','jason'); // 向对象中添加文件数据,获取input中传入的文件 myformdata.append('myfile',$('#d1')[0].files[0]); // 发送Ajax请求 $.ajax({ url:'', type:'post', data:myformdata, // 发送formdata数据需要指定两个关键性参数为false processData:false, // 让浏览器不要对数据做任何操作 contentType:false, // 不要使用任何编码,FormData自带编码格式Django能识别该对象 success:function (data) { alert(data) }
5、Django内置的序列化功能
Django内置的序列功能借助 serializers 模块,可以序列化对象
以前我们将后端将数据传递给前端通过json序列化
from app01 import models import json def xlh(request): book_query = models.Book.objects.all() book_list = [] for book_obj in book_query: book_list.append({ 'name': book_obj.name, 'price': book_obj.price }) res = json.dumps(book_list) return render(request, 'xlh.html', locals())
我们可以使用Django内置的序列化功能模块 serializers ,直接序列化,展示的结果更加多将类名,主键都能拿到
from django.core import serializers def xlh(request): book_query = models.Book.objects.all() res = serializers.serialize('json', book_query) return HttpResponse(res) # [{"model": "app01.book", "pk": 1, "fields": {"name": "jason", "price": "123"}}]
6、批量插入数据
我们之前学过的批量插入数据方式通过for循环再利用create方法,但是每次都要走数据库,效率很低
def insert_data(request): for i in range(1,501): models.Book.objects.create(name='第%s本书'%i) book_query = models.Book.objects.all() return render(request, 'insert.html', locals()) # 每次循环一次都需要去数据库插入一条,500条大约需要一分钟
我们可以通过先将数据添加到一个列表中再使用 bulk_create 方法插入数据,这样就只会走一次数据库,效率很高
def insert_data(request): book_list = [] for i in range(1,9999): book_list.append(models.Book(name='新添加的第%s书'%i)) models.Book.objects.bulk_create(book_list) # 批量插入数据 book_query = models.Book.objects.all() return render(request, 'insert.html', locals()) # 效率很高,大约1秒插入9999条数据
7、自定义分页器及使用
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)
后端代码:
# 获取当前页码,默认为1 current_page = request.GET.get('page', 1) # 获取总页数 all_count = book_queryset.count() # 生成一个自定义分页器类对象 page_obj=Pagination(current_page=current_page,all_count=all_count,pager_count=9) # 针对真实的queryset数据进行切片操作 page_queryset = book_queryset[page_obj.start:page_obj.end] return render(request,'ab_bc.html',locals())
前端代码:
{{ page_obj.page_html|safe }}
8、Ajax结合sweetalert实现删除二次确认
1、首先需要从sweetalert 连接找到Bootstrap-sweetalert包,将包下载到本地
我们只需要使用dist文件中的 js 和 css 文件
2、从sweetalert forBootstrap中找到需要使用的样式,引用其代码
我们以删除二次确认为例:
后端代码:
from django.http import JsonResponse import time def show_user(request): """ 前后端如果是通过ajax进行交互 那么交互的媒介一般情况下都是一个字典 """ if request.method == 'POST': # 模仿删除等待效果 time.sleep(3) # 定义一个字典,通过Ajax传给后端的data back_dic = {"code":1000,'msg':''} # 拿到需要删除的数据id delete_id = request.POST.get('delete_id') # 将数据删除 models.Userinfo.objects.filter(pk=delete_id).delete() back_dic['msg'] = '删除成功,准备跑路!!!' # 以json数据格式将字典返回给前端Ajax中的data return JsonResponse(back_dic) user_queryset = models.Userinfo.objects.all() return render(request,'show_user.html',locals())
前端代码:
首先需要引入sweetalert文件中的css和js文件
{% 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>
调用sweetalert中样式代码,加以修改
<script> // 给删除按钮绑定点击事件 $('.cancel').click(function () { // 定义变量指代某一条数据 var $aEle = $(this); // 引用sweetalert中的删除样式代码 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")}, 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>