django:分页功能源码与应用实现

分页功能作为网页中的必要功能之一,django提供的内置Paginator实现了如下功能:

  • 当前页面是否存在前后页。
  • 每页的数据量。
  • 访问的页数是否超范围。

一,django分页源码

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:
            if isinstance(number, float) and not number.is_integer():
                raise ValueError
            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."""
        c = getattr(self.object_list, 'count', None)
        if callable(c) and not inspect.isbuiltin(c) and method_has_no_args(c):
            return c()
        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 ceil(hits / 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
            )

其中的初始化参数如下:

  • object_list,需分页的数据对象,为必需的。使用count()或len()方法的列表、元组、查询集或其他可分割对象。为了实现一致的分页,应该对查询集进行排序,例如使用order by()子句,或者使用模型上的默认排序。这里要注意"对大型查询集进行分页的性能问题".
  • per_page,必需的。页面上要包括的最大项数,不包括“遗留项”。
  • orphans,可选的。当你不想最后一页条目很少的时候使用这个。如果最后一页通常有许多小于或等于遗留的项,那么这些项将被添加到前一页(即最后一页),而不是将这些项单独留在页面上。
  • allow_empty_first_pag,可选的。第一页是否允许为空。如果False且对象列表为空,则会引发一个EmptyPage错误。

其中的方法如下:

  • validate_number,验证当前分页数是否大于等于1。
  • get_page,返回给定的基于1索引的Page对象,同时还处理超出范围和无效的页码。如果页面不是一个数字,它返回第一个页面。如果页号为负数或大于页数,则返回最后一页。
  • page,据当前页数对object_list进行切片,返回给定的基于1索引的Page对象。如果给定的页码不存在,则引发InvalidPage。
  • _get_page将当前页数及页面对应的数据传给Page类,创建当前页数据对象。
  • count,返回object_list长度。
  • num_pages,获取分页后页面总数。
  • page_range,将页面总数变为可循环对象。
  • _check_object_list_is_ordered,对未排序的ORM object_list发出警告。

2,关于_get_page返回的数据:Page`类:

class Page(collections.abc.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

其中的初始化参数如下:

  • object_list,必选,此页上已被切片的对象列表。
  • number,必选,用户传递的页数。
  • paginator,必选,Paginator的实例化对象。

其中的方法如下:

  • has_next,Returns True if there’s a next page.
  • has_previous,Returns True if there’s a previous page.
  • has_other_pages,Returns True if there’s a next or previous page.
  • next_page_number,Returns the next page number. Raises InvalidPage if next page doesn’t exist.
  • previous_page_number,Returns the previous page number. Raises InvalidPage if previous page doesn’t exist.
  • start_index,输出当前页面的第一行数据在整个数据中的位置。
  • end_index,输出当前页面的最后一行数据在整个数据中的位置。

3,关于InvalidPage exceptions:

  • InvalidPage,如果所请求的页面无效(即不是整数)或不包含任何对象,则Paginator.page()方法会引发该异常。当然也可以使用下面的异常实现更细的粒度。
  • PageNotAnInteger,当page()被赋予非整数值时引发。
  • EmptyPage,当给page()一个有效值但该页上不存在对象时引发。

二,基本操作

通过以上部分源码分析,django以提供众多的属性与方法实现分页相关操作,所以分页操作比较固定。

>>> from django.core.paginator import Paginator 
>>> objects = ['john', 'paul', 'george', 'ringo']	
>>> p = Paginator(objects, 1)	
>>> p.object_list	
['john', 'paul', 'george', 'ringo']
>>> p.count		
4
>>> p.num_pages		
4
>>> type(p.page_range)
<class 'range'>
>>> p.page_range	
range(1, 5)
>>> page1 = p.page(1)	
>>> page1.object_list	
['john']
>>> page2 = p.page(2)
>>> page2.object_list
['paul']
>>> page2.has_next()	
True
>>> page2.has_previous() 
True
>>> page2.has_other_pages() 
True
>>> page2.next_page_number() 
3
>>> page1.has_previous()
False
>>> page2.start_index()
2
>>> p.page(0)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PythonProjects\web\django-yingyongkaifashizhan\MyDjango\venv\lib\site-packages\django\core\pag
inator.py", line 70, in page
    number = self.validate_number(number)
  File "F:\PythonProjects\web\django-yingyongkaifashizhan\MyDjango\venv\lib\site-packages\django\core\pag
inator.py", line 47, in validate_number
    raise EmptyPage(_('That page number is less than 1'))
django.core.paginator.EmptyPage: That page number is less than 1
>>> p.page(1)	
<Page 1 of 4>

三,案例实现

1,创建模型并添加数据

from django.db import models

class PersonInfo(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=20)
    age = models.IntegerField()

在这里插入图片描述

2,添加路由

proj/urls.py:
from django.urls import path, include
urlpatterns = [
    path('', include(('index.urls', 'index'), namespace='index')),
]

app/urls.py:
from django.urls import path
from .views import *
urlpatterns = [
    path('<page>/', index, name='index'),
]

3,功能视图

from django.shortcuts import render
from django.core.paginator import Paginator
from django.core.paginator import EmptyPage
from django.core.paginator import PageNotAnInteger
from .models import PersonInfo


def index(request, page):
    person = PersonInfo.objects.all().order_by('-age')
    # 设置每一页的数据量为2
    p = Paginator(person, 2)
    try:
        pages = p.get_page(page)
    except PageNotAnInteger:
        # 如果参数page的数据类型不是整型,就返回第一页数据
        pages = p.get_page(1)
    except EmptyPage:
        # 若用户访问的页数大于实际页数,则返回最后一页的数据
        pages = p.get_page(p.num_pages)
    return render(request, 'index.html', locals())

4,模板文件

<!DOCTYPE html>
<html lang="zh-hans">
<head>
    {
    
    % load static %}
    <title>分页功能</title>
    <link rel="stylesheet" href="{% static "css/base.css" %}"/>
    <link rel="stylesheet" href="{% static "css/lists.css" %}">
</head>
<body class="app-route model-hkrouteinfo change-list">
<div id="container">
    <div id="content" class="flex">
        <h1>分页功能</h1>
        <div id="content-main">
            <div class="module filtered" id="changelist">
                <form id="changelist-form" method="post">
                    <div class="results">
                        <table id="result_list">
                            <thead>
                            <tr>
                                <th class="action-checkbox-column">
                                    <div class="text">
                                        <span><input type="checkbox"/></span>
                                    </div>
                                </th>
                                <th><div class="text">姓名</div></th>
                                <th><div class="text">年龄</div></th>
                            </tr>
                            </thead>
                            <tbody>
                            {
    
    % for p in pages %}
                                <tr>
                                    <td class="action-checkbox">
                                        <input type="checkbox" class="action-select">
                                    </td>
                                    <td>{
    
    {
    
     p.name }}</td>
                                    <td>{
    
    {
    
     p.age }}</td>
                                </tr>
                            {
    
    % endfor %}
                            </tbody>
                        </table>
                    </div>
                    <p class="paginator">
                        {
    
    # 上一页的路由地址 #}
                        {
    
    % if pages.has_previous %}
                            <a href="{% url 'index:index' pages.previous_page_number %}">上一页</a>
                        {
    
    % endif %}
                        {
    
    # 列出所有的路由地址 #}
                        {
    
    % for n in pages.paginator.page_range %}
                            {
    
    % if n == pages.number %}
                                <span class="this-page">{
    
    {
    
     pages.number }}</span>
                            {
    
    % else %}
                                <a href="{% url 'index:index' n %}">{
    
    {
    
     n }}</a>
                            {
    
    % endif %}
                        {
    
    % endfor %}
                        {
    
    # 下一页的路由地址 #}
                        {
    
    % if pages.has_next %}
                            <a href="{% url 'index:index' pages.next_page_number %}">下一页</a>
                        {
    
    % endif %}
                    </p>
                </form>
            </div>
        </div>
    </div>
</div>
</body>
</html>

猜你喜欢

转载自blog.csdn.net/dangfulin/article/details/108247233