Django之后台限权限管理模板

  每一个公司都要用到后台管理.

那么我们如何用Django来写一个通用的后台管理?

这里是基于RBAC(Role Base Access Control)来写的一个权限管理

我们要用到的表单有:

class UserInfo(models.Model):
    username = models.CharField(max_length=32,verbose_name='用户名')
    password = models.CharField(max_length=64,verbose_name='密码')

    class Meta:
        verbose_name_plural = '用户信息'
        db_table='UserInfo'

    def __str__(self):
        return self.username

class Role(models.Model):
    caption = models.CharField(max_length=32,verbose_name='角色')

    class Meta:
        verbose_name_plural='角色表'
        db_table = 'Role'

    def __str__(self):
        return  self.caption

class User2Role(models.Model):
    user = models.ForeignKey(UserInfo,on_delete=models.CASCADE,verbose_name='角色用户',related_name='u')
    role = models.ForeignKey(Role,on_delete=models.CASCADE,verbose_name='角色',related_name='r')

    class Meta:
        verbose_name_plural ='用户角色表(多对多)'
        db_table = 'User2Role'
        unique_together=[
            ('user','role'),
        ]
    def __str__(self):
        return "%s-%s"%(self.user.username,self.role.caption)

class Permission(models.Model):
    caption = models.CharField(verbose_name='功能',max_length=32)
    url = models.CharField(verbose_name='URL',max_length=64)
    menu = models.ForeignKey('Menu',on_delete=models.CASCADE,null=True,blank=True)

    class Meta:
        verbose_name_plural = '权限'
        db_table = 'Permission'
    def __str__(self):
        return "%s-%s"%(self.caption,self.url[:20])

class Action(models.Model):
    caption = models.CharField(max_length=32,verbose_name='操作')
    code = models.CharField(max_length=32)

    class Meta:
        verbose_name_plural='操作'
        db_table = 'Action'
    def __str__(self):
        return self.caption

class Permission2Action(models.Model):
    url = models.ForeignKey(Permission,on_delete=models.CASCADE,related_name='reurl')
    act = models.ForeignKey(Action,on_delete=models.CASCADE,related_name='react')

    class Meta:
        verbose_name_plural = '权限操作表(多对多)'
        db_table ='Permission2Action'
        unique_together=[
            ('url','act'),
        ]

    def __str__(self):
        return "%s-%s:%s?t=%s"%(self.url.caption,self.act.caption,self.url.url,self.act.code)

class Role2PermissionAction(models.Model):
    role = models.ForeignKey(Role,on_delete=models.CASCADE,verbose_name='角色',related_name='rerole')
    P2A = models.ForeignKey(Permission2Action,on_delete=models.CASCADE,verbose_name='外键P2A',related_name='rep2a')

    class Meta:
        verbose_name_plural = 'R2P2A(角色分配权限)'
        db_table = 'R2P2A'
        unique_together=[
            ('role','P2A'),
        ]

    def __str__(self):
        return "%s===>%s"%(self.role.caption,self.P2A)

class Menu(models.Model):
    caption = models.CharField(max_length=32,verbose_name='菜单')
    parent = models.ForeignKey('self',on_delete=models.CASCADE,verbose_name='父菜单',null=True,blank=True,related_name='reparent')

    class Meta:
        verbose_name_plural ='菜单'
        db_table = 'Menu'

    def __str__(self):
        return  self.caption
View Code

我们将后台管理的功能封装成一个类

