我們前面完成的客戶紀錄展示,只有4條,如果有上百條就不能在1頁中全部展示了,那樣是不人性化的.另外一次性取出來,數據量也比較大.
假如現在有95條數據,我們想實現一個每頁展示20條,那就分為5頁.假如我們實現了,那么前端每一次請求就需要給后台提供參數了.這個參數就是告訴views里的視圖函數我取第幾頁.
需求分析:
95條,每頁20條
第一次請求 返回20條,並且后端返回當前返回是第幾頁 ,所以第一次返回是1
點擊下一頁 1+1=2 ,把2傳給后端,后端拿到后在把第二頁的內容返回給前端,並且把當前返回的頁這里是2,返回給前端.
按照這個需求,我們自己寫,也是很容易實現的(這是對於老手),但是這個分頁功能屬於一個常用而且通用的功能,Django就提供了Paginator模塊來實現后台分頁的功能.
Django提供的是后台分頁的功能,前端要使用bootstrap中的分頁示例代碼
我們先看看Django中處理分頁的模塊都有哪些方法:
$python3.5 manage.py shell >>> from django.core.paginator import Paginator # 導入Paginator >>> objects = ['john','paul','george','ringo'] >>> p = Paginator(objects,2) # 生成一個分頁的實例,兩個參數(objects是列表,2代表的是每2個元素分成一頁.)
那我們來看看p這個實例有幾個方法
>>> p.count # 查看有多少個元素 4 >>> p.num_pages # 查看總共有幾頁 2 >>> type(p.page_range) <class 'range'> >>> p.page_range # 當我們想循環每一頁時就需要用到這個. for num in p.page_range:page = p.page(1) range(1, 3) >>> page1 = p.page(1) # p.page(num) 取第幾頁 >>> page1 # page1 顯示這事第幾頁 <Page 1 of 2> >>> page1.object_list # 顯示頁里面的元素,以列表的方式 ['john', 'paul'] >>> p.object_list # 顯示p實例里有多少個元素 ['john', 'paul', 'george', 'ringo'] >>> page2 = p.page(2) # 第二頁 >>> page2.object_list # 查看第二頁有多少個元素 ['george', 'ringo'] >>> page2.has_next() # 查看當前頁是不是有下一頁,如果有返回True,如果沒有Flase False >>> page2.has_previous() # 查看當前頁是不是有上一頁,如果有返回True,如果沒有返回False True >>> page2.has_other_pages() # 查看除了當前頁之外還有沒有其它頁,如果有返回True,如果沒有返回False True >>> page1.next_page_number() # 查看當前頁的下一頁的頁碼 2 >>> page2.next_page_number() # 查看當前頁的下一頁的頁碼,如果沒有則報錯 Traceback (most recent call last): ...... django.core.paginator.EmptyPage: That page contains no results >>> page2.previous_page_number() # 查看當前頁的上一頁的頁碼. 1 >>> page1.previous_page_number() # 查看當前頁的上一頁的頁碼,如果沒有則報錯 Traceback (most recent call last): ...... django.core.paginator.EmptyPage: That page number is less than 1 >>> page1.start_index() # 查看當前頁中,第一個元素在總列表的索引值 1 >>> page2.start_index() # 查看當前頁中,第一個元素在總列表的索引值 3 >>> page1.end_index() # 查看當前頁中,最后一個元素在總列表的索引值 2 >>> page2.end_index() # 查看當前頁中,最后一個元素在總列表的索引值 4 >>> p.page(0) # 當所取頁超出p.page_range()范圍,就會報錯了 Traceback (most recent call last): ... django.core.paginator.EmptyPage: That page number is less than 1
Django分頁的官網
https://docs.djangoproject.com/en/1.9/topics/pagination/
我們來看下后台中到底如何使用,我們從django中查看有詳細的示例代碼.按照這些示例代碼完全沒問題
我們在crm/views.py文件中的代碼如下:
1 from django.shortcuts import render 2 from crm import models 3 from django.core.paginator import import Paginator,EmptyPage,PageNotAnInteger # 兩個異常 4 # Create your views here. 5 6 def dashboard(request): 7 return render(request,'crm/dashboard.html') 8 def customers(request): 9 customer_list = models.Customer.objects.all() 10 paginator = Paginator(customer_list,2) # 每頁顯示2條紀錄 11 page = request.GET.get('page') #獲取客戶端請求傳來的頁碼 12 try: 13 customer_list = paginator.page(page) # 返回用戶請求的頁碼對象 14 except PageNotAnInteger: # 如果請求中的page不是數字,也就是為空的情況下 15 customer_list = paginator.page(1) 16 except EmptyPage: 17 # 如果請求的頁碼數超出paginator.page_range(),則返回paginator頁碼對象的最后一頁 18 customer_list = paginator.page(paginator.num_pages) 19 20 return render(request,'crm/customers.html',{'customer_list':customer_list})
需要注意的是:通過costomer_list = paginator.page(數字)獲得的對象,是paginator分頁實例,但是當我們對這個對象進行for循環時,遍歷出來的還是里面的元素.
所以我們可以在html模版中代碼依然是直接對 custormer_list 進行for循環,我一開始還以為要用{% for custormer in customer_list.object_list %}呢,結果在官網的html示例代碼中是{% for custormer in customer_list %}
我試了下,兩個都可以使用
然后我們在看下官網上給我們指引的需要在html模版文件中需要做的改動:
更改templates/crm/customer.html文件

