菜單權限管理終章


深度權限管理系統

需求:

最終保證,每個人有不同的權限,而權限的分配主要是通過url來判定

先來看models表的演變過程,以及需求的數據演變過程

2 張表
用戶表 權限表 一對多關系 joker
/user/    /user/add ...

那如果這樣,新來名員工就會配置N個權限,操作復雜?如果加入角色?

3 張表 外加 1張 多對多表

用戶表 多對多 角色 一對多 權限表

joker             經理           /user/...

        ceo           /all/...
permiss_list=user.roles.filter(permissions__url__isnull=False).value('permissions__title','permissions__url').distinct() #

request.session['is_login'] = True
request.session['user'] = user.username #
print(user.username)

url_list = []
for url in permiss_list:
    print(url)
    url_list.append(url['permissions__url'])
request.session[settings.PERMISSION_SESSION_KEY] = url_list
添加到配置文件中去,在判斷用戶每次的輸入url是否在這里
數據展示

如果這樣,新來的員工直接分配角色就可以了,對?沒錯這樣基本實現了權限分配,但是對於我們開發前端的話,我們必須直到用戶進來有多少權限,才能針對頁面做按鈕級別的控制,因為不能用戶進來所有的增刪改查按鈕都在?那如果加入權限組?

4 張表 外加 2 張多對多表

用戶表 多對多 角色 一對多 權限表(增加code字段,對url的解釋,例如/user/,code為list) 多對一 權限組

joker             經理           /user/...                                                                                           1

        ceo           /all/....                                                                                              1

注意去重      
想要得到的數據效果,該用戶下的權限組ID下的所有權限
1
{'urls': ['/userinfo/', '/userinfo/del/(\\d+)', '/userinfo/edit/(\\d+)', '/userinfo/add/'],
 'codes': ['list', 'del', 'edit', 'add']}
2
{'urls': ['/order/', '/order/del/(\\d+)', '/order/add/', '/order/edit/(\\d+)'],
 'codes': ['list', 'del', 'add', 'edit']}

permiss_list = user.roles.all().values('permissions__code','permissions__group_id','permissions__url').distinct()  #

permiss_dict = {}
for item in permiss_list:
    codes = item['permissions__code']
    group_id = item['permissions__group_id']
    urls = item['permissions__url']
    if permiss_dict.get(group_id):
        permiss_dict[group_id]['codes'].append(codes)
        permiss_dict[group_id]['urls'].append(urls)
    else:
        permiss_dict[group_id]={
            'codes':[codes,],
            'urls':[urls,]
        }

for k,v in permiss_dict.items():
    print(k,v)

request.session['is_login'] = True
request.session['user'] = user.username  # user 傳過來的是一個對象

request.session[settings.PERMISSION_SESSION_KEY] =permiss_dict
將權限列表加入到settings里面去,在根據用戶輸入的url來判斷有什么權限
數據展示

這樣完全實現了權限分配,那我們將這個權限控制放在中間鍵吧?這樣可以每次只要請求過來就可以進行判定,很方便

from django.utils.deprecation import MiddlewareMixin
import re
from django.shortcuts import HttpResponse
from django.conf import settings

class RbacMiddleware(MiddlewareMixin):
    def process_request(self, request):

        # 1. 獲取白名單,讓白名單中的所有url和當前訪問url匹配
        for reg in settings.PERMISSION_VALID_URL:
            if re.match(reg, request.path_info):
                return None

        # 2. 獲取權限
           premiss_list = request.session.get(settings.PERMISSION_SESSION_KEY)
        if not url_list:
            return HttpResponse('未獲取到當前用戶的權限信息,無法訪問')

        # 3. 對用戶請求的url進行匹配
        flag = False
        for reg in url_list:
            # 需要注意
            regx = "^%s$" % (reg,)
            if re.match(regx, request.path_info):
                print('====')
                flag = True
                # break
                return None
        if not flag:
            return HttpResponse('無權訪問')
中間鍵過濾url

我們看到中間鍵里面有一些針對settings的配置我們看下配置了哪些?

1. settings導入方法是
from  django.conf import settings

2. 中間鍵的注冊
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'rbac.middlewares.rbac.RbacMiddleware'   
    應用.目錄.目錄.類名
    過濾url驗證
]

