權限管理-一級菜單-二級菜單-三級菜單-路徑導航和權限粒度控制到按鈕級別

權限管理 RBAC

 

  1. 權限管理css

1. 爲何要有權限?html

 

2. 開發一套權限的組件。爲何要開發組件?python

 

3. 權限是什麼?web

web 開發中 URL 約等於 權限數據庫

 

4. 表結構的設計django

 

權限表 緩存

ID URL session

1 /customer/list/app

2 /customer/add/ide

 

 

用戶表

ID name pwd

1 ward 123

 

 

用戶和權限的關係表(多對多)

ID user_id permission_id

1 1 1

1 1 2

 

5. 寫代碼

1. 查詢出用戶的權限寫入session

2. 讀取權限信息,判斷是否有權限

 

最第一版的權限管理梳理流程

表結構

from django.db import models


class Permission(models.Model):
   """
  權限表
  """
   title = models.CharField(max_length=32, verbose_name='標題')
   url = models.CharField(max_length=32, verbose_name='權限')
   
   class Meta:
       verbose_name_plural = '權限表'
       verbose_name = '權限表'
   
   def __str__(self):
       return self.title


class Role(models.Model):
   name = models.CharField(max_length=32, verbose_name='角色名稱')
   permissions = models.ManyToManyField(to='Permission', verbose_name='角色所擁有的權限', blank=True)
   
   def __str__(self):
       return self.name


class User(models.Model):
   """
  用戶表
  """
   name = models.CharField(max_length=32, verbose_name='用戶名')
   password = models.CharField(max_length=32, verbose_name='密碼')
   roles = models.ManyToManyField(to='Role', verbose_name='用戶所擁有的角色', blank=True)
   
   def __str__(self):
       return self.name
  • settings文件配置

    • # ###### 權限相關的配置 ######
      PERMISSION_SESSION_KEY = 'permissions'
      WHITE_URL_LIST = [
         r'^/login/$',
         r'^/logout/$',
         r'^/reg/$',
         r'^/admin/.*',
      ]
  • 其實權限就是用戶可以訪問那些url,不能訪問那些url,咱們所作的就是將每一個不一樣身份的人

    分配不一樣的url

  • 在最初用戶登陸的時候就查詢出用戶的權限。並將這次權限存入到session中

    • 爲何要存入session中啊,爲了避免重複讀取數據庫,存到session中

      咱們能夠配置session而後將session存到緩存中(非關係型數據庫中)

      這樣讀取的速度回很快

  • 登陸成功後如何查看當前用戶的權限並將其寫入到session中

    • from django.shortcuts import render, HttpResponse, redirect, reverse
      from rbac import models
      from django.conf import settings

      ...

      user = models.User.objects.filter(name=username, password=pwd).first()
      # 登陸成功
             # 將權限信息寫入到session
             
             # 1. 查當前登陸用戶擁有的權限
             permission_list = user.roles.filter(permissions__url__isnull=False).values_list(
                                                                                        'permissions__url').distinct()
             # for i in permission_list:
             #     print(i)
             
             # 2. 將權限信息寫入到session # 這裏的鍵值咱們作了全局配置
             request.session[settings.PERMISSION_SESSION_KEY] = list(permission_list)
             # 獲得的permission_list是一個QuerySet的元組對象,由於session的存儲是有數據類型限制因此轉換爲列表(列表中套元組)
  • 而後,該用戶可以訪問那些,不能訪問那些,這時,咱們能夠將這個邏輯寫在中間件這裏

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


      class PermissionMiddleware(MiddlewareMixin):
         # 每個請求來,都會走這個鉤子函數
         def process_request(self, request):
             # 對權限進行校驗
             # 1. 當前訪問的URL
             current_url = request.path_info

             # 白名單的判斷咱們這裏將白名單設置在了settings中,往settings中加就ok
             for i in settings.WHITE_URL_LIST:
                 if re.match(i, current_url):
                     return

             # 2. 獲取當前用戶的全部權限信息
             permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
             # 3. 權限的校驗
             print(current_url)  # Django的session作了轉換將元組轉換成爲一個列表
             for item in permission_list:
                 url = item[0]
                 if re.match("^{}$".format(url), current_url):
                     return
             else:
                 return HttpResponse('沒有權限')

升級版

動態生成一級菜單

 

表結構的設計

from django.db import models


