我們前面完成的客戶紀錄展示,只有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})
customer.html
訪問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%}
更改后的customer.html
我們瀏覽下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實現分頁的功能.看似簡單的一個功能,有那么知識!