閑來無事便寫了一個易使用,易移植的Python Web分頁組件。使用的技術棧是Python、Django、Bootstrap。 既然是易使用、易移植的組件,首先介紹一下其在django框架中的調用方式吧。我將組件封裝成了Django InclusionTag,在template模板中直接調用tag即 ...
閑來無事便寫了一個易使用,易移植的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分頁器有一個極為明顯的差別,可根據不同場景進行選擇。