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

 

 

 

 

 

參考

相關文章
相關標籤/搜索