zoukankan      html  css  js  c++  java
  • Django 权限管理-后台根据用户权限动态生成菜单

    Django权限管理

    实现目标:

    1、管理用户,添加角色,用户关联角色

    2、添加权限、角色关联权限

    3、添加动作、权限关联动作

    4、添加菜单、权限关联菜单

    实现动态生成用户权限菜单(可设置多级菜单嵌套)、根据前台URL自动选中菜单并折叠其余菜单

    最终实现类似这样的效果:

    菜单一
      菜单1.1
      菜单1.2
        菜单1.2.1
          订单管理
          分类管理

    菜单二

    一、首先是建立表格

    models

    from django.db import models
    
    
    # Create your models here.
    class User(models.Model):
        username = models.CharField(max_length=32)
        password = models.CharField(max_length=64)
    
        class Meta:
            verbose_name_plural = '用户表'
    
        def __str__(self):
            return self.username
    
    
    class Role(models.Model):
        role = models.CharField(max_length=32)
    
        class Meta:
            verbose_name_plural = '角色表'
    
        def __str__(self):
            return self.role
    
    
    class User2Role(models.Model):
        u = models.ForeignKey(User, on_delete=models.CASCADE)
        r = models.ForeignKey(Role, on_delete=models.CASCADE)
    
        class Meta:
            verbose_name_plural = '用户分配角色'
    
        def __str__(self):
            return '%s-%s' % (self.u.username, self.r.role)
    
    
    class Menu(models.Model):
        caption = models.CharField(max_length=32)
        parent = models.ForeignKey('self', related_name='p', null=True, blank=True, on_delete=models.CASCADE)
    
        def __str__(self):
            return '%s' % (self.caption,)
    
    
    class Permission(models.Model):
        caption = models.CharField(max_length=32)
        url = models.CharField(max_length=32)
        menu = models.ForeignKey(Menu, null=True, blank=True, on_delete=models.CASCADE)
    
        class Meta:
            verbose_name_plural = 'URL表'
    
        def __str__(self):
            return '%s-%s' % (self.caption, self.url)
    
    
    class Action(models.Model):
        caption = models.CharField(max_length=32)
        code = models.CharField(max_length=32)
    
        class Meta:
            verbose_name_plural = '操作表'
    
        def __str__(self):
            return self.caption
    
    
    class Permission2Action(models.Model):
        p = models.ForeignKey(Permission, on_delete=models.CASCADE)
        a = models.ForeignKey(Action, on_delete=models.CASCADE)
    
        class Meta:
            verbose_name_plural = '权限表'
    
        def __str__(self):
            return '%s-%s:-%s?t=%s' % (self.p.caption, self.a.caption, self.p.url, self.a.code)
    
    
    class Permission2Action2Role(models.Model):
        p2a = models.ForeignKey(Permission2Action, on_delete=models.CASCADE)
        r = models.ForeignKey(Role, on_delete=models.CASCADE)
    
        class Meta:
            verbose_name_plural = '角色分配权限'
    
        def __str__(self):
            return '%s=>%s' % (self.r.role, self.p2a)

    建立表后,用django的admin在表中添加一些数据

    1、用户表:建立几个用户

    2、角色表:建立几个角色,如:CEOCTO开发客服业务员

    3、给用户分配角色

    4、URL表:建立几个管理菜单,如:分类管理报表管理订单管理用户管理

    5、操作表:增删改查

    6、权限表:给URL添加操作内容

    7、角色分配权限:

    8、菜单表:设置三层菜单,如:菜单一:菜单1.1:菜单1.1.1

    二、url

    添加数据后建立url

    urlpatterns = [
        path('admin/', admin.site.urls),
        path('login/', views.login),
        path('logout/', views.logout),
        path('index/', views.index),
    ]

    三、login

    编辑login,登录成功后,将用户信息保存在session中,并通过MenuHelper类获取用户的权限和菜单,也保存在session中,实例化MenuHelper类

    def login(request):
        if request.method == "GET":
            return render(request, 'login.html')
        else:
            username = request.POST.get('username')
            pwd = request.POST.get('pwd')
            obj = models.User.objects.filter(username=username, password=pwd).first()
            if obj:
                # 登录成功,获取当前用户信息
                # 放到session中
                request.session['user_info'] = {'nid': obj.id, 'username': obj.username}
    
                # 获取当前用户的所有权限,获取所有菜单,获取在菜单中显示的权限(叶子节点)
                # 放到session中
                MenuHelper(request, obj.username)
                return redirect('/index')
            else:
                return redirect('/login')

    四、MenuHelper类

    首先获取当前用户,通过reques.path_info获取当前用户访问的url

    调用类的session_data方法:判断该用户当前session中是否已经有内容,如果有内容则与取出session中的内容,否则通过用户名分别获取当前用户的角色列表、权限列表、最终显示的菜单列表以及所有菜单,随后跳转至index

    class MenuHelper(object):
        def __init__(self, request, username):
            # 当前请求的request对象
            self.request = request
            # 当前用户名
            self.username = username
            # 当前url,如用户访问127.0.0.1:8000/index.html?p=123 会获得:index.html
            self.current_url = request.path_info
    
            # 当前用户的所有权限
            self.permission2action_dict = None
            # 当前用户菜单中显示的所有权限(叶子节点)
            self.menu_leaf_list = None
            # 所有菜单
            self.menu_list = 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_list = models.Role.objects.filter(user2role__u__username=self.username)
    
                # 获取当前用户的权限列表(url+action)
                permission2action_list = models.Permission2Action.objects.
                    filter(permission2action2role__r__in=role_list).
                    values('p__url', 'a__code').distinct()
    
                permission2action_dict = {}
                for item in permission2action_list:
                    if item['p__url'] in permission2action_dict:
                        permission2action_dict[item['p__url']].append(item['a__code'])
                    else:
                        permission2action_dict[item['p__url']] = [item['a__code'], ]
    
                # 获取菜单的叶子节点,即:菜单的最后一层应该显示的权限
                menu_leaf_list = list(models.Permission2Action.objects.
                                      filter(permission2action2role__r__in=role_list).
                                      exclude(p__menu__isnull=True).
                                      values('p_id', 'p__url', 'p__caption', 'p__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_list = permission2action_list
                # self.menu_leaf_list = menu_leaf_list
                # self.menu_list = menu_list
    
        def menu_data_list(self):
            # 设置一个空的叶子节点字典
            menu_leaf_dict = {}
            # 首先设置叶子父id节点为空
            open_left_parent_id = None
    
            for item in self.menu_leaf_list:
                # 将获取的叶子节点列表的每一个值转换为字典形式,并重新设置key,添加child,status,open字段
                item = {
                    'id': item['p_id'],
                    'url': item['p__url'],
                    'caption': item['p__caption'],
                    'parent_id': item['p__menu'],
                    'child': [],
                    'status': True,  # 是否显示
                    'open': False,  # 是否打开
                }
                # 判断每一个叶子节点的父节点,将每个叶子节点的内容添加至父节点id作为的key中
                # 判断父节点id作为的key是否在叶子节点字典中存在,如果存在,则将item值append进入
                if item['parent_id'] in menu_leaf_dict:
                    menu_leaf_dict[item['parent_id']].append(item)
                # 如果不存在,则直接在列表中生成一个key是叶子节点父节点id的,值为item的数据
                else:
                    menu_leaf_dict[item['parent_id']] = [item, ]
    
                # 判断用户输入的url是否与现在的url匹配,item['url']可以写成一个正则表达式,用match进行匹配
                # 如果匹配上,将叶子节点的open置为true,并将叶子节点的父节点id进行赋值
                import re
                if re.match(item['url'], self.current_url):
                    item['open'] = True
                    open_left_parent_id = item['parent_id']
    
            # 设置一个菜单空字典
            menu_dict = {}
    
            # 将菜单列表转换为字典,并增加child,status,open字段
            # 将列表中的id作为key,列表中的值作为值
            for item in self.menu_list:
                item['child'] = []
                item['status'] = False
                item['open'] = False
                menu_dict[item['id']] = item
    
            # 循环叶子字典,设置菜单字典中对应的child内容为叶子字典的值
            for k, v in menu_leaf_dict.items():
                menu_dict[k]['child'] = v
                # 设置菜单字典的parent_id的值为叶子字典的key(也就是叶子中的parent)
                parent_id = k
                # 设置菜单字典中的status状态为True,并循环设置父级菜单的status为True
                while parent_id:
                    menu_dict[parent_id]['status'] = True
                    parent_id = menu_dict[parent_id]['parent_id']
    
            # 判断叶子父级id,将open设置为True,并循环设置父级菜单的open为True
            while open_left_parent_id:
                menu_dict[open_left_parent_id]['open'] = True
                open_left_parent_id = menu_dict[open_left_parent_id]['parent_id']
    
            # print('循环权限用户url字典,将用户权限取得的id匹配菜单列表id并设置["child"]值为用户权限内容')
            # print('设置parent_id变量为:用户权限url的id')
            # print('如果有,菜单id的["status"]设置为True')
            # print('并且将parent_id的值设置为:菜单字典中菜单id的["parent"],等待下一次循环')
            # for k, v in menu_dict.items():
            #     print(k, v)
            # #####################处理菜单的等级关系#########################
            # menu_dict 应用:多级评论,多级菜单
    
            result = []
            # 按父子关系,将菜单列表中的值,层叠放入一个result中
            # 这里需要注意的是,只需要寻找一层的父id,并将自己放入,无需一层一层寻找到上一层的父节点。
            for row in menu_dict.values():
                if not row['parent_id']:
                    result.append(row)
                else:
                    menu_dict[row['parent_id']]['child'].append(row)
    
            return result
    
        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 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 action(self):
            """
            检查当前用户是否对当前URL有访问权,并获取对当前URL有什么权限
            :return:
            """
    
            action_list = []
    
            for k, v in self.permission2action_dict.items():
                if re.match(k, self.current_url):
                    action_list = v
                    break
    
            return action_list

    五、index

    index使用了一个装饰器,判断用户session中是否有用户信息,如果有用户信息,使用MenuHelper类实例化一个对象,调用对象的action方法,获得action_list

    如果列表为空则返回无权访问

    否则返回菜单树(调用了类的menu_data_list方法,循环递归的生成菜单树,返回的是后台生成的html代码

    以及权限列表

    最后通过权限列表中的内容分别进行操作,并返回至前台(这个位置没有编写完成,仅仅写了一个举例)

    def permission(func):
        def inner(request, *args, **kwargs):
            user_info = request.session.get('user_info')
            if not user_info:
                return redirect('/login.html')
            obj = MenuHelper(request, user_info['username'])
            action_list = obj.action()
            if not action_list:
                return HttpResponse('无权限访问')
            kwargs['menu_string'] = obj.menu_tree()
            kwargs['action_list'] = action_list
            return func(request, *args, **kwargs)
        return inner
    
    
    @permission
    def index(request, *args, **kwargs):
        actions_list = kwargs.get('actions_list')
        menu_string = kwargs.get('menu_string')
        if "GET" in actions_list:
            result = models.User.objects.all()
        else:
            result = []
        return render(request, 'index.html', {
            'menu_string': menu_string,
            'actions_list': actions_list,
            'result': result,
        })

    六、login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <form action="/login/" method="POST">
            {% csrf_token %}
            <input type="text" name="username" />
            <input type="text" name="pwd" />
            <input type="submit" value="提交" />
        </form>
    </body>
    </html>

    七、index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            .content{
                margin-left: 20px;
                display: none;
            }
            .content a{
                display: block;
            }
            .active > .content{
                display: block;
            }
        </style>
    </head>
    <body>
        <div style="float: left; 20%;">
            {{ menu_string|safe }}
        </div>
        <div style="float: left; 80%;">
        </div>
    
    </body>
    </html>
  • 相关阅读:
    Android 70道面试题汇总不再愁面试
    TOMCAT用Https替换Http的方法
    Struts2+Spring3+Hibernate3配置全过程
    javac 无效标签
    hibernate
    数据库命令行启动
    Tomcat检测程序
    SQL
    Unsupported major.minor version 49.0的错误解决
    ImportError: No module named pysqlite2
  • 原文地址:https://www.cnblogs.com/trunkslisa/p/9667029.html
Copyright © 2011-2022 走看看