權限管理系統

1、需求分析:

用戶登陸
- 獲取當前用戶具備的全部角色
- 獲取當前用戶具備的全部權限(去重)html

  -在訪問列表頁面時,須要判斷:有無添加權限,有無刪除權限,有無編輯權限;在頁面上顯示相應權限按鈕前端

  -訪問菜單URL時,默認展開其所屬菜單python

  -訪問非菜單URL,默認選中原菜單jquery

 

 

 2、方法步驟

a.首先建一個名爲rbac的APP

b.建立表結構,基於角色權限控制、菜單:

  -5個類,7張表django

 
 

 

 

 

from django.db import models


class Menu(models.Model):
    """
    菜單組
    """
    title = models.CharField(max_length=32)
    
    class Meta:
        verbose_name_plural = "菜單組"

    def __str__(self):
        return self.title


class Group(models.Model):
    """
    權限組
    """
    caption = models.CharField(verbose_name='組名稱', max_length=16)
    menu = models.ForeignKey(verbose_name='所屬菜單', to='Menu', default=1)
    
    class Meta:
        verbose_name_plural = "權限組"

    def __str__(self):
        return self.caption


class Permission(models.Model):
    """
    權限表
    """
    title = models.CharField(verbose_name='標題', max_length=32)
    url = models.CharField(verbose_name="含正則URL", max_length=64)

    menu_gp = models.ForeignKey(verbose_name='組內菜單', to='Permission', null=True, blank=True, related_name='x1')

    code = models.CharField(verbose_name="操做", max_length=16)
    group = models.ForeignKey(verbose_name='所屬組', to="Group")

    class Meta:
        verbose_name_plural = "權限表"

    def __str__(self):
        return self.title


class User(models.Model):
    """
    用戶表
    """
    username = models.CharField(verbose_name='用戶名', max_length=32)
    password = models.CharField(verbose_name='密碼', max_length=64)
    email = models.CharField(verbose_name='郵箱', max_length=32)

    roles = models.ManyToManyField(verbose_name='具備的全部角色', to="Role", blank=True)

    class Meta:
        verbose_name_plural = "用戶表"

    def __str__(self):
        return self.username


class Role(models.Model):
    """
    角色表
    """
    title = models.CharField(max_length=32)
    permissions = models.ManyToManyField(verbose_name='具備的全部權限', to='Permission', blank=True)

    class Meta:
        verbose_name_plural = "角色表"

    def __str__(self):
        return self.title
models.py

 

 

verbose_name---在admin模式下,字段顯示的名字
blank=True  ---在admin模式下,字段能夠爲空
class Meta:
verbose_name_plural = "表名"  ---在admin模式下,顯示的表名

爲了讓咱們的表能在admin模式下顯示,咱們須要在rbac文件下的admin.py中,進行以下設置session

 

from django.contrib import admin

from . import models


admin.site.register(models.Permission)
admin.site.register(models.User)
admin.site.register(models.Role)
admin.site.register(models.Group)
admin.site.register(models.Menu)
/rbac/admin.py

 

 

 

c.基於Django admin錄入權限數據 

  python manage.py createsuperuser數據結構

 

d.用戶登陸程序

  按照邏輯,當咱們輸入一個URL,在跳轉到相應頁面前,咱們須要作3件事:app

  1.首先應該先判斷此URL,是否限制用戶瀏覽,若是沒有限制,則不須要登陸,直接進入相應頁面(可在settings中設置白名單實現)ide

        2.若限制了,則須要檢驗用戶的訪問權限,在檢驗用戶權限以前,咱們得先檢驗用戶登陸狀態(經過判斷登錄成功後設置的session值是否存在),拿到用戶權限信息(先比對帳戶密碼,正確則設置一個session,用做檢驗登錄狀態)

        3.若是是登陸的狀態,咱們還須要檢驗用戶是否有瀏覽此頁面的權限(爲此咱們須要把當前URL和用戶全部權限裏的URL進行比對),如能訪問此URL,當進入URL後,咱們還須要根據用戶的身份,在頁面上顯示不一樣的操做權限(如增刪改查,有哪些操做權限就讓哪些操做權限顯示,沒有的就不讓其顯示出來)

 

