zoukankan      html  css  js  c++  java
  • 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})
    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">&laquo;</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">&laquo;</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">&raquo;</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实现分页的功能.看似简单的一个功能,有那么知识!
  • 相关阅读:
    equals标准写法
    抽象类的概述
    多态的弊端
    多态
    final关键字
    java 静态代码块 构造块 构造方法
    java 工具类
    逻辑运算符&&和&的区别 ||和|的区别
    react-route
    跨域
  • 原文地址:https://www.cnblogs.com/zhming26/p/5812641.html
Copyright © 2011-2022 走看看