class Permission(models.Model):
   """
  權限表
  """
   title = models.CharField(max_length=32, verbose_name='標題')
   url = models.CharField(max_length=32, verbose_name='權限')
# 用來判斷哪些url是菜單,哪些不是菜單
   is_menu = models.BooleanField(default=False, verbose_name='是不是菜單')
   # 記錄該菜單對應的圖標信息(這裏是屬性樣式類)
   icon = models.CharField(max_length=32, verbose_name='圖標', null=True, blank=True)

   class Meta:
       verbose_name_plural = '權限表'
       verbose_name = '權限表'
   
   def __str__(self):
       return self.title


class Role(models.Model):
   name = models.CharField(max_length=32, verbose_name='角色名稱')
   permissions = models.ManyToManyField(to='Permission', verbose_name='角色所擁有的權限', blank=True)
   
   def __str__(self):
       return self.name


class User(models.Model):
   """
  用戶表
  """
   name = models.CharField(max_length=32, verbose_name='用戶名')
   password = models.CharField(max_length=32, verbose_name='密碼')
   roles = models.ManyToManyField(to='Role', verbose_name='用戶所擁有的角色', blank=True)
   
   def __str__(self):
       return self.name

 

註冊層成功以後:

user = models.User.objects.filter(name=username, password=pwd).first()
# 將權限信息寫入到session中
init_permission(request, user)
def init_permission(request, user):
   # 1. 查當前登陸用戶擁有的權限
   permission_query = user.roles.filter(permissions__url__isnull=False).values(
       'permissions__url',
       'permissions__is_menu',
       'permissions__icon',
       'permissions__title'
  ).distinct()
   print('permission_query', permission_query)
   # 存放權限信息
   permission_list = []
   # 存放菜單信息
   menu_list = []
   for item in permission_query:
       permission_list.append({'url': item['permissions__url']})
       if item.get('permissions__is_menu'):  # 如若菜單這個字段爲True
           # 將這個菜單的信息先存入一個字典,而後存入session
           menu_list.append({
               'url': item['permissions__url'],  # 權限信息
               'icon': item['permissions__icon'],  # 圖標(Bootstrap的類樣式)
               'title': item['permissions__title'],  # 標題
          })

   # 2. 將權限信息寫入到session
   request.session[settings.PERMISSION_SESSION_KEY] = permission_list
   # 將菜單的信息寫入到session中
   request.session[settings.MENU_SESSION_KEY] = menu_list

母版中的菜單(一級菜單)

在母版中合適的位置導入這個include_tag

{% load rbac %}
{% menu request %}

在templatetags下的rbac.py文件中寫(自定義過濾器)

import re
from django import template
from django.conf import settings

register = template.Library()


@register.inclusion_tag('rbac/menu.html')
def menu(request):
   menu_list = request.session.get(settings.MENU_SESSION_KEY)
   for item in menu_list:
       url = item.get('url')
       if re.match('^{}$'.format(url), request.path_info):
           item['class'] = 'active'
   return {"menu_list": menu_list}

在templates下的rbac文件夾下建立enum.html

<div class="static-menu">

  {% for item in menu_list %}
       <a href="{{ item.url }}" class="{{ item.class }}">
           <span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span>{{ item.title }}</a>
  {% endfor %}

</div>
<--這個代碼的樣式能夠放到該app文件夾下的static下的css中創建一個menu.css-->

由於將數據存入了session中,因此咱們能夠經過request.session.來獲取數據

.left-menu .menu-body .static-menu {

}

.left-menu .menu-body .static-menu .icon-wrap {
   width: 20px;
   display: inline-block;
   text-align: center;
}