3. 記得在數據機構化的時候,我們把數據加到了settings里面,所以要在里面加入
PERMISSION_SESSION_KEY = "url_listt"   # 權限字典,包含列表
MENU_SESSION_KEY = 'menu_list' # 后面的菜單設計
4. 中間鍵過濾的url,我們發現一個問題,如果登陸都不再權限內,那將會無限死循環,所以我們需要一個url白名單
PERMISSION_VALID_URL = [   # 白名單
    '/login/',
    '/admin/.*',
    '/index/',
    '/menu/',
]
這里面的url都是可以不用進行url驗證

當我們url驗證通過,拿到用戶下的權限組下的所有權限,那我們如何處理這些權限呢,從而實現在前端可以針對性的控制按鈕?

還記得我們code么,url的別名?我們只需要判斷用戶輸入的url是否有通過驗證,通過就會有對應的code?

class PermissCode:
    def __init__(self, premiss_codes_list):
        self.premiss_codes_list = premiss_codes_list

    def has_add(self):
        if 'add' in self.premiss_codes_list:
            return True

    def has_del(self):
        if 'del' in self.premiss_codes_list:
            return True

    def has_edit(self):
        if 'edit' in self.premiss_codes_list:
            return True

def orderinfo(request):
    premiss_codes_list = request.premiss_codes_list # 拿到 code列表

    page_permission = PermissCode(premiss_codes_list) # 實例化

    return render(request,'order.html',{'page_permission':page_permission})


data_list是數據,只需要觀察如何顯示權限的就可以了
{% block content %}
    {% if page_permission.has_add %}
                <a href="/userinfo/add/">添加</a>
            {% endif %}
            <table>
                    {% for row in data_list %}
                        <tr>
                            <td>{{ row.id }}</td>
                            <td>{{ row.name }}</td>
                             {% if  page_permission.has_edit %}
                            <td><a href="#">編輯</a></td>
                            {% endif %}
                            {% if  page_permission.has_del %}
                            <td><a href="#">刪除</a></td>
                            {% endif %}
                        </tr>
                {% endfor %}

            </table>

{% endblock %}
視圖和前端的渲染

html用到了模版,因為頁面都一樣,這樣可以更加的彈性,伸縮

上面數據展示的代碼有rbac應用,server目錄下的init_permission初始化獲取,視圖里面調用這個函數就可以

加入菜單管理

菜單設計顧名思義,就是當我選中一個左側菜單時候,顯示該菜單下的權限,其他菜單下的權限要閉合?

那我們肯定是要增加表,那么菜單表要跟誰對應呢,應該是權限組,你要想,我們上面數據最終得到的是權限組=[url:{},code:{}],那我們將菜單組跟權限組關聯,是不是就可以替代權限組的位置,而且在前端顯示的時候,菜單下對應的權限也會多,也很美觀。

5 張表 外加 2 張多對多表

用戶表 多對多 角色 一對多 權限表(增加code字段,對url的解釋,例如/user/,code為list) 多對一 權限組  多對一  菜單

joker      經理        /user/...                                   1      菜單一                                                                                   1

       ceo        /all/...                                                       1          菜單二                                           1

注意去重 

我們首先從數據庫中拿到這樣的數據放入到settings中,我們有專門的函數來處理。

