对用户分配权限,和权限管理

权限这个问题有些麻烦,
角色对应权限,人物分配角色(一个人物可以有多个角色,一个角色也可以有多个人物)

    
 权限管理,
    
       - 动态菜单(关系存在数据库)
       - 基于角色分配(RBAC)Role Based Access Control
       
       流程:
            a. 用户登陆
            b. 根据用户获取所有的权限(url+action)
            c. 根据用户获取所有的权限(url+action)根据URL去重()
            d. 放在左侧菜单()

            1. 登陆,获取所有权限(url+action)
                ORM
                
                
            2. 所有菜单()
                v = models.Menus.objects.values('id','caption','parent_id')
                [
                    {id:'1','caption': '菜单1','parent_id':None},
                    {'caption': '菜单1.1','parent_id':1},
                    {'caption': '菜单1.2'},
                    {'caption': '菜单1.2.1'},
                    {'caption': '菜单2'},
                    {'caption': '菜单3'},
                    {'caption': '菜单3.1'},
                ]
                ==>
                [
                    {'caption': '菜单1','child': [{'caption': '菜单1.1'},{'caption': '菜单1.2','child':[{'caption': '菜单1.2.1'},]}]}
                    {'caption': '菜单2'}
                    {'caption': '菜单3'}
                ]
                
                # 递归
                # python中:字典,列表(引用类型)

        思考:
            a. 用递归的形式讲所有菜单的等级管理列
            b. 不显示无权限的菜单
            c. 仅展开当前访问的菜单,其他闭合


models.py 表格ORM

from django.db import models

class User(models.Model):
    username = models.CharField(max_length=32,verbose_name="用户名")
    password = models.CharField(max_length=64,verbose_name="密码")
    class Meta:
        verbose_name_plural = '用户表' # 这个修改的是admin中显示的表名
    def __str__(self):
        return self.username

class Role(models.Model):
    caption = models.CharField(max_length=32,verbose_name="角色")
    class Meta:
        verbose_name_plural = '角色表'
    def __str__(self):
        return self.caption

class User2Role(models.Model):
    u = models.ForeignKey(User,on_delete=models.CASCADE,verbose_name="用户")
    r = models.ForeignKey(Role,on_delete=models.CASCADE,verbose_name="角色")
    class Meta:
        verbose_name_plural = '用户分配角色'
    def __str__(self):
        return "%s-%s" %(self.u.username,self.r.caption)  # 使得显示的时候为:   libai-按摩

class Action(models.Model):
    # get    获取用户信息 1
    # post   创建用户 2
    # delete 删除用户 3
    # put    修改用户 4
    caption = models.CharField(max_length=32,verbose_name="操作")
    code = models.CharField(max_length=32)
    class Meta:
        verbose_name_plural = '操作表'
    def __str__(self):
        return self.caption

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)
    # blank = True 是在 admin 中可以为空
    def __str__(self):
        return self.caption

class Permission(models.Model):
    # http://127.0.0.1:8889/user.html 用户管理 1
    # http://127.0.0.1:8889/order.html 订单管理 2
    caption = models.CharField(max_length=32,verbose_name="标题")
    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 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.caption,self.p2a)


views.py 视图函数

from django.shortcuts import render
from django.shortcuts import redirect
from django.shortcuts import HttpResponse
from app01 import models

from django import forms
from django.forms import fields
from django.forms import widgets
from django.core.exceptions import ValidationError


class LoginForm(forms.Form):
    username = fields.CharField(
        max_length=32,
        required=True,
        error_messages={
            'required':'请输入用户名',
            'max_length':'最长输入32个字符',
        },
        widget=widgets.TextInput()
    )
    # def clean_username(self):
    #     v1 = self.cleaned_data['username']
    #     v2 = models.User.objects.filter(username=v1).first()
    #     if not v2:
    #         raise ValidationError("用户名错误") # 这个引发的错误会在errors.username显示
    #     return v1

    password = fields.CharField(
        max_length=32,
        required=True,
        error_messages={
            'required':'请输入密码',
            'max_length':'最长输入32个字符'
        },
        widget=widgets.PasswordInput()
    )

    def clean(self):
        v1 = self.cleaned_data.get('username')
        v2 = self.cleaned_data.get('password')
        obj = models.User.objects.filter(username=v1).first()
        if not obj:
            raise ValidationError("用户名错误")
        elif obj.password != v2:
            raise ValidationError("密码错误")
        return self.cleaned_data

from django.core.exceptions import NON_FIELD_ERRORS
def login(request):
    if request.method == "GET":
        obj = LoginForm()
        return render(request,"login.html",{"obj":obj})
    else:
        obj = LoginForm(request.POST)
        all_code = None
        if obj.is_valid():
            print(obj.cleaned_data)
            request.session["username"] = obj.cleaned_data['username']
            return redirect('/index.html')
        else:
            print(obj.errors)
            all_code = obj.errors[NON_FIELD_ERRORS]
        return render(request,"login.html",{"obj":obj,"all_code":all_code})

