用户角色权限 案例

#权限管理:rbac:role basic access control
# 一 根据用户获取权限, session中 中间件实现权限控制
# 二 菜单管理
#默认展开
#只显示当前用户菜单

一、权限用户表

a:SQL表结构

from django.db import models

# Create your models here.
class User(models.Model):
    """
    用户表
    """
    username = models.CharField(verbose_name='用户名', max_length=32)
    password = models.CharField(verbose_name='密码', max_length=64)
    email = models.EmailField(verbose_name='邮箱')

    def __str__(self):
        return self.username


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

    def __str__(self):
        return self.caption


class User2Role(models.Model):
    """
    用户角色关系表
    """
    user = models.ForeignKey(User, verbose_name='用户', related_name='roles')
    role = models.ForeignKey(Role, verbose_name='角色', related_name='users')

    def __str__(self):
        return '%s-%s' % (self.user.username, self.role.caption,)


class Menu(models.Model):
    """
    菜单表
    """
    caption = models.CharField(verbose_name='菜单名称', max_length=32)
    parent = models.ForeignKey('self', verbose_name='父菜单', related_name='p', null=True, blank=True)

    def __str__(self):
        prev = ""
        parent = self.parent
        while True:
            if parent:
                prev = prev + '-' + str(parent.caption)
                parent = parent.parent
            else:
                break
        return '%s-%s' % (prev, self.caption,)


class Permission(models.Model):
    """
    权限
    """
    caption = models.CharField(verbose_name='权限', max_length=32)
    url = models.CharField(verbose_name='URL正则', max_length=128)
    menu = models.ForeignKey(Menu, verbose_name='所属菜单', related_name='permissions',null=True,blank=True)

    def __str__(self):
        return "%s-%s" % (self.caption, self.url,)


class Action(models.Model):
    """
    操作:增删改查
    """
    caption = models.CharField(verbose_name='操作标题', max_length=32)
    code = models.CharField(verbose_name='方法', max_length=32)

    def __str__(self):
        return self.caption


class Permission2Action2Role(models.Model):
    """
    权限操作关系表
    """
    permission = models.ForeignKey(Permission, verbose_name='权限URL', related_name='actions')
    action = models.ForeignKey(Action, verbose_name='操作', related_name='permissions')
    role = models.ForeignKey(Role, verbose_name='角色', related_name='p2as')

    class Meta:
        unique_together = (
            ('permission', 'action', 'role'),
        )

    def __str__(self):
        return "%s-%s-%s" % (self.permission, self.action, self.role,)
models.py

b.输出菜单

urlpatterns = [
    url(r'^auth-menu.html$',view2.menu),

]
url.py
from django.contrib import admin

# Register your models here.
from app02 import models

