一、QuerySet中的切片
QuerySet是支持切片操作的,可以按需取出数据的个数:
#所有数据 book_queryset = models.Book.objects.all() #取出索引为1-3的数据,实际索引范围1-2 book_queryset = models.Book.objects.all()[1:3] #取出前10条数据实际索引范围0-9 book_queryset = models.Book.objects.all()[:10] #如果取最后的数据,不能使用负索引,但可以使用reverse方法和order_by # 1. 使用 reverse() models.Book.objects.all().reverse()[:3] # 最后3条 models.Book.objects.all().reverse()[0] # 最后1条 # 2. 使用 order_by,在字段名前加一个负号 Author.objects.order_by('-id')[:10] # id最大的10条
注意:索引取值的原则是左取右不取。
分页功能恰好就可以利用QuerySset的分片特性,每一次获取页码,根据页码以及每页显示多少条数据,从而得到QuerySet开始和结束的索引位置。
def BookList(request): #每页显示的数据条数 per_page = 3 #获取的页码 current_page = int(request.GET.get("p")) #得到开始索引 start = (current_page-1)*per_page #结束索引 end = current_page*per_page """ 推算出开始、结束索引公式 p=1 [0,3] 实际数据 0-2 p=2 [3,6] 实际数据 3-5 """ book_queryset = models.Book.objects.all()[start:end] return render(request,'books.html',locals())
二、Django中的内置分页
(一)使用
def BookList(request): book_queryset = models.Book.objects.all() #每页显示的数据条数 per_page = 3 #获取的页码 current_page = request.GET.get("p") paginator = Paginator(book_queryset, per_page) # per_page: 每页显示条目数量,需要传入的参数 # count: 数据总个数 # num_pages:总页数 # page_range:总页数的索引范围,如: (1,100) # page: page对象 try: pager = paginator.page(current_page) # has_next 是否有下一页 # next_page_number 下一页页码 # has_previous 是否有上一页 # previous_page_number 上一页页码 # object_list 分页之后的数据列表 # number 当前页 # paginator paginator对象 except PageNotAnInteger: #如果出现p的值不是整数 pager = paginator.page(1) except EmptyPage: #如果没有输入p的值 pager = paginator.page(paginator.num_pages) return render(request,'books.html',locals())
book.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul> {% for book in pager.object_list %}<!--object_list是分页后的数据--> <li>{{ book.title }}</li> {% endfor %} </ul> {% if pager.has_previous %} <a href="/booklist?p={{ pager.previous_page_number }}">上一页</a> {% else %} <a href="#">上一页</a> {% endif %} {% if pager.has_next %} <a href="/booklist?p={{ pager.next_page_number }}">下一页</a> {% endif %} <span> {{ pager.number }}/{{ pager.paginator.num_pages }}<!--或者调用pager.__repr__--> </span> </body> </html>
(二)源码分页
Django内置分页中有两个API需要注意:
1、Paginator
class Paginator: def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True): self.object_list = object_list self._check_object_list_is_ordered() self.per_page = int(per_page) self.orphans = int(orphans) self.allow_empty_first_page = allow_empty_first_page def validate_number(self, number): """Validate 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 get_page(self, number): """ Return a valid page, even if the page argument isn't a number or isn't in range. """ try: number = self.validate_number(number) except PageNotAnInteger: number = 1 except EmptyPage: number = self.num_pages return self.page(number) def page(self, number): """Return 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): """ Return 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): """Return 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): """Return 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): """ Return a 1-based range of pages for iterating through within a template for loop. """ return range(1, self.num_pages + 1) def _check_object_list_is_ordered(self): """ Warn if self.object_list is unordered (typically a QuerySet). """ ordered = getattr(self.object_list, 'ordered', None) if ordered is not None and not ordered: obj_list_repr = ( '{} {}'.format(self.object_list.model, self.object_list.__class__.__name__) if hasattr(self.object_list, 'model') else '{!r}'.format(self.object_list) ) warnings.warn( 'Pagination may yield inconsistent results with an unordered ' 'object_list: {}.'.format(obj_list_repr), UnorderedObjectListWarning, stacklevel=3 )
def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True): """ :param object_list: 传入的数据集合 :param per_page: 每页有多少条数据 :param orphans: :param allow_empty_first_page: """ self.object_list = object_list self._check_object_list_is_ordered() self.per_page = int(per_page) self.orphans = int(orphans) self.allow_empty_first_page = allow_empty_first_page
在这里需要传入数据的集合以及每页显示多少条数据,它的输出有:
- 数据总个数
self.count()
- 总页数
self.num_pages()
- 总页数的索引范围
self.page_range()
- Page对象(传入current_page)
self.page()
2、Page
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, (int, slice)): 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): """ Return 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): """ Return 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
def __init__(self, object_list, number, paginator): """ :param object_list: 已经是分页后的数据 :param number: 当前的页码 :param paginator: Paginator对象 """ self.object_list = object_list self.number = number self.paginator = paginator
Page对象可以通过Paginator对象中page方法获取,只需要传入当前页即可,它的输出:
# has_next 是否有下一页 # next_page_number 下一页页码 # has_previous 是否有上一页 # previous_page_number 上一页页码 # object_list 分页之后的数据列表 # number 当前页 # start_index 开始索引 # end_index 结束索引 # paginator paginator对象
(三)扩展
1、引出问题
上面的效果是这样的:
但是假如想给中间加上一些页码,那么在django分页的Paginator API中有page_range方法它返回的是是一个range(1,总的页码数),可以在模板中加入这个功能:
... {% for book in pager.object_list %}<!--object_list是分页后的数据--> <li>{{ book.title }}</li> {% endfor %} </ul> <!--上一页功能--> {% if pager.has_previous %} <a href="/booklist/?p={{ pager.previous_page_number }}">上一页</a> {% else %} <a href="#">上一页</a> {% endif %} <!--加入页码范围功能--> {% for i in pager.paginator.page_range %} {{ i }} {% endfor %} <!--下一页功能--> {% if pager.has_next %} <a href="/booklist/?p={{ pager.next_page_number }}">下一页</a> {% endif %} <span> {{ pager.number }}/{{ pager.paginator.num_pages }}<!--或者调用pager.__repr__--> </span> ...
效果如图:
你可能也已经注意到了,中间页码范围是从1到所有的页码,我们希望的是中间的页码是动态的,较为灵活的。所以此时需要自定制。
2、自定制页码范围
- 在内置Paginator的基础上进行定制:
class CustomerPaginator(Paginator): def __init__(self,current_page,per_pager_num,*args,**kwargs): self.current_page = int(current_page) self.per_pager_num = per_pager_num super(CustomerPaginator,self).__init__(*args,**kwargs) def get_range_page_num(self): """ self.current_page 当前页 self.per_pager_num 显示页码的个数 self.num_pages 总页数 :return: """ #1、页码不够多,达不到要求定制的页码个数 #如果总页数小于需要显示的页码个数,就将所有的页码全部显示出来 if self.num_pages < self.per_pager_num: return range(1,self.num_pages+1) #2、页码足够多,已经远远超过了要求定制的页码个数 # half = self.per_pager_num//2 half = int(self.per_pager_num/2) # 左临界条件,如果当前页没有超过显示页码个数的一半,就将定制的页码个数全部显示出来 if self.current_page <= half: return range(1,self.per_pager_num+1) #右临界条件,如果点击到最后一个页码 if (self.current_page+half) > self.num_pages: return range(self.num_pages-self.per_pager_num+1,self.num_pages+1) #中间情况,如果当前页超过显示页码个数的一半,根据当前页计算开始和结束的页码 return range(self.current_page-half,self.current_page+half+1)
- 使用
def BookList(request): book_queryset = models.Book.objects.all() #每页显示的数据条数 per_page = 3 #获取的页码 current_page = request.GET.get("p") paginator = CustomerPaginator(current_page,3,book_queryset, per_page) # per_page: 每页显示条目数量 # count: 数据总个数 # num_pages:总页数 # page_range:总页数的索引范围,如: (1,10),(1,200) # page: page对象 try: pager = paginator.page(current_page) # has_next 是否有下一页 # next_page_number 下一页页码 # has_previous 是否有上一页 # previous_page_number 上一页页码 # object_list 分页之后的数据列表 # number 当前页 # paginator paginator对象 except PageNotAnInteger: #如果出现p的值不是整数 pager = paginator.page(1) except EmptyPage: #如果没有输入p的值 pager = paginator.page(paginator.num_pages) return render(request,'books.html',locals())
注意:需要传入四个参数
#current_page 当前页 #per_pager_num 显示的页码个数 #object_list 传入的数据集合 #per_page 每页显示的数据个数
- books.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul> {% for book in pager.object_list %}<!--object_list是分页后的数据--> <li>{{ book.title }}</li> {% endfor %} </ul> {% if pager.has_previous %} <a href="/booklist/?p={{ pager.previous_page_number }}">上一页</a> {% else %} <a href="#">上一页</a> {% endif %} <!--加入自定制页码范围功能--> {% for i in pager.paginator.get_range_page_num %} {% if i == pager.number %} <a href="/booklist/?p={{ i }}">{{ i }}</a> {% else %} <a href="/booklist/?p={{ i }}">{{ i }}</a> {% endif %} {% endfor %} {% if pager.has_next %} <a href="/booklist/?p={{ pager.next_page_number }}">下一页</a> {% endif %} <span> {{ pager.number }}/{{ pager.paginator.num_pages }}<!--或者调用pager.__repr__--> </span> </body> </html>
三、通用分页组件
from django.utils.safestring import mark_safe class Paginator(object): def __init__(self,totalNum,currentPage,baseUrl,perPageNum=3,maxPageNum=3): # 总的数据个数 self.total_num = totalNum # 当前页 self.current_page = currentPage # 访问的baseurl self.base_url = baseUrl # 每页显示的数据条数 self.per_page_num = int(perPageNum) # 显示的最多页码个数 self.max_page_num = int(maxPageNum) def __repr__(self): return "Page %s of %s" % (self.validate_num(self.current_page), self.num_pages) @property def num_pages(self): """ 获取总的页数,总的数据个数%每页显示的个数 :return: 另一种计算方法 a = self.total_num//self.per_page_num 取整 b = self.total_num%self.per_page_num 取余 if b == 0 :return a :return a+1 """ if self.total_num == 0: return 0 a, b = divmod(self.total_num, self.per_page_num) if b == 0: return a return a + 1 def validate_num(self, num): """ 对页码进行验证,如果不是整数,就让其访问第一页 :param num: :return: """ try: num = int(num) except Exception as e: num = 1 if num <= 0: num = 1 return num def has_previous(self): return self.validate_num(self.current_page) >= 1 def previous_page_num(self): return self.validate_num(self.current_page) - 1 def has_next(self): return self.validate_num(self.current_page) <= self.num_pages def next_page_num(self): return self.validate_num(self.current_page) + 1 @property def start_index(self): return (self.validate_num(self.current_page) - 1) * self.per_page_num @property def end_index(self): return self.validate_num(self.current_page) * self.per_page_num def get_range_page_num(self): """ self.current_page 当前页 self.per_pager_num 显示页码的个数 self.num_pages 总页数 :return: """ # 1、页码不够多,达不到要求定制的页码个数 # 如果总页数小于需要显示的页码个数,就将所有的页码全部显示出来 if self.num_pages < self.per_page_num: return range(1, self.num_pages + 1) # 2、页码足够多,已经远远超过了要求定制的页码个数 # half = self.per_pager_num//2 half = int(self.per_page_num / 2) # 左临界条件,如果当前页没有超过显示页码个数的一半,就将定制的页码个数全部显示出来 if self.validate_num(self.current_page) <= half: return range(1, self.per_page_num + 1) # 右临界条件,如果点击到最后一个页码 if (self.validate_num(self.current_page) + half) > self.num_pages: return range( self.num_pages - self.per_page_num + 1, self.num_pages + 1) # 中间情况,如果当前页超过显示页码个数的一半,根据当前页计算开始和结束的页码 return range(self.validate_num(self.current_page) -half,self.validate_num(self.current_page) +half +1) def get_range_page__num_str(self): page_str_list = [] # 首页 first = "<li><a href='%s?p=%s'>首页</a></li>" % (self.base_url, 1) page_str_list.append(first) # 上一页 if self.has_previous(): if self.validate_num(self.current_page) == 1: previous = "<li><a href=''>上一页</a></li>" else: previous = "<li><a href='%s?p=%s'>上一页</a></li>" % ( self.base_url, self.previous_page_num()) page_str_list.append(previous) # 中间可选页码 for i in self.get_range_page_num(): if self.validate_num(self.current_page) == i: temp = "<li class='active'><a href='%s?p=%s'>%s</a></li>" % (self.base_url, i, i) else: temp = "<li><a href='%s?p=%s'>%s</a></li>" % (self.base_url, i, i) page_str_list.append(temp) # 下一页 if self.has_next(): if self.validate_num(self.current_page) == self.num_pages: next = "<li><a href=''>下一页</a></li>" else: next = "<li><a href='%s?p=%s'>下一页</a></li>" % ( self.base_url, self.next_page_num()) page_str_list.append(next) # 尾页 last = "<li><a href='%s?p=%s'>尾页</a></li>" % (self.base_url, self.num_pages) page_str_list.append(last) # 统计 Page 2 of 4 page_of_total = "<li class='disabled'><span>%s</span></li>"%(self.__repr__()) page_str_list.append(page_of_total) return mark_safe(''.join(page_str_list))
只需要向这个类中传入必需的参数即可:
totalNum #总的数据个数 currentPage #当前页码 baseUrl #baseurl
另外还有两个非必需参数:
perPageNum #每页显示的数据个数 maxPageNum #显示的可选页码个数
后台视图函数使用也很简单:
#1、导入分页API from app01.pager import Paginator as MyPaginator from django.urls import reverse #2、引用 def BookList(request): book_queryset = models.Book.objects.all() count = book_queryset.count() current_page = request.GET.get("p") base_url = reverse("book_list") paginator = MyPaginator(count,current_page,base_url) data = book_queryset[paginator.start_index:paginator.end_index] return render(request,'books2.html',locals())
在前端中include分页的html
{% load staticfiles %} <!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.css' %}"/> </head> <body> <ul> {% for book in data %}<!--data是分页后的数据--> <li>{{ book.title }}</li> {% endfor %} </ul> <!--引入分页--> {% include 'pager.html' %} </body> </html>
在pager.html中:
<nav aria-label=""> <ul class="pagination"> {{ paginator.get_range_page__num_str }} </ul> </nav>
这样就完成了通用的自定义分页功能: