Django使用django-guardian按关联对象权限过滤Queryset实现方案
问题1:关联字段的检测与管理器获取
你可以通过Django模型自带的_meta元数据接口遍历所有字段,筛选出正向关联字段(外键、一对一、多对多),不需要依赖queryset,直接用当前管理器绑定的self.model就能获取:
- 调用
self.model._meta.get_fields()拿到模型所有字段 - 筛选满足
field.is_relation且field.auto_created == False的字段,就是当前模型主动定义的正向关联字段 - 关联字段对应的模型可以通过
field.related_model获取,其管理器就是field.related_model.objects
问题2:递归校验多层关联对象权限的实现
我们可以在自定义Manager里加递归逻辑,遍历所有关联层级,逐层添加权限过滤条件,跨表查询用Django的双下划线语法关联到对应层级的权限判断即可,修改后的完整代码如下:
from django.db import models from guardian.shortcuts import get_objects_for_user class WithUser(models.Manager): user_obj = None # 缓存已处理过的模型,避免循环关联导致死递归 _processed_models = set() def assign_user(self, user_obj): self.user_obj = user_obj # 每次赋值用户清空处理缓存,避免不同请求串数据 self._processed_models = set() return self def _get_perm_filtered_pks(self, model, user): """获取指定模型下用户有查看权限的主键列表""" perm_codename = f"view_{model.__name__.lower()}" return get_objects_for_user( user, perm_codename, model.objects.all(), accept_global_perms=False ).values_list("pk", flat=True) def _recursive_add_perm_filter(self, qs, current_model, prefix=""): """递归添加当前模型和所有关联模型的权限过滤""" if current_model in self._processed_models: return qs self._processed_models.add(current_model) # 先过滤当前模型自身的权限 current_pks = self._get_perm_filtered_pks(current_model, self.user_obj) pk_field = f"{prefix}pk" if prefix else "pk" qs = qs.filter(**{f"{pk_field}__in": current_pks}) # 遍历所有正向关联字段,处理下一层 for field in current_model._meta.get_fields(): if field.is_relation and not field.auto_created: related_model = field.related_model new_prefix = f"{prefix}{field.name}__" qs = self._recursive_add_perm_filter(qs, related_model, new_prefix) return qs def get_queryset(self): qs = super().get_queryset() if self.user_obj: qs = self._recursive_add_perm_filter(qs, self.model) return qs # 模型定义和原有逻辑一致,不需要修改 class A(models.Model): # 自定义字段 objects = WithUser() class B(models.Model): # 自定义字段 a = models.ForeignKey(A, on_delete=models.CASCADE) objects = WithUser() class C(models.Model): # 自定义字段 b = models.ForeignKey(B, on_delete=models.CASCADE) objects = WithUser()
使用示例
# 假设当前登录用户为user,查询用户有权限的C对象,会自动校验C、B、A三层的view权限 c_qs = C.objects.assign_user(user).all()
补充说明
如果不需要对所有关联字段做权限校验,也可以在模型里新增自定义属性比如permission_related_fields指定需要校验的关联字段,遍历的时候只处理指定字段即可,灵活度更高。
内容的提问来源于stack exchange,提问作者AlASAD WAIL
相关产品推荐
相关产品推荐