admin.site.register(models.User)
admin.site.register(models.Role)
admin.site.register(models.User2Role)
admin.site.register(models.Menu)
admin.site.register(models.Permission)
admin.site.register(models.Action)
admin.site.register(models.Permission2Action2Role)
app02/admin.py
def menu(request):

   """
    需要用户名或用户ID,产出:用户关联所有菜单
    :param request:
    :return:
    """

    # 所有菜单:处理成当前用关联的菜单
    all_menu_list = models.Menu.objects.all().values('id', 'caption', 'parent_id')
    """
    [
        {'id':1, 'caption':'菜单1', parent_id:None},
        {'id':2, 'caption':'菜单2', parent_id:None},
        {'id':3, 'caption':'菜单3', parent_id:None},
        {'id':4, 'caption':'菜单1-1', parent_id:1},
    ]

    {
        1:{'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[]},
        2:{'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
        3:{'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},
        5:{'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},
    }
   """
    user = models.User.objects.filter(username='alex').first()
    role_list = models.Role.objects.filter(users__user=user)
    permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values('permission__id',
                                                                                              'permission__url',
                                                                                              'permission__menu_id',
                                                                                              'permission__caption').distinct()



    """

    [
        {'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 },
        {'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 2 },
        {'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 3 },
        {'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 4 },
    ]
    """
    ##### 将权限挂靠到菜单上 ########
    all_menu_dict = {}
    for row in all_menu_list:
        row['child'] = []  # 添加孩子
        row['status'] = False  # 是否显示菜单
        row['opened'] = False  # 是否默认打开
        all_menu_dict[row['id']] = row




    for per in permission_list:
        if not per['permission__menu_id']:
            continue

        item = {
            'id': per['permission__id'],
            'caption': per['permission__caption'],
            'parent_id': per['permission__menu_id'],
            'url': per['permission__url'],
            'status': True,
            'opened': False
        }

        # print(item["url"])
        if re.match(per['permission__url'],request.path_info):
        # if re.match(per['permission__url'], "/orders.html"):
            item['opened'] = True
        pid = item['parent_id']
        all_menu_dict[pid]['child'].append(item)

        # 将当前权限前辈status=True
        temp = pid  # 1.父亲ID
        while not all_menu_dict[temp]['status']:
            all_menu_dict[temp]['status'] = True
            temp = all_menu_dict[temp]['parent_id']
            if not temp:
                break

        # 将当前权限前辈opened=True
        if item['opened']:
            temp1 = pid  # 1.父亲ID
            while not all_menu_dict[temp1]['opened']:
                all_menu_dict[temp1]['opened'] = True
                temp1 = all_menu_dict[temp1]['parent_id']
                if not temp1:
                    break
    # ############ 处理菜单和菜单之间的等级关系 ############
    """
    all_menu_dict = {
        1:{'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 },]},
        2:{'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
        3:{'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},
        5:{'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},
    }


    all_menu_list= [
        {'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 }, {'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},]},
        {'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
        {'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},

    ]
    """

    result = []
    for row in all_menu_list:
        pid = row['parent_id']
        if pid:
            all_menu_dict[pid]['child'].append(row)
        else:
            result.append(row)


    ##################### 结构化处理结果 #####################
    # print(result)
    # for row in result:
    #     # print(row['caption'], row['status'], row['opened'], )
    #     print(row)

    ##################### 通过结构化处理结果,生成菜单开始 #####################

    def menu_tree(menu_list):
        tpl1 = """
        <div class='menu-item'>
            <div class='menu-header'>{0}</div>
            <div class='menu-body {2}'>{1}</div>
        </div>
        """
        tpl2 = """
        <a href='{0}' class='{1}'>{2}</a>
        """

        menu_str = ""
        for menu in menu_list:
            if not menu['status']:
                continue
            # menu: 菜单,权限(url)
            if menu.get('url'):
                # 权限
                menu_str += tpl2.format(menu['url'],'active' if menu['opened'] else "",menu['caption'])
            else:
                # 菜单
                if menu['child']:
                    child_html = menu_tree(menu['child'])
                else:
                    child_html = ""
                menu_str += tpl1.format(menu['caption'], child_html,"" if menu['opened'] else 'hide')

        return menu_str

    menu_html = menu_tree(result)




    return render(request, "menu_html.html", {"menu_html":menu_html})
app02/views.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .menu-body{
            margin-left: 20px;
        }
        .menu-body a{
            display: block;
        }
        .menu-body a.active{
            color: red;
        }
        .hide{
            display: none;
        }
    </style>
</head>
<body>

    {{ menu_html|safe }}

    <script src="/static/jquery-3.2.1.js"></script>
    <script>
        $(function () {
            $(".menu-header").click(function () {
                $(this).next().removeClass("hide").parent().siblings().find(".menu-body").addClass("hide")
            })
        })
    </script>
</body>
</html>
menu_html

c.result 结果递归 debug调试

result =
    [
    {'opened': True, 'parent_id': None, 'caption': '菜单一', 'id': 1, 'status': True,
        'child': [
                {'opened': False, 'parent_id': 1, 'caption': '帅哥管理', 'id': 5, 'status': True, 'url': '/shuaige.html'},
                {'opened': True, 'parent_id': 1, 'caption': '菜单一  一', 'id': 4, 'status': True,
                    'child': [
                        {'opened': True, 'parent_id': 4, 'caption': '用户管理', 'id': 1, 'status': True, 'url': '/users.html'},
                        {'opened': False, 'parent_id': 4, 'caption': '订单管理', 'id': 2, 'status': True, 'url': '/orders.html'}
                    ]
                },
                {'opened': False, 'parent_id': 1, 'caption': '菜单一 二', 'id': 5, 'status': False, 'child': []},
                {'opened': False, 'parent_id': 1, 'caption': '菜单一  三', 'id': 6, 'status': False, 'child': []}
            ]
    },
    {'opened': False, 'parent_id': None, 'caption': '菜单二', 'id': 2, 'status': False, 'child': []},
    {'opened': False, 'parent_id': None, 'caption': '菜单三', 'id': 3, 'status': False, 'child': []}
    ] 
