Django中如何基于模型字段值限制用户查看DetailView
Django变更管理项目权限控制修复:允许访问非机密Moc实例
问题场景
我在学习Django,负责一个变更管理项目,Moc模型包含confidential字段(默认False):
- 当
confidential=True时,仅该记录的发起者(initiator)和签署人(coordinators/reviewers/approvers等关联模型中的用户)能查看实例 - 当前实现的
MocDetailView仅允许签署人访问机密实例,但所有用户都无法打开confidential=False的非机密实例,需要补充逻辑允许登录用户直接访问非机密实例
现有代码
MocDetailView 实现
# MOC DetailView class MocDetailView(LoginRequiredMixin, DetailView): model = Moc template_name = 'moc/moc_detail.html' def get_queryset(self, *args, **kwargs): qs = super().get_queryset(*args, **kwargs) for obj in qs: print(obj) confidential = obj.confidential initiator = obj.initiator coordinators = obj.coordinators.all() reviewers = obj.reviewers.all() approvers = obj.approvers.all() preimplements = obj.preimplements.all() authorizers = obj.authorizers.all() postimplements = obj.postimplements.all() closers = obj.closers.all() if initiator and initiator == self.request.user and confidential == True: qs = qs.filter(Q(confidential=True) & Q(initiator=self.request.user)) return qs for signee in coordinators: coordinator_name = signee.coordinator_name if coordinator_name and coordinator_name == self.request.user and confidential == True: qs = qs.filter(Q(confidential=True) & Q(coordinators__coordinator_name=self.request.user)) return qs for signee in reviewers: reviewer_name = signee.reviewer_name if reviewer_name and reviewer_name == self.request.user and confidential == True: qs = qs.filter(Q(confidential=True) & Q(reviewers__reviewer_name=self.request.user)) return qs for signee in approvers: approver_name = signee.approver_name if approver_name and approver_name == self.request.user and confidential == True: qs = qs.filter(Q(confidential=True) & Q(approvers__approver_name=self.request.user)) return qs for signee in preimplements: actionee_name = signee.actionee_name if actionee_name and actionee_name == self.request.user and confidential == True: qs = qs.filter(Q(confidential=True) & Q(preimplements__actionee_name=self.request.user)) return qs for signee in authorizers: authorizer_name = signee.authorizer_name if authorizer_name and authorizer_name == self.request.user and confidential == True: qs = qs.filter(Q(confidential=True) & Q(authorizers__authorizer_name=self.request.user)) return qs for signee in postimplements: actionee_name = signee.actionee_name if actionee_name and actionee_name == self.request.user and confidential == True: qs = qs.filter(Q(confidential=True) & Q(postimplements__actionee_name=self.request.user)) return qs for signee in closers: closer_name = signee.closer_name if closer_name and closer_name == self.request.user and confidential == True: qs = qs.filter(Q(confidential=True) & Q(closers__closer_name=self.request.user)) return qs
核心模型代码
# MOC model class Moc(models.Model): moc_status = FSMField(choices=STATES, default='draft', protected=False) initiator = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE) area = models.ForeignKey(Area, related_name='area_moc', blank=True, null=True, on_delete=models.CASCADE) category = models.ForeignKey(Category, related_name='category_moc', blank=True, null=True, on_delete=models.CASCADE) confidential = models.BooleanField(default=False) # Coordinator model(其余签署人模型结构类似) class Coordinator(models.Model): moc = models.ForeignKey(Moc, related_name='coordinators', on_delete=models.CASCADE, default='1') cooridnator_group = models.CharField(max_length=36, blank=True, null=True) coordinator_name = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE,) coordinator_due = models.DateField(blank=True, null=True)
修复方案
原代码的核心问题是仅处理了confidential=True的权限逻辑,完全遗漏了confidential=False的场景,且通过循环遍历查询集逐个判断的方式效率低下、逻辑混乱。
修改后的get_queryset方法采用Django的Q对象组合条件,一次性过滤出所有用户有权限访问的实例:
from django.db.models import Q class MocDetailView(LoginRequiredMixin, DetailView): model = Moc template_name = 'moc/moc_detail.html' def get_queryset(self, *args, **kwargs): user = self.request.user # 基础查询集 qs = super().get_queryset(*args, **kwargs) # 权限条件: # 1. 非机密实例,所有登录用户都能访问 # 2. 机密实例,满足以下任一条件: # - 用户是发起者 # - 用户是任一签署人(coordinators/reviewers/approvers等关联模型中的用户) permission_conditions = Q(confidential=False) | \ Q(confidential=True, initiator=user) | \ Q(confidential=True, coordinators__coordinator_name=user) | \ Q(confidential=True, reviewers__reviewer_name=user) | \ Q(confidential=True, approvers__approver_name=user) | \ Q(confidential=True, preimplements__actionee_name=user) | \ Q(confidential=True, authorizers__authorizer_name=user) | \ Q(confidential=True, postimplements__actionee_name=user) | \ Q(confidential=True, closers__closer_name=user) return qs.filter(permission_conditions).distinct()
关键说明
- 覆盖非机密场景:通过
Q(confidential=False)直接允许所有登录用户访问非机密实例 - 合并机密权限逻辑:用
|(逻辑或)组合所有机密实例的合法访问条件,避免循环判断 - 去重处理:添加
.distinct()避免同一实例因关联多个签署人而被重复查询
优化建议
如果后续签署人类型增加,可以将关联字段名整理成列表,简化代码:
signee_fields = [ 'initiator', 'coordinators__coordinator_name', 'reviewers__reviewer_name', 'approvers__approver_name', 'preimplements__actionee_name', 'authorizers__authorizer_name', 'postimplements__actionee_name', 'closers__closer_name' ] # 生成机密实例的权限条件 confidential_conditions = Q() for field in signee_fields: confidential_conditions |= Q(confidential=True, **{field: user}) # 最终权限条件 permission_conditions = Q(confidential=False) | confidential_conditions
内容的提问来源于stack exchange,提问作者SS_SS
相关产品推荐
相关产品推荐

