Django之权限管理插件

 

1、功能分析:

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

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

因此 对于用户来讲 1个能够访问url 就等于1个权限 前端

 

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

一、91.91p15.space/Chinese/数据库

二、91.91p15.space/Japanese and Korean/django

三、91p15.space/Euramerican/浏览器

四、91p15.space/Latin America/session

五、91p15.space/African/app

--------------------------------------------------------------------------------------------------------ide

普通用户:能够访问 5

白金用户:能够访问 四、五、1

黄金用户:能够访问一、二、三、四、5

 

为何某些网站会为广大用户作角色划分呢(好比 普通、会员、黑金、白金)?

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

因此咱们把这种基于角色来作得权限控制,称为RBAC(Role Basic Access Control)

 

 

 

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

 

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

           

       

二、角色表:角色表和权限也是多对多关系,一个角色能够有多个权限,一个权限能够划分给多个角色

         

 

 

 

 

 

三、菜单表:用于在前端引导用户找到本身的权限,并能够设置多级菜单对用户权限进行划分;因此权限表和菜单表是1对多关系;

因为须要构建多级菜单,而且拥有嵌套关系,因此菜单表自引用;

 

 

 

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

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

 

 

3、modal.py数据模型

 

一、建立一个独立的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

 

 

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

当用户登陆以后获取到用户名、密码查询用户表连表查询获得角色、权限信息,写入当前用户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

 

 

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

当用户使用当前访问的经过中间件以后,要作的事情只有2步;

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

二、根据用户访问的当前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

 

 

6、自定义模板语言 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

 

 

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

一、 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

 

二、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

 

三、添加数据库以外的字段,实时数据更新

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

 

 

 

 

 

参考

相关文章
相关标签/搜索