MTV与MVC(了解)??
MTV模型(django):??
M:模型层(models.py)T:templatesV:views
MVC模型:??
M:模型层(models.py)V:视图层(views.py)C:控制器(Controller) urls.py 本质:?django的MTV也是MVC
多对多表三种创建方式
1.第一种 django orm自动帮我们创建??
models.py 模型表
1 from django.db import models 2 3 Create your models here. 4 # 第一种创建方式: 5 class Book(models.Model): 6 name = models.CharField(max_length=32) 7 authors = models.ManyToManyField(to=‘Author‘) 多对多的关系,Book与Author之间的绑定 8 9 class Author(models.Model): 10 name = models.CharField(max_length=32)
结论:django orm 自动帮我们创建的第三张表是不可以对第三张表进行操作,如增加字段??
2.第二种纯手动创建第三张表??
models.py 模型表
from django.db import models Create your models here. class Book(models.Model): name = models.CharField(max_length=32) class Author(models.Model): name = models.CharField(max_length=32) class Book2Author(models.Model): #foreignkey自动加_id book = models.ForeignKey(to=‘Book‘,on_delete=models.CASCADE) author = models.ForeignKey(to=‘Author‘,on_delete=models.CASCADE) #可以加字段,如果是jdango自动创建第三张表就不可以 info = models.CharField(max_length=32)
test.py 测试文件
1 import os 2 import sys 3 4 if __name__ == ‘__main__‘: 5 os.environ.setdefault(‘DJANGO_SETTINGS_MODULE‘, ‘day60.settings‘) 6 import django 7 django.setup() 8 from app01 import models 9 10 # 查询书籍对应的作者 11 # 正向 12 res = models.Book.objects.filter(pk=1).values(‘author__name‘) 13 print(res) 14 # 报错信息(手动创建不支持orm方式取值) 15 # django.core.exceptions.FieldError: Cannot resolve keyword ‘author‘ into field. Choices are: book2author, id, name 16 # 反向 17 book_obj = models.Book.objects.filter(pk=1).first() 18 res= book_obj.author_set 19 print(res) 20 # 报错信息(不支持) 21 # django.core.exceptions.FieldError: Cannot resolve keyword ‘author‘ into field. Choices are: book2author, id, name
结论:纯手动创建多对多关系第三张表,可以增加字段,但是不符合ORM查询!!!????
3.第三种半自动创建第三张表??
models.py 模型表
1 from django.db import models 2 3 Create your models here. 4 5 class Book(models.Model): 6 name = models.CharField(max_length=32) 7 #through=‘Book2Author‘ 告诉diango我自己创建了第三张表,你不用帮我创建了 8 #through_fields=((‘(字母小写)通过哪个参数可以查到我这张表‘,‘(字母小写)需要查询字段的表‘)) 9 #虚拟字段,数据库看不到 10 authors = models.ManyToManyField(to=‘Author‘,through=‘Book2Author‘,through_fields=(‘book‘,‘author‘)) 11 12 13 class Author(models.Model): 14 name = models.CharField(max_length=32) 15 # 也可以在这里关联 16 # books = models.ManyToManyField(to=‘Book‘, through=‘Book2Author‘, through_fields=(‘author‘, ‘book‘)) 17 18 class Book2Author(models.Model): 19 #foreignkey自动加_id 20 book = models.ForeignKey(to=‘Book‘,on_delete=models.CASCADE) 21 author = models.ForeignKey(to=‘Author‘,on_delete=models.CASCADE) 22 #可以加字段,如果是jdango自动创建第三张表就不可以 23 info = models.CharField(max_length=32)
test.py 测试文件
1 import os 2 import sys 3 4 if __name__ == ‘__main__‘: 5 os.environ.setdefault(‘DJANGO_SETTINGS_MODULE‘, ‘day60.settings‘) 6 import django 7 django.setup() 8 from app01 import models 9 10 11 #正向查 12 book = models.Book.objects.filter(pk=1).values(‘authors__name‘) 13 print(book) 14 book_obj = models.Book.objects.filter(pk=1).first() 15 res1 = book_obj.authors.all() 16 print(res1) 17 #反向查询 18 res = models.Author.objects.filter(pk=1).values(‘book__name‘) 19 print(res) 20 res2 = models.Author.objects.filter(pk=1).values(‘book2author__info‘) 21 print(res2) 22 23 24 # 结果: 25 # < QuerySet[{‘authors__name‘: ‘yaya‘}] > 26 # < QuerySet[ < Author: Author object(1) >] > 27 # < QuerySet[{‘book__name‘: ‘书名‘}] > 28 # < QuerySet[{‘book2author__info‘: ‘hahah‘}] >
结论:可扩展性高,可以增加字段,并且符合ORM查询方式 ???
前后端传输数据编码格式contentType
urlencoded??
index.html 前端文件
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> 7 8 <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> 9 <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> 10 11 <link href="https://cdn.bootcss.com/font-awesome/5.8.2/css/fontawesome.min.css" rel="stylesheet"> 12 <script src="https://cdn.bootcss.com/font-awesome/5.8.2/js/fontawesome.min.js"></script> 13 14 <link href="https://cdn.bootcss.com/sweetalert/1.1.3/sweetalert.min.css" rel="stylesheet"> 15 <script src="https://cdn.bootcss.com/sweetalert/2.1.2/sweetalert.min.js"></script> 16 </head> 17 <body> 18 <form action="" method="post" > 19 <input type="text" name="name"> 20 <input type="text" name="password"> 21 22 <input type="submit"> 23 </form> 24 </body> 25 </html>
views.py 视图函数文件
from django.shortcuts import render,HttpResponse,redirect def index(request): print(request.POST) return render(request,‘index.html‘,locals())
结论:urlencoded ??
- 对应的数据格式:name=egon&password=123
- 后端获取的数据:request.POST
- ps:django会将urlencoded编码的数据解析自动放到request.POST里
formdata
index.html 前端页面
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> 7 8 <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> 9 <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> 10 11 <link href="https://cdn.bootcss.com/font-awesome/5.8.2/css/fontawesome.min.css" rel="stylesheet"> 12 <script src="https://cdn.bootcss.com/font-awesome/5.8.2/js/fontawesome.min.js"></script> 13 14 <link href="https://cdn.bootcss.com/sweetalert/1.1.3/sweetalert.min.css" rel="stylesheet"> 15 <script src="https://cdn.bootcss.com/sweetalert/2.1.2/sweetalert.min.js"></script> 16 </head> 17 <body> 18 <form action="" method="post" > 19 20 <input type="text" name="name"> 21 <input type="text" name="password"> 22 <input type="file" name="myfile"> 23 <input type="submit"> 24 </form>
views.py 视图函数文件
1 from django.shortcuts import render,HttpResponse,redirect 2 import json 3 # Create your views here. 4 from app01 import models 5 from app01.utils import my_page 6 # form 表单支持urlencoded和formdata这两种方式 7 def index(request): 8 print(request.POST) 9 # <QueryDict: {‘name‘: [‘egon‘], ‘password‘: [‘123‘], ‘myfile‘: [‘1.png‘]}> 10 # ‘myfile‘: [‘5.png‘]拿到的是文件的名字,并不是文件的内容 11 print(request.FILES) 12 # <MultiValueDict: {}> 为空 13 return render(request,‘index.html‘,locals())
结论:form 表单默认的是urlencoed, 不能获取到文件的对象,就是文件的内容,所以我们在form表单中要指定??
enctype="multipart/form-data"??????后端中request.FILES中获取文件的对象
formdata form表单传输文件的编码格式 后端获取文件格式数据:request.FILES 后端获取普通键值对数据:request.POST 首先先声明一下:????????
前端有哪些方式可以朝后端发请求:
浏览器窗口手动输入网址(get请求)a标签的href属性(get请求)form表单(get/post请求(默认是get请求))ajax(get/post请求)
ajax
AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
特点:
1.局部刷新:AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)如:github的注册,名字重新会提醒。
2.异步提交:客户发出一个请求之后,无需等待服务器响应结束,就可以发出第二个请求。
ajax基本语法:
提交的地址(url)提交的方式(type)提交的数据(data)回调函数(success)
<script> $(‘#d1‘).click(function(){ $.ajax({ //提交的地址 url :‘/index‘, //提交的方式 type:‘post‘, //提交的数据 data:{‘name‘:‘jason‘,‘password‘:‘123‘}, //回调函数 success: function(data){ //data异步提交返回的结果 alert(data) } }) </script>来个小例子趴??页面输入两个整数,通过AJAX传输到后端计算出结果并返回。views.py
1 from django.shortcuts import render,HttpResponse,redirect 2 import json 3 Create your views here. 4 from app01 import models 5 from app01.utils import my_page 6 7 def index(request): 8 print(request.POST) 9 if request.method == ‘POST‘: 10 i1 = request.POST.get(‘i1‘) 11 i2 = request.POST.get(‘i2‘) 12 # res=i1+i2 字符串 13 res = int(i1)+int(i2) 要转成整型才可以进行运算 14 print(res,type(res)) 15 return HttpResponse(res) 16 return render(request, ‘index.html‘, locals())
index.py
1 <input type="text" id="i1">+<input type="text" id="i2">=<input type="text" id="i3"> 2 <button id="d1">加我加我~</button> 3 <script> 4 $(‘#d1‘).click(function(){ 5 $.ajax({ 6 url:‘‘, //默认当前路径 7 type:‘post‘, //post小写就好 8 data:{‘i1‘:$(‘#i1‘).val(), ‘i2‘:$(‘#i2‘).val()}, 9 success:function(data){ 10 $(‘#i3‘).val(data) 11 } 12 }) 13 }) 14 </script>
yaya对ajax的小理解:??????
前端的数据通过ajax方式提交给后端,然后会有一个返回的结果,前端并不需要等待结果,所以页面不刷新,可以继续输入,提交,页面都会显示你刚刚输入的数据(如果是form表单提交,提交页面会刷新,前端页面的数据就没有了),然后提交的数据结果是通过回调函数要回来进行展示的。
- ajax传输json数据:
index.html
1 <input type="text" id="i1">+<input type="text" id="i2">=<input type="text" id="i3"> 2 <button id="d1">加我加我~</button> 3 <script> 4 $(‘#d1‘).click(function(){ 5 $.ajax({ 6 url:‘‘, //默认当前路径 7 type:‘post‘, //post小写就好 8 contentType:‘application/json‘, 9 data :JSON.stringify({‘name‘:‘jason‘,‘hobby‘:‘study‘}), 10 success:function(data){ 11 $(‘#i3‘).val(data) 12 } 13 }) 14 }) 15 16 </script>
views.py
1 def index(request): 2 3 if request.method == ‘POST‘: 4 print(request.POST) 5 print(request.body) #byte字符串 body除了用ajax方式提交数据才可以查看,其他方式查看会报错 6 data= request.body 7 #第一种方式 8 res = data.decode(‘utf-8‘) 9 #第二种方式 10 # res2 = str(data,encoding=‘uyf-8‘) 11 # print(res2,type(res2)) 12 res3 = json.loads(res) 13 print(res3, type(res3)) 14 15 return HttpResponse(‘ok‘) 16 return render(request, ‘index.html‘, locals())
注意:?????
1.ajax提交的方式默认是urlencoed,如果前端在传输数据的时候没有说明是json格式传输数据的(没有写这句话contentType:‘application/json‘)就会以urlencoed的方式传输给后端,print(request.POST) 结果是?????? (以整个传输的字典数据当作是key,value是空)
2.ajax以json传输的数据不是在POST中(打印request.POST为空),而是在body里(打印request.body得到二进制数据??????),所以要进行解码
3.前后端传输数据,你不要骗人家,数据是什么格式就要告诉别人是什么格式,数据与编码一一对应??
4.body除了用ajax方式提交数据才查看,其他方式查看会报错
5.解码方式有两种:????
第一种方式res = data.decode(‘utf-8‘)
第二种方式res = str(data, encoding=‘utf-8‘)
- ajax基于formdata传输文件:
index,html??
1 <input type="file" name="myfile" id="i1"> 2 <button id="d1">加我加我~</button> 3 4 <script> 5 $(‘#d1‘).click(function() 点击id为d1的button按钮触发事件{ 6 //ajax需要借助对象FormData 7 let formdata = new FormData(); 8 //FormData 不仅可以传文件还可以传普通的键值对 9 formdata.append(‘name‘,‘jason‘); 10 //如何获取input存放的文件 11 $(‘#i1‘)[0] //jquery转原生js对象,js对象中有files的方法,然后从files[0]列表中取出文件对象 12 formdata.append(‘myfile‘,$(‘#i1‘)[0].files[0]); 13 $.ajax({ 14 url:‘‘, //默认当前路径 15 type:‘post‘, //post小写就好 16 contentType:formdata,//django能够自动识别该formdata对象, 17 // 不要用任何编码,用我自带的formdata自带的编码格式 18 data :formdata, 19 //发送文件需要修改两个固定的参数 20 processData:false, //告诉浏览器不要处理我的数据 21 22 success:function(data){不要用任何的编码,就用我formdata自带的编码格式,django能够自动识别改formdata对象 23 alert(‘收到了,老弟‘) 24 } 25 }) 26 }) 27 28 </script>
views.py
1 def index(request): 2 if request.method == ‘POST‘: 3 print(request.POST) 4 print(request.FILES) 5 6 return HttpResponse(‘ok‘) 7 return render(request, ‘index.html‘, locals())
结论:??
$(‘button标签的id‘).click(function () { let (FormData产生的对象变量名接收) = new FormData(); // FormData对象不仅仅可以传文件还可以传普通的键值对 (FormData产生的对象变量名).append(‘字典的key‘,‘字典的value‘); // 获取input框存放的文件 //$(‘input的标签id‘)[0].files[0] (FormData产生的对象变量名).append(‘自定义文件名‘,$(‘input的标签id‘)[0].files[0]); $.ajax({ url:‘‘, type:‘post‘, data:formdata, // ajax发送文件需要修改两个固定的参数 processData:false, // 告诉浏览器不要处理我的数据 contentType:false, // 不要用任何的编码,就用我formdata自带的编码格式,django能够自动识别改formdata对象 // 回调函数 success:function (后端返回的结果,变量名来接收) { alert(后端返回的结果,变量名来接收) } })});??
form表单与ajax异同点
1.form表单不支持异步提交局部刷新??2.form表单不支持传输json格式数据??3.form表单与ajax默认传输数据的编码格式都是urlencoded??
批量插入数据
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> 7 8 <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> 9 <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> 10 11 <link href="https://cdn.bootcss.com/font-awesome/5.8.2/css/fontawesome.min.css" rel="stylesheet"> 12 <script src="https://cdn.bootcss.com/font-awesome/5.8.2/js/fontawesome.min.js"></script> 13 14 <link href="https://cdn.bootcss.com/sweetalert/1.1.3/sweetalert.min.css" rel="stylesheet"> 15 <script src="https://cdn.bootcss.com/sweetalert/2.1.2/sweetalert.min.js"></script> 16 </head> 17 <body> 18 <div class="container"> 19 <div class="row"> 20 <div class="col-md-8 col-md-offset-2"> 21 <table class="table table-hover table-striped table-bordered"> 22 <thead> 23 <tr> 24 <th>id</th> 25 <th>name</th> 26 </tr> 27 </thead> 28 <tbody> 29 {% for book_obj in book_list %} 30 <tr> 31 <td>{{ book_obj.pk }}</td> 32 <td>{{ book_obj.name }}</td> 33 </tr> 34 </tbody> 35 {% endfor %} 36 </table> 37 </div> 38 </div> 39 </div> 40 </body> 41 </html>
booklist.html
1 def booklist(request): 2 #动态插入100条数据 3 for i in range(100): 4 models.Book2.objects.create(name=‘第%s本书‘ %i) 5 # 查询所有的书籍展示到前端页面 6 bool_list = models.Book2.objects.all() 7 return render(request, ‘booklist.html‘,locals())
views.py
以上是没有使用
models.表名.objects.bulk_create(列表) 没有使用所以网速刷新很慢
1 def booklist(request): 2 #动态插入100000条数据 批量操作 3 l=[] 4 for i in range(10000): 5 l.append(models.Book2(name=‘第%s本书‘ %i)) 6 #批量操作(插入的数据多并且速度快) 7 models.Book2.objects.bulk_create(l) 8 # 查询所有的书籍展示到前端页面 9 book_list = models.Book2.objects.all() 10 return render(request, ‘booklist.html‘,locals())
自定义分页器
思路:??
1.?? book_list = models.Book2.objects.all()[0:10]all()获取到Book2表中所有的书籍,列表套对象。然后列表可以切片索引(顾头不顾尾),这里是将书籍从索引为0的书籍作为第一个取出一直到索引为10的书籍展示在一个页面上;
2.??每页展示10条数据per_page_num = 10
当前页(current_page) | 起始位置(page_start) | 终止位置(page_end) |
1 | 0 | 10 |
2 | 10 | 20 |
3 | 20 | 30 |
page_start = (current_page-1)*per_page_numpage_end = current_page*per_page_num
book_list = models.Book2.objects.all()[page_start:page_end
不管第1,第2,第3还是切换其他的当前页都可以显示对应的书籍序列,以上就推出了代码实现的变动。3.??获取到访问的当前页crrent_page=request.get(‘page‘,1) 用户不传默认展示第一页current_page=int(current_page) 得到的是字符串,所以要转成整型,才成进行运算
在前端需要访问url的时候还需要携带手动的输入当前页(不合理)4.??然后再引用bootstrap的分页器,再为一个页面的a标签加上携带curent_page的参数,在用户点击的时候可以跳转相应的页面,但是缺陷是我这个页面只能显示固定的当前页数5.??如果是100条数据,一页显示10条,需要显示10页,如果是101条,需要显示11页,用代码如何实现呢?所以就用到了divmod
获取到数据的总条数:all_count = models.Book2.objects.all().count()获取到总页数:pager_nums,more=divmod(all_count,per_page_num)6.??页码的渲染通常是单数1,3,5,7,9符合中国人的审美标准
7.??由于需要循环拿出每一个当前页,但是前端没有range方法,所以只能到后端实现了之后再传给前端
pager_nums,more = divmod(all_count,per_page_num) if more: pager_nums += 1 html = ‘‘ for i in range(1,pager_nums+1): html += ‘<li><a href="?page=%s">%s</a></li>‘%(i,i)
8.??前端需要写{{page_obj.page_html|safe}} #safe将后端传入的html语法数据进行展示
自定义分页器的使用
1 from django.db import models?? 2 3 Create your models here. 4 5 class Book2(models.Model): 6 name = models.CharField(max_length=32)
在app01下自创的utils文件夹,导入第三方文件
1 class Pagination(object): 2 def __init__(self, current_page, all_count, per_page_num=2, pager_count=11): 3 """ 4 封装分页相关数据 5 :param current_page: 当前页 6 :param all_count: 数据库中的数据总条数 7 :param per_page_num: 每页显示的数据条数 8 :param pager_count: 最多显示的页码个数 9 10 用法: 11 queryset = model.objects.all() 12 page_obj = Pagination(current_page,all_count) 13 page_data = queryset[page_obj.start:page_obj.end] 14 获取数据用page_data而不再使用原始的queryset 15 获取前端分页样式用page_obj.page_html 16 """ 17 try: 18 current_page = int(current_page) 19 except Exception as e: 20 current_page = 1 21 22 if current_page < 1: 23 current_page = 1 24 25 self.current_page = current_page 26 27 self.all_count = all_count 28 self.per_page_num = per_page_num 29 30 # 总页码 31 all_pager, tmp = divmod(all_count, per_page_num) 32 if tmp: 33 all_pager += 1 34 self.all_pager = all_pager 35 36 self.pager_count = pager_count 37 self.pager_count_half = int((pager_count - 1) / 2) 38 39 @property 40 def start(self): 41 return (self.current_page - 1) * self.per_page_num 42 43 @property 44 def end(self): 45 return self.current_page * self.per_page_num 46 47 def page_html(self): 48 # 如果总页码 < 11个: 49 if self.all_pager <= self.pager_count: 50 pager_start = 1 51 pager_end = self.all_pager + 1 52 # 总页码 > 11 53 else: 54 # 当前页如果<=页面上最多显示11/2个页码 55 if self.current_page <= self.pager_count_half: 56 pager_start = 1 57 pager_end = self.pager_count + 1 58 59 # 当前页大于5 60 else: 61 # 页码翻到最后 62 if (self.current_page + self.pager_count_half) > self.all_pager: 63 pager_end = self.all_pager + 1 64 pager_start = self.all_pager - self.pager_count + 1 65 else: 66 pager_start = self.current_page - self.pager_count_half 67 pager_end = self.current_page + self.pager_count_half + 1 68 69 page_html_list = [] 70 # 添加前面的nav和ul标签 71 page_html_list.append(‘‘‘ 72 <nav aria-label=‘Page navigation>‘ 73 <ul class=‘pagination‘> 74 ‘‘‘) 75 first_page = ‘<li><a href="?page=%s">首页</a></li>‘ % (1) 76 page_html_list.append(first_page) 77 78 if self.current_page <= 1: 79 prev_page = ‘<li class="disabled"><a href="#">上一页</a></li>‘ 80 else: 81 prev_page = ‘<li><a href="?page=%s">上一页</a></li>‘ % (self.current_page - 1,) 82 83 page_html_list.append(prev_page) 84 85 for i in range(pager_start, pager_end): 86 if i == self.current_page: 87 temp = ‘<li class="active"><a href="?page=%s">%s</a></li>‘ % (i, i,) 88 else: 89 temp = ‘<li><a href="?page=%s">%s</a></li>‘ % (i, i,) 90 page_html_list.append(temp) 91 92 if self.current_page >= self.all_pager: 93 next_page = ‘<li class="disabled"><a href="#">下一页</a></li>‘ 94 else: 95 next_page = ‘<li><a href="?page=%s">下一页</a></li>‘ % (self.current_page + 1,) 96 page_html_list.append(next_page) 97 98 last_page = ‘<li><a href="?page=%s">尾页</a></li>‘ % (self.all_pager,) 99 page_html_list.append(last_page) 100 # 尾部添加标签 101 page_html_list.append(‘‘‘ 102 </nav> 103 </ul> 104 ‘‘‘) 105 return ‘‘.join(page_html_list)
my_page.py
1 from django.shortcuts import render,HttpResponse 2 from app01 import models 3 from app01.utils import my_page 4 5 def booklist(request): 6 book_list = models.Book2.objects.all() 7 all_count = book_list.count() 8 current_page = request.GET.get(‘page‘,1) 9 page_obj = my_page.Pagination(current_page=current_page,all_count=all_count) 10 page_queryset = book_list[page_obj.start:page_obj.end] 11 return render(request,‘booklist.html‘,locals())
小总结:??
自定义分页器 页码的渲染通常都是单数1,3,5,7,9,符号中国人的审美标准 需要完善的 1.加首页和尾页 2.页码的个数应该是固定的 自定义分页器的使用 后端: book_list = models.Book2.objects.all() # 数据总条数 all_count = book_list.count() # 当前页 current_page = request.GET.get(‘page‘,1) # 示例一个分页器对象 page_obj = my_page.Pagination(current_page=current_page,all_count=all_count) # 对总数据进行切片 page_queryset = book_list[page_obj.start:page_obj.end] 前端: {{ page_obj.page_html|safe }} # 帮你渲染的是带有bootstrap样式的分页器
原文地址:https://www.cnblogs.com/huangxuanya/p/11025898.html