result = [{'opened': True, 'parent_id': None, 'caption': '菜单一', 'id': 1, 'status': True, 'child': [{'opened': False, 'parent_id': 1, 'caption': '帅哥管理', 'id': 5, 'status': True, 'url': '/shuaige.html'}, {'opened': True, 'parent_id': 1, 'caption': '菜单一  一', 'id': 4, 'status': True, 'child': [{'opened': True, 'parent_id': 4, 'caption': '用户管理', 'id': 1, 'status': True, 'url': '/users.html'}, {'opened': False, 'parent_id': 4, 'caption': '订单管理', 'id': 2, 'status': True, 'url': '/orders.html'}]}, {'opened': False, 'parent_id': 1, 'caption': '菜单一 二', 'id': 5, 'status': False, 'child': []}, {'opened': False, 'parent_id': 1, 'caption': '菜单一  三', 'id': 6, 'status': False, 'child': []}]}, {'opened': False, 'parent_id': None, 'caption': '菜单二', 'id': 2, 'status': False, 'child': []}, {'opened': False, 'parent_id': None, 'caption': '菜单三', 'id': 3, 'status': False, 'child': []}]


def menu_tree(menu_list):
    tpl1 = """
    <div class='menu-item'>
        <div class='menu-header'>{0}</div>
        <div class='menu-body {2}'>{1}</div>
    </div>
    """
    tpl2 = """
    <a href='{0}' class='{1}'>{2}</a>
    """

    menu_str = ""
    for menu in menu_list:
        if not menu['status']:
            continue
        # menu: 菜单,权限(url)
        if menu.get('url'):
            # 权限
            menu_str += tpl2.format(menu['url'], 'active' if menu['opened'] else "", menu['caption'])
            print("***", menu_str)
        else:
            # 菜单
            if menu['child']:
                child_html = menu_tree(menu['child'])
                print("----", child_html)
            else:
                child_html = ""
                print("111")
            menu_str += tpl1.format(menu['caption'], child_html, "" if menu['opened'] else 'hide')
            print("AAAAA", menu_str)
    print(123)
    return menu_str


menu_html = menu_tree(result)
print("xx", menu_html)

二、组件

生成公共app
       - 权限限制
       - 生成菜单
python3 manage.py startapp rbac  

配置文件:

复制代码
#白名单url, 不验证

VALID_URL = [
    '/app01/.*',
    '/app02/.*'
    '/login.html'
    '/logout.html'
]
config.py

rbac models:

from django.db import models


class User(models.Model):
    """
    用户表
    """
    username = models.CharField(verbose_name='用户名', max_length=32)
    password = models.CharField(verbose_name='密码', max_length=64)
    email = models.EmailField(verbose_name='邮箱')

    def __str__(self):
        return self.username


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

    def __str__(self):
        return self.caption


class User2Role(models.Model):
    """
    用户角色关系表
    """
    user = models.ForeignKey(User, verbose_name='用户', related_name='roles')
    role = models.ForeignKey(Role, verbose_name='角色', related_name='users')

    def __str__(self):
        return '%s-%s' % (self.user.username, self.role.caption,)


class Menu(models.Model):
    """
    菜单表
    """
    caption = models.CharField(verbose_name='菜单名称', max_length=32)
    parent = models.ForeignKey('self', verbose_name='父菜单', related_name='p', null=True, blank=True)

    def __str__(self):
        prev = ""
        parent = self.parent
        while True:
            if parent:
                prev = prev + '-' + str(parent.caption)
                parent = parent.parent
            else:
                break
        return '%s-%s' % (prev, self.caption,)