class MenuHelp(object):
    def __init__(self,request,username):
        self.username = username

        self.current_url = request.path_info

        self.request = request

        #  当前用户的所有权限
        self.Permission2Action_dict = None

        # 菜单中显示的权限
        self.menu_leaf_list = None

        # 获取所有菜单
        self.menu_list = None

        self.result = None
        self.session_data()

    def session_data(self):
        permission_dict = self.request.session.get('permission_info')

        if permission_dict:
            self.Permission2Action_dict = permission_dict['Permission2Action_dict']
            self.menu_leaf_list = permission_dict['menu_leaf_list']
            self.menu_list = permission_dict['menu_list']


        else:
            # 直接通过role表获取role_list 通过外键反向跨表查询:
            # 通过r外键跨表到User2Role然后__user__username= 可以取到所有的角色Queryset[obj,obj]
            role_list = models.Role.objects.filter(r__user__username=self.username)

            # 方式一
            # 获取个人所有权限列表.放置在session中,缺点:无法获取实时的权限信息,需要重新登录
            # 通过跨表查询找到该用户所有角色所拥有的权限
            # 取所有权限的url和code方法存到session里,点击事件的时候先判断是否在session里
            Permission2Action_list = models.Permission2Action.objects.filter(rep2a__role__in=role_list)\
                .values('url__url', 'act__code').distinct()

            Permission2Action_dict = {}

            for item in Permission2Action_list:
                if item['url__url'] in Permission2Action_dict:
                    Permission2Action_dict[item['url__url']].append(item['act__code'])
                else:
                    Permission2Action_dict[item['url__url']] =[item['act__code'],]


            #  ****url__menu 是外键menu对应的id*****
            ## 获取菜单的叶子节点,即:菜单的最后一层应该显示的权限
            # 查询需要显示到菜单里的caption(比如用户管理)   通过url__menu__isnull=True判断是该caption是否要显示到菜单上
            menu_leaf_list = list(models.Permission2Action.objects.filter(rep2a__role__in=role_list).exclude(
                url__menu__isnull=True) \
                .values('url_id', 'url__url', 'url__caption', 'url__menu').distinct())


            menu_list = list(models.Menu.objects.values('id', 'caption', 'parent_id'))


            self.request.session['permission_info']={
                'Permission2Action_dict':Permission2Action_dict,
                'menu_leaf_list':menu_leaf_list,
                'menu_list':menu_list
            }

            self.Permission2Action_dict=Permission2Action_dict
            self.menu_leaf_list=menu_leaf_list
            self.menu_list=menu_list

    def menu_data_list(self):
        menu_leaf_dict={}
        # 用来装需要显示在菜单里的caption字典
        # 将里面的关键字换名字,更好的查询和管理 同时增加了child
        parent_open_id = None
        for row in self.menu_leaf_list:
            row = {
                'id': row['url_id'],  # 对应的permission的id
                'url': row['url__url'],
                'caption': row['url__caption'],
                'parent_id': row['url__menu'],  # url__menu是该menu的id,把要显示的数据和对应的menu的id相关联
                'child': [],
                'open': False,
                'status': True,
            }
            # 将在同一个的menu下的如(都在菜单1.2下的)放在一起, parent_id是菜单1.2的id
            if row['parent_id'] in menu_leaf_dict:
                menu_leaf_dict[row['parent_id']].append(row)
            else:
                menu_leaf_dict[row['parent_id']] = [row,]

            print(self.current_url,row['url'])
            if re.match(self.current_url,row['url']):
                row['open'] = True
                parent_open_id = row['parent_id']

        # 全部的菜单

        menu_list_dict = {}

        # 我们想要的数据的列表
        result = []

        # 将菜单遍历成一个字典,并且菜单的id为key,values为他的内容,还有child
        for item in self.menu_list:
            item['child'] = []
            item['open'] = False
            item['status'] = False
            menu_list_dict[item['id']] = item

        while parent_open_id:
            menu_list_dict[parent_open_id]['open'] = True
            parent_open_id = menu_list_dict[parent_open_id]['parent_id']

        for k,v in menu_list_dict.items():
            print(k,v)

        # 将需要显示的数据放在该菜单下的child里
        # 将需要显示的菜单的status替换成True
        for k, v in menu_leaf_dict.items():
            menu_list_dict[k]['child'] = v
            parent_id = k
            while parent_id:
                menu_list_dict[parent_id]['status'] = True
                parent_id = menu_list_dict[parent_id]['parent_id']

        # 处理等级关系
        for item in menu_list_dict.values():
            if not item['parent_id']:
                result.append(item)
            else:
                menu_list_dict[item['parent_id']]['child'].append(item)
        return result

    def menu_tree(self):
        response = ""
        tpl = """
                <div class="item %s">
                    <div class="title">%s</div>
                    <div class="content">%s</div>
                </div>
        """
        for row in self.menu_data_list():
            if not row['status']:
                continue
            active = ""
            if row['open']:
                active = 'active'
            title = row['caption']
            content = self.menu_content(row['child'])
            response += tpl % (active, title, content)
        return response

    def menu_content(self,child_list):

        response = ""
        tpl = """
                    <div class="item %s">
                        <div class="title">%s</div>
                        <div class="content">%s</div>
                    </div>
            """
        for row in child_list:
            if not row['status']:
                continue
            active = ""
            if row['open']:
                active = 'active'
            if 'url' in row:
                response += "<a class='%s' href='%s'>%s</a>" % (active, row['url'], row['caption'])
            else:
                title = row['caption']
                content = self.menu_content(row['child'])
                response += tpl % (active, title, content)
        return response

    def active(self):
        """
            检查当前用户是否对当前URL有权访问,并获取对当前URL有什么权限
        """
        action_list = []
        # 当前所有权限
        # {
        #     '/index.html': ['GET',POST,]
        # }
        for k, v in self.Permission2Action_dict.items():
            if re.match(k, self.current_url):
                action_list = v  # ['GET',POST,]
                break
        return action_list
View Code

最后写一个装饰器,每次访问都装饰器都会去查询你是否拥有权限

def permission(func):
    def inner(request,*args,**kwargs):
        user_info = request.session.get('user_info')
        if not user_info:
            return redirect('/login/')
        obj = MenuHelp(request,user_info['username'])
        action_list = obj.active()
        if not action_list:
            return HttpResponse('无权访问')
        kwargs['action_list'] =action_list
        kwargs['menu_string'] =obj.menu_tree()
        return func(request,*args,**kwargs)
    return inner
View Code

 

posted @ 2018-06-23 21:42  R00M  阅读(1147)  评论(1编辑  收藏  举报