django:分页

一,什么是分页

网页是用户与网站进行交互的主要场所,这种交互主要指数据收集与数据展示。

从试想一下,为什么我们几乎不在网页中一次性展示请求获得的所有数据呢?

如果这个数据量相当小,比如只有几十条,那么一般情况下无需担心,一股脑渲染到页面中就行。

但如果这个数据量比较大,比如几百几千几万条,且一旦这种操作比较频繁,显然就会增加服务器负载,主要瓶颈是数据库。

这里不谈如何实现高并发,只谈如何以轻量化的方式获取并展示数据。

一种有效的方式就是实现分页查询:将一次性获取所有数据的操作,分解为多次查询操作,每次操作只查询并展示一部分数据。其中每一次查询就是获取一页数据。

效果如下:
在这里插入图片描述

具体来说有三种方式实现:

  1. 自定义视图,通过请求参数获取指定范围内的数据。
  2. 使用分页组件。
  3. 使用 AJAX 滚动加载。

二,django的分页组件

django的分页组件可以将数据被分割在几个页面上,并带有“上一页/下一页”链接。

(一)Paginator 类和 Page 类

Paginator 类是分页组件的核心,它完成将 QuerySet 拆分为 Page 对象的所有繁重工作。

>>> from django.core.paginator import Paginator

# objects 是一个列表、元组、QuerySet 或其他具有 count() 或 __len__() 方法的可切片对象。
# 如果是 QuerySet,则应该是有序的,例如使用 order_by() 子句或使用模型上的默认 ordering。
>>> objects = ['john', 'paul', 'george', 'ringo']

# 获取分页器实例
>>> p = Paginator(objects, 2)	# 可迭代对象,每页数量

# 获取所有页面中的对象总数
>>> p.count
4

# 获取总页数
>>> p.num_pages
2

# 以 1 为基础的页码范围迭代器
>>> type(p.page_range)
<class 'range_iterator'>
>>> p.page_range
range(1, 3)

# 获取指定页
>>> page1 = p.page(1)
>>> page1
<Page 1 of 2>
# 获取指定页中的内容
>>> page1.object_list
['john', 'paul']
>>> page2 = p.page(2)
>>> page2.object_list
['george', 'ringo']

# 判断当前页是否有下一页
>>> page2.has_next()
False
# 判断当前页是否有上一页
>>> page2.has_previous()
True
>>> page2.has_other_pages()
True

# 当前页的下一页页码
>>> page2.next_page_number()
Traceback (most recent call last):
...
EmptyPage: That page contains no results
# 当前页的上一页页码
>>> page2.previous_page_number()
1

# 获取当前页第一条数据在所有数据中的索引
>>> page2.start_index() # The 1-based index of the first item on this page
3
# 获取当前页末一条数据在所有数据中的索引
>>> page2.end_index() # The 1-based index of the last item on this page
4

# 获取错误的页码将报错
>>> p.page(0)
Traceback (most recent call last):
...
EmptyPage: That page number is less than 1
>>> p.page(3)
Traceback (most recent call last):
...
EmptyPage: That page contains no results

(二)在函数视图中使用分页组件

# FBV
from django.core.paginator import Paginator
from django.shortcuts import render

from myapp.models import Contact

def listing(request):
    contact_list = Contact.objects.all()
    paginator = Paginator(contact_list, 25) # Show 25 contacts per page.

    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    return render(request, 'list.html', {'page_obj': page_obj})
# template
{% for contact in page_obj %}
    {# Each "contact" is a Contact model object. #}
    {{ contact.full_name|upper }}<br>
    ...
{% endfor %}

<div class="pagination">
    <span class="step-links">
        {% if page_obj.has_previous %}
            <a href="?page=1">&laquo; first</a>
            <a href="?page={{ page_obj.previous_page_number }}">previous</a>
        {% endif %}

        <span class="current">
            Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
        </span>

        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}">next</a>
            <a href="?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
        {% endif %}
    </span>
</div>

在URL中携带page参数。

(三)在类视图中使用分页组件

# CBV
class StudentList(ListView):
    model = StudentInfo
    paginate_by = 25
    template_name = 'list.html'

CBV 会在指定了 paginate_by 属性后

(四)对分页的改进

以上两种方法够简单,但正因如此,它俩都有一个缺点:会一股脑儿地将页码全部显示出来。
比如:
在这里插入图片描述
显然不够友好,要是页码有切割就好了:
在这里插入图片描述
最简单的解决方法就是使用第三方的分页插件一劳永逸。

(五)使用分页插件Django-pure-pagination

Django-pure-pagination是比较出名的分页插件。

1,安装并注册:

pip install django-pure-pagination
INSTALLED_APPS = (
    ...
    'pure_pagination',
)

2,配置分页切割方式:
在这里插入图片描述

# 分页配置
PAGINATION_SETTINGS = {
    'PAGE_RANGE_DISPLAYED': 5,
    'MARGIN_PAGES_DISPLAYED': 2,
    'SHOW_FIRST_PAGE_WHEN_INVALID': True,
}

3,函数视图:

from pure_pagination import Paginator, PageNotAnInteger


def test_dpp(request):
    school_list = SchoolInfo.objects.all()
    paginator = Paginator(school_list, 5, request=request)
    try:
        page_number = request.GET.get('page', 1)
    except PageNotAnInteger:
        page_number = 1
    page_obj = paginator.page(page_number)
    return render(request, 'list.html', {'page_obj': page_obj})

4,模板:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style type="text/css">
        a {
            padding: 5px 10px;
            color: white;
            background-color: #2caa0d;
            margin: 1px; /*设置标签 a 之间的间隔*/
            text-decoration: none; /*去除页码数字下面的下划线*/
        }

        a:hover {
            color: black;
        }

        .current {
            color: black;
        }
    </style>