因爲每次跳轉一個URL前,都會重複上面的步驟,咱們能夠將上面的方法代碼寫入中間件中,以避免爲此給每一個視圖函數都加上一個裝飾器(麻煩)

 

比對帳戶密碼,設置session的過程應該是在視圖函數裏完成,因爲咱們在中間件中會檢驗登錄狀態時會用到session,因此咱們能夠用戶的一些權限信息放到session中

如今咱們須要考慮的是session中到底須要存放哪些信息?

首先,咱們來考慮一下,做爲權限管理系統,咱們但願能根據用戶的權限,在頁面顯示一些相應菜單信息和操做按鈕,因此session設置,咱們分兩個部分:

一個是權限相關,用來檢驗用戶訪問權限,以及頁面權限相關按鈕(添加、編輯、刪除)顯示與否

二是菜單相關,好比咱們點擊菜單一里的子選項:用戶列表,顯示頁面信息後,菜單一仍然是展開狀態,當咱們在用戶列表點擊添加用戶,跳轉新頁面時,仍然展開菜單一

 

session權限相關

根據需求,咱們知道,權限相關裏,咱們須要拿到用戶可訪問的全部頁面(url,用來匹配訪問權限)用戶全部可執行的操做(code,用來匹配可顯示的操做鍵)、和(permissions__group_id)用來把權限歸類

爲了方便後取數據,因此咱們最終想要的結構是

