You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()

关键说明

  1. 覆盖非机密场景:通过Q(confidential=False)直接允许所有登录用户访问非机密实例
  2. 合并机密权限逻辑:用|(逻辑或)组合所有机密实例的合法访问条件,避免循环判断
  3. 去重处理:添加.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 06:10:36