项目目前的权限类需求
需要在test_ptl admin中注册,配置后发现只能管理到model级别
# 权限注册 Permission model页面,ContentType类似页面中的方法
admin.site.regeister(Permission)
admin.site.regeister(ContentType)
确定组(角色)和用户的对应关系
我们决定在项目管理中使用组来充当角色(开发、测试等)
# models.PJmember role 注释或删掉
groups = models.ManyToManyField(Group, related_name='member_belong_to', verbose_name='角色(组)')
migrate之后会生成一张关联表:test_plt_pjmember_groups
# PjMemberAdmin 中关于role的需要调整
# 多选样式组件,样式同权限管理页面
filter_horizontal = ['groups']
@admin.display(description='组(角色)')
def roles(self, obj: PjMember):
return [g.name for g in obj.groups.all()]
此时会存在一个问题:在项目页面中选择了某个用户为开发角色,进入权限管理页面发现该用户并没有添加为开发。赋权是割裂开的
现在有一个需求:项目里面的内容只有加入了项目的成员才能展示!!!
从一般的设计原则来看,我们应该自己设计一套RBAC(Role Based Access Control)的权限控制系统,但是可以基于django提供的auth app来定制; 常规的RBAC也不考虑诸如Project的问题。这个问题在于要提供对象粒度的权限控制,而auth提供的是Mode1粒度的。
变通方案有:
1) 使得auth的用户和组关系,要随着项目成员和组关系的变化而变化(同步)这样至少保证在django (auth) 眼里,用户加入的组都是 (在项目中) 客观存在的
2)用户只能选择自己加入过的项目;之后才能看到此项目的接口、用例等
以上方案存在的漏洞:在不同项目中具有不同角色时,会在所有加入的项目中获得所持有的组的权限的合集
解决方案:可以在ModelAdmin的has xxx permission中进行更细粒度的控制(判断当前用户在选中的项目中是否有此角色)
django的信号-监听功能
m2m_changed 当一个model实例上的 ManyToManyField 被改变时发出。用它来监听pjmember这个model对应的test_plt_pjmember_groups(中间表),发生变化时触发我们想要执行的方法(把test_plt_pjmember_groups表关系单项同步到auth_user_groups)
![](https://img-blog.csdnimg.cn/img_convert/e29c5a45cb08d33e976ab6792cad252c.png)
@receiver(m2m_changed, sender=PjMember.groups.through)
def member_groups_changed(sender, instance, **kwargs):
pks = kwargs.get('pk_set') # 当前被添加/删除的Group的PKs
user = instance.user # 当前被变更的User
if kwargs.get('action') == 'post_add': # 项目成员中添加组的情况
curr_gs = [g.pk for g in user.groups.all()] # auth用户当前所在组
for pk in pks:
if pk not in curr_gs:
user.groups.add(Group.objects.get(pk=pk))
if kwargs.get('action') == 'post_remove': # 项目成员中删除组的况
for member in user.project_members.all(): # 在其他项月中作为成员
in_prj_gs = [g.pk for g in member.groups.all()]
for pk in pks:
if pk not in in_prj_gs: # 已经从当前项目除,且不企其他项目,则丽除
user.groups.remove(Group.objects.get(pk=pk))
仅展示用户加入的项目
def get_queryset(self, request):
qs: QuerySet = super().get_queryset(request)
return qs if request.user.is_superuser else qs.filter(members__id=request.user.id)
自定义中间件,用来控制用户选择默认项目
在test_plt下创建一个 中间件.py来定义我们的方法。方法中要用到django的session机制
from django.http import HttpRequest
from django.shortcuts import redirect
from django.contrib import messages
from django.utils.http import urlencode
class EnsureProjectIdMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request: HttpRequest):
# 中间件被调用时自动执行
pi = request.path_info # 获取uri路径
prj_id = request.session.get('default_project_id', default=None)
while True:
if not request.user.is_authenticated: # 未登录
break
if prj_id: # 已选择默认项目
break
if '/admin/test_plt/' not in pi and '/admin/django_celery_results/' not in pi and '/admin/django_celery_beat/' not in pi:
break
if '/admin/test_plt/pj/' in pi:
break
messages.warning(request, '请您指定一个默认项目!')
request.session['test_plt_redirect_url'] = request.path
return redirect('/admin/test_plt/pj/')
response = self.get_response(request)
return response
选择默认项目的方法也需要修改下
# PjAdmin
def select_pj(self, request, queryset):
proj = queryset.first() # queryset.first取到的就是 Model的对象
logger = logging.getLogger('test_plt')
# 缺省项目存到django的 request.session里,用字典格式维护
request.session['default_project_id'] = proj.id
# pj默认调用__str__
self.message_user(request, f'默认项目为【{proj}】,后续操作基于此项目。您可以随时重新切换', level=messages.WARNING)
redirect_ulr = request.session.get('test_plt_redirect_url', default=None)
if redirect_ulr:
del request.session['test_plt_redirect_url']
return redirect(redirect_ulr)
select_pj.short_description = '设为默认项目'
自定义项目加入权限控制
举个例子,我想要这个自定义的方法加入权限控制。需要在它对应的model中加入permissions = [() ]
![](https://img-blog.csdnimg.cn/img_convert/1d91c05d4720eb5d4e96bd628c2cc62b.png)
class Meta:
verbose_name = '接口定义'
verbose_name_plural = verbose_name
permissions = [
('run_api', '运行所选的接口')
]
完成后记得makemigrations migrate
![](https://img-blog.csdnimg.cn/img_convert/900f521384565f9aab884756087b961c.png)
想要检测用户是否有权限的话,在自定义的actions中开头加入:
has_perm = request.user.has_perm('test_plt.run_api') ;用has_perm判断是否有权限展示 或 跳到其他页面