django自定义后端过滤

DRF自带的过滤

  1. 第一个 DjangoFilterBackend 是需要安装三方库见[搜索:多字段筛选]
  2. 两外两个是安装注册了rest_framework就有。

如上图,只要配置了三个箭头所指的方向,就能使用。

第一个单字段过滤

用户视图集中加上filterset_fields 后,后端搜索过滤就生效了

特点

  • 是准确匹配,如搜王老五,能搜索来,搜老五 ,是搜不出来的

第二个关键字过滤

配置SearchFilter, 即可实现。就不举例了。

python 复制代码
  # 后端过滤、搜索与排序
  filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]  

现在已经知道使用djangRF 视图集定义的接口,比如列表接口 可以加参数 ?search=xxx , 进行数据的过滤。

第三个排序

也不举例了,见 添加链接描述

这三个的原理

简单总结得说:

  • 就是DjangoFilterBackend, SearchFilter, OrderingFilter三个类中有一个filter_queryset的方法, 传入一个querySet,返回过滤后的querySet。

不清楚得看下面源码说明, 接口都使用了 filter_queryset 方法。

而filter_queryset 是GenericAPIView 类中的代码如下:

(大致逻辑:给一个querySet,返回出过滤后的 querySet)

python 复制代码
    def filter_queryset(self, queryset):
        """
        Given a queryset, filter it with whichever filter backend is in use.

        You are unlikely to want to override this method, although you may need
        to call it either from a list view, or from a custom `get_object`
        method if you want to apply the configured filtering backend to the
        default queryset.
        """
        for backend in list(self.filter_backends):
            queryset = backend().filter_queryset(self.request, queryset, self)
        return queryset

结合已有知识可知,

filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]

继续观察上面代码的写法(如下图)

每一项backend后面都.filter_queryset ,

说明SearchFilter 或者 OrderingFilter 和DjangoFilterBackend 这是三个类,且里面都有filter_queryset 的方法

看源码,果然是 ,它们的filter_queryset 里面就有过滤逻辑:

自定义过滤

针对第一个单字段过滤,修改成模糊匹配

找个地方写py文件,如在utils包下,新建一个取名为filters.py 的文件

python 复制代码
import django_filters

from system.models import User

class UsersManageFilter(django_filters.rest_framework.FilterSet):
    """
    用户管理 简单过滤器
    URL格式:http://127.0.0.1:8100/?start_time=2022-10-02 08:00:00&end_time=2023-12-31 23:59:59
    """
    # 1.创建时间从
    start_time = django_filters.DateTimeFilter(field_name='create_datetime', lookup_expr='gte')
    # 2.创建时间到
    end_time = django_filters.DateTimeFilter(field_name='create_datetime', lookup_expr='lte')
    # 3.用户名-模糊搜索
    username = django_filters.CharFilter(field_name='username', lookup_expr='icontains')
    # 4.姓名号码-模糊搜索
    realname = django_filters.CharFilter(field_name='mobile', lookup_expr='icontains')

    class Meta:
        model = User
        fields = []

在到视图集下,写 filterset_class即可生效。

除了改成模糊支持模糊搜索,还可以比如根据某个字段的时间范围来过滤等等

具体可以搜索 :django_filters如何使用

根据原理自己写一个过滤类,比如实现数据过滤

同样在filters.py中, 自定义一个类,需要继承BaseFilterBackend,写一个filter_queryset方法即可、 里面就可以写根据用户角色,角色的权限来返回不同的数据了

同时,视图集中,在filter_backends中,要加上这个类。

这里先写一个简单易懂的例子,试试

  • 根据登录用户,超级管理员就返回所有的数据
  • 非超级管理员,假设只能看见自己创建的数据
python 复制代码
from rest_framework.filters import BaseFilterBackend
class DataPermissionFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        # 1.超级管理员,返回所有数据
        if request.user.is_superuser == 0:
            # 2.非超级管理管理,还要进行判断
            # 获取用户id
            user_id = getattr(request.user, 'id', None)
            # 返回创建者 登录人自己创建的数据
            return queryset.filter(creator=user_id)
        else:
            return queryset

验证:为非超级管理员时

为超级管理员时,就返回的全部

  1. 以上是一个简单的例子。是根据queryset中的创建人来的,首先要满足你所查询的querySet是拥有创建人这个字段的。

  2. 复杂点的逻辑一,可以是根据用户拥有的角色,角色又拥有哪些数据,

    假如

    A角色关联的是 1公司,2公司数据,

    B角色关联的是 2公司,3公司数据

    当查询某种数据时,对应的模型也就需要有公司字段。