{group_id1:{

  urls:[url一、url二、url3....],

  codes:[list、del、edit、add]},

group_id2:{

  urls:[url一、url二、url3....],

  codes:[list、del、edit、add]}

 

session菜單相關

根據需求,咱們知道,權限相關裏,咱們須要menu_title(大菜單標題)menu_id(大菜單的id,用來經過此id歸類子菜單)menu_gp_id(組內id,用於判斷是否能夠做爲子菜單,以及非子菜單所屬的子菜單)permission_id(經過可作菜單的id,分組其下面的url)permission_title(子菜單的標題)url(用於放在a標籤裏做跳轉連接,若是不是子菜單,則打開的是此連接,其所屬組所在菜單欄應該展開)

 

因此咱們一共要取出

複製代碼
    list = user.roles.values('permissions__code',  # 權限操做
                             'permissions__title',  # 權限名
                             'permissions__url',  # 權限url
                             'permissions__id',  # 權限id
                             'permissions__menu_gp_id',  # 組內菜單id
                             'permissions__group_id',  # 權限組id
                             'permissions__group__menu__id',  # 菜單id
                             'permissions__group__menu__caption',  # 菜單名
                             ).distinct()
複製代碼

必定要去重,由於有的人有幾個身份,有的權限會重複,將上面拿到的數據,再處理一下,分別做爲,菜單相關,權限相關所需的數據

因爲以上步驟過多,咱們應該從視圖函數裏抽離出來,單獨在rbac文件夾下建一個service文件夾,裏面建一個名爲init_permission.py的初始化文件

from django.conf import settings


def init_permission(user,request):
    """
    初始化權限信息,獲取權限信息並放置到session中。
    """
    permission_list = user.roles.values('permissions__id',
                                        'permissions__title',              # 用戶列表
                                        'permissions__url',
                                        'permissions__code',
                                        'permissions__menu_gp_id',         # 組內菜單ID,Null表示是菜單
                                        'permissions__group_id',
                                        'permissions__group__menu_id',     # 菜單ID
                                        'permissions__group__menu__title',#  菜單名稱
                                        ).distinct()     #要去重,由於每一個人可能有多種身份,權限會重複

    # 菜單相關(之後再匹配)
    sub_permission_list = []
    for item in permission_list:
        tpl = {
            'id':item['permissions__id'],
            'title':item['permissions__title'],
            'url':item['permissions__url'],
            'menu_gp_id':item['permissions__menu_gp_id'],
            'menu_id':item['permissions__group__menu_id'],
            'menu_title':item['permissions__group__menu__title'],
        }
        sub_permission_list.append(tpl)
    request.session[settings.PERMISSION_MENU_KEY] = sub_permission_list


    # 權限相關
    result = {}
    for item in  permission_list:
        group_id = item['permissions__group_id']
        code = item['permissions__code']
        url = item['permissions__url']
        if group_id in result:
            result[group_id]['codes'].append(code)
            result[group_id]['urls'].append(url)
        else:
            result[group_id] = {
                'codes':[code,],
                'urls':[url,]
            }

    request.session[settings.PERMISSION_URL_DICT_KEY] = result
init_permission.py

咱們只須要在login的視圖函數裏驗證完用戶名密碼後,調用函數就行

init_permission(user,request)

要記得先導入,才能成功調用

from rbac.service.init_permission import init_permission

這樣咱們就完成了session值的設置,接下來咱們能夠寫咱們的中間件了

 

在rbac中創建一個middlewares文件夾,而後建一個名爲rbac.py的文件,用來編寫咱們自定義的中間件代碼

import re

from django.shortcuts import redirect,HttpResponse
from django.conf import settings

class MiddlewareMixin(object):
    def __init__(self, get_response=None):
        self.get_response = get_response
        super(MiddlewareMixin, self).__init__()

    def __call__(self, request):
        response = None
        if hasattr(self, 'process_request'):
            response = self.process_request(request)
        if not response:
            response = self.get_response(request)
        if hasattr(self, 'process_response'):
            response = self.process_response(request, response)
        return response


class RbacMiddleware(MiddlewareMixin):

    def process_request(self,request):
        # 1. 獲取當前請求的URL
        # request.path_info
        # 2. 獲取Session中保存當前用戶的權限
        # request.session.get("permission_url_list')
        current_url = request.path_info

        # 當前請求不須要執行權限驗證
        for url in settings.VALID_URL:
            if re.match(url,current_url):
                return None

        #在視圖函數那邊驗證用戶登陸成功後,會設置session,若是登陸成功則會有此session
        permission_dict = request.session.get(settings.PERMISSION_URL_DICT_KEY)
        if not permission_dict:
            #沒有session值,則說明沒有登陸,須要跳轉登陸頁面
            return redirect('/login/')

        flag = False
        for group_id,code_url in permission_dict.items():

            for db_url in code_url['urls']:
                regax = "^{0}$".format(db_url)  #爲了精準匹配
                # 將當前頁面與當前用戶可訪問的頁面進行匹配
                if re.match(regax, current_url):
                    #匹配成功後將用戶的可執行的(增、刪、改、列表等操做)存到request中,方便調用
                    request.permission_code_list = code_url['codes']
                    flag = True
                    break

            if flag:
                break
        #匹配失敗,則顯示無權訪問
        if not flag:
            return HttpResponse('無權訪問')
rbac/middlewares/rbac.py

 

寫完自定義中間件後,記得去settings.py中加上咱們寫的中間件,來讓咱們的中間件生效

MIDDLEWARE = ['rbac.middlewares.rbac.RbacMiddleware',] 

在settings中,咱們還須要寫上咱們自定義的字符串

PERMISSION_URL_DICT_KEY = "permission_url_dict"

之因此這樣寫,是爲了方便之後修改時,只須要修改settings裏的值,其餘引用到的地方都會自動應用

還有加上URL白名單

VALID_URL = [
    "/login/",
    "/admin.*"
]

 

因爲頁面上都會顯示菜單,因此咱們先寫一個模板,其餘的只有繼承就好

{% load rbac %}    {# 引用自定義標籤 #}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/rbac/rbac.css" />
    <title>Title</title>
</head>
<body>
        <div style="float: left;width: 20%;height: 500px;background-color: #dddddd">
            {% menu_html request %}  {# 調用自定義menu_html標籤,並傳入request參數 #}
        </div>
        <div style="float: left;width: 80%">
            {% block content %}{% endblock %}
        </div>

            <script src="/static/jquery-3.2.1.min.js"></script>
            <script src="/static/rbac/rbac.js"></script>

</body>
</html>
layout

 

在rbac下創建一個static再在裏面創建rbac文件夾,在建咱們的css和js文件

.item-permission{
    padding: 3px 10px;
}
.item-permission a{
    display: block;
}
.item-permission a.active{
    color: red;
}
.hide{
    display: none;
}
rbac.css
/**
 * Created by Administrator on 2017/11/8.
 */
 $(function () {
    $('.item-title').click(function () {
        if($(this).next().hasClass('hide')){
            $(this).next().removeClass('hide')
        }else{
            $(this).next().addClass('hide')
        }
    })


});
rbac.js

 

注意根目錄下必定要有static文件下,不然咱們的css,JS可能不生效

jquery文件咱們能夠放在根目錄下的static文件夾中

 

接下來咱們寫自定義標籤的內容在rbac下創建templatetags文件下,建立rbac.py

import re
from django.template import Library
from django.conf import settings
register = Library()

@register.inclusion_tag("xxxxx.html")
def menu_html(request):
    """
    去Session中獲取菜單相關信息,匹配當前URL,生成菜單
    :param request:
    :return:
    """
    menu_list = request.session[settings.PERMISSION_MENU_KEY]
    current_url = request.path_info

    menu_dict = {}
    for item in menu_list:
        if not item['menu_gp_id']:
            menu_dict[item['id']] = item

    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

    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}
                ]
            }

    return {'menu_dict':result}