class Permission(models.Model):
    """
    权限
    """
    caption = models.CharField(verbose_name='权限', max_length=32)
    url = models.CharField(verbose_name='URL正则', max_length=128)
    menu = models.ForeignKey(Menu, verbose_name='所属菜单', related_name='permissions',null=True,blank=True)

    def __str__(self):
        return "%s-%s" % (self.caption, self.url,)


class Action(models.Model):
    """
    操作:增删改查
    """
    caption = models.CharField(verbose_name='操作标题', max_length=32)
    code = models.CharField(verbose_name='方法', max_length=32)

    def __str__(self):
        return self.caption


class Permission2Action2Role(models.Model):
    """
    权限操作关系表
    """
    permission = models.ForeignKey(Permission, verbose_name='权限URL', related_name='actions')
    action = models.ForeignKey(Action, verbose_name='操作', related_name='permissions')
    role = models.ForeignKey(Role, verbose_name='角色', related_name='p2as')

    class Meta:
        unique_together = (
            ('permission', 'action', 'role'),
        )

    def __str__(self):
        return "%s-%s-%s" % (self.permission, self.action, self.role,)
models.py

中间件认证  

#验证中间件


from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
from rbac import config
import re


class RbacMiddleware(MiddlewareMixin):

    def process_request(self,request,*args,**kwargs):
        for pattern in config.VALID_URL:
            if re.match(pattern,request.path_info):
                return None

        action = request.GET.get('md') # GET
        user_permission_dict = request.session.get('user_permission_dict')
        if not user_permission_dict:
            return HttpResponse('无权限')

        # action_list = user_permission_dict.get(request.path_info)
        flag = False
        for k,v in user_permission_dict.items():
            if re.match(k,request.path_info):
                if action in v:
                    flag = True
                    break
        if not flag:
            return HttpResponse('无权限')
/middleware/md.py

后端代码

import re
from rbac import models
from django.utils.safestring import mark_safe

def permission_session(user_id,request):
    """

    :param user_id:  rbac中的user表中一条数据id
    :param request:
    :return:
    """
    # obj = models.User.objects.filter(username='杨明').first()
    #
    # # x = models.User2Role.objects.filter(user_id=obj.id)
    # # [User2Role,User2Role,User2Role]
    #
    # role_list = models.Role.objects.filter(users__user_id=obj.id)
    # # [Role,]
    # from django.db.models import Count
    # # permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values('permission__url','action__code').annotate(c=Count('id'))
    # permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values('permission__url','action__code').distinct()
    """
    [
        {permission_url: '/index.html', action_code:'GET'},
        {permission_url: '/index.html', action_code:'POST'},
        {permission_url: '/index.html', action_code:'DEL'},
        {permission_url: '/index.html', action_code:'Edit'},
        {permission_url: '/order.html', action_code:'GET'},
        {permission_url: '/order.html', action_code:'POST'},
        {permission_url: '/order.html', action_code:'DEL'},
        {permission_url: '/order.html', action_code:'Edit'},
    ]
    放在Session中
    /index.html?md=GET

    {
        '/index.html': [GET,POST,DEL,Edit],
        '/order.html': [GET,POST,DEL,Edit],
    }

    """

    user_permission_dict = {
        '/ah-index.html': ["GET","POST","DEL","Edit"],
        '/order.html':  ["GET","POST","DEL","Edit"],
        '/index-(d+).html':  ["GET","POST","DEL","Edit"],
    }

    request.session['user_permission_dict'] = user_permission_dict


