如何扩展Django权限实现教练仅查看同品牌患者?
当然可以!完全不用在每个视图里重复写过滤逻辑,我们可以通过自定义Django权限系统+全局查询过滤的方式来实现这个需求,既优雅又能统一控制。下面是具体的实现步骤:
核心思路
我们要完成两件核心事情:
- 实现对象级权限控制:确保教练只能访问和自己同品牌的患者对象
- 实现自动查询集过滤:让列表视图自动只返回同品牌的患者,不用每个视图单独写过滤逻辑
1. 自定义对象级权限后端
Django默认的权限是模型级的,我们需要扩展它来支持对象级的权限判断。在你的app下创建permissions.py文件:
from django.contrib.auth.backends import ModelBackend from .models import Coach, Patient class BrandingPermissionBackend(ModelBackend): def has_perm(self, user_obj, perm, obj=None): # 如果用户不是教练,沿用默认权限逻辑 if not isinstance(user_obj, Coach): return super().has_perm(user_obj, perm, obj) # 针对查看患者的权限,判断对象是否和教练同品牌 if perm == 'your_app.view_patient' and obj is not None: return user_obj.branding == obj.branding # 其他权限类型沿用默认逻辑 return super().has_perm(user_obj, perm, obj)
然后在项目的settings.py里配置这个权限后端,让Django优先使用它:
AUTHENTICATION_BACKENDS = [ 'your_app.permissions.BrandingPermissionBackend', 'django.contrib.auth.backends.ModelBackend', ]
2. 自定义查询集自动过滤同品牌患者
为了让列表视图自动过滤数据,我们给Patient模型自定义查询集和管理器,这样可以统一调用过滤逻辑:
修改models.py中的Patient模型:
from django.db import models from django.contrib.auth.models import User # 先定义查询集 class PatientQuerySet(models.QuerySet): def for_coach(self, coach): # 过滤出和当前教练同品牌的患者 return self.filter(branding=coach.branding) # 再定义管理器 class PatientManager(models.Manager): def get_queryset(self): return PatientQuerySet(self.model, using=self._db) def for_coach(self, coach): return self.get_queryset().for_coach(coach) # 修改Patient模型 class Patient(User): name = models.CharField(max_length=100) # 记得给CharField加max_length参数哦 email = models.EmailField() branding = models.CharField(choices=BRANDINGS, max_length=50) # 替换默认管理器 objects = PatientManager()
3. 基类视图统一应用逻辑
创建一个基类视图,所有教练访问患者的视图都继承它,这样不用重复写登录验证、权限判断和查询集过滤:
from django.views.generic import ListView, DetailView from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from .models import Patient, Coach class CoachPatientBaseView(LoginRequiredMixin, UserPassesTestMixin): def test_func(self): # 确保当前登录用户是教练 return isinstance(self.request.user, Coach) def get_queryset(self): # 自动返回当前教练同品牌的患者 return Patient.objects.for_coach(self.request.user) # 示例:患者列表视图 class PatientListView(CoachPatientBaseView, ListView): model = Patient template_name = 'patients/patient_list.html' # 示例:患者详情视图 class PatientDetailView(CoachPatientBaseView, DetailView): model = Patient template_name = 'patients/patient_detail.html'
注意事项
- 如果你用的是多表继承(而非代理模型),比如
Coach是User的一对一关联模型,那需要调整权限后端里的判断逻辑,比如通过user_obj.coach获取教练实例,再对比品牌。 - 记得给
CharField类型的字段加上max_length参数,不然Django会报错哦。
内容的提问来源于stack exchange,提问作者Dmitry Sazhnev




