zoukankan      html  css  js  c++  java
  • Django之权限管理插件

     

    一、功能分析:

    一个成熟的web应用,对权限的控制、管理是不可少的;对于一个web应用来说是什么权限?

    这要从web应用的使用说起,用户在浏览器输入一个url,访问server端,server端返回这个url下对应的资源;

    所以 对于用户来说 1个可以访问url 就等于1个权限 

    比如某人开发了一个web应用包含以下5个url,分别对于不同资源;

    1、91.91p15.space/Chinese/

    2、91.91p15.space/Japanese and Korean/

    3、91p15.space/Euramerican/

    4、91p15.space/Latin America/

    5、91p15.space/African/

    --------------------------------------------------------------------------------------------------------

    普通用户:可以访问 5

    白金用户:可以访问 4、5、1

    黄金用户:可以访问1、2、3、4、5

    为什么某些网站会为广大用户做角色划分呢(比如 普通、会员、黑金、白金)?

    因为给用户归类后,便于权限的划分、控制、管理;

    所以我们把这种基于角色来做得权限控制,称为RBAC(Role Basic Access Control)

    二、权限管理数据库表结构设计

    1、用户表:用户表和角色表为多对多关系,1个用户可以有多个角色,1个角色可以被多个用户划分;

               

           

    2、角色表:角色表和权限也是多对多关系,一个角色可以有多个权限,一个权限可以划分给多个角色

             

     

     

    3、菜单表:用于在前端引导用户找到自己的权限,并可以设置多级菜单对用户权限进行划分;所以权限表和菜单表是1对多关系;

    由于需要构建多级菜单,并且拥有嵌套关系,所以菜单表自引用;

     

     启发:一般设计包含层级结构嵌套,切嵌套的层级无法预测的表结构使用自关联;(表1外键-----》表2----》外键表3是行不通的,因为无法预测嵌套层级的深度)

    例如:多级评论(无法预测,评论树的深度)

     

     

    三、modal.py数据模型

    1、创建一个独立的app作为公共模块,以备后期遇到权限相关项目时使用;

    from django.db import models
    
    from django.db import models
    
    class Menu(models.Model):
        ''' 菜单表'''
        caption=models.CharField(max_length=32)
        parent=models.ForeignKey('Menu',null=True,blank=True)   #自关联
        def __str__(self):
            caption_list = [self.caption,]
            p=self.parent
            while p:  #如果有父级菜单,一直向上寻找
                caption_list.insert(0,p.caption)
                p=p.parent
    
            return "-".join(caption_list)
    
    
    class Permission(models.Model):
        '''权限表'''
        title = models.CharField(max_length=64)
        url = models.CharField(max_length=255)
        menu = models.ForeignKey('Menu', null=True, blank=True)#和菜单是1对多关系
        def __str__(self):
            return '权限名称:  %s--------权限所在菜单   %s'% (self.title,self.menu)
    
    class Role(models.Model):
        '''角色表'''
        rolename=models.CharField(max_length=32)
        permission=models.ManyToManyField('Permission')
        def __str__(self):
            return '角色:  %s--------权限   %s'% (self.rolename,self.permission)
    
    class UserInfo(models.Model):
        '''用户表'''
        name=models.CharField(max_length=32)
        pwd=models.CharField(max_length=64)
        rule=models.ManyToManyField('Role')
        def __str__(self):
            return self.name
    View Code

    四、权限初始化设置、中间件获取、判断、生成权限菜单;

    当用户登录之后获取到用户名、密码查询用户表连表查询得到角色、权限信息,写入当前用户session(用session来保存用户的权限信息)

    写入session之后每次用户请求到来,通过Django中间件判断用户权限;

    1.用户首次登录,初始时该用户权限,写入session;

    from app02 import models
    from app02.service import init_session
    from django.conf import settings
    import re
    
    def login(reqeust):
        if reqeust.method == 'GET':
            return render(reqeust, 'login.html')
        else:
            user = reqeust.POST.get('user')
            pwd = reqeust.POST.get('pwd')
            user_obj = models.UserInfo.objects.filter(name=user, pwd=pwd).first()
            if user:
                # init_session(reqeust,user_obj)
                init_session.per(reqeust,user_obj)#用户首次登录初始化用户权限信息
                return redirect('/index/')
            else:
                return render(reqeust, 'login.html')
    
    
    def index(request):
    
        return HttpResponse('INDEX')
    
    
    def test_query(request):
        return render(request,'test.html')
    视图
    from django.conf import settings
    from .. import models
    def per(reqeust,user_obj):
        permission_list = user_obj.rule.values('permission__title', 'permission__url',
                                               'permission__menu_id', ).distinct()
        permission_urllist = []  # 当前用户可以访问的url(权限列表)
        permission_menulist = []  # 当前用户应该挂靠到菜单上显示的权限
        for iteam in permission_list:
            permission_urllist.append(iteam['permission__url'])
            if iteam['permission__menu_id']:
                temp = {'title': iteam['permission__title'], 'url': iteam['permission__url'],
                        'menu_id': iteam['permission__menu_id']}
                permission_menulist.append(temp)
        menulist = list(models.Menu.objects.values('id', 'caption', 'parent_id'))  # 获取所有菜单(以便当前用户的菜单挂靠)
        from django.conf import settings
        reqeust.session[settings.SESSION_PERMISSION_URL_KEY] = permission_urllist
        reqeust.session[settings.SESSION_PERMISSION_MENU_URL_KEY] = {
            'k1': permission_menulist,
            'k2': menulist
        }
    init_session.per

    2.用户再次登录通过Django中间件 检查当前用户session中携带的权限信息,进而判断用户是否对当前request.path有访问权限?;

    from django.utils.deprecation import MiddlewareMixin
    import re
    from django.shortcuts import render,redirect,HttpResponse
    from django.conf import settings
    class Mddile1(MiddlewareMixin):
        def process_request(self,request):
            #如果用户访问的url是登录、注册页面,记录到白名单,放行
            for url in settings.PASS_URL_LIST:
                if re.match(url,request.path_info):
                    return None
    
            Permission_url_list=request.session.get(settings.SESSION_PERMISSION_URL_KEY)
            #如果用户访问的url 不在当前用户权限之内 返回login页面
            if not Permission_url_list:
                return redirect(settings.LOGIN_URL)
            current_url=request.path_info
            #由于数据库的数据,可能是正则所有 一定要精确匹配
            flag=False
            for url in Permission_url_list:
                url='^%s$'%(url)
                if re.match(url,current_url):
                    flag=True
                    break
            if not flag:
                if settings.DEBUG:  #如果是程序调试应该 显示用户可以访问的权限
                    url_html='<br/>'.join(Permission_url_list)
                    return HttpResponse('无权访问您可以访问%s'%url_html)
                else:
                    return HttpResponse('没有权限')
    
    
    
        def process_response(self, request,response):
            return response
    View Code

     

    五、根据用户权限生成菜单

    当用户使用当前访问的通过中间件之后,要做的事情只有2步;

    1、根据用户session中的权限列表,生成该用户的菜单;

    2、根据用户访问的当前url,把这个菜单 从当前url(权限)从下到上展开;

    def test_query(request):
        menu_permission_list=request.session[settings.SESSION_PERMISSION_MENU_URL_KEY]
        permission_list=menu_permission_list['k1'] #获取需要挂靠在菜单上显示的权限
        menu_list=menu_permission_list['k2']       #获取全部菜单
        all_menu_dict={}
        # status 是用户全部权限,挂靠显示的菜单;
        # open 当前url(权限)对应的父级菜单展开?
        for item in menu_list:
            item['child']=[]
            item['status']=False
            item['open']=False
            all_menu_dict[item['id']]=item
        current_url=request.path_info
        for row in permission_list:
           row['status'] = True
           row['open']=False
           if re.match('^%s$'% (row['url']),current_url):
               row['open']=True
           all_menu_dict[row['menu_id']]['child'].append(row)
           pid=row['menu_id']
           while pid:
               all_menu_dict[pid]['status']=True
               pid=all_menu_dict[pid]['parent_id']
           if row['open']:
               PID=row['menu_id']
               while PID:
                   all_menu_dict[PID]['open']=True
                   PID=all_menu_dict[PID]['parent_id']
    
        return HttpResponse('OK')
    View Code

    六、自定义模板语言 simple_tag 把用户菜单渲染到前端

    from django.template import Library
    from django.conf import settings
    import re,os
    from django.utils.safestring import mark_safe
    register=Library()
    
    
    #生成菜单所有数据
    def men_data(request):
        menu_permission_list = request.session[settings.SESSION_PERMISSION_MENU_URL_KEY]
        permission_list = menu_permission_list['k1']  # 获取需要挂靠在菜单上显示的权限
        menu_list = menu_permission_list['k2']  # 获取全部菜单
        all_menu_dict = {}
        # status 是用户全部权限,挂靠显示的菜单;
        # open 当前url(权限)对应的父级菜单展开?
        # 把用户所有的权限挂靠到对应的菜单
        for item in menu_list:
            item['child'] = []
            item['status'] = False
            item['open'] = False
            all_menu_dict[item['id']] = item
        current_url = request.path_info
        for row in permission_list:
            row['status'] = True
            row['open'] = False
            if re.match('^%s$' % (row['url']), current_url):
                row['open'] = True
            all_menu_dict[row['menu_id']]['child'].append(row)
            pid = row['menu_id']
            while pid:
                all_menu_dict[pid]['status'] = True
                pid = all_menu_dict[pid]['parent_id']
            if row['open']:
                PID = row['menu_id']
                while PID:
                    all_menu_dict[PID]['open'] = True
                    PID = all_menu_dict[PID]['parent_id']
        # 把用户所有菜单挂父级菜单
        res = []
        for k, v in all_menu_dict.items():
            if not v.get('parent_id'):
                res.append(v)
            else:
                pid = v.get('parent_id')
                all_menu_dict[pid]['child'].append(v)
        return res
    
    
    #生成菜单所用HTML
    def process_menu_html(menu_list):
        #盛放菜单所用HTML标签
        tpl1 = """
                   <div class='rbac-menu-item'>
                       <div class='rbac-menu-header'>{0}</div>
                       <div class='rbac-menu-body {2}'>{1}</div>
                   </div>
               """
        #盛放权限的HTML
        tpl2 = """
                   <a href='{0}' class='{1}'>{2}</a>
               """
        html=''
        for item in menu_list:
            if not item['status']:
                continue
            else:
                if item.get('url') :
                    # 权限
                    html+= tpl2.format(item['url'],'rbac_active' if item['open'] else '',item['title'])
                else:
                    #菜单
                    html+= tpl1.format(item['caption'],process_menu_html(item['child']),''if item['open'] else 'rbac-hide')
    
    
    
        return mark_safe( html)
    
    
    
    @register.simple_tag
    def rbac_menus(request):
        res= men_data(request)
        html=process_menu_html(res)
        return html
    
    
    @register.simple_tag
    def rbac_css():
        file_path = os.path.join('app02', 'theme', 'rbac.css')
        if os.path.exists(file_path):
            return mark_safe(open(file_path, 'r', encoding='utf-8').read())
        else:
            raise Exception('rbac主题CSS文件不存在')
    
    
    @register.simple_tag
    def rbac_js():
        file_path = os.path.join('app02', 'theme', 'rbac.js')
        if os.path.exists(file_path):
            return mark_safe(open(file_path, 'r', encoding='utf-8').read())
        else:
            raise Exception('rbac主题JavaScript文件不存在')
    View Code

    七、使用 ModelForm组件 填充插件中数据

    1、 Modal Form插件的简单使用

     Modal Form 顾名思义 就是把Modal和Form验证的功能紧密集合起来,实现对数据库数据的增加、编辑操作;

    添加

    from app02 import models
    from django.forms import ModelForm
    class UserModalForm(ModelForm):
        class Meta:
            model=models.UserInfo #(该字段必须为 model  数据库中表)
            fields= '__all__'   #(该字段必须为 fields 数据库中表)
    
    def add(request):
         # 实例化models_form
        if request.method=='GET':
            obj = UserModalForm()
            return render(request,'rbac/user_add.html',locals())
        else:
            obj=UserModalForm(request.POST)
            if obj.is_valid():
                data=obj.cleaned_data
                obj.save()  #form验证通过直接 添加用户信息到数据库
            return render(request, 'rbac/user_add.html', locals())
    View Code

     使用

    def user_edit(request):
        pk = request.GET.get('id')
        user_obj = models.UserInfo.objects.filter(id=pk).first()
        if request.method=='GET':
            if not user_obj:
                return redirect('/app02/user_edit/')
            else:
                #在form表单中自动填充默认值
                model_form_obj=UserModalForm(instance=user_obj)
                return render(request,'rbac/user_edit.html',locals())
        else:
            #修改数据 需要instance=user_obj
            model_form_obj = UserModalForm(request.POST,instance=user_obj)
            if model_form_obj.is_valid():
                model_form_obj.save()
        return redirect('/app02/userinfo/')
    View Code

    2、Modal Form 参数设置

    from django.shortcuts import render,HttpResponse,redirect
    from app02 import models
    from django.forms import ModelForm
    from django.forms import widgets as wid
    from django.forms import fields as fid
    
    class UserModalForm(ModelForm):
        class Meta:
            model=models.UserInfo #(该字段必须为 model  数据库中表)
            fields= '__all__'   #(该字段必须为 fields '__all__',显示数据库中所有字段,
                                    # fields=['指定字段']
                                    #  exclude=['排除指定字段'] )
            # fields=['name',]
            # exclude=['pwd']
            #error_messages 自定制错误信息
            error_messages={'name':{'required':'用户名不能为空'},
                            'pwd': {'required': '密码不能为空'},
                            }
    
            #widgets 自定制插件
            # widgets={'name':wid.Textarea(attrs={'class':'c2'})}
            #由于数据库里的字段 和前端显示的会有差异,可以使用 labels 定制前端显示
            labels={'name':'姓名','pwd':'密码','rule':'角色'}
            #自定制 input标签 输入信息提示
            help_texts={'name':'别瞎写,瞎写打你哦!'}
            #自定制自己 form 字段.CharField()  email()等
            field_classes={
                'name':fid.CharField
            }
    View Code

    3、添加数据库之外的字段,实时数据更新

    ModelForm 可以结合Model把所有数据库字段在页面上生成,也可以增加额外的字段;

    规则:如果增加的字段和数据里的filed重名则覆盖,不重名则新增;

    也可以通过重写__init__ ,每次实例化1个form对象,实时更新数据;

    class PermissionModelForm(ModelForm):
          #ModelForm 可以结合Model把所有数据库字段在页面上生成,也可以增加额外的字段;
        url=fields.ChoiceField()
        class Meta:
            fields = "__all__"
            model = models.Permission  #注意不是models
        def __init__(self,*args,**kwargs):   #重写父类的 __init__方法,每次实例化实时更新 form中的数据
            super(PermissionModelForm,self).__init__(*args,**kwargs)
            from pro_crm.urls import urlpatterns
            self.fields['url'].choices=get_all_url(urlpatterns,'/', True)
    View Code

    八、总结

    如何把权限精确到按钮,按钮就是子菜单就是一个url

    权限管理的思路是 

    把用户权限记录到数据库里面

    当用户首次登录时,从数据库里取出数据把用户的权限(url)和挂靠的菜单菜单/写入到session中

    以后每次访问在中间件进行check;

    难度在于:多级菜单之间的拼接挂靠会用到递归,所以我选择了二级菜单;

    参考

  • 相关阅读:
    献给初学者:谈谈如何学习Linux操作系统
    Spring MVC 教程,快速入门,深入分析
    缓存、缓存算法和缓存框架简介
    计算机科学中最重要的32个算法
    12岁的少年教你用Python做小游戏
    当你输入一个网址的时候,实际会发生什么?
    java程序员从笨鸟到菜鸟之(七)一—java数据库操作
    java中的类修饰符、成员变量修饰符、方法修饰符。
    Java内存分配全面浅析
    Java知多少(19)访问修饰符(访问控制符)
  • 原文地址:https://www.cnblogs.com/sss4/p/7575250.html
Copyright © 2011-2022 走看看