zoukankan      html  css  js  c++  java
  • Django学习笔记一十四——页面数据分页的实现

    我们常常在页面上发现有分页的效果,具体的实现方法是怎么样的呢?

    环        境

     为了演示,创建一个Django项目,主要是配置好数据库,这里用了一个最简单的ORM的类

    class Books(models.Model):
        id = models.AutoField(primary_key=True)
        title = models.CharField(max_length=16)
    
        def __str__(self):
            return "<object:{}>".format(self.title

    然后创建一个python文件,用下面的代码生成50条数据

    '''
    创建书籍数据
    '''
    import os
    if __name__ == '__main__':
        os.environ.setdefault("DJANGO_SETTINGS_MODULE", "分页.settings")
        import django
        django.setup()
    
        from app1 import models
        for i in range(1,50):
            t = '书籍'+str(i)
            book = models.Books(title=t)
            book.save()

    然后我们的模板和视图是这样的,模板里调用了Bootstrap了组件,其中调用了一些静态的文件,如果有的话还好,没有就直接放个表格就行。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>书籍列表</title>
        <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    </head>
    <body>
    
    <div class="container">
        <table class="table table-bordered">
        <thead>
            <tr>
                <th>序号</th>
                <th>id</th>
                <th>title</th>
            </tr>
        </thead>
        <tbody>
            {%for book in books%}
            <tr>
                <td>{{forloop.counter}}</td>
                <td>{{book.id}}</td>
                <td>{{book.title}}</td>
                {%endfor%}
            </tr>
        </tbody>
    </table>
        
    </body>
    </html>
    模板
    def book(request):
        all_book = models.Books.objects.all()
        return render(request,'book.html',{'books':all_book})
    视图views.py

    url就随便设置把,只要能映射到视图里的函数就可以了。

    运行项目,访问地址,会发现所有的数据都显示了

     这样明显不符合我们最常见到的状态,所以必须加上分页的效果

    分页的基本实现

    我们先基于这50条数据试一下如何达到分页的效果:

    显示部分数据

    在获取了所有的数据以后,我们可以用切片的方式显示一部分数据

    def book(request):
        all_book = models.Books.objects.all()[0:10]
        return render(request,'book.html',{'books':all_book})

    这样就可以显示了部分的数据

    获取页码

    页码的获取我们可以通过url里加参数来实现,然后在视图中通过GET方法获取。

    URL附带参数http://127.0.0.1:8000/book/?page=1,然后在视图中就可以获取一下对应的参数的值。

    def book(request):
        page_num = request.GET.get('page')
        print(page_num)
        all_book = models.Books.objects.all()[0:10]
        return render(request,'book.html',{'books':all_book})

    页码和数据的关系

    假设我们每一页上显示10条数据,然后看看怎么排列的

     仔细看一下页码和数据之间是有一定的关系的,每一页上开始的数据是(页码-1)×10+1,最后一条的数据是页码*10。然后我们在URL中获取的数据是字符串,需要转换成int(其实这个时候还是要考虑到异常处理的。在这里就先忽略掉吧)

    def book(request):
        page_num = request.GET.get('page')
        page_num = int(page_num)
        page_start = (page_num-1)*10+1
        page_end = page_num*10
        all_book = models.Books.objects.all()[page_start:page_end]
        return render(request,'book.html',{'books':all_book})

    这样就实现了通过URL来显示分页以后的数据了。然后,我们在模板中插入一个最简单的分页组件

    分页组件

    分页的控件,还是从Bootstrap上找一个最简单的分页效果,把他放在模板中

    现在这里的组件,是通过a标签来实现页码的切换的,但是模板中还没有对a标签赋链接参数,实现的思路就是在视图中创建一段html代码,然后插到这个模板中,所以就先要修改一下模板的代码,再把上面吗那一段分页的组件添加在table标签后面

    <!-- 文件名:book.html -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>书籍列表</title>
        <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    </head>
    <body>
    
    <div class="container">
        <table class="table table-bordered">
        <thead>
            <tr>
                <th>序号</th>
                <th>id</th>
                <th>title</th>
            </tr>
        </thead>
        <tbody>
            {%for book in books%}
            <tr>
                <td>{{forloop.counter}}</td>
                <td>{{book.id}}</td>
                <td>{{book.title}}</td>
                {%endfor%}
            </tr>
        </tbody>
    </table>
    <nav aria-label="Page navigation">
      <ul class="pagination">
        <li>
          <a href="#" aria-label="Previous">
            <span aria-hidden="true">&laquo;</span>
          </a>
        </li>
        {{page_html}}
        <li>
          <a href="#" aria-label="Next">
            <span aria-hidden="true">&raquo;</span>
          </a>
        </li>
      </ul>
    </nav>
        
    </body>
    </html>
    带基本分页的html模板

    在视图函数中,就要把这一段分页的HTML代码生成出来,然后推到模板中。注意我们在这里用了个求模运算,因为有可能最后剩了几条数据,还是要把他显示出来的。

    总之视图函数就是这样的:

    def book(request):
        page = request.GET.get('page')
        page_num=10                                         #每页显示数据条数                                              
        page = int(page)
    
        totle_num = models.Books.objects.all().count()      #ORM方法,获取数据总条数
        totle_page,m = divmod(totle_num,page_num)         #求模运算,求出页数和最后一页的数据数量
    
        print(totle_page)
    
        if m:                                               #求总页数:如果最后一页上的数据数量不为0,则为页数+1
            totle_page += 1
        print(totle_page)
        all_book = models.Books.objects.all()[10*(page-1):10*page]
        html_str = []
        for i in range(1,totle_page+1):
            temp = "<li><a href='/book/?page={0}'>{0}</a></li>".format(i)    #format的用法,前面用两个被传值对对象是通过一个变量传值的
            html_str.append(temp)
    
        page_html = ''.join(html_str)
        print(page_html)
        return render(request,'books.html',{'books':all_book,'page_html':page_html})
    视图函数

     发现一个问题:

     添加进去的并不是li标签而是一段字符串,原因在这里:我们要告诉模板生成的是安全的html代码,所以要在模板那里加上过滤器:

    {{page_html|safe}}

    这样就好了!

     每页显示固定数量的页码

    用上面的方法已经实现了最基本的 分页了,但是有一个问题:我们的数据量还是比较少的,如果我们有1000条数据的话会怎么样?我们在数据库里用truncate把刚才的50条数据删除

    mysql> truncate app1_books;

    然后重新在那个test.py里创建1000条数据,重新访问一下页面,看看成什么样子了!

     明显和我们平时看到的效果是不同的,那么就需要对刚才的视图函数进行修改了!我们就以博客园的分页效果来看一看是什么形式的

    我们先不管最后那个200的页码,一般情况,为了看起来比较美观我们显示的分页标签都是奇数数量的,就可以做到按照中心对中的效果(中间一个,左边5个右边5个,一共11个)

    其实也没什么难得,只要是了解了上面的思路,看看下面代码的思路就可以了(注意改了函数的名字,要修改路由) 

    def book(request):
        page = request.GET.get('page')
        page_num=10                                         #每页显示数据条数                                              
        page = int(page)
    
        totle_num = models.Books.objects.all().count()      #ORM方法,获取数据总条数
        totle_page,m = divmod(totle_num,page_num)         #求模运算,求出页数和最后一页的数据数量
    
        print(totle_page)
    
        if m:                                               #求总页数:如果最后一页上的数据数量不为0,则为页数+1
            totle_page += 1
        print(totle_page)
        all_book = models.Books.objects.all()[10*(page-1):10*page]
        html_str = []
        for i in range(1,totle_page):
            temp = "<li><a href='/book/?page={0}'>{0}</a></li>".format(i)    #format的用法,前面用两个被传值对对象是通过一个变量传值的
            html_str.append(temp)
    
        page_html = ''.join(html_str)
        return render(request,'book.html',{"books":all_book,"page_html":page_html})
    
    
    
    #指定显示分页标签数量的分页方式
    def book2(request):
        page = request.GET.get('page')
        page = int(page)                        #当前页码
    
        page_num = 10                           #每页显示数量
        page_max = 11                           #最大页码数
    
        page_half = page_max //2                #中间页码数
        page_start = page - page_half
        page_end = page+page_half 
    
        books_totle = models.Books.objects.all().count()
        page_totle,m = divmod(books_totle,page_num)
    
        page_totle = page_totle+1 if m else page_totle
    
        if page_start <= 1:
            page_start = 1
            page_end = page_max
    
    
        if page_end >= page_totle:
            page_end = page_totle
            page_start = page_totle - page_max +1
    
        all_book = models.Books.objects.all()[10*(page-1):10*page]
    
    
        for i in range(page_start,page_end+1):
            temp = "<li><a href='/book2/?page={0}'>{0}</a></li>".format(i)    
            html_str.append(temp)
    
    
        page_html = ''.join(html_str)
    
        return render(request,'book.html',{"books":all_book,"page_html":page_html})
    升级版——固定显示了页码的数量

    注意里面的这一段代码

    if page_start <= 1:
        page_start = 1
        page_end = page_max
    
    
    if page_end >= page_totle:
        page_end = page_totle
        page_start = page_totle - page_max +1

    定义了超出了最小值和最大值的那一部分,因为最后是通过for循环生成的代码,for循环是顾头不顾腚的,很容易迷糊。

    添加首位前一页和后一页

     我们前面的方法都是直接点击页码来实现跳转的效果,其实还有一种情况很常见:前后页或者直接跳转首尾页

    首尾页的实现

    首尾页的实现方法比较简单:我们的分页的html代码是通过一个for循环生成的列表实现的,要实现这个功能就在for循环前后各加一个标签就可以了

    def book2(request):
        page = request.GET.get('page')
        page = int(page)                        #当前页码
    
        page_num = 10                           #每页显示数量
        page_max = 11                           #最大页码数
    
        page_half = page_max //2                #中间页码数
        page_start = page - page_half
        page_end = page+page_half 
    
        books_totle = models.Books.objects.all().count()
        page_totle,m = divmod(books_totle,page_num)
    
        page_totle = page_totle+1 if m else page_totle
    
        if page_start <= 1:
            page_start = 1
            page_end = page_max
    
    
        if page_end >= page_totle:
            page_end = page_totle
            page_start = page_totle - page_max +1
    
        all_book = models.Books.objects.all()[10*(page-1):10*page]
    
        html_str = ["<li><a href='/book2/?page=1'>首页</a></li>"]           #跳转首页
        for i in range(page_start,page_end+1):
            temp = "<li><a href='/book2/?page={0}'>{0}</a></li>".format(i)    
            html_str.append(temp)
    
        html_str.append("<li><a href='/book2/?page={}'>尾页</a></li>".format(page_totle))     #跳转尾页
    
        page_html = ''.join(html_str)
    
        return render(request,'book.html',{"books":all_book,"page_html":page_html})
    带首尾页的视图函数

    上下页的实现

    上下也的实现也很简单,有两种方法,一种是和前面的首尾页的方法一样,直接把整段li标签嵌套的a标签生成以后填到模板中,还有一种是求出来上一页和下一页的页码,只把这个页码发送到模板中,我比较喜欢第二种方法,这里不放全部的代码,把上面的函数修改一下就可以了。

        page_next = page+1 if page<page_totle else page_totle
    
        page_last = page-1 if page>1 else 1
        print(page,page_next)
    
        return render(request,'book.html',{"books":all_book,
                                            "page_html":page_html,
                                            "page_next":page_next,
                                            "page_last":page_last})

    因为是直接从上面的函数修改过来的,保持了原函数的缩进,可以忽略!

    然后就是模板文件也要大概修改一下

    <nav aria-label="Page navigation">
      <ul class="pagination">
        <li>
          <a href="/book2/?page={{page_last}}" aria-label="Previous">
            <span aria-hidden="true">&laquo;</span>
          </a>
        </li>
        {{page_html|safe}}
        <li>
          <a href="/book2/?page={{page_next}}" aria-label="Next">
            <span aria-hidden="true">&raquo;</span>
          </a>
        </li>
      </ul>
    </nav>

    直接把上一页和下一页对应的页码塞进去就行了。注意这里用了三元运算来赋值,留意一下三元运算的格式。

    于是乎,前后页和首尾页的效果就实现了

    有个小BUG

    其实上面的这段代码其实是有个小BUG的,因为前面都是满足了可以分出来几页的情况,但是如果数据量不支持能够分这么多页会怎么样呢?

    把数据库文件完全删除,然后创建20条数据,因为每页只能显示10条数据,分页只能分2页,看看是不是出问题了

     因为我们定义了最大显示的页码数量是11个,但是真实情况是只有2页就完了,那么就不能用这个写死的方法。改一下最大的页码数量就行了,

    def book3(request):
        page = request.GET.get('page')
        page = int(page)                        #当前页码
    
    
        page_num = 10                           #每页显示数量
        page_max = 11                           #最大页码数
    
        books_totle = models.Books.objects.all().count()
        page_totle,m = divmod(books_totle,page_num)
    
        page_totle = page_totle+1 if m else page_totle   
    
        page_max = 11 if page_totle >page_max else page_totle
    
        if page_totle < page_max:
            page_max = page_totle
    
        page_half = page_max //2                #中间页码数
    
        page_start = page - page_half
        page_end = page+page_half 
    
        if page_start <= 1:
            page_start = 1
            page_end = page_max
    
    
        if page_end >= page_totle:
            page_end = page_totle
            page_start = page_totle - page_max +1
    
        all_book = models.Books.objects.all()[10*(page-1):10*page]
    
        print(page_totle,page_start,page_end)
    
        html_str = ["<li><a href='/book2/?page=1'>首页</a></li>"]           #跳转首页
        for i in range(page_start,page_end+1):
            temp = "<li><a href='/book2/?page={0}'>{0}</a></li>".format(i)    
            html_str.append(temp)
    
        html_str.append("<li><a href='/book2/?page={}'>尾页</a></li>".format(page_totle))     #跳转尾页
    
        page_html = ''.join(html_str)
    
        page_next = page+1 if page<page_totle else page_totle
    
        page_last = page-1 if page>1 else 1
    
        return render(request,'book.html',{"books":all_book,
                                            "page_html":page_html,
                                            "page_next":page_next,
                                            "page_last":page_last})
    去除BUG版本

    还是用了一个三元运算

    page_max = 11 if page_totle >page_max else page_totle

    但是一定要注意这条赋值语句的位置。要先对page_max进行赋值才可以用。

    但是还有个问题,现在的页码都是通过a标签跳转的URL获取的,但是我们如果在地址栏输入的page参数不是int类型的字符串呢?程序就报错了,最简单的方法就是对page进行int方法的时候加个异常处理。

    page = request.GET.get('page')
    try:
        page = int(page)                        #当前页码
    except ValueError as e:
        page = 1

    这样即便出现了输入数据的数据类型不符合要求,还可以处理一下。

    同理,当我们输入的值如果超出了所有的页数,也可以再处理一下

    page = page if page_totle > page else page_totle

    继续升个级

    注意观察一下博客园的分页组件的效果

     在选中的时候是有个选中的效果的,这个选中效果的实现方法:

    就是在for循环中进行一下判断:当page和i相等的时候给li标签加上一个属性就行了

    for i in range(page_start,page_end+1):
        if i != page:
            temp = "<li><a href='/book2/?page={0}'>{0}</a></li>".format(i) 
        else:
            temp = "<li class='active'><a href='/book2/?page={0}'>{0}</a></li>".format(i)
        html_str.append(temp

    这样就实现了选中的效果:

     分页模块的封装

    如果我们的web页面有很多个都需要这种分页的效果,为了实现程序的复用,我们可以把上面那段分页的代码封装成一个类,然后在视图里进行重复的调用。 

    先把封装好的代码放下来,再对几个点分析一下

    from django.utils.safestring import mark_safe
    class Page_Cut():
        def __init__(self,page,url_prefix,data_totle,data_per_page=10,page_show_num=11):
            """
            :param: page:当前页码
            :url_prefix:url前缀
            :data_totle:总数据量
            :date_per_page:每页显示数据条数,默认值为10
            :page_show_num:显示的分页数量,默认值为11
            """
            self.url_prefix = url_prefix  #url前缀
    
            page_totle,m = divmod(data_totle,data_per_page)
            page_totle = page_totle if not m else page_totle +1    #计算总页码数
            self.page_totle = page_totle
    
            self.page_show_num = page_show_num if page_show_num < self.page_totle else self.page_totle  #显示最大页码数
    
            self.data_per_page = data_per_page
    
            page = 1 if page<1 else page
            page = page_totle if page>page_totle else page
    
            self.page = page
    
            if page_totle < page_show_num:
                page_show_num = page_totle
    
            page_half = page_show_num // 2                #中间页码数
    
            page_start = page - page_half
            page_end = page + page_half 
    
            if page_start <= 1:
                page_start = 1
                page_end = page_show_num
    
    
            if page_end >= page_totle:
                page_end = page_totle
                page_start = page_totle - page_show_num +1
    
            self.page_start = page_start
            self.page_end = page_end
    
        @property               #静态属性,调用的时候可以省略括号
        def ID_start(self):     #当前页开始数据ID
            return (self.page-1) * self.data_per_page
            
            
    
        @property
        def ID_end(self):
            return(10 * self.page)
    
    
    
        def page_html(self):
            html_begin = '<nav aria-label="Page navigation"> <ul class="pagination">'
    
            last_page = self.page-1 if self.page > 1 else 1
    
            if last_page == 1:
                html_last = """
                <li> 
                <a href="/{0}/?page={1}">
                <span aria-hidden="true">&laquo;</span>
                </a>
                </li>""".format(self.url_prefix,last_page)
            
            else:
                html_last = """
                <li>
                <a href="/{}/?page={}" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
                </a>
                </li>""".format(self.url_prefix,last_page)
    
    
            page_cut_html = "<li><a href='/{}/?page=1'>首页</a></li>".format(self.url_prefix)  #html开始为跳转首页标签
            
            for i in range(self.page_start,self.page_end+1):
                page_cut_html += """
                <li><a href='/{0}/?page={1}'>{1}</a></li>""".format(self.url_prefix,i)
    
            page_cut_html += "<li><a href='/{}/?page={}'>尾页</a></li>".format(self.url_prefix,self.page_totle)
    
            html_end = """
            <li>
            <a href="/{0}/?page={1}" aria-label="Next">
            <span aria-hidden="true">&raquo;</span>
            </a>
            </li></ul></nav>""".format(self.url_prefix,self.page_end)
    
    
            page_html = html_begin + html_last + page_cut_html + html_end
            
            return mark_safe(page_html)
    pagecut.py
    def func_book(request):
        page = request.GET.get('page')
        page = int(page)
        data_totle = models.Books.objects.all().count()
        page = pagecut.Page_Cut(page = page,url_prefix = 'book3',data_totle=data_totle)
        cut_html = page.page_html()
        print(page.ID_start,page.ID_end)
        all_book = models.Books.objects.all()[page.ID_start:page.ID_end]
        print(page.page_start,page.page_end)
        print(page.page_totle)
    
        return render(request,'book3.html',{'books':all_book,'page_html':cut_html})
    视图函数

    这里还有一个地方可以修改,我们在视图中直接把url写死了(book3),除此以外还可以使用内置方法直接获取url

    path= request.path_info

    然后对path进行split就可以了。

    构造函数

    先看看这个类都定义了几个参数,

    因为要实现功能的复用,可以把不同页面要实现分页效果的共用参数提出来

    def __init__(self,page,url_prefix,data_totle,data_per_page=10,page_show_num=11):
        """
        :param: page:当前页码
        :url_prefix:url前缀
        :data_totle:总数据量
        :date_per_page:每页显示数据条数,默认值为10
        :page_show_num:显示的分页数量,默认值为11
        """

    可以看出来,必须要的参数就是上面几个:当前的页码,url前缀,数据总量,每页显示的数据量以及一共显示的分页标签数量。有了这几个参数,我们就可以生成一段HTML代码的字符串。最后就把这个字符串返回就好了

    mark_safe

    我们在前面是把html标签形式的字符串整个发送到模板中进行替换,但是在模板中还要调用safe这个filter,比较麻烦,Django为我们提供了一个方法,对字符串进行处理(页面转义)。处理以后的结果还是字符串,最后把处理以后的字符串直接return就好了。

    from django.utils.safestring import mark_safe
    a = '<a href="www.baidu.com">百度</a>'
    print(a)
    b = mark_safe(a)
    print(b)

    上面的代码,变量a传递给模板中的变量以后是要用safe这个filter的,否则浏览器就会按照字符串去渲染,但是b就可以直接传递,浏览器会直接按照标签渲染。

    装饰器@property

    通过这个装饰器,我们把类中的方法直接变成了属性,在实例化以后调用这个方法的时候可以不用加括号

    html字符串的生成

    这里就是为了演示如何使用封装的方法来使用,其实在html生成这里还是有值得推敲的地方的,最好的方式是一层层的生成标签,但是上面用的方法是最直接的。但是不利于后期的修改。

  • 相关阅读:
    四、java IO--使用字节流拷贝文件
    三、java IO--使用字节流写入文件
    二、java IO--使用字节流读取文件
    一、java--IO概念
    xml解析/读取--dom4j
    java程序执行顺序
    Flask学习——cookie操作
    Linux13 shell函数、数组及awk、awk中的数组
    Linux12 RPM 和yum的使用
    Linux11 IP网段划分及主机接入网络
  • 原文地址:https://www.cnblogs.com/yinsedeyinse/p/12770023.html
Copyright © 2011-2022 走看看