rbac.py

 

此自定義標籤中函數的做用是去Session中獲取菜單相關信息,匹配當前URL,生成菜單

 1.首先咱們須要從用戶的權限中拿出菜單相關的信息,並獲取當前訪問的URL

 menu_list = request.session[settings.PERMISSION_MENU_KEY] 

 current_url = request.path_info 

2.從menu_list中獲取用戶權限裏能做爲菜單的權限:

    menu_dict = {}
    for item in menu_list:
        if not item['menu_gp_id']:
            menu_dict[item['id']] = item

3.匹配當前URL,判斷當前URL是否可作菜單,若是能夠,則展開此菜單,若是不是菜單,則將其所屬菜單展開(給菜單加狀態)

複製代碼
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
複製代碼

4,菜單相關信息已經整理完畢,存在了menu_dict中,接下來就要建立菜單結構,將菜單數據結構化,將同屬一個大菜單下的菜單鍵放一個大菜單下

複製代碼
result = {}
for item in menu_dict.values():
    active = item.get('active')
    menu_id = item['menu_id']
    if menu_id in result: #一個大菜單下有多個子菜單,則只須要將子菜單的item和url以及狀態加入便可
        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}
            ]
        }
複製代碼

 

 

@register.inclusion_tag("xxxxx.html")做用是:能夠將其所裝飾的函數menu_html的返回值直接給xxxxx.html(此頁面是用來生成菜單相關的html)中使用,

當調用自定義標籤menu_html時,會將通過渲染後的xxxxx.html頁面以字符串的形式返回給調用的地方

用來造成菜單

 

 

這樣之後其餘頁面直接繼承layout.html就會有其對應的菜單,內容部分,咱們還須要判斷一下用戶的操做權限,以顯示相應操做按鈕,如userinfo頁面

from django.conf.urls import url
from django.contrib import admin
from rbac import views
from app01 import views as app01_views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^test/', views.test),
    url(r'^login/', app01_views.login),
    url(r'^index/', app01_views.index),
    url(r'^userinfo/$', app01_views.userinfo),
    url(r'^userinfo/add/$', app01_views.userinfo_add),
    url(r'^order/$', app01_views.order),
    url(r'^order/add/$', app01_views.order_add),
]
permission\urls.py
class BasePagePermission(object):
    #定義這個類,是爲了html頁面方便調用判斷
    def __init__(self,code_list):
        self.code_list = code_list

    def has_add(self):
        if "add" in self.code_list:
            return True

    def has_edit(self):
        if 'edit' in self.code_list:
            return True
    def has_del(self):
        if 'del' in self.code_list:
            return True

