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;

 

難度在於:多級菜單之間的拼接掛靠會用到遞歸,所以我選擇了二級菜單;

 

 

 

 

 

 

 

 

 

 

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM