• 分页组件


    一、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
                )
    Paginator
        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
    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())
    BookList

    注意:需要传入四个参数

    #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>
    books.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>
    books2.html

    在pager.html中:

    <nav aria-label="">
      <ul class="pagination">
    {{ paginator.get_range_page__num_str }}
      </ul>
    </nav>

    这样就完成了通用的自定义分页功能:

  • 相关阅读:
    Django学习日记04_模板_overview
    Python并发实践_01_线程与进程初探
    web自动化测试笔记(二)
    web自动化测试笔记(一)
    app版本升级的测试点
    移动测(APP)试与web端测试的区别
    Dubbo服务器与普通服务器的区别
    java的错误分类
    安卓手机与iOS手机的区别
    在webstorm里使用git
  • 原文地址:https://www.cnblogs.com/shenjianping/p/11517892.html
Copyright © 2020-2023  润新知