首页 > 其他 > 详细

分页组件

时间:2019-09-25 22:44:54      阅读:91      评论:0      收藏:0      [点我收藏+]

一、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>

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

技术分享图片

 

分页组件

原文:https://www.cnblogs.com/shenjianping/p/11517892.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!