跳转至

分页和过滤

您需要高效学习,找工作? 点击咨询 报名实战班

点击查看学员就业情况

现在我们的系统在列出,药品、客户、订单等数据的时候,都是全部用一张表 在一页里面显示全部内容

如下图所示,

image

聪明的读者,你来想想这样做有什么问题呢?

对了,如果我们有大量的数据, 比如系统中存储10万种药品,这个表将会非常的长,需要后端程序从数据库中读取大量的数据,并且传递给前端。

而用户通常只需要看其中的一点点数据。这是非常大的性能浪费。


怎么解决这个问题?大家只要看看淘宝、京东这些购物网站就知道了,

方案是 分页过滤

分页 就是 每次只读取一页的信息,返回给前端。

过滤 就是 根据用户的提供的筛选条件,只读取符合条件的部分信息。

分页

先看分页的实现。

既然要分页,那么前端发送的请求中需要携带 两个信息: 每页包含多少条记录需要获取第几页

我们定义列出数据请求中 添加 2个url 参数: pagesizepagenum 分别对应这两个信息。

为了实现分页 和 过滤 ,接口也做了相应的修改,修改后就是的文档:API接口文档1.2, 点击查看


Django提供了对分页的支持,具体的信息,大家可以点击这里查看其官方文档

以列出药品的代码为例, 我们可以修改 listmedicine 函数,如下

# 增加对分页的支持
from django.core.paginator import Paginator, EmptyPage

def listmedicine(request):
    try:
        # 返回一个 QuerySet 对象 ,包含所有的表记录
        qs = Medicine.objects.values()

        # 要获取的第几页
        pagenum = request.params['pagenum']

        # 每页要显示多少条记录
        pagesize = request.params['pagesize']

        # 返回一个 QuerySet 对象 ,包含所有的表记录
        qs = Medicine.objects.values()

        # 使用分页对象,设定每页多少条记录
        pgnt = Paginator(qs, pagesize)

        # 从数据库中读取数据,指定读取其中第几页
        page = pgnt.page(pagenum)

        # 将 QuerySet 对象 转化为 list 类型
        retlist = list(page)

        # total指定了 一共有多少数据
        return JsonResponse({'ret': 0, 'retlist': retlist,'total': pgnt.count})

    except EmptyPage:
        return JsonResponse({'ret': 0, 'retlist': [], 'total': 0})

    except:
        return JsonResponse({'ret': 2,  'msg': f'未知错误\n{traceback.format_exc()}'})


注意,我们返回的信息,包括一页的数据, 还需要告诉前端, 符合条件的 总共有多少条记录 。为什么?

因为这样,前端可以计算出, 总共有多少页,从而正确的显示出分页界面,如下所示

image

这行代码 创建了 分页对象,在初始化参数里面设定每页多少条记录

pgnt = Paginator(qs, pagesize)

返回的 分页对象 赋值给变量 pgnt。

然后:

一页的数据 就可以通过 pgnt.page(pagenum) 获取。

而总共有多少页,通过 pgnt.count 得到。

好的,我们的代码完成了,大家可以先使用 requests构建前端请求,自己测试一下。

测试代码可以是这样

import  requests,pprint

# 先登陆,获取sessionid
payload = {
        'username': 'byhy',
        'password': '88888888'
    }

response = requests.post("http://localhost/api/mgr/signin",
                             data=payload)

retDict = response.json()

sessionid = response.cookies['sessionid']

# 再发送列出请求,注意多了 pagenum 和 pagesize
payload = {
    'action': 'list_medicine',
    'pagenum': 1,
    'pagesize' : 3
}

response = requests.get('http://localhost/api/mgr/medicines',
              params=payload,
              cookies={'sessionid': sessionid})

pprint.pprint(response.json())

我们指定了 每页3条记录,获取第一页

返回结果类似下面这样

