Django權限控制進階


一、一級菜單的排序

我們用字典存放菜單信息,而字典是無序的,當一級菜單過多時可能會出現亂序情況,因此需要給一級菜單排序

1.給一級菜單表的model中加一個weight權重的字段 ,權重越大越靠前

 weight = models.IntegerField(default=1, verbose_name='權重')

2.應用有序字典存放菜單信息

引用:

from collections import OrderedDict

排序:

 # sorted  按照權重的大小對字典的key進行排序
   for i in sorted(menu_dict, key=lambda x: menu_dict[x]['weight'], reverse=True):
       order_dict[i] = menu_dict[i]

二.非菜單權限的歸屬問題

一部分權限不是菜單權限,不在菜單欄顯示;

如:

  • 信息列表
    • 客戶列表
    • 添加客戶
    • 編輯客戶

在這個菜單中添加客戶與編輯客戶就不屬於菜單權限,但在進入添加客戶或編輯客戶頁面時需要客戶列表展開;這就要給非菜單權限歸類;將所屬二級菜單權限作為其父權限

可以設計為如下結構:

  • 信息列表
    • 客戶列表
      • 添加客戶
      • 編輯客戶

在model的權限表中添加一個自關聯的字段

# 二級菜單的歸屬,創建自關聯字段
    parent = models.ForeignKey('Permission', null=True, blank=True, verbose_name="二級菜單歸屬", on_delete=models.CASCADE)

三、路徑導航(面包屑)

1、在權限中間件中設置一個列表,通過反射將其設為request的屬性

setattr(request, settings.BREADCRUMB, [
            {'url': '/index/', 'title': '首頁'}
        ])

2、權限校驗通過后給添加到路徑導航信息中

3、自定義@register.inclusion_tag('rbac/breadcrumb.html')

4、頁面中顯示

引入rbac {% load rbac %}
顯示路徑導航欄 {% breadcrumb request %}

四、權限控制實現到按鈕級別

控制:當前登錄用戶如果有該權限顯示按鈕,如果沒有該權限不顯示按鈕

1、首先在權限表中創建一個存放url別名的字段

    name = models.CharField(max_length=32, verbose_name='URL別名')

 

2、url中設置url別名,並用admin后台錄入數據庫


3、權限初始化時將URL別名也保存到權限字典中,一起存入session


4、自定義過濾器,判斷前端傳入的name在不在權限字典中,在就返回Turn


5、模板頁面引用自定義的過濾器進行判斷,返回Turn判斷通過,表示該用戶有該權限,就顯示相應的按鈕,否則就不顯示

功能實現代碼:

組件目錄結構:

 

1.首先是表結構models.py:

from django.db import models

# Create your models here.


class Menu(models.Model):
    """菜單表 一級菜單"""
    title = models.CharField(max_length=32)
    icon = models.CharField(max_length=64, null=True, blank=True, verbose_name='圖標')
    weight = models.IntegerField(default=1, verbose_name='權重')

    def __str__(self):
        return self.title