.left-menu .menu-body .static-menu a {
   text-decoration: none;
   padding: 8px 15px;
   border-bottom: 1px solid #ccc;
   color: #333;
   display: block;
   background: #efefef;
   background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
   background: -ms-linear-gradient(bottom, #efefef, #fafafa);
   background: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%);
   background: -o-linear-gradient(bottom, #efefef, #fafafa);
   filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff');
   -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')";
   box-shadow: inset 0px 1px 1px white;
}

.left-menu .menu-body .static-menu a:hover {
   color: #2F72AB;
   border-left: 2px solid #2F72AB;
}

.left-menu .menu-body .static-menu a.active {
   color: #2F72AB;
   border-left: 2px solid #2F72AB;
}

settings的配置

# ###### 權限相關的配置 ######
PERMISSION_SESSION_KEY = 'permissions'
MENU_SESSION_KEY = 'menus'
WHITE_URL_LIST = [
   r'^/login/$',
   r'^/logout/$',
   r'^/reg/$',
   r'^/admin/.*',
]

 

中間件的配置

在middlewares目錄(中間件目錄中)建立rbac.py文件

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


class PermissionMiddleware(MiddlewareMixin):
   def process_request(self, request):
       # 對權限進行校驗
       # 1. 當前訪問的URL
       current_url = request.path_info

       # 白名單的判斷(settings中配置好了)
       for i in settings.WHITE_URL_LIST:
           if re.match(i, current_url):
               return

       # 2. 獲取當前用戶的全部權限信息
       permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
       
       # 3. 權限的校驗
       for item in permission_list:
           url = item['url']
           if re.match("^{}$".format(url), current_url):
               return
       else:
           return HttpResponse('沒有權限')

應用rbac組件

 

一、拷貝rbac組件到新的項目中並註冊APP

二、配置權限的相關信息

# ###### 權限相關的配置 ######
PERMISSION_SESSION_KEY = 'permissions'
MENU_SESSION_KEY = 'menus'
WHITE_URL_LIST = [
   r'^/login/$',
   r'^/logout/$',
   r'^/reg/$',
   r'^/admin/.*',
]

三、建立跟權限相關的表

  • 執行命令

    • python3 manage.py makemigrations

    • python3 manage.py migrate

四、錄入權限信息

  • 建立超級用戶

  • 錄入全部權限信息

  • 建立角色 給角色分權限

  • 建立用戶 給用戶分角色

五、在登陸成功以後 寫入權限和菜單的信息到session中

六、配置上中間件,進行權限的校驗

七、使用動態菜單

<!-導入靜態文件-->
<link rel="stylesheet" href="{% static 'css/menu.css' %}">

使用inclusion_tag
<div class="left-menu">
   <div class="menu-body">
      {% load rbac %}
      {% menu request %}
   </div>
</div>

 

母版中的菜單(動態生成二級菜單)

 

信息管理

客戶列表

財務管理

繳費列表

 

User name pwd

Role name permissions(FK) 2user

Permission title(二) url menu(FK) 2role

Menu title(一)

Models.py

from django.db import models


class Menu(models.Model):
   """
  一級菜單
  """
   title = models.CharField(max_length=32, unique=True)  # 一級菜單的名字
   icon = models.CharField(max_length=32, verbose_name='圖標', null=True, blank=True)

   class Meta:
       verbose_name_plural = '菜單表'
       verbose_name = '菜單表'

   def __str__(self):
       return self.title


class Permission(models.Model):
   """
  權限表
  有關聯Menu的二級菜單
  沒有關聯Menu的不是二級菜單,是不能夠作菜單的權限
  """
   title = models.CharField(max_length=32, verbose_name='標題')
   url = models.CharField(max_length=32, verbose_name='權限')
   menu = models.ForeignKey('Menu', null=True, blank=True)

   class Meta:
       verbose_name_plural = '權限表'
       verbose_name = '權限表'

   def __str__(self):
       return self.title


class Role(models.Model):
   name = models.CharField(max_length=32, verbose_name='角色名稱')
   permissions = models.ManyToManyField(to='Permission', verbose_name='角色所擁有的權限', blank=True)

   def __str__(self):
       return self.name


class User(models.Model):
   """
  用戶表
  """
   name = models.CharField(max_length=32, verbose_name='用戶名')
   password = models.CharField(max_length=32, verbose_name='密碼')
   roles = models.ManyToManyField(to='Role', verbose_name='用戶所擁有的角色', blank=True)

   def __str__(self):
       return self.name

登陸

 

from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from django.conf import settings
import copy
from rbac.server.init_permission import init_permission


def login(request):
   if request.method == 'POST':
       username = request.POST.get('username')
       pwd = request.POST.get('pwd')

       user = models.User.objects.filter(name=username, password=pwd).first()

       if not user:
           err_msg = '用戶名或密碼錯誤'
           return render(request, 'login.html', {'err_msg': err_msg})
       # 登陸成功
       # 將權限信息寫入到session
       init_permission(request, user)
       return redirect(reverse('customer'))
   return render(request, 'login.html')
def init_permission(request, user):
   # 1. 查當前登陸用戶擁有的權限
   permission_query = user.roles.filter(permissions__url__isnull=False).values(
       'permissions__url',
       'permissions__title',
       'permissions__menu_id',
       'permissions__menu__title',
       'permissions__menu__icon',
  ).distinct()
   print(permission_query)
   # 存放權限信息
   permission_list = []
   # 存放菜單信息
   menu_dict = {}
   for item in permission_query:
       permission_list.append({'url': item['permissions__url']})
       menu_id = item.get('permissions__menu_id')
       if not menu_id:
           continue
       if menu_id not in menu_dict:
           menu_dict[menu_id] = {
               'title': item['permissions__menu__title'],
               'icon': item['permissions__menu__icon'],
               'children': [
                  {
                       'title': item['permissions__title'],
                       'url': item['permissions__url']}
              ]
          }
       else:
           menu_dict[menu_id]['children'].append(
              {'title': item['permissions__title'], 'url': item['permissions__url']})

   # 2. 將權限信息寫入到session
   request.session[settings.PERMISSION_SESSION_KEY] = permission_list
   # 將菜單的信息寫入到session中
   request.session[settings.MENU_SESSION_KEY] = menu_dict

將拿到的數據存入session

寫在一個自定義inclusion_tag

母版

{% load rbac %}
{% menu request %}

rbac.py

import re
from django import template
from django.conf import settings

register = template.Library()


@register.inclusion_tag('rbac/menu.html')
def menu(request):
  menu_list = request.session.get(settings.MENU_SESSION_KEY)
  return {"menu_list": menu_list}

menu.html

<div class="multi-menu">
  {% for item in menu_list.values %}
       <div class="item">
           <div class="title"><i class="fa {{ item.icon }}"></i> {{ item.title }}</div>
           <div class="body hide">
              {% for child in item.children %}
                   <a href="{{ child.url }}">{{ child.title }}</a>
              {% endfor %}
           </div>
       </div>
  {% endfor %}
</div>

menu.css0

.static-menu .icon-wrap {
   width: 20px;
   display: inline-block;
   text-align: center;
}

.static-menu a {
   text-decoration: none;
   padding: 8px 15px;
   border-bottom: 1px solid #ccc;
   color: #333;
   display: block;
   background: #efefef;
   background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
   background: -ms-linear-gradient(bottom, #efefef, #fafafa);
   background: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%);
   background: -o-linear-gradient(bottom, #efefef, #fafafa);
   filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff');
   -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')";
   box-shadow: inset 0px 1px 1px white;
}

.static-menu a:hover {
   color: #2F72AB;
   border-left: 2px solid #2F72AB;
}

.static-menu a.active {
   color: #2F72AB;
   border-left: 2px solid #2F72AB;
}

.multi-menu .item {
   background-color: white;
}

.multi-menu .item > .title {
   padding: 10px 5px;
   border-bottom: 1px solid #dddddd;
   cursor: pointer;
   color: #333;
   display: block;
   background: #efefef;
   background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
   background: -ms-linear-gradient(bottom, #efefef, #fafafa);
   background: -o-linear-gradient(bottom, #efefef, #fafafa);
   filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff');
   -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')";
   box-shadow: inset 0 1px 1px white;
}

.multi-menu .item > .body {
   border-bottom: 1px solid #dddddd;
}

.multi-menu .item > .body a {
   display: block;
   padding: 5px 20px;
   text-decoration: none;
   border-left: 2px solid transparent;
   font-size: 13px;

}

.multi-menu .item > .body a:hover {
   border-left: 2px solid #2F72AB;
}

.multi-menu .item > .body a.active {
   border-left: 2px solid #2F72AB;
}

 

menu.js

$('.item .title').click(function () {
   $(this).next().toggleClass('hide')
})

三級菜單

 

此實現的是,點擊菜單其餘菜單關閉(js的操做)而後點擊不是菜單的連接

菜單的狀態保持不變,仍是展現當前狀態

 

models.py

from django.db import models


class Menu(models.Model):
   """
  一級菜單
  """
   title = models.CharField(max_length=32, unique=True)  # 一級菜單的名字
   icon = models.CharField(max_length=32, verbose_name='圖標', null=True, blank=True)
   weight = models.IntegerField(default=1)

   class Meta:
       verbose_name_plural = '菜單表'
       verbose_name = '菜單表'

   def __str__(self):
       return self.title


class Permission(models.Model):
   """
  權限表
  有關聯Menu的二級菜單
  沒有關聯Menu的不是二級菜單,是不能夠作菜單的權限
  """
   title = models.CharField(max_length=32, verbose_name='標題')
   url = models.CharField(max_length=32, verbose_name='權限')
   menu = models.ForeignKey('Menu', null=True, blank=True)
   # 該權限關聯的其餘權限是否也是在當前url上展現
   parent = models.ForeignKey(to='Permission', null=True, blank=True)

   class Meta:
       verbose_name_plural = '權限表'
       verbose_name = '權限表'

   def __str__(self):
       return self.title


class Role(models.Model):
   name = models.CharField(max_length=32, verbose_name='角色名稱')
   permissions = models.ManyToManyField(to='Permission', verbose_name='角色所擁有的權限', blank=True)

   def __str__(self):
       return self.name


class User(models.Model):
   """
  用戶表
  """
   name = models.CharField(max_length=32, verbose_name='用戶名')
   password = models.CharField(max_length=32, verbose_name='密碼')
   roles = models.ManyToManyField(to='Role', verbose_name='用戶所擁有的角色', blank=True)

   def __str__(self):
       return self.name

登陸

from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from django.conf import settings
import copy
from rbac.server.init_permission import init_permission


def login(request):
   if request.method == 'POST':
       username = request.POST.get('username')
       pwd = request.POST.get('pwd')

       user = models.User.objects.filter(name=username, password=pwd).first()

       if not user:
           err_msg = '用戶名或密碼錯誤'
           return render(request, 'login.html', {'err_msg': err_msg})

       # 登陸成功
       # 將權限信息寫入到session
       init_permission(request, user)

       return redirect(reverse('customer'))

   return render(request, 'login.html')

當中間件校驗經過以後init_permission.py將權限寫入到session

from django.conf import settings

def init_permission(request, user):
   # 1. 查當前登陸用戶擁有的權限
   permission_query = user.roles.filter(permissions__url__isnull=False).values(
       'permissions__url',
       'permissions__title',
       'permissions__id',
       'permissions__parent_id',
       'permissions__menu_id',
       'permissions__menu__title',
       'permissions__menu__icon',
       'permissions__menu__weight',  # 帶單排序用的
  ).distinct()
   print(permission_query)
   # 存放權限信息
   permission_list = []
   # 存放菜單信息
   menu_dict = {}
   for item in permission_query:
       permission_list.append({'url': item['permissions__url'],
                               'id': item['permissions__id'],
                               'parent_id': item['permissions__parent_id'], })
       menu_id = item.get('permissions__menu_id')
       if not menu_id:
           continue
       if menu_id not in menu_dict:
           menu_dict[menu_id] = {
               'title': item['permissions__menu__title'],
               'icon': item['permissions__menu__icon'],
               'weight': item['permissions__menu__weight'],
               'children': [
                  {
                       'title': item['permissions__title'],
                       'url': item['permissions__url'],
                       'id': item['permissions__id'],
                       'parent_id': item['permissions__parent_id'],
                  }
              ]
          }
       else:
           menu_dict[menu_id]['children'].append(
              {
                   'title': item['permissions__title'],
                   'url': item['permissions__url'],
                   'id': item['permissions__id'],
                   'parent_id': item['permissions__parent_id'],
              })

   # 2. 將權限信息寫入到session
   request.session[settings.PERMISSION_SESSION_KEY] = permission_list
   # 將菜單的信息寫入到session中
   request.session[settings.MENU_SESSION_KEY] = menu_dict

 

中間件

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


class PermissionMiddleware(MiddlewareMixin):
   def process_request(self, request):
       # 對權限進行校驗
       # 1. 當前訪問的URL
       current_url = request.path_info

       # 白名單的判斷
       for i in settings.WHITE_URL_LIST:
           if re.match(i, current_url):
               return

       # 2. 獲取當前用戶的全部權限信息
       permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
     
  # 3. 權限的校驗
       for item in permission_list:
           url = item['url']
           if re.match("^{}$".format(url), current_url):
               parent_id = item['parent_id']
               id = item['id']
               if parent_id:
                   # 表示當前權限是子權限,讓父權限是展開
                   request.current_menu_id = parent_id
               else:
                   # 表示當前權限是父權限,要展開的二級菜單
                   request.current_menu_id = id
               return
       else:
           return HttpResponse('沒有權限')

templatetags的rbac.py的inclusion_tag

import re
from collections import OrderedDict
from django import template
from django.conf import settings

register = template.Library()

@register.inclusion_tag('rbac/menu.html')
def menu(request):
   menu_list = request.session.get(settings.MENU_SESSION_KEY)
   order_dict = OrderedDict()  # 建立一個有序的字典,爲輸出的菜單屬性作準備

   for key in sorted(menu_list, key=lambda x: menu_list[x]['weight'], reverse=True):
       order_dict[key] = menu_list[key]
       item = order_dict[key]
       item['class'] = 'hide'  # 一級菜單加類屬性
       for i in item['children']:
           # 至關於事件的委託,若是是菜單就展現菜單,若是不是菜單,就委託給菜單展現頁面
           if i['id'] == request.current_menu_id:
               i['class'] = 'active'  # 二級菜單加類屬性
               item['class'] = ''  # 若是二級菜單是展開的將隱藏屬性去掉
   return {"menu_list": order_dict}

menu.html

<div class="multi-menu">
   <!-- 循環展現一級菜單 -->
  {% for item in menu_list.values %}
       <div class="item">
           <div class="title"><i class="fa {{ item.icon }}"></i> {{ item.title }}</div>
           <div class="body {{ item.class }}">
               <!-- 循環展現二級菜單,設置該按鈕爲被選中 -->
              {% for child in item.children %}
                   <a href="{{ child.url }}" class="{{ child.class }}">{{ child.title }}</a>
              {% endfor %}
           </div>
       </div>
  {% endfor %}
</div>

路徑導航和權限粒度控制到按鈕級別

 

表結構

models.py

from django.db import models


class Menu(models.Model):
   """
  一級菜單
  """
   title = models.CharField(max_length=32, unique=True)  # 一級菜單的名字
   icon = models.CharField(max_length=32, verbose_name='圖標', null=True, blank=True)
   # 用來記錄菜單的展示前後順序
   weight = models.IntegerField(default=1)

   class Meta:
       verbose_name_plural = '菜單表'
       verbose_name = '菜單表'

   def __str__(self):
       return self.title


class Permission(models.Model):
   """
  權限表
  有關聯Menu的二級菜單
  沒有關聯Menu的不是二級菜單,是不能夠作菜單的權限
  """
   title = models.CharField(max_length=32, verbose_name='標題')
   url = models.CharField(max_length=32, verbose_name='權限')
   menu = models.ForeignKey('Menu', null=True, blank=True)
   # 該權限關聯的其餘權限是否也是在當前url上展現
   parent = models.ForeignKey(to='Permission', null=True, blank=True)
# 用來記錄次url對應的名字 eg: web:customer
   name = models.CharField(max_length=32, null=True, blank=True, unique=True)

   class Meta:
       verbose_name_plural = '權限表'
       verbose_name = '權限表'

   def __str__(self):
       return self.title


class Role(models.Model):
   name = models.CharField(max_length=32, verbose_name='角色名稱')
   permissions = models.ManyToManyField(to='Permission', verbose_name='角色所擁有的權限', blank=True)

   def __str__(self):
       return self.name


class User(models.Model):
   """
  用戶表
  """
   name = models.CharField(max_length=32, verbose_name='用戶名')
   password = models.CharField(max_length=32, verbose_name='密碼')
   roles = models.ManyToManyField(to='Role', verbose_name='用戶所擁有的角色', blank=True)

   def __str__(self):
       return self.name

登陸:

from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from django.conf import settings
import copy
from rbac.server.init_permission import init_permission


def login(request):
   if request.method == 'POST':
       username = request.POST.get('username')
       pwd = request.POST.get('pwd')

       user = models.User.objects.filter(name=username, password=pwd).first()

       if not user:
           err_msg = '用戶名或密碼錯誤'
           return render(request, 'login.html', {'err_msg': err_msg})

       # 登陸成功
       # 將權限信息寫入到session
       init_permission(request, user)
       return redirect(reverse('web:customer'))

   return render(request, 'login.html')

登陸成功後將權限信息寫入到session中init_permission.py文件

from django.conf import settings
def init_permission(request, user):
   # 1. 查當前登陸用戶擁有的權限
   permission_query = user.roles.filter(permissions__url__isnull=False).values(
       'permissions__url',  # 當前用戶的權限信息
       'permissions__title',  # 當前用戶的權限信息的標題
       'permissions__id',  # 當前用戶的id
       'permissions__name',  # 當前用戶權限對應的名字
       'permissions__parent_id',  # 權限的外鍵
       'permissions__parent__name',  # 該權限關聯的權限的名字
       'permissions__menu_id',
       'permissions__menu__title',
       'permissions__menu__icon',
       'permissions__menu__weight',  # 帶單排序用的
  ).distinct()
   print(permission_query)
   # 存放權限信息
   permission_dict = {}
   # 存放菜單信息
   menu_dict = {}
   for item in permission_query:  # 遍歷該用戶全部的權限
       # 存放權限信息,以字典的形式,鍵爲權限的名字
       # 將每個權限的信息已字典的信息存起來,這些信息包括
       # 權限、權限id、該權限的父權限的id、name、權限的標題
       permission_dict[item['permissions__name']] = ({
           'url': item['permissions__url'],
           'id': item['permissions__id'],
           'parent_id': item['permissions__parent_id'],
           'parent_name': item['permissions__parent__name'],
           'title': item['permissions__title'],
      })
       # 獲取每個權限的菜單id
       menu_id = item.get('permissions__menu_id')
       # 若是沒有menu_id則沒有菜單
       if not menu_id:
           continue
       # 若是有menu_id則有菜單
       if menu_id not in menu_dict:
           # 若是這一個權限有菜單,那麼將這個權限的菜單信息存下來
           # 菜單的標題、菜單的圖標、菜單的權重、還有就是子菜單信息
           menu_dict[menu_id] = {
               'title': item['permissions__menu__title'],
               'icon': item['permissions__menu__icon'],
               'weight': item['permissions__menu__weight'],
               # 子菜單存,標題、訪問權限、id、和他的父權限
               'children': [
                  {
                       'title': item['permissions__title'],
                       'url': item['permissions__url'],
                       'id': item['permissions__id'],
                       'parent_id': item['permissions__parent_id'],
                  }
              ]
          }
       else:
           # 若是鍵相同只須要將子菜單存起來就行
           menu_dict[menu_id]['children'].append(
              {
                   'title': item['permissions__title'],
                   'url': item['permissions__url'],
                   'id': item['permissions__id'],
                   'parent_id': item['permissions__parent_id'],
              })

   # 2. 將權限信息寫入到session
   request.session[settings.PERMISSION_SESSION_KEY] = permission_dict
   # 將菜單的信息寫入到session中
   request.session[settings.MENU_SESSION_KEY] = menu_dict

 

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


class PermissionMiddleware(MiddlewareMixin):
   def process_request(self, request):
       # 對權限進行校驗
       # 1. 當前訪問的URL
       current_url = request.path_info

       # 白名單的判斷
       for i in settings.WHITE_URL_LIST:
           if re.match(i, current_url):
               return

       # 2. 獲取當前用戶的全部權限信息
       permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY)

       # 路勁導航
       request.breadcrumd_list = [
          {"title": '首頁', 'url': '#'},
      ]

       # 3. 權限的校驗
       print(current_url)

       # 從session中拿出當前用戶的權限信息,遍歷每一個權限,與當前訪問的權限作比較
       # 在權限列表中的就能夠訪問,沒有權限的就沒有權限
       for item in permission_dict.values():
           url = item['url']  # 當前這個權限的url
           # 若是這個權限url與當前訪問的url匹配上了
           if re.match("^{}$".format(url), current_url):
               # 拿到這個權限的父權限的id、自己的id、父權限的名字
               parent_id = item['parent_id']
               id = item['id']
               parent_name = item['parent_name']
               # 若是存在父權限
               if parent_id:
                   # 表示當前權限是子權限,讓父權限是展開
                   request.current_menu_id = parent_id
                   # 添加麪包屑導航
                   # 若是當前權限是自權限,就應該先將父權限的內容加進來
                   request.breadcrumd_list.extend([
                      {"title": permission_dict[parent_name]['title'],
                        'url': permission_dict[parent_name]['url']},
                      {"title": item['title'], 'url': item['url']},
                  ])
               else:
                   # 表示當前權限是父權限,要展開的二級菜單
                   request.current_menu_id = id
                   # 添加麪包屑導航
                   # 若是當前權限是父權限直接將當前導航的內容存起來就行
                   request.breadcrumd_list.append({"title": item['title'], 'url': item['url']})
               return
       else:
           return HttpResponse('沒有權限')

templatetags中的rbac.py

import re
from collections import OrderedDict
from django import template
from django.conf import settings

register = template.Library()


# 用來展現菜單
@register.inclusion_tag('rbac/menu.html')
def menu(request):
   menu_list = request.session.get(settings.MENU_SESSION_KEY)
   order_dict = OrderedDict()

   for key in sorted(menu_list, key=lambda x: menu_list[x]['weight'], reverse=True):
       order_dict[key] = menu_list[key]
       item = order_dict[key]
       item['class'] = 'hide'

       for i in item['children']:

           if i['id'] == request.current_menu_id:
               i['class'] = 'active'
               item['class'] = ''
   return {"menu_list": order_dict}


# 用來控制路徑導航
@register.inclusion_tag('rbac/ssssss.html')
def breadcrumb(request):
   return {"breadcrumd_list": request.breadcrumd_list}

# 用來控制 權限力度控制到按鈕級別
@register.filter
def has_permission(request, permission):
   if permission in request.session.get(settings.PERMISSION_SESSION_KEY):
       return True

menu.html

<div class="multi-menu">
  {% for item in menu_list.values %}
       <div class="item">
           <div class="title"><i class="fa {{ item.icon }}"></i> {{ item.title }}</div>
           <div class="body {{ item.class }}">
              {% for child in item.children %}
                   <a href="{{ child.url }}" class="{{ child.class }}">{{ child.title }}</a>
              {% endfor %}
           </div>
       </div>
  {% endfor %}
</div>

ssssss.html

<ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">

   <!-- 這個判斷就是爲了讓最後一個導航不能有連接 -->
  {% for li in breadcrumd_list %}
      {% if forloop.last %}
       <li>{{ li.title }}</li>
      {% else %}
       <li><a href="{{ li.url }}">{{ li.title }}</a></li>
      {% endif %}
  {% endfor %}


</ol>

layout.html

<!--用來生成菜單與inclution_tag配合-->
{% load rbac %}
{% menu request %}

<!--用來生成導航欄與inclution_tag配合-->
{% breadcrumb request %}

customer_list.html用來展現

經過權限的名字判斷來按鈕的展現(與路由分發的include的namespace和方向解析的name配合使用)

{% extends 'layout.html' %}

{% block content %}
  {% load rbac %}

   <div class="luffy-container">
       <div class="btn-group" style="margin: 5px 0">
           <!--判斷當前權限是否擁有-->
          {% if request|has_permission:'web:customer_add' %}
               <a class="btn btn-default" href="{% url 'web:customer_add' %}">
                   <i class="fa fa-plus-square" aria-hidden="true"></i> 添加客戶
               </a>
          {% endif %}

       </div>
       <table class="table table-bordered table-hover">
           <thead>
           <tr>
               <th>ID</th>
               <th>客戶姓名</th>
               <th>年齡</th>
               <th>郵箱</th>
               <th>公司</th>
              {% if request|has_permission:'web:customer_edit' or request|has_permission:'web:customer_del' %}
                   <th>選項</th>
              {% endif %}
           </tr>
           </thead>
           <tbody>
          {% for row in data_list %}
               <tr>
                   <td>{{ row.id }}</td>
                   <td>{{ row.name }}</td>
                   <td>{{ row.age }}</td>
                   <td>{{ row.email }}</td>
                   <td>{{ row.company }}</td>
                  {% if request|has_permission:'web:customer_edit' or request|has_permission:'web:customer_del' %}
                   <td>
                  {% if request|has_permission:'web:customer_edit' %}
                       <a style="color: #333333;" href="{% url 'web:customer_edit' row.id %}">
                           <i class="fa fa-edit" aria-hidden="true"></i></a>
                      {% endif %}
                  {% if request|has_permission:'web:customer_del' %}
                       <a style="color: #d9534f;" href="{% url 'web:customer_del' row.id %}"><i class="fa fa-trash-o"></i></a>
                  {% endif %}
                   </td>
                  {% endif %}
               </tr>
          {% endfor %}
           </tbody>
       </table>
   </div>
{% endblock %}
相關文章
相關標籤/搜索