1 from django.shortcuts import render 2 from crm import models 3 from django.core.paginator import import Paginator,EmptyPage,PageNotAnInteger # 兩個異常 4 # Create your views here. 5 6 def dashboard(request): 7 return render(request,'crm/dashboard.html') 8 def customers(request): 9 customer_list = models.Customer.objects.all() 10 paginator = Paginator(customer_list,2) # 每頁顯示2條紀錄 11 12 page = request.GET.get('page') #獲取客戶端請求傳來的頁碼 13 14 try: 15 customer_list = paginator.page(page) # 返回用戶請求的頁碼對象 16 except PageNotAnInteger: # 如果請求中的page不是數字,也就是為空的情況下 17 customer_list = paginator.page(1) 18 except EmptyPage: 19 # 如果請求的頁碼數超出paginator.page_range(),則返回paginator頁碼對象的最后一頁 20 customer_list = paginator.page(paginator.num_pages) 21 22 return render(request,'crm/customers.html',{'customer_list':customer_list})
訪問http://127.0.0.1:8000/crm/customers,結果如圖:
以上我們完成了一個最簡單的分頁.接下來我們可以對分頁進行優化下.
我們打開百度,隨便搜索一個關鍵字
要實現這種只要在前端進行更改就行了,我們可以直接使用bootcss.com中找分頁的組件.
我們將代碼拷貝到我們的customer.html文件中,然后進行更改,最終代碼如下:

