• 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数据模型
     
    View Code
    
    表结构
    
    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;

    View Code
    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')
    View Code
    View Code
    
    
    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
        }
    View Code

    2.用户再次登录通过Django中间件 检查当前用户session中携带的权限信息,

    进而判断用户是否对当前路径 reqeust.path 有访问权限

    View Code
    
    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 权限中从下到上展开;

    代码如下

    View Code
    
    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 把用户菜单渲染到前段

    View Code
    
    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验证的功能紧密集合起来,实现对数据库数据的增加、编辑操作;

    添加

    View Code
    
    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

    使用

    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

    Modal Form 参数设置

    View Code
    
    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对象,实时更新数据;

    View Code
    
    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
  • 相关阅读:
    doctype是什么?
    <img>的title和alt有什么区别
    鼠标悬浮时,蒙版显示;否则,蒙版消失
    右下角内容与正文之间 假 响应式
    上传input中file文件到云端,并返回链接
    获取input标签中file的内容
    HTML中Meta标签中http-equiv属性小结
    select和其元素options
    将数组中的信息准确分开,修改过后再保存到一起
    Bootstrap方法为页面添加一个弹出框
  • 原文地址:https://www.cnblogs.com/zzy7372/p/9965565.html
Copyright © 2020-2023  润新知