Django框架—分页器paginator
1.数据准备
models.py文件创建表类
from django.db import models # Create your models here. class Author(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) age=models.IntegerField() authorDetail=models.OneToOneField(to="AuthorDetail",to_field="nid") def __str__(self): return self.name class AuthorDetail(models.Model): nid = models.AutoField(primary_key=True) birthday=models.DateField() telephone=models.BigIntegerField() addr=models.CharField( max_length=64) class Publish(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) city=models.CharField( max_length=32) email=models.EmailField() def __str__(self): return self.name class Book(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField( max_length=32) publishDate=models.DateField() price=models.DecimalField(max_digits=5,decimal_places=2) publish=models.ForeignKey(to="Publish",to_field="nid") authors=models.ManyToManyField(to='Author',) def __str__(self): return self.title
指定数据库同步指令:makemigrations和migrate
在views外使用Django环境创建数据
在应用文件app01下创建一个py文件,名字随意,如init_book_table.py
import os if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day068_model_form_lesson.settings") # 引入django的环境 import django django.setup() # 运行Django环境,之后可以使用django中的其他文件 from app01 import models import random l1 = [] for i in range(1, 81): # 创建100条记录 obj = models.Book( title='红楼梦第%s回'%i, price= random.randint(100,200), publishDate='1998-06-%s'%(random.randint(1,30)), publish_id=random.randint(1,3), ) l1.append(obj) models.Book.objects.bulk_create(l1)
创建好后,直接右键运行即可,就可以用DJango环境执行这段代码,在数据库中创建100天记录。
2.paginator对象常用属性和方法
使用django内置的分页器需要导入分页器类
from django.core.paginator import Paginator,EmptyError # EmptyError是一个没有取到页面的错误
通过给Paginator类传递所有数据的queryset对象,以及每页显示数量来实例化一个paginator对象。这个对象有所有对分页的处理方法。
paginator对象属性
paginator.count:返回所有记录的总数量
paginator.num_pages:返回分页后的总页数
paginator.page_range:返回分页后的页码范围,一个range对象
all_books = models.Book.objects.all() paginator = Paginator(all_books,10) # 将所有的book对象传给Paginator生成分页器对象,10表示每一页展示的数据数量 """paginator对象的属性""" print("count",paginator.count) # 显示数据总数量 count 83 print("num_pages",paginator.num_pages) # 显示分页后总页数 num_pages 9 print("page_range",paginator.page_range) # 显示页面的范围 page_range range(1, 10)
paginator对象的方法
-
page_obj = paginator.page(page):获取某一页所有数据的一个对象
-
如果页码值不在页码范围内,抛出异常error:EmptyPage 超过页码范围
-
如果页码值非法:抛出异常error:PageNotAnInteger 非法页码值
-
page_obj对象的方法
-
page_obj.has_next():判断当前页是否有下一页
-
page_obj.next_page_number():下一页的页码
-
page_obj.has_previous():判断当前页是否有上一页
-
page_obj.previous_page_number():上一页的页码
all_books = models.Book.objects.all() paginator = Paginator(all_books,10) # 将所有的book对象传给Paginator生成分页器对象,10表示每一页展示的数据数量 """paginator对象的属性""" print("count",paginator.count) # 显示数据总数量 count 83 print("num_pages",paginator.num_pages) # 显示分页后总页数 num_pages 9 print("page_range",paginator.page_range) # 显示页面的范围 page_range range(1, 10)
3.Django分页器实现分页
视图函数部分
from django.shortcuts import render, redirect, HttpResponse from utils.modelFormLesson import BookForm from app01 import models from django.core.paginator import Paginator,EmptyPage def show(request): if request.method == "GET": all_books = models.Book.objects.all() paginator = Paginator(all_books,10) # 将所有的book对象传给Paginator生成分页器对象,10表示每一页展示的数据数量 """paginator对象的属性""" print("count",paginator.count) # 显示数据总数量 count 83 print("num_pages",paginator.num_pages) # 显示分页后总页数 num_pages 9 print("page_range",paginator.page_range) # 显示页面的范围 page_range range(1, 10) # """paginator对象的方法""" # page1 = paginator.page(1) # 获取第一页的所有数据 # print(page1) # for i in page1: # print(i) # # page_obj = paginator.page(2) # print("是否有下一页",page_obj.has_next()) # 是否有上一页 True # print("下一页的页码",page_obj.next_page_number()) # 下一页的页码 3 # print("是否有上一页",page_obj.has_previous()) # 是否有下一页 True # print("上一页的页码",page_obj.previous_page_number()) # 上一页的页码 1 """分页逻辑部分""" current_page_num = request.GET.get("page",1) # 获取请求页面,也就是当前页 current_page_num = int(current_page_num) all_pages = paginator.num_pages # 分页后的所有页数 try: current_books = paginator.page(current_page_num) # 获取当前页的所有数据 except EmptyPage: current_books = paginator.page(paginator.num_pages) print(">>>>>>>>>>",current_books) if current_page_num <= 3: page_range = range(1,6) elif 3 < current_page_num <= paginator.num_pages - 2: page_range = range(current_page_num-2,current_page_num+3) else: page_range = range(all_pages-4,all_pages+1) return render(request,"show.html",{"page_range":page_range,"current_books":current_books,"current_page_num":current_page_num})
模板文件show.html
在前端页面注意点:
在实现上一页和下一页的功能时,需要判断当前页是不是最后一页,也就是当前页有没有上一页或下一页。
如果是,那么在a标签中的href属性中不能再使用current_books.next_page_number或current_books.previous_page_number,不然会EmptyError错误。
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}"> </head> <body> <h1 style="text-align: center">书籍展示页面</h1> <div class="container-fluid"> <div class="row"> <div class="col-sm-8 col-sm-offset-2"> <!--===============================数据展示部分==============================--> <div style="height: 450px"> <table class="table table-hover table-striped"> <thead> <tr> <th>序号</th> <th>书名</th> <th>出版日期</th> <th>价格</th> <th>出版社</th> <th>作者</th> <th>菜单</th> </tr> </thead> <tbody> {% for book in current_books %} <tr> <td>{{ forloop.counter }}</td> <td>{{ book.title }}</td> <td>{{ book.publishDate|date:"Y-m-d" }}</td> <td>{{ book.price }}</td> <td>{{ book.publish }}</td> <td> {% for author in book.authors.all %} {{ author.name }} {% if forloop.last %} {% endif %} {% endfor %} </td> <td> <a href="" class="btn btn-warning btn-xs">编辑</a> <button class="btn btn-danger btn-xs">删除</button> <span class="hidden">{{ book.pk }}</span> </td> </tr> {% endfor %} </tbody> </table> </div> <!--===============================分页部分==================================--> <nav aria-label="Page navigation" class="pull-right"> <ul class="pagination"> <!--上一页设置--> <!--判断是否有上一页,有的话可以点击左箭头跳上一页--> {% if current_books.has_previous %} <li> <a href="{% url 'show' %}?page={{ current_books.previous_page_number }}" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> <!--没有上一页,让按钮变为disable,不让点击--> {% else %} <li class="disabled"> <a aria-label="Previous"> <!--注意第一页没有上一页,所以这里a标签的href属性不能再写了,如果写了,那么current_books.previous_page_number方法会一直报错。--> <span aria-hidden="true">«</span> </a> </li> {% endif %} <!--页面设置--> {% for page in page_range %} {% if page == current_page_num %} <li class="active"> <a href="{% url 'show' %}?page={{ page }}" aria-label="Previous"> <span aria-hidden="true">{{ page }}</span> </a> </li> {% else %} <li> <a href="{% url 'show' %}?page={{ page }}" aria-label="Previous"> <span aria-hidden="true">{{ page }}</span> </a> </li> {% endif %} {% endfor %} <!--下一页设置--> <!--判断是否有下一页,有的话可以点击右箭头跳下一页--> {% if current_books.has_next %} <li> <a href="{% url 'show' %}?page={{ current_books.next_page_number }}" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> <!--没有上一页,让按钮变为disable,不让点击--> {% else %} <li class="disabled"> <a aria-label="Next"> <!--注意最后一页没有下一页,所以这里a标签的href属性不能再写了,如果写了,那么current_books.next_page_number方法会一直报错。--> <span aria-hidden="true">»</span> </a> </li> {% endif %} </ul> </nav> </div> </div> </div> <script src="{% static 'jquery-3.4.1.js' %}"></script> <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script> </body> </html>
分页后的前端页面
二、自定义分页器CustomerPaginator
1.为什么使用自定义分页器
django的内置分页器可以实现简单的分页设置,但是如果我们需要涉及到更多细节的设置,更人性化的配置的时候,django内置分页器就无法满足我们的使用需求。
这种场景,我们需要自己定义分页器组件,通过后端传递数据,在分页器组件中,根据分页的业务做逻辑处理,然后拼接成分页器标签返回。拿到分页器标签后,直接render给前端页面,生成在页面中。
2.自定义分页器实例
这里的自定义分页器,仅仅博主本人写的一个简单的自定义分页器,能够解决常用的分页需求。如果无法满足具体需求,可以根据具体需求进行扩展。
自定义分页器组件
项目下新建utils文件夹,用来存放扩展的功能组件,在utils下新建customPaginator.py,分页器组件代码如下:
import copy class Paginator: """自定义分页器""" def __init__(self, request, data_counts, per_counts=10, show_counts=5): """ 初始属性 :param request: 请求的所有请求数据 :param data_counts: 展示的总数据数量 :param per_counts: 每页展示的数据数量 :param show_counts: 在前端显示的页码数量 """ self.request = request self.data_counts = data_counts self.per_counts = per_counts self.show_counts = show_counts self.params = copy.deepcopy(request.GET) # 获取请求的GET数据的深拷贝querydict可以使用 # 获取数据分页后总页数 def page_counts(): """ 获取总的页码数量 :return: """ page_counts, mod = divmod(self.data_counts, self.per_counts) if mod: page_counts += 1 return page_counts # 获取总页数,并添加在self属性中 self.page_counts = page_counts() # 获取当前页页码 self.current_page = request.GET.get("page", "") try: # 异常处理当前页码是否为数字 self.current_page = int(self.current_page) except Exception: self.current_page = 1 # 判断当前页码在不在合理范围内 if self.current_page < 1: self.current_page = 1 if self.current_page > self.page_counts: self.current_page = 1 @property # 装饰为普通属性 def start(self): """ 返回某一页数据展示的开始位置 :return: """ return (self.current_page - 1) * self.per_counts @property # 装饰为普通属性 def end(self): """ 返回某一页数据展示的结束位置 :return: """ return (self.current_page) * self.per_counts def paginate(self): """ 根据数据数据量,每页数据,每页页码,拼接分页标签返回 :return:返回拼接好的分页器标签 """ tag = "" page_counts = self.page_counts # 设置首页标签url self.params["page"] = 1 params = self.params.urlencode() # condition=name&q=qwerqw&page=1 if self.current_page == 1: first = f""" <li class="active"> <a href="{self.request.path}?{params}" aria-label="Previous"> <span aria-hidden="true">首页</span> </a> </li> """ else: first = f""" <li> <a href="{self.request.path}?{params}" aria-label="Previous"> <span aria-hidden="true">首页</span> </a> </li> """ # 设置上一页标签的url if self.current_page <= 1: # 如果当前页是第一页,上一页不可选 previous = """ <li disabled> <a href="#" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> """ else: # 不是第一页,拼接上一页的url路径 self.params["page"] = self.current_page - 1 params = self.params.urlencode() # condition=name&q=qwerqw&page=num previous = f""" <li> <a href="{self.request.path}?{params}" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> """ # 获取要显示的页码范围 half_num = self.show_counts // 2 if self.page_counts < self.show_counts: # 判断总页数是不是小于要显示的页数 range_start = 1 range_end = self.page_counts else: # 总页数大于要显示的页数 if self.current_page <= half_num + 1: range_start = 1 range_end = self.show_counts elif half_num + 1 < self.current_page < page_counts - half_num: range_start = self.current_page - half_num range_end = self.current_page + half_num else: range_start = page_counts - self.show_counts + 1 range_end = page_counts # 获取中间的页码标签 middle = "" for num in range(range_start, range_end + 1): # 保留查询参数的路径 self.params["page"] = num params = self.params.urlencode() # condition=name&q=qwerqw&page=num # 判断哪一页是当前页,是的话,添加active效果 if self.current_page == num: one_tag = f"""<li class="active"><a href="{self.request.path}?{params}">{num}</a></li>""" else: one_tag = f"""<li><a href="{self.request.path}?{params}">{num}</a></li>""" middle += one_tag # 设置下一页标签的url if self.current_page >= self.page_counts: # 如果当前页已经是最大页数,下一页禁用 next = """ <li disabled> <a href="#" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> """ else: # 如果不是正常使用 self.params["page"] = self.current_page + 1 params = self.params.urlencode() # condition=name&q=qwerqw&page=num next = f""" <li> <a href="{self.request.path}?{params}" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> """ # 设置尾页标签url self.params["page"] = self.page_counts params = self.params.urlencode() # condition=name&q=qwerqw&page=1 if self.current_page == self.page_counts: last = f""" <li class="active"> <a href="{self.request.path}?{params}" aria-label="Previous"> <span aria-hidden="true">尾页</span> </a> </li> """ else: last = f""" <li> <a href="{self.request.path}?{params}" aria-label="Previous"> <span aria-hidden="true">尾页</span> </a> </li> """ tag = f""" <nav aria-label="Page navigation"> <ul class="pagination"> {first} {previous} {middle} {next} {last} <li style="margin-right:10px"><span>共{self.page_counts}页</span></li>  </ul> </nav> """ return tag def jump_page(self): """ 产生跳转页标签 :return: 返回跳转页的标签 """ url = self.request.get_full_path() if not "/?" in url: url = url+"?page=" else: if "page" in url: url = url.split("page")[0]+"page=" else: url = url + "&page=" jump_tag =f""" <div class="input-group input-group-sm"> <span class="input-group-btn">跳转到</span> <input type="text" class="form-control" name="page" placeholder="跳转页码"> <span class="input-group-btn">页</span> <span class="input-group-btn"> <a id="jump_page" href="{url}" class="btn btn-info btn-flat">Go!</a> </span> </div> """ return jump_tag def jump_js(self): """ 生成跳转页使用的js代码 :return: """ jump_js = """ <script> $("#jump_page").click(function () { var page = $("[name=page]").val(); var url = $(this).attr("href") + page; $(this).attr("href", url); }); </script> """ return jump_js
自定分页组件使用
1.分页器使用
分页器接受参数
-
request: 请求的所有请求数据
-
data_counts: 展示的总数据数量
-
per_counts: 每页展示的数据数量,默认是每页10条
-
show_counts: 在前端显示的页码数量,默认是5页
分页器方法
-
start():方法根据提供的当前所在页码,返回当前页数据的开始位置
-
end():方法根据提供的当前所在页码,返回当前页数据的结束位置
-
paginate():返回拼接好的分页标签的html文本,在前端使用
-
jump_page():返回拼接好的跳转页标签html文本,在前端使用
-
jump_js():返回跳转页使用对应的js代码,在前端使用
2.具体使用实例
后端views视图
from crmweb import models from django import views from utils.customPaginator import Paginator # 导入自定义分页器类 # 展示客户数据的视图 class CommonData(views.View): @method_decorator(login_required) # 验证客户登录状态的装饰器,这里可有可无 def dispatch(self, request, *args, **kwargs): res = super().dispatch(request, *args, **kwargs) return res def get(self, request): # 查询要显示的所有数据 all_customers = models.Customer.objects.filter(consultant__isnull=True, status="unregistered").order_by("-pk") # 开始分页展示 data_counts = all_customers.count() # 获取分页的总数据数量 # 生成一个分页对象 paginator = Paginator(request, data_counts, 10) # 获取当前页展示数据的范围 try: # 异常是否查到了数据,查到了才切片,不然会报错 all_customers = all_customers[paginator.start:paginator.end] except Exception: pass # 获取分页的标签 paginator_tag = paginator.paginate() # 调用定义好的分页方法 # 获取跳转页的标签 jump_tag = paginator.jump_page() # 调用定义好的跳转页方法获取跳转页标签 jump_js = paginator.jump_js() # 调用定义好的跳转页方法获取跳转页js代码 return render(request, "customers_common_list.html", {"all_customers": all_customers, "paginator_tag": paginator_tag, "jump_tag": jump_tag, "jump_js": jump_js})
前端使用分页器
<table id="example2" class="table table-bordered table-hover text-center"> <thead> <tr> <th style="width: 5%">序号</th> <th>qq</th> <th>姓名</th> <th>电话</th> <th>来源</th> <th>咨询课程</th> <th>客户状态</th> <th>销售老师</th> <th>跟进信息</th> <th>操作</th> </tr> </thead> <tbody> <tr> <td>{{ forloop.counter }}</td> <td>{{ customer.qq }}</td> <td>{{ customer.qq_name }}</td> <td> {{ customer.phone|default:"暂无" }} </td> <td>{{ customer.get_source_display|default:'暂无' }}</td> <td>{{ customer.get_course_display|default:"暂无" }}</td> <td>{{ customer.get_status_display }}</td> <td>{{ customer.consultant.username|default:'暂无' }}</td> <td><a href="{% url 'consult_record' customer.pk %}">详情</a></td> <td> <a style="color: #00c3cc;" href="{% url 'consult_record_edit' customer.pk %}"> <i class="fa fa-edit" aria-hidden="true"></i> </a> | <a style="color: #d9534f;" href="{% url 'consult_record_del' customer.pk %}"> <i class="fa fa-trash-o"></i> </a> </td> </tr> </tbody> </table> <!--自定义分页器使用--> <div class="pull-right" style="display:inline-block; width: 120px;margin: 22px 10px"> {{ jump_tag|safe }} </div> <!--自定义跳转页使用--> <div class="pull-right"> {{ paginator_tag|safe }} </div> {{ jump_js|safe }}
效果图如下