{'ret': 0,
 'retlist': [{'desc': '青霉素注射剂', 'id': 1, 'name': '青霉素', 'sn': 'sn345556235'},
             {'desc': '来适可盒装', 'id': 2, 'name': '来适可', 'sn': 'sn886839452'},
             {'desc': '盐酸盐片剂、胶囊', 'id': 3,'name': '环丙沙星','sn': 'Ciprofloxacin'}
            ],
 'total': 15}

可以发现,药品总数有 15条记录,按照每页3条记录的话,返回的第一页内容就是retlist里面的信息。

大家可以修改测试代码,获取第2页的内容,对比查看一下。

过滤

我们再看过滤如何实现。

过滤 就是 根据用户的提供的筛选条件,只读取符合条件的部分信息。

比如,列出药品,需要根据 药品描述 中包含的关键字来 查询 。

而且用户可能会输入多个关键字, 比如 乳酸注射液

这就有一个问题, 多个关键字查询 是 的关系 还是 的关系。 前者要求 药品描述 同时包含 多个关键字, 后者只需 包含其中任意一个关键字即可。

我们这里 先以 的关系 为例。

多条件 且关系

首先,我们需要在 列出药品的请求消息里面 添加一个参数 保存关键字信息。我们这里使用 keywords 参数。

里面包含的多个关键字之间用 空格 分开。

查询过滤条件,前面我们学过,可以通过 QuerySet 对象的 filter方法, 比如

qs.filter(name__contains='乳酸')

注意,上面的 name__contains='乳酸' 表示 name 字段包含乳酸这个关键字。

Django执行该代码是,会转换为下面的SQL条件从句到数据库进行查询

WHERE name LIKE '%乳酸%'

如果有 多个 过滤条件, 可以继续在后面调用filter方法,比如

qs.filter(name__contains='乳酸',name__contains='注射液')

就等价于下面的 SQL条件从句

WHERE name LIKE '%乳酸%' AND name LIKE '%注射液%'


多个 过滤条件, 也可以多个filter方法链在一起,比如

qs.filter(name__contains='乳酸').filter(name__contains='注射液')


通常在同一张表中过滤,这两种写法结果一样。

但是如果 过滤涉及到关联表,结果可能会不同,感兴趣的请点击这里,查看这篇文章

这个帖子的讲解也比较直观

多条件 或关系

上面两种写法,都是表示一种 AND 关系, 也就是要同时满足这些条件。

如果我们想表示的是 OR 的关系该怎么办呢?

这时候,可以使用 Django 里面提供 的 Q 对象 。

参考 官方文档 https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects

了解 Q对象的详细用法

Q 对象 的初始化参数里面 携带 和 filter 语法一致的 条件,比如

from django.db.models import Q
qs.filter(Q(name__contains='乳酸'))

如果我们查询的多个过滤条件是 的关系,就用 竖线 | 符号 连接多个Q对象,比如

qs.filter( Q(name__contains='乳酸') | Q(name__contains='注射液'))

等价于 下面的 SQL条件从句

WHERE name LIKE '%乳酸%' OR name LIKE '%注射液%'


如果我们查询的多个过滤条件是 的关系,也可以用 & 符号 连接多个Q对象,比如

qs.filter( Q(name__contains='乳酸') & Q(name__contains='注射液'))

等价于 下面的 SQL条件从句

WHERE name LIKE '%乳酸%' AND name LIKE '%注射液%'

和前面且的写法 效果相同。


利用 Q 的写法,有个独特的用途,就是过滤条件是运行时动态获取时,没法预先像下面这样写在代码中

qs.filter(name__contains='乳酸',name__contains='注射液')

这时,可以使用Q ,后面会讲到。

过滤类型

除了 等于、包含 这两种过滤类型, 还有 值大于、值小于、值在(列表中)等等。

还有对 过滤值的一些 特殊处理,比如 购买时间的 年、月、日 部分在某个范围内 等等,这些底层都是对应SQL的一些内置函数。