# user 是個對象
menu_list = user.roles.all().values('permissions__id', # 權限ID 'permissions__url', # 權限 url 'permissions__code', # 權限 code 別名 'permissions__title', # 權限名稱 'permissions__is_menu', # 是否是菜單 'permissions__group_id', # 權限組ID 'permissions__parent_id', # 自關聯ID 'permissions__group__menu__id', # 菜單ID 'permissions__group__menu__title').distinct() # 菜單名稱 print(menu_list) # 用於生成菜單 menu_list_all = []

for item in menu_list:   tpl = {   'id': item['permissions__id'], 'title': item['permissions__title'], 'url': item['permissions__url'], 'menu_gp_id': item['permissions__parent_id'], 'menu_id': item['permissions__group__menu__id'], 'menu_title': item['permissions__group__menu__title'],   } menu_list_all.append(tpl) request.session[settings.MENU_SESSION_KEY] = menu_list_all ######### 菜單 print(menu_list_all)
{'title': '用戶列表', 'url': '/userinfo/', 'pid': None, 'id': 1, 'menu_id': 1, 'menu_title': '菜單一'}, 
{'title': '用戶刪除', 'url': '/userinfo/del/(\\d+)', 'pid': None, 'id': 2, 'menu_id': 1, 'menu_title': '菜單一'},
{'title': '訂單列表', 'url': '/order/', 'pid': None, 'id': 3, 'menu_id': 2, 'menu_title': '菜單二'},
{'title': '訂單刪除', 'url': '/order/del/(\\d+)', 'pid': None, 'id': 4, 'menu_id': 2, 'menu_title': '菜單二'},
{'title': '用戶修改', 'url': '/userinfo/edit/(\\d+)', 'pid': None, 'id': 5, 'menu_id': 1, 'menu_title': '菜單一'},
{'title': '用戶添加', 'url': '/userinfo/add/', 'pid': None, 'id': 6, 'menu_id': 1, 'menu_title': '菜單一'},
{'title': '訂單添加', 'url': '/order/add/', 'pid': None, 'id': 7, 'menu_id': 2, 'menu_title': '菜單二'},
{'title': '訂單修改', 'url': '/order/edit/(\\d+)', 'pid': None, 'id': 8, 'menu_id': 2, 'menu_title': '菜單二'}

我們在將上面得到數據進行數據結構化?

菜單一:{
    1: {
    'active': True, # 是否展開
'menu_id': 1, 'children': [{'url': '/userinfo/', 'active': True, 'title': '用戶列表'}, {'url': '/order/', 'active': None, 'title': '訂單列表'}], 'menu_title': '菜單一'} } } '''

如果得到上面的結構,我們就可以通過active的值來判定誰展開,誰關閉。children代表了用戶權限,通過active來判定是否選中,是通過用戶的url來判定。

並且通過孩子的active屬性來判定父的active屬性值,默認為None,但是當孩子的active為True,父就會隨之變為True,展開。

我們知道為何要這種數據結構了,還有個問題?如果視圖非常多(也肯定非常多,比如用戶列表,訂單列表,還要增刪改查),我們不能在視圖里面重復過多的寫入這種數據結構的演變,我們可以寫入一個單獨文件內,這樣去調用就可以了,還寂寞模版么?所有的html都是基於它展現出來,而結構出來的菜單設計也是需要跟模版進行渲染,所以,我們把結構化數據,和一部分模版上的左邊菜單進行渲染,利用到了register.inclusion_tag方法?

應用下面創建  templatetags目錄,在創建rbac.py文件

from django.template import Library

register = Library()  # 實例化

@register.simple_tag
def menu_html():
    return '菜單'  #  返回什么就是什么


from django.conf import settings
import re
@register.inclusion_tag('xxxxx.html')
def menu_html_new(request):

    menu_list = request.session.get(settings.MENU_SESSION_KEY)
    current_url = request.path_info  # 請求的URL

    menu_dict = {}
    for item in menu_list:
        if not item['menu_gp_id']:
            menu_dict[item['id']] = item
    print(menu_dict)
    '''
    {1: {'id': 1, 'menu_id': 1, 'url': '/userinfo/', 'menu_title': '菜單一', 'menu_gp_id': None, 'title': '用戶列表'}, 
    3: {'id': 3, 'menu_id': 1, 'url': '/order/', 'menu_title': '菜單一', 'menu_gp_id': None, 'title': '訂單列表'}}

    '''
    for item in menu_list:
        regex = "^{0}$".format(item['url'])
        if re.match(regex, current_url):
            menu_gp_id = item['menu_gp_id']
            if menu_gp_id:
                menu_dict[menu_gp_id]['active'] = True
            else:
                menu_dict[item['id']]['active'] = True

    print('=======')
    print(menu_dict)
    '''
     {
    {
    1: {'active': True, 'id': 1, 'menu_id': 1, 'url': '/userinfo/', 'menu_title': '菜單一', 'menu_gp_id': None, 'title': '用戶列表'},  # 默認展開
    3: {'id': 3, 'menu_id': 1, 'url': '/order/', 'menu_title': '菜單一', 'menu_gp_id': None, 'title': '訂單列表'}}                     # 不展開
     }

    '''
    result = {}
    for item in menu_dict.values():
        active = item.get('active')
        menu_id = item['menu_id']
        if menu_id in result:
            result[menu_id]['children'].append({'title': item['title'], 'url': item['url'], 'active': active})
            if active:
                result[menu_id]['active'] = True
        else:
            result[menu_id] = {
                'menu_id': item['menu_id'],
                'menu_title': item['menu_title'],
                'active': active,
                'children': [
                    {'title': item['title'], 'url': item['url'], 'active': active}
                ]
            }
    print(result)
    '''
