分页组件

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})
Views
<!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>
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对象,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
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>
View Code

每一个都会有上一页下一页,都会分页。我们可以新建个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})
views
<!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>
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)
pager.py

如何使用:

  在视图逻辑中:

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

posted @ 2020-04-22 18:22  张仁国  阅读(565)  评论(0编辑  收藏  举报
目录代码