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
原文地址:https://www.cnblogs.com/zzy7372/p/9965565.html