怎么实现这些过滤呢?

详情请参考官方文档这里

应用到项目代码

了解了上的知识,那么我们继续修改 listmedicine 函数,如下所示

def listmedicine(request):
    try:
        # .order_by('-id') 表示按照 id字段的值 倒序排列
        # 这样可以保证最新的记录显示在最前面
        qs = Medicine.objects.values().order_by('-id')

        # 查看是否有 关键字 搜索 参数
        keywords = request.params.get('keywords',None)
        if keywords:
            conditions = [Q(name__contains=one) for one in keywords.split(' ') if one]
            query = Q()
            for condition in conditions:
                query &= condition
            qs = qs.filter(query)

        # 要获取的第几页
        pagenum = request.params['pagenum']

        # 每页要显示多少条记录
        pagesize = request.params['pagesize']

        # 使用分页对象,设定每页多少条记录
        pgnt = Paginator(qs, pagesize)

        # 从数据库中读取数据,指定读取其中第几页
        page = pgnt.page(pagenum)

        # 将 QuerySet 对象 转化为 list 类型
        retlist = list(page)

        # total指定了 一共有多少数据
        return JsonResponse({'ret': 0, 'retlist': retlist,'total': pgnt.count})

    except EmptyPage:
        return JsonResponse({'ret': 0, 'retlist': [], 'total': 0})

    except:
        return JsonResponse({'ret': 2,  'msg': f'未知错误\n{traceback.format_exc()}'})

其中 下面这段代码

            query = Q()
            for condition in conditions:
                query &= condition
            qs = qs.filter(query)

我们先构建一个空Q对象 , 表示没有任何过滤条件, 然后 循环取出过滤关键字, 使用 & 叠加过滤条件。

最后 query 就是 多个 过滤条件 同时满足 的 Q 对象

如果我们想 构建 或 的关系, 就应该这样

query |= condition


好的,我们可以修改测试代码,加上过滤条件,再测试一下

import  requests,pprint

payload = {
        'username': 'byhy',
        'password': '88888888'
    }

response = requests.post("http://localhost/api/mgr/signin",
                             data=payload)

retDict = response.json()

sessionid = response.cookies['sessionid']

# 再发送列出请求,注意多了 keywords
payload = {
    'action': 'list_medicine',
    'pagenum': 1,
    'pagesize' : 3,
    'keywords' : '乳酸 注射液'
}

response = requests.get('http://localhost/api/mgr/medicines',
              params=payload,
              cookies={'sessionid': sessionid})

pprint.pprint(response.json())


这里我们加上 删除订单的代码,如下

def deleteorder(request):
    # 获取订单ID
    oid = request.params['id']

    try:

        one = Order.objects.get(id=oid)
        with transaction.atomic():

            # 一定要先删除 OrderMedicine 里面的记录
            OrderMedicine.objects.filter(order_id=oid).delete()
            # 再删除订单记录
            one.delete()

        return JsonResponse({'ret': 0, 'id': oid})

    except Order.DoesNotExist:
        return JsonResponse({
            'ret': 1,
            'msg': f'id 为`{oid}`的订单不存在'
        })

    except:
        err = traceback.format_exc()
        return JsonResponse({'ret': 1, 'msg': err})

集成前端

我们的前端开发人员也没有闲着,他们根据接口的变动,修改代码的同时,也大大的修改了一下界面,如下。

是不是好看很多了 :)

image

我们后端开发者可以把前端发布的包拿过来进行集成测试。

在如下百度网盘中的 bysms_front_v1.5.zip

百度网盘链接:https://pan.baidu.com/s/1nUyxvq6IYykBNtPUf4Ho6w

提取码:9w2u


目前为止,我们项目代码,在如下百度网盘中的 bysms_12.zip

百度网盘链接:https://pan.baidu.com/s/1nUyxvq6IYykBNtPUf4Ho6w

提取码:9w2u