python web 分页组件

闲来无事便写了一个易使用,易移植的Python Web分页组件。使用的技术栈是Python、Django、Bootstrap。

既然是易使用、易移植的组件,首先介绍一下其在django框架中的调用方式吧。我将组件封装成了Django InclusionTag,在template模板中直接调用tag即可,代码如下:

{% load pager_tags %}

{% pager request 100 10 %}

   其中 pager_tags是封装的tag文件名,这个无需多说,不了解django框架custom filter、tag的可在官方文档查看。

pager是注册的tag名,后面是传递到pager的参数。request是django的请求对象,100是最大页数,10是导航页码的个数。

可以看到,整个组件的调用是非常简单的,比起django自带的分页器paginator要轻巧很多,这也是我决定开发这个组件的主要原因之一。当然组件使用的便利性带来的是灵活度的牺牲,其扩展能力十分有限,而且依赖于前端框架Django。所以,想要定制一些特别的功能或者样式的小伙伴还是应该选择paginator自行开发。

接下来我们看看tag是怎么写的吧。

from django import template

from garra_rbac.services import page_service

register = template.Library()


@register.inclusion_tag('garra_pager.html')
def pager(request, max_index, show_count=5):
    _page = request.GET.get('page')
    page = 1
    if _page:
        page = int(_page)
    param = request.GET.urlencode()
    page_obj = page_service.PageObject(page, max_index, param, show_count)
    return {'page_obj': page_obj}

  我在tag中并没有做太多的事情,只是处理了当前页面的请求参数。其中当前页默认为0,并把GET的参数传递下去,当点击其他页码时要保留参数条件。

我们再看看garra_pager.html吧,html页面是依赖于Bootstrap开发的,不过对于样式的修改也很容易实现。

{% if page_obj.has_page_item %}
    <nav aria-label="Page navigation">
        <ul class="pagination">
            {% for page_index in page_obj.page_index_list %}
                <li {% if page_obj.current_index == page_index.index %}class="active"{% endif %}>
                    <a href="{{ page_index.url }}"
                       class="{{ page_obj.current_index }}">{{ page_index.text }}</a>
                </li>
            {% endfor %}
        </ul>
    </nav>

{% else %}
    <div class="alert alert-danger" role="alert">
        <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
        <span class="sr-only">Error:</span>
        暂无数据
    </div>
{% endif %}

  html代码很好理解,都是数据的展示,并添加了暂无数据的提示。

最后我们看看分页组件功能的主要实现部分,page_service的代码。

import math


def get_lhalf(show_count):
    """
    获取当前页的左侧最多右多少个页码
    :param show_count:展示的页码个数
    :return:当前页的左侧最多右多少个页码
    """
    if show_count % 2 == 0:  # 如果为偶数,则让当前页的左侧页码个数比右侧少1
        return show_count / 2 - 1
    else:  # 如果为奇数,左右侧页码个数相同
        return math.floor(show_count / 2)


def get_param(param, index):
    '''
    保留原搜索条件,并更新page当前页条件
    :param param:原搜索条件
    :param index:当前页
    :return:更新当前页后的搜索条件
    '''
    if param:
        param = '?' + param
        if '?page' in param or '&page' in param:
            import re
            param = re.sub(r'page=\d+', 'page=%s' % index, param)
        else:
            param += '&page=%s' % index
    else:
        param = "?page=%s" % index
    return param


class PageObject(object):
    def __init__(self, current_index, max_index, param, show_count=5):
        '''
        分页组件对象
        :param current_index:当前页码
        :param max_index: 最大页码
        :param param: 搜索条件
        :param show_count: 展示的页码个数
        '''
        self.current_index = current_index
        self.max_index = max_index
        self.show_count = show_count if show_count > 5 else 5
        self.page_index_list = list()
        self.__set_page_index_list(param)

    @property
    def has_page_item(self):
        """
        是否有页码数据
        :return:
        """
        return self.max_index > 0

    def __set_page_index_list(self, param):
        '''
        设置页码按钮
        :param param:原搜索条件
        :return:
        '''
        lhalf = get_lhalf(self.show_count)  # 左半边应有的数量

        l_temp_index = self.current_index - lhalf  # 临时左页码
        _roffest = 0  # 右迁移量
        if l_temp_index < 1:  # 左页码需大于0,否则应向右偏移
            _roffest = -l_temp_index + 1
        lindex = l_temp_index + _roffest  # 左页码 = 临时左页码向左偏移后的值

        r_temp_index = lindex + self.show_count - 1  # 临时右页码
        _loffest = 0  # 左位移量
        if self.max_index - r_temp_index < 0:  # 右页码需大于最大页码,否则应向左偏移
            _loffest = r_temp_index - self.max_index
            lindex = lindex - _loffest  # 左页码向左偏移
            if lindex < 1:  # 左页码偏移后的最小值为1,当max_index<show_count时 多余部分需舍弃
                lindex = 1
        rindex = r_temp_index - _loffest  # 右页码 = 临时右页码向右偏移后的值

        # region 添加按钮

        # 首页按钮
        page_first = PageIndex(1, '首页', param)
        page_first.index = -1  # 此处设置是为了避免page=1时首页按钮被标记为选中状态的情况,尾页同理
        self.page_index_list.append(page_first)

        # 上一页按钮,如果当前页是第一页则不显示
        if self.current_index > 1:
            page_prev = PageIndex(self.current_index - 1, '上一页', param)
            self.page_index_list.append(page_prev)

        # 快速后退跳转按钮
        if lindex > lhalf:
            page_left_skip = PageIndex(lindex - lhalf, "‧‧‧", param)
            self.page_index_list.append(page_left_skip)

        # 页码按钮
        for index in range(lindex, rindex + 1):
            page_index = PageIndex(index, str(index), param)
            self.page_index_list.append(page_index)

        # 快速前进跳转按钮
        if self.max_index - rindex > lhalf:
            page_left_skip = PageIndex(rindex + lhalf, "‧‧‧", param)
            self.page_index_list.append(page_left_skip)

        # 下一页按钮,如果当前页是最后一页则不显示
        if self.current_index < self.max_index:
            page_next = PageIndex(self.current_index + 1, '下一页', param)
            self.page_index_list.append(page_next)

        # 尾页按钮
        page_last = PageIndex(self.max_index, '尾页', param)
        page_last.index = -1
        self.page_index_list.append(page_last)

        # endregion


class PageIndex(object):
    def __init__(self, index, text, param):
        self.index = index
        self.text = text
        self.url = get_param(param, index)

  整个page_service主要分为两个功能,第一是求出组件的最左侧页码和最右侧页码的数值,第二是添加全部按钮给template渲染。

稍微解释一下算法,我首先取到了当前页左侧页码数的最大值,并求出此时最左侧的页码值,如果其小于1 则整个页码组件需要向右侧移动 直至最左侧的页码为1。尔后计算出最右侧的页码,并和最大页码进行比较,如果最右侧页码大于最大页码,则整个页码组件需要向左侧移动。注:由于最左侧页码已经算出,如需左移组件,最左侧页码也应该进行移动,并应注意 最左侧页码不能小于1。

此时我们拿到了分页组件的最左侧页码和最右侧页码,就可以添加按钮并进行渲染了。

如此一来,分页所需要的主要功能就都被我们实现了,而且调用简单,源码简洁,为此我牺牲了扩展性,使其和paginator分页器有一个极为明显的差别,可根据不同场景进行选择。

点赞