def menu(user_id,current_url):
    """
    根据用户ID,当前URL:获取用户所有菜单以及权限,是否显示,是否打开
    :param user_id:
    :param current_url:
    :return:
    """
    # 所有菜单:处理成当前用关联的菜单
    all_menu_list = models.Menu.objects.all().values('id','caption','parent_id')
    user = models.User.objects.filter(id=user_id).first()
    role_list = models.Role.objects.filter(users__user=user)
    permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values('permission__id','permission__url','permission__menu_id','permission__caption').distinct()
    ##### 将权限挂靠到菜单上 ########
    all_menu_dict = {}
    for row in all_menu_list:
        row['child'] = []      # 添加孩子
        row['status'] = False # 是否显示菜单
        row['opened'] = False # 是否默认打开
        all_menu_dict[row['id']] = row

    for per in permission_list:
        if not per['permission__menu_id']:
            continue

        item = {
            'id':per['permission__id'],
            'caption':per['permission__caption'],
            'parent_id':per['permission__menu_id'],
            'url': per['permission__url'],
            'status': True,
            'opened': False
        }
        if re.match(per['permission__url'],current_url):
            item['opened'] = True
        pid = item['parent_id']
        all_menu_dict[pid]['child'].append(item)

        # 将当前权限前辈status=True
        temp = pid # 1.父亲ID
        while not all_menu_dict[temp]['status']:
            all_menu_dict[temp]['status'] = True
            temp = all_menu_dict[temp]['parent_id']
            if not temp:
                break

        # 将当前权限前辈opened=True
        if item['opened']:
            temp1 = pid # 1.父亲ID
            while not all_menu_dict[temp1]['opened']:
                all_menu_dict[temp1]['opened'] = True
                temp1 = all_menu_dict[temp1]['parent_id']
                if not temp1:
                    break
    # ############ 处理菜单和菜单之间的等级关系 ############
    result = []
    for row in all_menu_list:
        pid = row['parent_id']
        if pid:
            all_menu_dict[pid]['child'].append(row)
        else:
            result.append(row)


    ##################### 结构化处理结果 #####################
    for row in result:
        print(row['caption'],row['status'],row['opened'],row)


    def menu_tree(menu_list):
        tpl1 = """
        <div class='menu-item'>
            <div class='menu-header'>{0}</div>
            <div class='menu-body {2}'>{1}</div>
        </div>
        """
        tpl2 = """
        <a href='{0}' class='{1}'>{2}</a>
        """

        menu_str = ""
        for menu in menu_list:
            if not menu['status']:
                continue
            # menu: 菜单,权限(url)
            if menu.get('url'):
                # 权限
                menu_str += tpl2.format(menu['url'],'active' if menu['opened'] else "",menu['caption'])
            else:
                # 菜单
                if menu['child']:
                    child_html = menu_tree(menu['child'])
                else:
                    child_html = ""
                menu_str += tpl1.format(menu['caption'], child_html,"" if menu['opened'] else 'hide')

        return menu_str
    menu_html = menu_tree(result)
    return menu_html


# simple_tag
def css():
    v = """
        <style>
        .hide{
            display: none;
        }
        .menu-body{
            margin-left: 20px;
        }
        .menu-body a{
            display: block;
        }
        .menu-body a.active{
            color: red;
        }
    </style>
        """
    return v

# simple_tag
def js():
    v = """
        <script>
        $(function(){

            $('.menu-header').click(function(){
                $(this).next().removeClass('hide').parent().siblings().find('.menu-body').addClass('hide');

            })

        })
    </script>
    """
    return v
service.py

a:封装以后调用

加入中间件

#自定义的中间件加入到setting

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'rbac.middleware.md.RbacMiddleware',    
]
setting.py
#后端
from rbac import service
 
1. 用户登录后,拿到用户的ID,调用permission_session()函数(函数代码还没写)
   函数获取用户角色的权限,格式如:
   “”“
    user_permission_dict = {
        '/ah-index.html': ["GET","POST","DEL","Edit"],
        '/order.html':  ["GET","POST","DEL","Edit"],
        '/index-(d+).html':  ["GET","POST","DEL","Edit"],
    }
   ”“”
 
    def login():
      permission_session(用户ID,request)
      return .....
 
2.setting中加入中间件,如上
  
 
3.#获取菜单
    current_url= request.pathinfo
    menu_list = service.menu(用户ID,current_url)
 
4.尽量用simple_tag
    css = servicr.css()
    js = servicr.js()
 
5.前端
 
    {{ css|safe }}
    {{ menu_list|safe }}
    {{ js|safe }}  

  

 
 
 
原文地址:https://www.cnblogs.com/bingabcd/p/7508141.html