class Permission(models.Model):
    """
    權限表
    可以做二級菜單的權限   menu 關聯 菜單表
    不可以做菜單的權限    menu=null
    """
    url = models.CharField(max_length=32, verbose_name='權限')
    title = models.CharField(max_length=32, verbose_name='標題')
    menu = models.ForeignKey("Menu",null=True, blank=True, verbose_name="所屬菜單",on_delete=models.CASCADE)
    # 二級菜單的歸屬,創建自關聯字段
    parent = models.ForeignKey('Permission', null=True, blank=True, verbose_name="二級菜單歸屬", on_delete=models.CASCADE)
    name = models.CharField(max_length=32, verbose_name='URL別名', default='customer_list')

    class Meta:
        # 這個選項是指定,模型的復數形式是什么,比如:
        # verbose_name_plural = "學校"
        # 如果不指定Django會自動在模型名稱后加一個’s’
        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('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('Role', verbose_name='用戶擁有的角色',blank=True)

    def __str__(self):
        return self.name

2.然后當用戶登錄成功后進行權限信息的初始化

service/permission.py

from django.conf import settings


def init_permisson(request, obj):
    """
        權限信息的初識化
        保存權限和菜單的信息
        :param request:
        :param obj:
        :return:
     """
    # 登陸成功,保存權限的信息(可能存在創建了角色沒有分配權限,有的用戶擁有多個角色權限重復的要去重.distinct())
    ret = obj.roles.all().filter(permissions__url__isnull=False).values('permissions__url',
                                                                        'permissions__title',
                                                                        'permissions__menu__title',
                                                                        'permissions__menu__icon',
                                                                        'permissions__menu_id',
                                                                        'permissions__menu__weight',
                                                                        'permissions__parent_id',
                                                                        'permissions__parent__name',
                                                                        'permissions__id',
                                                                        'permissions__name',
                                                                        ).distinct()

    # 存放權限信息
    permission_dict = {}
    '''
    權限的數據結構
    permission_dict = {1: {
    'url': '/customer/list/',
    'id': 1,
    'pid': None
}, 2: {
    'url': '/customer/add/',
    'id': 2,
    'pid': 1
}, 3: {
    'url': '/customer/edit/(\\d+)/',
    'id': 3,
    'pid': 1
}}'''
    # 存放菜單信息
    menu_dict = {}

    for item in ret:
        # 將所有的權限信息添加到permission_dict
        permission_dict[item['permissions__name']] = ({'url': item['permissions__url'],
                                                       'id': item['permissions__id'],
                                                       'pid': item['permissions__parent_id'],
                                                       'pname': item['permissions__parent__name'],
                                                       'title': item['permissions__title'],
                                                       })

        # 構造菜單的數據結構
        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'],
                     },
                ]
            }
        else:
            menu_dict[menu_id]['children'].append(
                {'title': item['permissions__title'], 'url': item['permissions__url'],
                 'id': item['permissions__id'], })

    # print(permission_dict)
    # print(menu_dict)
    # 保留權限信息到session(因為session可以存到內存中,提高工作效率)
    print(request)
    request.session[settings.PERMISSION_SESSION_KEY] = permission_dict

    # 保存菜單信息
    request.session[settings.PERMISSION_MENU_KEY] = menu_dict

3.在中間件中進行權限信息的校驗

middlewares/rbac.py

import re

from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import HttpResponse, redirect, reverse


class RbacMiddleware(MiddlewareMixin):

    def process_request(self, request):
        # 1.獲取當前訪問的url
        url = request.path_info

        # 白名單 #(拿后面的url與i匹配,沒匹配上Nnoe)
        for i in settings.WHITE_LIST:
            if re.match(i, url):
                return

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

        # 沒有登錄的訪問
        if not permission_dict:
            return redirect(reverse('login'))

        # 需要登錄但是不需要進行權限校驗的列表
        for i in settings.NO_PERMISSION_LIST:
            if re.match(i, url):
                return

        # 路徑導航
        # 反射(設置屬性)setattr(object, name, values)  給對象的屬性賦值,若屬性不存在,先創建再賦值。
        setattr(request, settings.BREADCRUMB, [
            {'url': '/index/', 'title': '首頁'}
        ])

        # for i in permission_dict:
        #     print('i',i, type(i)) # 注意i存入session中后會序列化為數字字符串,與之校驗也要轉化
        ## 1 <class 'str'>2 <class 'str'>3 <class 'str'>4 <class 'str'>5 <class 'str'>

        # 3.權限校驗
        for item in permission_dict.values():
            if re.match(r"^{}$".format(item['url']), url):
                pid = item.get('pid')
                id = item.get('id')
                pname = item.get('pname')
                if pid:
                    # 表示該權限是二級菜單的子權限,有父權限要讓父權限展開
                    # request.current_parent_id = pid
                    setattr(request, settings.CURRENT_MENU, pid)

                    # permission_dict的key存入session后會json序列化為str所以pid也要變為str
                    # print('pid', pid, type(pid))  # pid 1 <class 'int'>

                    p_dict = permission_dict[str(pid)]  # 獲取父權限信息
                    # 路徑導航
                    getattr(request, settings.BREADCRUMB).append({'url': p_dict['url'], 'title': p_dict['title']})
                    getattr(request, settings.BREADCRUMB).append({'url': item['url'], 'title': item['title']})

                else:
                    # 表示當前訪問的權限是父權限, 要讓自己展開
                    # request.current_parent_id = id
                    setattr(request, settings.CURRENT_MENU, id)

                    # 路徑導航
                    getattr(request, settings.BREADCRUMB).append({'url': item['url'], 'title': item['title']})

                return
        # 拒絕訪問
        return HttpResponse("沒有訪問權限 ")