3.复杂点的逻辑二, 可以是根据用户拥有的角色,角色又拥有部门,

要查询的数据也拥有部门,实现了数据权限

角色模型如下:

业务模型:

如这个Project项目模型,它是存在depart_id的。

另外:序列化器基类中,重写了create方法,比如,在新建一个 project时,该数据的depart_id,字段就是当前用户的数据。

过滤类的写法如下:

主要解析:

  1. 用户是超级管理员, 就 所有数据不过滤
  2. 用户(部门id,和角色是必有的,但也要考虑无),根据用户角色们,如果其中有个角色的 data_range为3, 或者有个角色为 管理员admin, 就是 所有数据 不过滤
  3. 根据用户角色们,如果角色们的data_range没有3, 有 data_range为0 ,那么就 会查业务数据的creator为当前用户的数据,且depart_id 为自己部门的数据, 业务数据没有depart_id也能查
  4. 根据用户角色们,如果角色们的data_range没有3和0,那就是124, 就会查业务数据depart_id满足的数据
    以下代码现有逻辑:当存在多个角色,一个角色为0 仅本人数据, 一个角色为1本部门数据时, 会优先满足0 的情况

此代码仅做参考

  • 因为还没解决仅本人数据,和 本部门数据同时存在的矛盾。、
  • 业务数据是哪个部门的人创建,就属于哪个部门的数据(创建业务数据时,depart_id的存值(上面那张图)),如果业务中,要求各个部门只看自己部门的数据,才有存在的价值
python 复制代码
"""
@Remark: 自定义过滤器
"""
from rest_framework.filters import BaseFilterBackend
from system.models import Depart
import django_filters
from system.models import User

class DataLevelPermissionsFilter(BaseFilterBackend):
    """
    功能:《数据级权限过滤器》,步骤如下:
    第一步. 当前用户为超级管理员,则返回所有数据
    第二步. 获取用户所属部门的id,没有部门则返回空数据
    第三步. 判断用户是否分配角色,没有则返回空数据
    第四步. 获取用户所有角色的数据权限列表(会有多个角色,进行去重)------> 得到 data_permissions_list,
        如:[0, 1, 2,4 ],其中0表示本人数据,1表示本部门数据,2表示本部门及以下数据,  (3 添加不进来的)

    第五步. 仅访问本人数据(考虑到部门可能会变更,所以再添加当前部门条件)------如果有0,仅本人数据,就不管有 有角色为124 的数据权限(因为已经提前返回了)
    第六步. 判断业务模型是否有部门ID字段(depart_id),没有返回所有数据

    第七步. 根据各个角色的数据权限部门并集,返回对应部门的数据
    """

    def filter_queryset(self, request, queryset, view):
        # 1.超级管理员,返回所有数据
        if request.user.is_superuser == 0:
            # 2. 获取用户所属部门id,没有部门,则返回None(无数据)
            depart_id = getattr(request.user, 'depart_id', None)
            if not depart_id:
                return queryset.none()

            # 3. 判断用户是否分配角色,没有则仅返回【空】
            if not hasattr(request.user, 'role'):
                return queryset.none()
                # return queryset.filter(depart_id=depart_id) 本部门的数据

            # 4. 获取用户的所有角色列表,得到【数据】范围列表(如:本人数据、本部门数据、本部门及以下数据等)
            role_list = request.user.role.filter(status=1).values('admin', 'data_range')
            data_permissions_list = []
            for ele in role_list:
                # 4.1 全部数据(3)或者角色为管理员,则返回全部数据
                if 3 == ele.get('data_range') or ele.get('admin') == True:
                    return queryset
                data_permissions_list.append(ele.get('data_range'))

            # 4.2 得到所有角色无重复的【数据权限】列表
            data_permissions_list = list(set(data_permissions_list))        # 可能是 [0, 1, 2,4]

            # 5. 仅访问本人数据(考虑到部门可能会变更,所以再添加当前部门条件)
            if 0 in data_permissions_list:
                if not getattr(queryset.model, 'depart_id', None):
                    return queryset.filter(creator=request.user)
                else:
                    # 换部门了,就不能访问原来部门的数据
                    return queryset.filter(creator=request.user, depart_id=depart_id)       # (创建人是当前用户,查询的数据是当前部门的数据)

            # 6. 判断业务模型是否有部门ID字段(depart_id),没有返回所有数据
            if not getattr(queryset.model, 'depart_id', None):
                return queryset

            # 7. 处理:1=本部门数据、2=本部门及以下数据、4=自定数据权限 的情况
            depart_list = []
            for data_range in data_permissions_list:
                if data_range == 4:  # 6.1 自定义数据权限(读取role里面的depart部门)
                    depart_list.extend(request.user.role.filter(status=1).values_list('depart__id', flat=True))
                elif data_range == 2:  # 6.2 本部门及以下数据权限
                    depart_list.extend(get_depart(depart_id, ))
                elif data_range == 1:  # 6.3 本部门数据权限
                    depart_list.append(depart_id)
            return queryset.filter(depart_id__in=list(set(depart_list)))
        else:
            return queryset