def index(request):
    username = 'libai'
    # ########## 获取角色列表 ##########
    # 方式一 : role_list
    # 前提:m = models.ManyToManyField("Role")
    # user_obj = models.User.objects.get(username=username)
    # role_list = user_obj.m.all() # [Role(),Role]

    # 方式二 : user2role_list
    # user_obj = models.User.objects.get(username=username)
    # user2role_list = models.User2Role.objects.filter(u=user_obj) #[User2Role,User2Role,]
    # print(user2role_list)
    #<QuerySet [<User2Role: libai-搬砖者>, <User2Role: libai-按摩>, <User2Role: libai-技师>]>
    # 循环,把所有role_id, [1,2,3,4]

    # 方式三: role_list
    # user_obj = models.User.objects.get(username=username)
    # role_list = models.Role.objects.filter(user2role__u=user_obj)
    # print(role_list)
    # <QuerySet [<Role: 搬砖者>, <Role: 按摩>, <Role: 技师>]>

    # 方式四: role_list
    role_list = models.Role.objects.filter(user2role__u__username=username)
    print(role_list)
    # <QuerySet [<Role: 搬砖者>, <Role: 按摩>, <Role: 技师>]>


    # l = models.Permission2Action2Role.objects.filter(r__in=role_list)
    # print(l)
    # permission2action_list = models.Permission2Action.objects.filter(permission2action2role__r__in=role_list)
    # print(permission2action_list)
    # <QuerySet [<Permission2Action: 报表管理--获取--/report.html?t=get>, <Permission2Action: 姑娘管理--添加--/gril.html?t=post>,
    # <Permission2Action: 姑娘管理--获取--/gril.html?t=get>, <Permission2Action: 姑娘管理--修改--/gril.html?t=put>,
    # <Permission2Action: 姑娘管理--修改--/gril.html?t=put>]>

    # 获取个人所有权限列表,放置在 session 中,缺点:无法获取实时权限信息,需重新登陆
    # 去重
    # permission2action_list = models.Permission2Action.objects.\
    #     filter(permission2action2role__r__in=role_list).\
    #     values("p__url","a__code").distinct()
    # for item in permission2action_list:
    #     print(item)
    # {'p__url': '/report.html', 'a__code': 'get'}
    # {'p__url': '/gril.html', 'a__code': 'post'}
    # {'p__url': '/gril.html', 'a__code': 'get'}
    # {'p__url': '/gril.html', 'a__code': 'put'}

    # return render(request,"index.html",{"user_role":role_list})

    # 将权限信息放到session中,不用每次请求都去检索数据库
    # 如果服务器给角色新添加了权限就会使得更新不及时,这时候就要提示用户重新登陆生效


    # 应该在菜单中显示的 权限 --- 在最后一层(exclude 排除不能显示的)
    menu_leaf_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_leaf_dict = {
        # 2:[
        #   {'p__url': '/report.html', 'p__caption': '报表管理', 'p__menu': 2},
        #   {'p__url': '/gril.html', 'p__caption': '姑娘管理', 'p__menu': 2},
        # ],
        # 3: [{'p__url': '/userinfo.html', 'p__caption': '用户管理', 'p__menu': 3},]
    }
    for item in menu_leaf_list:
        # [{'p_id': 1, 'p__url': '/userinfo.html', 'p__caption': '用户管理', 'p__menu': 3}]
        item = {
            'id': item['p_id'],
            'url': item['p__url'],
            "caption": item['p__caption'],
            "parent_id": item['p__menu'],
            'child': [],
        }
        if item['parent_id'] in menu_leaf_dict:
            menu_leaf_dict[item['parent_id']].append(item)
        else:
            menu_leaf_dict[item['parent_id']] = [item,]
    for k,v in menu_leaf_dict.items():
        print(k,v)
    # {'p__url': '/report.html', 'p__caption': '报表管理'}
    # {'p__url': '/gril.html', 'p__caption': '姑娘管理'}


    menu_list = models.Menu.objects.values("id","caption","parent_id")
    menu_dic = {}
    for item in menu_list:
        item['child'] = []
        menu_dic[item['id']] = item

    for k,v in menu_leaf_dict.items():
        menu_dic[k]['child'] = v

    # ##################### 处理等级关系
    # menu_dict: 应用:评论(models.xx.objects.values('...'))
    result = []
    for k,v in menu_dic.items():  # 注意:这里操作的是内存地址,
        if not v['parent_id']:
            result.append(v)
        else:
            menu_dic[v['parent_id']]['child'] = v
    for i in result:
        print('------------------------',i)
    # 剩下主菜单,包含子菜单
    #------------------------ {'id': 1, 'caption': '菜单1', 'parent_id': None, 'child': {'id': 5, 'caption': '菜单1.2', 'parent_id': 1, 'child': {'
    # id': 6, 'caption': '菜单1.2.1', 'parent_id': 5, 'child': []}}}
    # ------------------------ {'id': 2, 'caption': '菜单2', 'parent_id': None, 'child': [{'id': 3, 'url': '/report.html', 'caption': '报表管理', 'p
    # arent_id': 2, 'child': []}, {'id': 4, 'url': '/gril.html', 'caption': '姑娘管理', 'parent_id': 2, 'child': []}]}
    # ------------------------ {'id': 3, 'caption': '菜单3', 'parent_id': None, 'child': [{'id': 1, 'url': '/userinfo.html', 'caption': '用户管理',
    # 'parent_id': 3, 'child': []}]}

    return render(request,"index.html",{"user_role":role_list})


