django之分页
分页
在当前网页如果特别的数据,如果不想当前网页显示太多的内容数据(例如一些文章的目录或者一些小说的章节目录很多地方都用到分页),这时候就需要用分页了。
在Django中数据被分在不同页面中,并带有“上一页/下一页”标签。这些类位于django/core/paginator.py中。
分页器Paginator
Paginator.py源码
import collections from math import ceil from django.utils import six from django.utils.functional import cached_property class InvalidPage(Exception): pass class PageNotAnInteger(InvalidPage): pass class EmptyPage(InvalidPage): pass class Paginator(object): def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True): self.object_list = object_list self.per_page = int(per_page) self.orphans = int(orphans) self.allow_empty_first_page = allow_empty_first_page def validate_number(self, number): """ Validates the given 1-based page number. """ try: number = int(number) except (TypeError, ValueError): raise PageNotAnInteger('That page number is not an integer') if number < 1: raise EmptyPage('That page number is less than 1') if number > self.num_pages: if number == 1 and self.allow_empty_first_page: pass else: raise EmptyPage('That page contains no results') return number def page(self, number): """ Returns a Page object for the given 1-based page number. """ number = self.validate_number(number) bottom = (number - 1) * self.per_page top = bottom + self.per_page if top + self.orphans >= self.count: top = self.count return self._get_page(self.object_list[bottom:top], number, self) def _get_page(self, *args, **kwargs): """ Returns an instance of a single page. This hook can be used by subclasses to use an alternative to the standard :cls:`Page` object. """ return Page(*args, **kwargs) @cached_property def count(self): """ Returns the total number of objects, across all pages. """ try: return self.object_list.count() except (AttributeError, TypeError): # AttributeError if object_list has no count() method. # TypeError if object_list.count() requires arguments # (i.e. is of type list). return len(self.object_list) @cached_property def num_pages(self): """ Returns the total number of pages. """ if self.count == 0 and not self.allow_empty_first_page: return 0 hits = max(1, self.count - self.orphans) return int(ceil(hits / float(self.per_page))) @property def page_range(self): """ Returns a 1-based range of pages for iterating through within a template for loop. """ return six.moves.range(1, self.num_pages + 1) QuerySetPaginator = Paginator # For backwards-compatibility. class Page(collections.Sequence): def __init__(self, object_list, number, paginator): self.object_list = object_list self.number = number self.paginator = paginator def __repr__(self): return '<Page %s of %s>' % (self.number, self.paginator.num_pages) def __len__(self): return len(self.object_list) def __getitem__(self, index): if not isinstance(index, (slice,) + six.integer_types): raise TypeError # The object_list is converted to a list so that if it was a QuerySet # it won't be a database hit per __getitem__. if not isinstance(self.object_list, list): self.object_list = list(self.object_list) return self.object_list[index] def has_next(self): return self.number < self.paginator.num_pages def has_previous(self): return self.number > 1 def has_other_pages(self): return self.has_previous() or self.has_next() def next_page_number(self): return self.paginator.validate_number(self.number + 1) def previous_page_number(self): return self.paginator.validate_number(self.number - 1) def start_index(self): """ Returns the 1-based index of the first object on this page, relative to total objects in the paginator. """ # Special case, return zero if no items. if self.paginator.count == 0: return 0 return (self.paginator.per_page * (self.number - 1)) + 1 def end_index(self): """ Returns the 1-based index of the last object on this page, relative to total objects found (hits). """ # Special case for the last page because there can be orphans. if self.number == self.paginator.num_pages: return self.paginator.count return self.number * self.paginator.per_page
使用分页器Paginator
在视图中使用 Paginator来为查询集分页。我们提供视图以及相关的模板来展示如何展示这些结果。
Paginator常用属性
per_page: 每页显示条目数量 count: 数据总个数 num_pages:总页数 page_range:总页数的索引范围,页码的范围,从1开始,例如[1, 2, 3, 4]。
Paginator所需参数
object_list 一个列表,元祖或则Django 的Queryset 对象 或则其他对象带有 count() or __len__()的方法 per_page :就是1页显示几条数据
Paginator对象的方法
page(number) :返回在提供的下标处的Page对象,下标以1开始。
使用page对象
方法
Page.has_next() 如果有下一页,则返回True。 Page.has_previous() 如果有上一页,返回 True。 Page.has_other_pages() 如果有上一页或下一页,返回True。 Page.next_page_number() 返回下一页的页码。如果下一页不存在,抛出InvalidPage异常。 Page.previous_page_number() 返回上一页的页码。如果上一页不存在,抛出InvalidPage异常。 Page.start_index() 返回当前页上的第一个对象,相对于分页列表的所有对象的序号,从1开始。比如,将五个对象的列表分为每页两个对象,第二页的start_index()会返回3。 Page.end_index() 返回当前页上的最后一个对象,相对于分页列表的所有对象的序号,从1开始。 比如,将五个对象的列表分为每页两个对象,第二页的end_index() 会返回 4。
属性
Page.object_list 当前页上所有对象的列表。 Page.number 当前页的序号,从1开始。 Page.paginator 相关的Paginator对象。
django内置分页
代码示例
views.py
from django.shortcuts import render from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger # Create your views here. #模拟测试网页数据 USER_LIST = [] for i in range(1,999): temp = {"name":"root"+str(i),"age":i} USER_LIST.append(temp) def listing(request): current_page = request.GET.get('p') paginator = Paginator(USER_LIST,10) try: paginator = paginator.page(current_page) #获取前端传过来显示当前页的数据 except PageNotAnInteger: # 如果有异常则显示第一页 paginator = paginator.page(1) except EmptyPage: # 如果没有得到具体的分页内容的话,则显示最后一页 paginator = paginator.page(paginator.num_pages) return render(request,'index.html',{"users":paginator})
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul> {% for user in users.object_list %} <li>{{ user.name }}-{{ user.age }}</li> {% endfor %} {% if users.has_previous %} <a href="/index?p={{ users.previous_page_number }}">上一页</a> {% endif %} {% if users.has_next %} <a href="/index?p={{ users.next_page_number }}">下一页</a> {% endif %} <span>{{ users.number }}/{{ users.paginator.num_pages }}</span> </ul> </body> </html>
模拟访问测试
http://127.0.0.1:8080/index/?p=123132 http://127.0.0.1:8080/index/?p=阿斯顿发斯蒂芬
扩展Django内置分页
上面差不多久这些功能,为了扩张更多的功能我们来扩展Paginator分页器,例如网页页面显示的页码不只是只显示(上一页,下一页)
代码示例
from django.shortcuts import render from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger # Create your views here. #模拟测试网页数据 USER_LIST = [] for i in range(1,999): temp = {"name":"root"+str(i),"age":i} USER_LIST.append(temp) class CustomPaginator(Paginator): def __init__(self,current_page,per_pager_num,*args,**kwargs): # per_pager_num 显示的页码数量 self.current_page = int(current_page) self.per_pager_num = int(per_pager_num) super(CustomPaginator,self).__init__(*args,**kwargs) def pager_num_range(self): ''' 自定义显示页码数 第一种:总页数小于显示的页码数 第二种:总页数大于显示页数 根据当前页做判断 a 如果当前页大于显示页一半的时候 ,往右移一下 b 如果当前页小于显示页的一半的时候,显示当前的页码数量 第三种:当前页大于总页数 :return: ''' if self.num_pages < self.per_pager_num: return range(1,self.num_pages+1) half_part = int(self.per_pager_num/2) if self.current_page <= half_part: return range(1,self.per_pager_num+1) if (self.current_page+half_part) > self.num_pages: return range(self.num_pages-self.per_pager_num+1,self.num_pages) return range(self.current_page-half_part,self.current_page+half_part+1) def listing(request): current_page = request.GET.get('p') paginator = CustomPaginator(current_page,11,USER_LIST,10) try: paginator = paginator.page(current_page) #获取前端传过来显示当前页的数据 except PageNotAnInteger: # 如果有异常则显示第一页 paginator = paginator.page(1) except EmptyPage: # 如果没有得到具体的分页内容的话,则显示最后一页 paginator = paginator.page(paginator.num_pages) return render(request,'index.html',{"users":paginator})
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul> {% for user in users.object_list %} <li>{{ user.name }}-{{ user.age }}</li> {% endfor %} {% if users.has_previous %} <a href="/index?p={{ users.previous_page_number }}">上一页</a> {% endif %} {% for number in users.paginator.pager_num_range %} {% if number == users.number %} <a href="/index?p={{ number }}" style="font-size: 33px">{{ number }}</a> {% else %} <a href="/index?p={{ number }}" >{{ number }}</a> {% endif %} {% endfor %} {% if users.has_next %} <a href="/index?p={{ users.next_page_number }}">下一页</a> {% endif %} <span>{{ users.number }} /{{ users.paginator.num_pages }}</span> </ul> </body> </html>
自定制分页器
分页功能在每个网站都是必要的,对于分页来说,其实就是根据用户的输入计算出应该在数据库表中的起始位置。
1、设定每页显示数据条数
2、用户输入页码(第一页、第二页...)
3、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置
4、在数据表中根据起始位置取值,页面上输出数据
需求,需要在页面上显示分页的页面。如:[上一页][1][2][3][4][5][下一页]
1、设定每页显示数据条数
2、用户输入页码(第一页、第二页...)
3、设定显示多少页号
4、获取当前数据总条数
5、根据设定显示多少页号和数据总条数计算出,总页数
6、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置
7、在数据表中根据起始位置取值,页面上输出数据
8、输出分页html,如:[上一页][1][2][3][4][5][下一页]
代码示例;
1.新建一个分页器的py文件
#!/usr/bin/env python #-*- coding:utf-8 -*- from django.utils.safestring import mark_safe #mark_safe 把字符串转换成html语言 class Pagination(object): def __init__(self,totalCount,currentPage,perPageItemNum=10,maxPageNum=7): ''' :param totalCount: 数据总个数 :param currentPage: 当前页 :param perPageItemNum: 每页显示行数 :param maxPageNum: 最多显示页面 ''' self.total_count = totalCount try: current_page = int(currentPage) if current_page <=0: current_page = 1 self.current_page = current_page except Exception as e: self.current_page = 1 self.per_page_item_num = perPageItemNum self.max_page_num = maxPageNum def start(self): ''' 自定义一个表达式进行显示几页到几页 :return: ''' return (self.current_page-1)*self.per_page_item_num def end(self): return self.current_page * self.per_page_item_num @property def num_pages(self): ''' num_pages 属性 总页数 :return: ''' a,b=divmod(self.total_count,self.per_page_item_num) if b == 0: return a return a+1 def pager_num_range(self): ''' :return: ''' #总页码小于显示的页码区域 if self.num_pages < self.max_page_num: return range(1,self.max_page_num+1) #总页数大于最多显示页 根据当前页做判断 a,b,c #a 如果当前页小于显示页一半的时候 ,就只显示当前的页码区域 part = int(self.max_page_num / 2) if self.current_page <= part: return range(1, self.max_page_num + 1) #b 如果当前页大于显示页一半的时候 ,往右移一下页码 if (self.current_page + part) > self.num_pages: return range(self.num_pages - self.max_page_num + 1, self.num_pages + 1) #c:如果当前页大于总页数的 return range(self.current_page - part, self.current_page + part + 1) def page_str(self): ''' 把自定义后端的一些上一页和下一页的一些标签等等相关内容放到前端去显示 :return: ''' page_list = [] first = "<li><a href='/index2?p=1'>首页</a></li>" page_list.append(first) if self.current_page == 1: prev = "<li><a href='#'>上一页</a></li>" else: prev = "<li><a href='/index2?p=%s'>上一页</a></li>" % (self.current_page - 1,) page_list.append(prev) for i in self.pager_num_range(): if i == self.current_page: temp = "<li class='active'><a href='/index2?p=%s'>%s</a></li>" % (i, i) else: temp = "<li><a href='/index2?p=%s'>%s</a></li>" % (i, i) page_list.append(temp) if self.current_page == self.num_pages: nex = "<li><a href='#'>下一页</a></li>" else: nex = "<li><a href='/index2?p=%s'>下一页</a></li>" % (self.current_page + 1,) page_list.append(nex) last = "<li><a href='/index2?p=%s'>尾页</a></li>" % (self.num_pages,) page_list.append(last) res = ''.join(page_list) return mark_safe(res)
2.url路由
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^index/$', views.listing), url(r'^index2/$', views.index2), ]
3.视图
from django.shortcuts import render from app01.pager import Pagination #模拟测试网页数据 USER_LIST = [] for i in range(1,999): temp = {"name":"root"+str(i),"age":i} USER_LIST.append(temp) def index2(request): current_page = request.GET.get('p') page_obj = Pagination(len(USER_LIST), current_page) data_list = USER_LIST[page_obj.start():page_obj.end()] return render(request, 'index2.html', {'data': data_list, 'page_obj': page_obj})
3.模板的html文件index2.html的内容
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul> {% for row in data %} <li>{{ row.name }}-{{ row.age }}</li> {% endfor %} </ul> {% for i in page_obj.pager_num_range %} 页码显示部分区域 <a href="/index2?p={{ i }}">{{ i }}</a> {% endfor %} <hr> <ul >{{ page_obj.page_str }}</ul> </body> </html>
4.访问测试
http://127.0.0.1:9999/index2/?p=4 注:是用的本机做测试
总结:
总结,分页时需要做三件事:
- 创建处理分页数据的类
- 根据分页数据获取数据
- 输出分页HTML,即:[上一页][1][2][3][4][5][下一页]