## 菜單
菜單一:{
    1: {
    'active': True, 'menu_id': 1, 
    'children': [{'url': '/userinfo/', 'active': True, 'title': '用戶列表'}, {'url': '/order/', 'active': None, 'title': '訂單列表'}], 
    'menu_title': '菜單一'}
    }
}
'''

    return {'menu_dict':result}
自定義標簽,數據結構化過程

里面的xxxx.html是判定左邊菜單的展示還是收縮,還判定權限是否被選中

{% for value in menu_dict.values %}
        <div class="item-title">
            <div class="header item">{{ value.menu_title }}</div>
            {% if value.active %}
                <div class="body">
            {% else %}
                <div class="body hide">
            {% endif %}
            {% for child in value.children %}
                {% if child.active %}
                    <a href="{{ child.url }}" class="active">{{ child.title }}</a>
                {% else %}
                    <a href="{{ child.url }}">{{ child.title }}</a>
                {% endif %}

            {% endfor %}
            </div>
        </div>
    {% endfor %}
xxxx.html

將上面結構數據,和html渲染過后的數據放回模版里面  dynamic_menu.html

{% load rbac %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>


   <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

    <script src="https://cdn.bootcss.com/jquery/3.1.1/jquery.js"></script>
            <link rel="stylesheet" href="/static/rbac/rbac.css">

</head>
<body>
<div class="pg-header">表頭</div>
    <div class="context">
        <div style="width: 20%;float: left;background-color: cadetblue">
{#                   這里代碼移交至 #}
        {% menu_html_new request%}

        </div>
        <div style="width: 80%;float: left">
            {% block content %}

            {% endblock %}

            <script src="/static/rbac/rbac.js"></script>
        </div>
    </div>
</body>
</html>
模版基板

其他的html文件只需要繼承就可以了,在block里面寫入當前頁面需要的內容

例如用戶添加頁面

{% extends "dynamic_menu.html" %}

{% block content %}
    <h1>添加用戶頁面</h1>
    <input type="text">
{% endblock %}

最后你需要注意的是,settings文件配置了static靜態文件,你可以將一些css,js的引用放到這里,收縮你的代碼

項目加入權限系統的過程

1. rbac清空migrations目錄(除__init__.py以外)

2. 業務的用戶表和權限的用戶表OneToOne關聯,如:
        from django.db import models
        from rbac import models as rbac_model

        class DepartMent(models.Model):
            """
            部門
            """
            title = models.CharField(max_length=32)


        class User(models.Model):
            user_info = models.OneToOneField(to=rbac_model.UserInfo)
            nickname = models.CharField(max_length=32)
            momo = models.CharField(max_length=32)
            gender_choices = (
                (1,''),
                (2,''),
            )
            gender = models.IntegerField(choices=gender_choices)

3. 通過頁面admin錄入權限信息


4. 用戶登錄成功之后,初始化權限和菜單信息
    init_permission(權限的用戶表對象,request)

    加入權限相關的配置:
        PERMISSION_SESSION_KEY = “url_list”
        MENU_SESSION_KEY = “menu_list”

5. 對用戶請求的url進行權限的驗證
    應用中間件:
        MIDDLEWARE = [
            'django.middleware.security.SecurityMiddleware',
             ...
            'rbac.middlewares.rbac.RbacMiddleware',  #中間鍵對於url的判定
        ]

    中間鍵里面有白名單,
    setting.py
        PERMISSION_VALID_URL = [
            '/login/',
            '/admin/.*',
        ]

    有針對數據權限的判定  PERMISSION_SESSION_KEY

    只要通過驗證,在視圖函數的request中 permission_codes字段: [list,add,edit,del....]
    如果想要面向對象方式,
    from rbac.permission.base import BasePermission
    class PermissCode:
    def __init__(self, premiss_codes_list):
        self.premiss_codes_list = premiss_codes_list

    def has_add(self):
        if 'add' in self.premiss_codes_list:
            return True
    給前端傳過實例之后,就可以判斷有沒有這個屬性
    
    判斷url所有權限,通過code判定,然后可以針對按鈕級別是否顯示

6. 動態菜單
   在Html模板中引入:
      {% load rbac %}

      引入樣式
      <link rel='stylesheet' href='/static/rbac/rbac.css' />
      <script src='/static/rbac/rbac.js' />

      {% menu request %}

   推薦:放到母板中。
權限與項目的結合


免責聲明!

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



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