4.自定義過濾器,inclusion_tag,控制頁面數據的顯示

templatetags/rbac.py

from django import template
from django.conf import settings
# 引入有序字典
from collections import OrderedDict
import re

register = template.Library()


# 菜單權限
@register.inclusion_tag('rbac/menu.html')
def menu(request):
    menu_dict = request.session.get(settings.PERMISSION_MENU_KEY)

    # 因為字典是無序的,要使菜單顯示有序:
    # 1.在model的菜單表中設置weight權重字段,
    # 2.引用sorted()按權重排序倒敘,權重越大顯示越靠前
    # 3.將數據放到有序字典中
    # sorted() 函數對所有可迭代的對象進行排序操作。
    # sort 是應用在 list 上的方法,sorted 可以對所有可迭代的對象進行排序操作。

    order_dict = OrderedDict()
    for i in sorted(menu_dict, key=lambda x: menu_dict[x]['weight'], reverse=True):
        # 復制到order_dict中
        order_dict[i] = menu_dict[i]
        # 取一級菜單的信息
        item = order_dict[i]

        # 控制當前權限如果是二級菜單的子權限,菜單展開
        item['class'] = 'hide'
        for i in item['children']:
            if i['id'] == request.current_parent_id:
                # 子權限
                i['class'] = 'active'
                item['class'] = ''
                break

    return {'menu_list': order_dict.values()}


# 路徑導航,面包屑
@register.inclusion_tag('rbac/breadcrumb.html')
def breadcrumb(request):
    breadcrumb_list = getattr(request, settings.BREADCRUMB)
    return {'breadcrumb_list': breadcrumb_list}


# 控制權限到按鈕級別
@register.filter()
def has_permission(request, name):
    # 判斷name是否在權限的字典中
   if name in request.session.get(settings.PERMISSION_SESSION_KEY):
        return True

templates/rbac/menu.html 生成菜單

<div class="multi-menu">
    {% for item in menu_list %}
        <div class="item">
            <div class="title"><i class="fa {{ item.icon }}"></i>&nbsp&nbsp{{ 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>

templates/rbac/breadcrumb.html  生成路徑導航html片段

<ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">
    {% for li in breadcrumb_list %}
        {% if forloop.last %}
              <li class="active">{{ li.title }}</li>
        {% else %}
              <li><a href="{{ li.url }}"> {{ li.title }}</a></li>
        {% endif %}

    {% endfor %}


</ol>

5.模板中應用

應用菜單

{#            <!--引入rbac -->#}
            {% load rbac %}
{#            <!--應用inclusion_tag('rbac/menu.html')-->#}
            {% menu request %}

路徑導航

{% breadcrumb request %}

將權限控制到按鈕級別,應用過濾器

 {% if request|has_permission:'customer_del' or  request|has_permission:'customer_edit' %}
                        <td>
                            {% if request|has_permission:'customer_edit' %}
                                <a style="color: #333333;" href="{% url 'customer_edit' row.pk %}">
                                    <i class="fa fa-edit" aria-hidden="true"></i></a>
                            {% endif %}

                            {% if request|has_permission:'customer_del' %}
                                <a style="color: #d9534f;" href="{% url 'customer_del' row.pk %}"><i
                                        class="fa fa-trash-o"></i></a>
                            {% endif %}
                        </td>
                    {% endif %}

 settings中的權限相關配置:

# session中保留權限key
PERMISSION_SESSION_KEY = 'permissions'
# 保留菜單信息key
PERMISSION_MENU_KEY = 'menus'
# 白名單
WHITE_LIST = [
    r'^/login/$',
    r'^/reg/$',
    r'^/admin/.*',
]
# 需要登錄但不需要校驗的權限列表
NO_PERMISSION_LIST = [
    r'^/index/$',
]

# 路徑導航(面包屑)
BREADCRUMB = 'breadcrumb_list'
# 路徑導航
CURRENT_MENU = 'current_parent_id'

 


免責聲明!

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



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