1 {% extends 'base.html' %} 2 {% block page-header %} 3 Customers List 4 {% endblock %} 5 {% block page-content %} 6 <table class="table table-hover"> 7 <thead> 8 <tr> 9 <th>ID</th> 10 <th>QQ</th> 11 <th>姓名</th> 12 <th>渠道</th> 13 <th>咨詢課程</th> 14 <th>課程類型</th> 15 <th>客戶備注</th> 16 <th>狀態</th> 17 <th>課程顧問</th> 18 <th>日期</th> 19 </tr> 20 </thead> 21 <tbody> 22 {% for coustomer in customer_list.object_list %} 23 <tr> 24 <td>{{ coustomer.id }}</td> 25 <td>{{ coustomer.qq }}</td> 26 <td>{{ coustomer.name }}</td> 27 <td>{{ coustomer.source }}</td> 28 <td>{{ coustomer.course }}</td> 29 <td>{{ coustomer.get_course_type_display }}</td> 30 <td>{{ coustomer.consult_memo|truncatechars:10 }}</td> 31 <td class ="{{ coustomer.status }}"> {{ coustomer.get_status_display }} </td> 32 <td>{{ coustomer.consultant }}</td> 33 <td>{{ coustomer.date }}</td> 34 </tr> 35 {% endfor %} 36 37 </tbody> 38 </table> 39 <div class="pagination"> 40 41 <nav> 42 <ul class="pagination"> 43 <li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">«</span></a></li> 44 <!--<li class="active"><a href="#">1 <span class="sr-only">(current)</span></a></li>--> 45 <!--我們要想獲得所有的頁面是不是先要知道有多少頁,然后對頁碼進行循環 46 這里需要注意:我們知道paginator有一個方法page_range,可以獲得range(1,總頁數+1)這個<class 'range'>,但是我們這里的customer_list只是一個頁碼實例,它怎么獲得range類型呢 47 可以,可以使用customer_list.paginator.page_range這樣就獲得了range(1,總頁數+1)這個<class 'range'>,接下來就是對這個range進行循環就可以了. 48 --> 49 50 {% for page_num in customer_list.paginator.page_range %} 51 <li class="active"><a href="?page={{page_num}}">{{page_num}}<span class="sr-only">(current)</span></a></li> 52 {% endfor %} 53 </ul> 54 </nav> 55 </div> 56 {% endblock%}
我們瀏覽下http://127.0.0.1:8000/crm/customers/
結果如下:
我們看到圖中標簽都是藍色,這是不對的,是因為
<li class="active"><a href="?page={{page_num}}">{{page_num}}<span class="sr-only">(current)</span></a></li>
里的class = "active",所以我們應該寫一個if,如果當前循環頁的頁碼和當前頁的頁碼一樣時才active
那么怎么獲得當前頁的頁碼呢.custormer_list.number就能獲得
代碼如圖:
再次訪問http://127.0.0.1:8000/crm/customers/
如圖:
下面我們在代碼中加入判斷,讓"上一頁按鈕"和"下一頁按鈕"生效
代碼如下:
1 <div class="pagination"> 2 3 <nav> 4 <ul class="pagination"> 5 {% if customer_list.has_previous %} 6 <li class=""><a href="?page={{customer_list.previous_page_number}}" aria-label="Previous"><span aria-hidden="true">«</span></a></li> 7 {% endif %} 8 <!--<li class="active"><a href="#">1 <span class="sr-only">(current)</span></a></li>--> 9 <!--我們要想獲得所有的頁面是不是先要知道有多少頁,然后對頁碼進行循環 10 這里需要注意:我們知道paginator有一個方法page_range,可以獲得range(1,總頁數+1)這個<class 'range'>,但是我們這里的customer_list只是一個頁碼實例,它怎么獲得range類型呢 11 可以,可以使用customer_list.paginator.page_range這樣就獲得了range(1,總頁數+1)這個<class 'range'>,接下來就是對這個range進行循環就可以了. 12 --> 13 {% for page_num in customer_list.paginator.page_range %} 14 {% if page_num == customer_list.number %} 15 <li class="active"><a href="?page={{page_num}}">{{page_num}}<span class="sr-only">(current)</span></a></li> 16 {% else %} 17 <li class=""><a href="?page={{page_num}}">{{page_num}}<span class="sr-only">(current)</span></a></li> 18 {% endif %} 19 {% endfor %} 20 21 {% if customer_list.has_next %} 22 <li class=""><a href="?page={{customer_list.next_page_number}}" aria-label="Next"><span aria-hidden="true">»</span></a></li> 23 {% endif %} 24 25 </ul> 26 </nav> 27 </div>
這時候頁碼功能就差不多了.
我們接着優化,這里我們只有2頁內容,按照上面的代碼,你有100頁,它也會在頁碼中顯示出來,這么一來就不人性化了.
我們看到百度里,一般會顯示指定數量的頁碼標簽,並且選中的標簽永遠在最中間.
如圖
那么上圖的效果如何實現呢?
我們把視圖重新改下,改成每一條紀錄為一頁,那么就會有4頁,我們就設置成在前端顯示3個頁碼標簽.被選中的顯示在中間.
思路:
1.首先通過customer_list.num能知道當前頁的頁碼.
2.循環顯示頁碼的時候,判斷比當前頁碼少多少可顯示,多多少可顯示,否則不顯示
3.這里有一個聰明的做法,取循環的值-當前頁頁碼所得差的絕對值就可.問題這是前端代碼,沒有abs(2-1)取絕對值的語法.那用什么語法?
前端的template前端里沒有求絕對值的語法,怎么辦呢?可以自己寫,Django中允許自定義template的語法.接下來我們來看如何自定義前端語法.
自定義Django的template語法
即自定義template tags(自定義模版標簽)
https://docs.djangoproject.com/es/1.9/howto/custom-template-tags/
1.你要想寫自定義標簽,首先要在你的app目錄,我們這里是crm目錄下創建一個python包文件(目錄名稱必須是templatetags/),在這個目錄下創建你的自定義標簽.
2.自定義標簽的內容怎么寫呢?
我們先看下官網上提供的一個示例代碼,實現全大寫的代碼:
1 from django import template 2 3 register = template.Library() # 生成一個注冊器 4 5 @register.filter # 注冊到語法庫,過濾語法 ,就是把數據輸入進來,內部執行后把改變的結果在反回來 6 def alex_upper(value): 7 return value.upper()
前端模版想用這個自定義template 標簽,前端頁面得知道有這個,默認前端肯定不知道,所以你想在前端使用,首先要在前端導入一下:
在{% extends 'base.html'%}下面一行導入,因為如果先導入會被覆蓋.
緊接着是如何在代碼中使用.
比如我們在顯示name字段時,把英文字母全部大寫.
完成上面代碼,你以為成功了,我們訪問測試http://127.0.0.1:8000/crm/customers/
結果出錯了,為什么?因為要重啟,這里是Django中為數不多更改后的內容需要重啟的地方.
$ python3.5 manage.py runserver 127.0.0.1:8000
重啟后,我們看結果
至此簡單的創建一個自定義的tempate 標簽我們就已經會了,接下來我們就可以自定義取絕對值的自定義標簽了.
我們看到剛剛創建的自定義template tags中注冊是:@register.filter用的是filter,filter過濾語法的特點是接收一個參數,處理后返回處理結果
我們這里要取絕對值,就需要傳入兩個參數,一個是當前頁的頁碼,還有一個for循環的頁碼.這就要用到支持多個參數的語法了叫做@register.simple_tag
django 的@register.simple_tag可以寫的很復雜,我們先不關,先用它實現我們簡單的需求:
1 from django import template 2 3 register = template.Library() 4 5 @register.filter 6 def alex_upper(value): 7 return value.upper() 8 9 @register.simple_tag 10 def guess_page(current_page,loop_num): 11 offset = abs(current_page - loop_num) 12 return offset
注意了,之前我們調用自定義的filter注冊的方法是通過"|alex_upper"
而現在我們是通過simple_tag注冊的,調用的方法是:
{% 函數名 參數1 參數2 %}
這里是:
{% guess_page customer_list.number page_num %}
但是我們的問題來了,{% guess_page customer_list.num page_num %} 返回的是一個具體的數值,我們需要設置一個變量,接收函數調用后返回的值.
但是前端是不能創建變量的,那么如何解決呢?
我們看,guess_page返回的是字符串值,那么我們干脆就直接返回html代碼,那么前端就可以就直接寫這段代碼就可以了
{% guess_page customer_list.num page_num %}
那么我們就把 guess_page函數重新改一遍:
把前端代碼的內容刪掉:
{% if page_num == customer_list.number %} <li class="active"><a href="?page={{page_num}}">{{page_num}}<span class="sr-only">(current)</span></a></li> {% else %} <li class=""><a href="?page={{page_num}}">{{page_num}}<span class="sr-only">(current)</span></a></li> {% endif %}
然后把這段內容,放到guess_page函數里,但是要把內容用python實現.
@register.simple_tag def guess_page(current_page,loop_num): offset = abs(current_page - loop_num) # 如果絕對值小於2,就返回page_ele if offset < 2: # 如果當前頁碼等於循環的頁碼,則class="active" if current_page == loop_num: page_ele = ''' <li class="active"><a href="?page={{%s}}">{{%s}}<span class="sr-only">(current)</span></a></li> '''%(loop_num,loop_num) # 如果當前頁碼不等於循環的頁碼,則class="" else: page_ele = ''' <li class=""><a href="?page={{%s}}">{{%s}}<span class="sr-only">(current)</span></a></li> '''%(loop_num,loop_num) return page_ele
我們訪問http://127.0.0.1:8000/crm/customers/查看結果如圖:
拿不能就返回字符串啊,當然Django中有處理的方法,用format_html()
具體代碼如下:
1 from django import template 2 from django.utils.html import format_html # 引入format_html模塊 3 4 register = template.Library() 5 6 @register.filter 7 def alex_upper(value): 8 return value.upper() 9 10 # @register.simple_tag 11 # def guess_page(current_page,loop_num): 12 # offset = abs(current_page - loop_num) 13 # return offset 14 @register.simple_tag 15 def guess_page(current_page,loop_num): 16 offset = abs(int(current_page) - int(loop_num)) 17 # 如果絕對值 18 if offset < 2: 19 if current_page == loop_num: 20 page_ele = ''' 21 <li class="active"><a href="?page=%s">%s<span class="sr-only">(current)</span></a></li> 22 '''%(loop_num,loop_num) 23 else: 24 page_ele = ''' 25 <li class=""><a href="?page=%s">%s<span class="sr-only">(current)</span></a></li> 26 '''%(loop_num,loop_num) 27 return format_html(page_ele)
訪問http://127.0.0.1:8000/crm/customers/,查看結果
怎么處理這個None,簡單在if offset < 2:不滿足時返回空字符串
代碼如下
1 @register.simple_tag 2 def guess_page(current_page,loop_num): 3 offset = abs(int(current_page) - int(loop_num)) 4 # 如果絕對值 5 if offset < 2: 6 if current_page == loop_num: 7 page_ele = ''' 8 <li class="active"><a href="?page=%s">%s<span class="sr-only">(current)</span></a></li> 9 '''%(loop_num,loop_num) 10 else: 11 page_ele = ''' 12 <li class=""><a href="?page=%s">%s<span class="sr-only">(current)</span></a></li> 13 '''%(loop_num,loop_num) 14 return format_html(page_ele) 15 else: 16 return ''
我們在訪問http://127.0.0.1:8000/crm/customers/
結果如圖:
至此我們就實現了在Django框架結合bootstrap實現分頁的功能.看似簡單的一個功能,有那么知識!