</head>
<body>

<div>
    {% for school in page_obj.object_list %}
        <ul>
            <li>
                {{ school.name }}<br>
            </li>
        </ul>
    {% endfor %}
</div>

{#Django-pure-pagination基础渲染方法#}
{#<div id="pagination">#}
{#    {{ page_obj.render }}#}
{#</div>#}

<div class="pageturn">
        {% if page_obj.has_previous %}
            <a href="?{{ page_obj.previous_page_number.querystring }}">上一页</a>
        {% endif %}

        {% for page in page_obj.pages %}
            {% if page %}
                {% ifequal page page_obj.number %}
                    <a href="?{{ page.querystring }}"><span class="page-link current">{{ page }}</span></a>
                {% else %}
                    <a href="?{{ page.querystring }}" class="page">{{ page }}</a>
                {% endifequal %}
            {% else %}
                <a href="">...</a>
            {% endif %}
        {% endfor %}
        {% if page_obj.has_next %}
            <a href="?{{ page_obj.next_page_number.querystring }}">下一页</a>
        {% endif %}
</div>
</body>
</html>

5,路由:

from pagination.views import test_dpp

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', test_dpp),
]

效果如下:
在这里插入图片描述
实际上,django project的分页应该是参考过这个插件的实现方式。

三,AJAX 与分页

(一)AJAX 简介

AJAX 就是用来描述一种使用现有技术(HTML 或 XHTM、CSS、JavaScript、DOM、XML、XSLT以及最重要的 XMLHttpRequest)集合的异步处理方法。

当使用结合了这些技术的 AJAX 模型以后,网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面。这使得程序能够更快地回应用户的操作。

1,原生JavaScript实现 AJAX

原生 JavaScript 实现 AJAX 请求的过程比较复杂,简单来说就分为以下几步:

  • 定义 XMLHttpRequest对象。
  • onreadystatechange 方法中注册事件处理函数,准备接收响应数据,并进行处理。
  • 调用 XMLHtφRequest 对象的 open() 方法访问服务器端 URL 地址。
  • 调用 XMLHttpRequest 对象的 send() 方法发送请求。

举个例子🌰:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>AJAX</title>
    <script>
        function loadXMLDoc() {
            let xmlhttp;
            if (window.XMLHttpRequest) {
                //  IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
                xmlhttp = new XMLHttpRequest();
            } else {
                // IE6, IE5 浏览器执行代码
                xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
            
            xmlhttp.onreadystatechange = function () {
                if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
                    //取到的内容:一个JSON对象。相当于一个python的列表,列表里面是嵌套的字典
                    //[{"model": "cmdb.article", "pk": 1, "fields": {"title": "\u7b2c\u4e00\u4e2a\u6807\u9898", "content": "\u7b2c\u4e00\u6761\u5185\u5bb9\uff01\uff01\uff01"}}]
                    const data = JSON.parse(xmlhttp.responseText);
                    const element = document.getElementById("myDiv");
                    element.innerText = JSON.stringify(data, null, "\t");
                    display(element, data);
                }
            }

            xmlhttp.open("GET", "/thejson/", true);
            
            xmlhttp.send();
            
            alert(xmlhttp.getAllResponseHeaders());
        }

        function display(element, data) {
            const children = element.childNodes;
            for (var i = 0; i < children.length; i++) {
                if (children[i].id === "content")
                    element.removeChild(children);
            }
            const len = eval(data).length;
            for (var i = 0; i < len; i++) {
                let newP = document.createElement("p");
                newP.setAttribute("id", "content");
                var content = data[i].pk + " " + data[i].fields.title + " " + data[i].fields.content
                let newContent = document.createTextNode(content);
                newP.appendChild(newContent);
                element.appendChild(newP);
            }
        }
    </script>
</head>
<body>

<div id="myDiv"><h2>使用 AJAX 获得文本内容</h2></div>
<button type="button" onclick="loadXMLDoc()">获得内容</button>

</body>
</html>

还能进一步设置请求头以实现内容协商。

2,jQuery实现 AJAX

jQuery 封装了常用的、复杂的一些 JavaScript 操作。

除了可以使用全局性函数load()get()post()实现页面的异步调用和与服务器交互数据外,在 jQuery 还有一个功能更为强悍的最底层的方法$.ajax(),该方法不仅可以方便地实现上述三个全局函数完成的功能,还更多地关注实现过程中的细节。

举个例子🌰:

$.ajax({
   type: "POST",
   url: "some.php",
   data: "name=John&location=Boston",
   success: function(msg){
     alert( "Data Saved: " + msg );
   }
});

(二)在django中使用 AJAX

一个简单的参考,可见使用AJAX与Django通信

(三)使用 AJAX 实现页面的滚动加载

AJAX需要一个触发条件,所以我们通常将它绑定到一个 DOM 事件上。

我们可以将将“下一页”等操作的触发绑定到 DOM 的点击事件上,配合 Ajax 实现实现页面的局部刷新。

也能只将“下一页”操作的触发绑定到页面滚事件,当滚动到末尾时加载下一页,就能实现 infinite scroll 或者 endless 效果,比如在 reddit 首页不断滚都鼠标滚轮所得到的那种效果(注意网页右边的滚动条):
在这里插入图片描述

(四)使用插件实现页面的滚动加载

有一些插件可用,比如django-infinite-scroll-pagination

也能使用一些纯JavaScript插件来实现,比如Waypoints

滚动分页的具体代码就不展示了,请参考文档,这里就看一下简单的效果:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值