分页组件
Django的分页器(paginator)简介
django自带分页组件,但是自带的不是很全,我们需要扩展它内置的原生分页。
django里面是封装了两个类,通过这两个类去创造了分页器:
我们在视图函数里面通过django的model操作,获取了数据,然后嵌套到前端HTML文档里去渲染展示,如果数据量多大的话很显示的特别长,这时候我们可以做分页。
需求:
每页显示10条内容,看第五页的内容。
索引是从0开始的,所以第一页的10条内容,索引是0-10。第二页索引是10-20,第三页的索引的20-30.我们发现对所有的内容进行切片就可以了。
开始的索引是当前页减去1然后乘以10,结束的索引是当前页乘以10。当前页的内容就是所有内容的切片。
后台实现逻辑传递:
def show(request): #规定每页显示条数 per_page_count = 10 #当前页数 current_page = request.GET.get('page_num') current_page = int(current_page) #索引开始位置 start = (current_page-1) * per_page_count #索引结束位置 stop = current_page * per_page_count # 从数据库获取内容 data_list = model...... # 当前页的内容 current_page_data = data_list[start:stop] # 向前端嵌套需要展示的内容 return render(request,'html文档',{'data_list':current_page_data})
前端接收数据用模板语言。
前端的上一页、下一页怎么做?
a标签跳转
def show(request): # 如果当前页就是第一页没有上一页 if current_page <=1: prev_page = 1 # 上一页 prev_page = current_page-1 # 判断当前页是否是最后一页 # 下一页 next_page = current_page+1 # 向前端嵌套需要展示的内容 return render(request,'html文档',{'data_list':current_page_data,'prev_page':prev_page,'next_page':next_page})
<ul> <!--接收后台传过来的内容--> {% for data in data_list %} <li>{{ 数据库的具体记录 }}</li> {% endfor %} </ul> <!--接收后台传过来的内容,上一页,下一页--> <a href="请求的url?p={{ prev_page }}">上一页</a> <a href="请求的url?p={{ next_page }}">上一页</a>
以上就是分页的原理。
def show(request): #规定每页显示条数 per_page_count = 10 #当前页数 current_page = request.GET.get('page_num') current_page = int(current_page) #索引开始位置 start = (current_page-1) * per_page_count #索引结束位置 stop = current_page * per_page_count # 从数据库获取内容 data_list = model...... # 当前页的内容 current_page_data = data_list[start:stop] # 如果当前页就是第一页没有上一页 if current_page <=1: prev_page = 1 # 上一页 prev_page = current_page-1 # 判断当前页是否是最后一页 # 下一页 next_page = current_page+1 # 向前端嵌套需要展示的内容 return render(request,'html文档',{'data_list':current_page_data,'prev_page':prev_page,'next_page':next_page})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <ul> <!--接收后台传过来的内容--> {% for data in data_list %} <li>{{ 数据库的具体记录 }}</li> {% endfor %} </ul> <!--接收后台传过来的内容,上一页,下一页--> <a href="请求的url?p={{ prev_page }}">上一页</a> <a href="请求的url?p={{ next_page }}">上一页</a> </body> </html>
以上我们写的代码django原生的就能满足我们的需求。所以我们可以不用自己写这个代码。
使用django内置的分页:
第一步:需要引入分页组件,django分页的所有东西都在里面了
from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger
我们发现Paginator就是一个类。我们使用这个类时就可以
class Paginator(object): 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): """ 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) 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 ) QuerySetPaginator = Paginator # For backwards-compatibility.
我们使用这个类时就可以给它传值。
第二步:创建Paginator对象,Paginator的参数有object_list, per_page, orphans=0,allow_empty_first_page=True
- object_list 是要分页的对象,如果是django的话可以直接写model的操作,比如:
- per_page 每页显示条目数量
from django.shortcuts import render from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger def show(request): paginator = Paginator(book_list, 10)
paginator里面封装了几个常用的方法:
- paginator.per_page 查看每页显示条目数量
- paginator.count 查看总个数
- paginator.num_pages 查看总页数
- paginator.range 总页数的索引范围
- paginator.page 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, (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
def show(request): current_page = request.GET.get('page-num') paginator = Paginator(models.Book.obiects.all(),10) # 异常处理,以防客户输入的不正确 try: posts = paginator.page(current_page) #填的不是个整数 except PageNotAnInteger: posts = paginator.page(1) # 空页 except EmptyPage: posts = paginator.page(paginator.num_pages) return render(request,'book.html',{'posts':posts})
page对象里面封装了结果方法:
- posts.has_next 是否有下一页
- posts.next_page_number 下一页页码
- posts.has_previous 是否有上一页
- posts.previous_page_number 上一页页码
- posts.object_list 分页之后的数据列表,已经切片好的数据
- posts.number 当前页
- posts.paginator paginator对象
<!-- 数据内容 --> <ul> {% for book in posts.object_list %} <li>{{ book.id }} - {{ book.name }}</li> {% endfor %} </ul> <!-- 上一页和下一页 --> {% if posts.has_prvious %} <a href="url?={{ posts.prvious_page_number }}">上一页</a> {% else %} <a href="#">上一页</a> {% endif %} {% if posts.has_next %} <a href="url?={{ posts.next_page_number }}">下一页</a> {% else %} <a href="#">下一页</a> {% endif %}
显示总页数和当前页。
<span>
{{ posts.number }}/{{ posts.paginator.num_pages }}
</span>
和bootstrap配合使用:
<nav aria-label="..."> <ul class="pager"> {% if posts.has_previous %} <li><a href="/show?p={{ posts.previous_page_number }}">上一页</a></li> {% else %} <li><a href="#">上一页</a></li> {% endif %} {% if posts.has_next %} <li><a href="/show?p={{ posts.next_page_number }}">下一页</a></li> {% else %} <li><a href="#">下一页</a></li> {% endif %} </ul> </nav> <span> {{ posts.number }}/{{ posts.paginator.num_pages }} </span>
每一个都会有上一页下一页,都会分页。我们可以新建个include文件夹,将写的这些模板语言放到pager.html文件里面。如果别的HTML文档需要用可以{% include 'include/pager.html'%}就可以了。
用django的分页器也就只能做到这里了。
from django.shortcuts import render,HttpResponse # Create your views here. from app01.models import * from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger def index(request): ''' 批量导入数据: Booklist=[] for i in range(100): Booklist.append(Book(title="book"+str(i),price=30+i*i)) Book.objects.bulk_create(Booklist) ''' ''' 分页器的使用: book_list=Book.objects.all() paginator = Paginator(book_list, 10) print("count:",paginator.count) #数据总数 print("num_pages",paginator.num_pages) #总页数 print("page_range",paginator.page_range) #页码的列表 page1=paginator.page(1) #第1页的page对象 for i in page1: #遍历第1页的所有数据对象 print(i) print(page1.object_list) #第1页的所有数据 page2=paginator.page(2) print(page2.has_next()) #是否有下一页 print(page2.next_page_number()) #下一页的页码 print(page2.has_previous()) #是否有上一页 print(page2.previous_page_number()) #上一页的页码 # 抛错 #page=paginator.page(12) # error:EmptyPage #page=paginator.page("z") # error:PageNotAnInteger ''' book_list=Book.objects.all() paginator = Paginator(book_list, 10) page = request.GET.get('page',1) currentPage=int(page) try: print(page) book_list = paginator.page(page) except PageNotAnInteger: book_list = paginator.page(1) except EmptyPage: book_list = paginator.page(paginator.num_pages) return render(request,"index.html",{"book_list":book_list,"paginator":paginator,"currentPage":currentPage})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <div class="container"> <h4>分页器</h4> <ul> {% for book in book_list %} <li>{{ book.title }} -----{{ book.price }}</li> {% endfor %} </ul> <ul class="pagination" id="pager"> {% if book_list.has_previous %} <li class="previous"><a href="/index/?page={{ book_list.previous_page_number }}">上一页</a></li> {% else %} <li class="previous disabled"><a href="#">上一页</a></li> {% endif %} {% for num in paginator.page_range %} {% if num == currentPage %} <li class="item active"><a href="/index/?page={{ num }}">{{ num }}</a></li> {% else %} <li class="item"><a href="/index/?page={{ num }}">{{ num }}</a></li> {% endif %} {% endfor %} {% if book_list.has_next %} <li class="next"><a href="/index/?page={{ book_list.next_page_number }}">下一页</a></li> {% else %} <li class="next disabled"><a href="#">下一页</a></li> {% endif %} </ul> </div> </body> </html>
扩展
需求:
我们想要上一页和下一页中间显示数值,但不是全部显示出来,就显示几个就可以了。
怎么做?
目前django自带的分页器不带这个功能,所以需要我们自己开发出来这个功能。django的分页器只涉及Paginator对象和Page对象,如果再这两个对象里随便一个,return一个范围,能在页面让它这样输出。比如在Page对象return 一到六,那那个页面就显示1到6。
但是上面的Page对象是Paginator类里面的方法创建的,我们自己想要定义一个类需要修改源码,但是源码不能随便改。
但是Paginator对象使我们自己创建的。
我们可以自己创建一个类继承Paginator,并且为了不影响其他的功能我们可以super。
这个类可以传参数,可以自己定义方法。
显示左5,右5,总共11个页, 如果总页码大于11 1.1 if 当前页码减5小于1,要生成1到12的列表(顾头不顾尾,共11个页码) page_range=range(1,12) 1.2 elif 当前页码+5大于总页码,生成当前页码减10,到当前页码加1的列表(顾头不顾尾,共11个页码) page_range=range(paginator.num_pages-10,paginator.num_pages+1) 1.3 else 生成当前页码-5,到当前页码+6的列表 page_range=range(current_page_num-5,current_page_num+6) 其它情况,生成的列表就是pageinator的page_range page_range=paginator.page_range '''
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger class CustomPaginator(Paginator): def __init__(self, *args, **kwargs): super(CustomPaginator, self).__init__(*args, **kwargs) def pager_num_range(self): # 前端怎么获取?前端现在有page对象,page对象.paginator就是上层的paginator对象,我们这里改了paginator对象现在变成CustomPaginator对象了。我们多增加了pager_num_range参数。 return range(1, 12)
def show(request): current_page = request.GET.get('p') book_list = models.Book.objects.all() paginator = CustomPaginator(book_list, 10) # 异常处理,以防客户输入的不正确 try: posts = paginator.page(current_page) # 填的不是个整数 except PageNotAnInteger: posts = paginator.page(1) # 空页 except EmptyPage: posts = paginator.page(paginator.num_pages) return render(request,'show.html', {'posts': posts})
在前端怎么获取?
在上一页和下一页的中间来个for循环,并且给当前页的页码来个样式
{% for i in posts.paginator.pager_num_range %} {% if i == posts %} <a style="font-size: 20px" href="/show?p={{ i }}">{{ i }}</a> {% else %} <a href="/show?p={{ i }}">{{ i }}</a> {% endif %}
问题:
range应该是动态生成的,不能写死了。
根据当前页动态生成!
根据当前页、最多显示多少页和数据的总页数。
如果总页数小于最大显示的页码,我们就之显示总页数的页码。
总页数特别多
如果当前页是1-6之间的时候
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from app01 import models class CustomPaginator(Paginator): def __init__(self,current_page,per_pager_num,*args, **kwargs): # 当前页 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): # 前端怎么获取?前端现在有page对象,page对象.paginator就是上层的paginator对象,我们这里改了paginator对象现在变成CustomPaginator对象了。我们多增加了pager_num_range参数。 if self.num_pages < self.per_pager_num: return range(1,self.num_pages+1) part = int(self.per_pager_num/2) if self.current_page <= part: return range(1,self.per_pager_num+1) if (self.current_page+5) > self.num_pages: return range(self.num_pages-self.per_pager_num-1,self.num_pages+1) return range(self.current_page-part, self.current_page+part+1)
def show(request): current_page = request.GET.get('p') book_list = models.Book.objects.all() paginator = CustomPaginator(current_page,11,book_list, 10) # 异常处理,以防客户输入的不正确 try: posts = paginator.page(current_page) # 填的不是个整数 except PageNotAnInteger: posts = paginator.page(1) # 空页 except EmptyPage: posts = paginator.page(paginator.num_pages) return render(request,'show.html', {'posts': posts})
自定义分页组件
自定义需要传入:
- 所有数据的个数
- 比如有100万条数据,如果全拿出来服务器就炸了,所以应该去数据库查一共有多少条数据,然后通过计算这一次只取多少到多少,把索引告诉数据库,让数据库把这一块位置拿出来就好了。。
- 当前页
- 每页显示30条
- 最多显示7页
效果:
自定义的分页类:
在逻辑函数里用:
第一步是导入这个类
第二步是创建这个类的对象,并且传入参数
第一个参数是所有数据的个数[在数据库中获取所有数据的个数方法是:total_count = models.数据库表名.objects.all().count()]
第二个参数是当前页[在request请求里获取:current_page = request.GET.get('p')]
第三个参数是每页显示的行数
我们自定义的分页py文件内容:
class Pagination(object): def __init__(self, totalCount, currentPage, perPageItemNum=30, maxPageNum=7): # 数据总个数 self.total_count = totalCount # 当前页,客户有可能写错 try: v = int(currentPage) if v <= 0: v = 1 else: self.current_page = v except Exception as e: self.current_page = 1 # 每页显示的行数 self.per_page_item_num = perPageItemNum # 最多显示的页码 self.max_page_num = maxPageNum # 数据切片 def start(self): 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): a, b = divmod(self.total_count, self.per_page_item_num) if b == 0: return a else: return a + 1 # 分页的范围 def pager_num_range(self): # 前端怎么获取?前端现在有page对象,page对象.paginator就是上层的paginator对象,我们这里改了paginator对象现在变成CustomPaginator对象了。我们多增加了pager_num_range参数。 if self.num_pages < self.max_page_num: return range(1, self.num_pages + 1) part = int(self.max_page_num / 2) if self.current_page <= part: return range(1, self.max_page_num + 1) if (self.current_page + 5) > self.num_pages: return range(self.num_pages - self.max_page_num - 1, self.num_pages + 1) return range(self.current_page - part, self.current_page + part + 1) # 自动生成a标签 def page_str(self): page_list = [] first = "<li><a href='/show?p=1'>首页</a></li>" page_list.append(first) if self.current_page == 1: prev = "<li><a href='#'>上一页</a></li>" else: prev = "<li><a href='/show?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='/book?p=%s'>%s</a></li>" % (i, i) else: temp = "<li><a href='/show?p=%s'>%s</a></li>" % (i, i) page_list.append(temp) if self.current_page == self.num_pages: next = "<li><a href='#'>下一页</a></li>" else: next = "<li><a href='/show?p=%s'>下一页</a></li>" % (self.current_page + 1) page_list.append(next) last = "<li><a href='/show?p=%s'>尾页</a></li>" % (self.num_pages) page_list.append(last) return ''.join(page_list)
如何使用:
在视图逻辑中:
def show(request): from app01.pager import Pagination current_page = request.GET.get('p') total_count = models.Book.objects.all().count() pager_obj = Pagination(total_count,current_page) book_list = models.Book.objects.all() data_list = book_list[pager_obj.start():pager_obj.end()] return render(request,'book.html',{'data':data_list,'page_obj':pager_obj})
在HTML文件中:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> <title>Title</title> </head> <body> <h4>分页器</h4> <ul> {% for book in data %} <li>{{ book.title }} -----{{ book.price }}</li> {% endfor %} </ul> <ul class="pagination pagination-sm"> {{ page_obj.page_str|safe }} </ul> </body> </html>
end