def userinfo(request):
    page_permission = BasePagePermission(request.permission_code_list)
    data_list = [
        {'id':1,'name':'xxx1'},
        {'id':2,'name':'xxx2'},
        {'id':3,'name':'xxx3'},
        {'id':4,'name':'xxx4'},
        {'id':5,'name':'xxx5'},
    ]
    return render(request,'userinfo.html',{'data_list':data_list,'page_permission':page_permission})


def userinfo_add(request):
    page_permission = BasePagePermission(request.permission_code_list)
    return render(request, 'userinfo_add.html', { 'page_permission': page_permission})


class OrderPagePermission(BasePagePermission):
    #繼承基礎的,方便擴展
    def has_report(self):
        if 'report' in self.code_list:
            return True

def order(request):
    order_permission = OrderPagePermission(request.permission_code_list)


    return render(request,'order.html')
permission\app01\views.py
{% extends "layout.html" %}


{% 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 %}
templates\userinfo.html

 

 

3、總結

rbac裏有什麼?

 

settings中有什麼?

複製代碼
#########註冊rbac的app,使其生效########
INSTALLED_APPS = [
    'rbac.apps.RbacConfig',   ###也可直接寫rbac
]

#########加入咱們自定義的中間件,使其生效########
MIDDLEWARE = [
    'rbac.middlewares.rbac.RbacMiddleware',
]


#############CSS,JS,JQ############
STATICFILES_DIRS = (
    os.path.join(BASE_DIR,'static'),
)

##########Session中用到的變量#############
PERMISSION_URL_DICT_KEY = "permission_url_dict"
PERMISSION_MENU_KEY = "permission_menu_key"

#####白名單############

VALID_URL = [
    "/login/",
    "/admin.*"
]
複製代碼

 

使用方法

1.建立project

2.複製rbac文件

3.在settings中按上面的設置

4.去admin中配置文件,錄入權限信息

5.在登錄邏輯中,登錄成功時,調用init_permission方法(爲此要在視圖函數中導入

from rbac import models
from rbac.service.init_permission import init_permission

 

6.視圖函數中用來判斷是否具備code操做

複製代碼
class BasePagePermission(object):
    #定義這個類,是爲了html頁面方便調用判斷
    def __init__(self,code_list):
        self.code_list = code_list

    def has_add(self):
        if "add" in self.code_list:
            return True

    def has_edit(self):
        if 'edit' in self.code_list:
            return True
    def has_del(self):
        if 'del' in self.code_list:
            return True

def userinfo(request):
    page_permission = BasePagePermission(request.permission_code_list)
    data_list = [
        {'id':1,'name':'xxx1'},
        {'id':2,'name':'xxx2'},
        {'id':3,'name':'xxx3'},
        {'id':4,'name':'xxx4'},
        {'id':5,'name':'xxx5'},
    ]
    return render(request,'userinfo.html',{'data_list':data_list,'page_permission':page_permission})


def userinfo_add(request):
    page_permission = BasePagePermission(request.permission_code_list)
    return render(request, 'userinfo_add.html', { 'page_permission': page_permission})


class OrderPagePermission(BasePagePermission):
    #繼承基礎的,方便擴展
    def has_report(self):
        if 'report' in self.code_list:
            return True

def order(request):
    order_permission = OrderPagePermission(request.permission_code_list)

    return render(request,'order.html')
複製代碼

前端頁面只須要

{% if page_permission.has_add %}
    <a href="/userinfo/add/">添加</a>
{% endif %}



7. 模板中:
{%load rbac %}  加載自定義標籤

<link rel="stylesheet" href="/static/rbac/rbac.css" />  引用rbac中的CSS



{% menu_html  request %}      調用自定義menu_html標籤,並傳入request參數

 

<script src="/static/jquery-3.2.1.min.js"></script><script src="/static/rbac/rbac.js"></script>  引用rbac中的JS
相關文章
相關標籤/搜索