urls.py 文件

from django.contrib import admin
from django.urls import path
from app01 import views

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

admin 注册表格

from django.contrib import admin
from app01 import models

admin.site.register(models.Role)
admin.site.register(models.Action)
admin.site.register(models.Permission)
admin.site.register(models.Permission2Action)
admin.site.register(models.Permission2Action2Role)
admin.site.register(models.User)
admin.site.register(models.User2Role)
admin.site.register(models.Menu)

最终的权限程序,这东西十分绕脑子,

from django.shortcuts import render,HttpResponse,redirect
from . import models
import re

class MenuHelper(object):

    def __init__(self,request,username):
        # 当前请求的request对象
        self.request = request
        # 当前用户名
        self.username = username
        # 获取当前URL
        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)
            # v = [
            #     {'url':'/inde.html','code':'GET'},
            #     {'url':'/inde.html','code':'POST'},
            #     {'url':'/order.html','code':'PUT'},
            #     {'url':'/order.html','code':'GET'},
            # ]
            # v = {
            #     '/inde.html':['GET']
            # }
            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 = {}
        open_leaf_parent_id = None

        # 归并所有的叶子节点
        for item in self.menu_leaf_list:
            item = {
                'id': item['p_id'],
                'url': item['p__url'],
                'caption': item['p__caption'],
                'parent_id': item['p__menu'],
                'child': [],
                'status': True,  # 是否显示
                'open': False
            }
            if item['parent_id'] in menu_leaf_dict:
                menu_leaf_dict[item['parent_id']].append(item)
            else:
                menu_leaf_dict[item['parent_id']] = [item, ]
            if re.match(item['url'], self.current_url):
                item['open'] = True
                open_leaf_parent_id = item['parent_id']

        # 获取所有菜单字典
        menu_dict = {}
        for item in self.menu_list:
            item['child'] = []
            item['status'] = False
            item['open'] = False
            menu_dict[item['id']] = item

        # 讲叶子节点添加到菜单中
        for k, v in menu_leaf_dict.items():
            menu_dict[k]['child'] = v
            parent_id = k
            # 将后代中有叶子节点的菜单标记为【显示】
            while parent_id:
                menu_dict[parent_id]['status'] = True
                parent_id = menu_dict[parent_id]['parent_id']

        # 将已经选中的菜单标记为【展开】
        while open_leaf_parent_id:
            menu_dict[open_leaf_parent_id]['open'] = True
            open_leaf_parent_id = menu_dict[open_leaf_parent_id]['parent_id']

        # 生成树形结构数据
        result = []
        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 actions(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


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:
            # obj.id,  obj.username
            # 当前用户信息放置session中
            request.session['user_info'] = {'nid':obj.id,'username':obj.username}

            # 获取当前用户的所有权限
            # 获取在菜单中显示的权限
            # 获取所有菜单
            # 放置session中
            MenuHelper(request,obj.username)
            return redirect('/index.html')
        else:
            return redirect('/login.html')

def logout(request):
    request.session.clear()
    return redirect('/login.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.actions()
        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):
    action_list = kwargs.get('action_list')
    menu_string = kwargs.get('menu_string')

    return render(request,"index.html",{"menu_string":menu_string,"action_list":action_list})

登陆页面

# login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login.html" 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;width: 20%;">
        {{ menu_string|safe }}
    </div>
    <div style="float: left;width: 80px;">
        {% if 'POST' in action_list %}
            <a href="http://www.baidu.com">添加</a>
        {% endif %}
    </div>

    {{ action_list }}
</body>
</html>

urls.py 路径文件

from django.conf.urls import url
from django.contrib import admin
from rbac import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login.html$', views.login),
    url(r'^logout.html$', views.logout),

    url(r'^index.html$', views.index),
]

models.py 文件和上面一样的

效果图:

1.png

链接:https://pan.baidu.com/s/1eY0iIF8H-jKs6ICZS9_x3w 密码:gj1e

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容