# 没用到
class UsersManageFilter(django_filters.rest_framework.FilterSet):
    """
    用户管理 简单过滤器
    URL格式:http://127.0.0.1:8100/?start_time=2022-10-02 08:00:00&end_time=2023-12-31 23:59:59
    """
    # 1.创建时间从
    start_time = django_filters.DateTimeFilter(field_name='create_datetime', lookup_expr='gte')
    # 2.创建时间到
    end_time = django_filters.DateTimeFilter(field_name='create_datetime', lookup_expr='lte')
    # 3.用户名-模糊搜索
    username = django_filters.CharFilter(field_name='username', lookup_expr='icontains')
    # 4.手机号码-模糊搜索
    mobile = django_filters.CharFilter(field_name='mobile', lookup_expr='icontains')

    class Meta:
        model = User
        fields = []


def get_depart(depart_id, depart_all_list=None, depart_list=None):
    """
    递归获取部门的所有下级部门
    :param depart_id: 需要获取的部门id
    :param depart_all_list: 所有部门列表
    :param depart_list: 递归部门list
    :return:
    """
    if not depart_all_list:
        depart_all_list = Depart.objects.all().values('id', 'parent')
    if depart_list is None:
        depart_list = [depart_id]
    for ele in depart_all_list:
        if ele.get('parent') == depart_id:
            depart_list.append(ele.get('id'))
            get_depart(ele.get('id'), depart_all_list, depart_list)
    return list(set(depart_list))

使用自定义类

方式1 笨办法:在新的视图集中,重写filter_backends, 把自定义的类装到列表里即可

方式2 : 假如已经定义了视图集基类

并且在继承视图集基类后,不想重写filter_backends

就可以这样:

在基类写一个other_backends, 并重写filter_queryset

python 复制代码
    # 6.重写filter_queryset方法-后端过滤器并集(搜索过滤和并数据过滤)
    def filter_queryset(self, queryset):
        # 取filter_backends 和 other_filter_backends 的无重复并集,data_filter_backends有可能是None
        for backend in set(set(self.filter_backends) | set(self.other_backends or [])):
            queryset = backend().filter_queryset(self.request, queryset, self)
        return queryset

在具体的视图集中,这样写就实现了

相关推荐
brzhang5 分钟前
代码越写越乱?掌握这 5 种架构模式,小白也能搭出清晰系统!
前端·后端·架构
Asthenia04127 分钟前
为什么MySQL关联查询要“小表驱动大表”?深入解析与模拟面试复盘
后端
南雨北斗9 分钟前
分布式系统中如何保证数据一致性
后端
不爱学英文的码字机器11 分钟前
数字孪生的浪潮:从虚拟镜像到现实世界的 IT 变革
大数据·python
Asthenia041213 分钟前
Feign结构与请求链路详解及面试重点解析
后端
小白—人工智能14 分钟前
数据可视化 —— 直方图
python·信息可视化·数据可视化
左灯右行的爱情16 分钟前
缓存并发更新的挑战
jvm·数据库·redis·后端·缓存
brzhang20 分钟前
告别『上线裸奔』!一文带你配齐生产级 Web 应用的 10 大核心组件
前端·后端·架构
shepherd11121 分钟前
Kafka生产环境实战经验深度总结,让你少走弯路
后端·面试·kafka
袋鱼不重34 分钟前
Cursor 最简易上手体验:谷歌浏览器插件开发3s搞定!
前端·后端·cursor