python2.0_day19_前端分页功能的实现
Posted zhming
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python2.0_day19_前端分页功能的实现相关的知识,希望对你有一定的参考价值。
我们前面完成的客户纪录展示,只有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实现分页的功能.看似简单的一个功能,有那么知识!
以上是关于python2.0_day19_前端分页功能的实现的主要内容,如果未能解决你的问题,请参考以下文章
python2.0_s12_day10_Twsited异步网络框架
python2.0_s12_day9_协程&Gevent协程