rbac——界面、權限

1、模板繼承

知識點:css

  users.html / roles.html 繼承自 base.htmlhtml

  頁面滾動時,固定python

.menu {
    background-color: bisque;
    position: fixed;
    top: 60px;
    bottom: 0px;
    left: 0px;
    width: 200px;
}
.content {
    position: fixed;
    top: 60px;
    bottom: 0;
    right: 0;
    left: 200px;
    overflow: auto;  /* 滾動條 */
}

  base.html:ajax

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 引入 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <style>
        .header {
            width: 100%;
            height: 60px;
            background-color: #336699;
        }
        .menu {
            background-color: bisque;
            position: fixed;
            top: 60px;
            bottom: 0px;
            left: 0px;
            width: 200px;
        }
        .content {
            position: fixed;
            top: 60px;
            bottom: 0;
            right: 0;
            left: 200px;
            overflow: auto;  /* 滾動條 */
        }
    </style>
</head>
<body>

<div class="header">
    {{ user.name }}
</div>
<div class="contain">
    <div class="menu">
        menu
    </div>
    <div class="content">
        {% block con%}

        {% endblock %}
    </div>
</div>

</body>
</html>

  users.html:正則表達式

{% extends 'base.html' %}

{% block con %}
<h4>用戶列表</h4>
    {% for user in user_list %}
    <p>{{ user }}</p>
    {% endfor %}
    
{% endblock con%}

  roles.html:數據庫

{% extends 'base.html' %}

{% block con %}
<h4>角色列表</h4>
<ul>
    {% for role in role_list %}
        <p>{{ role }}</p>
    {% endfor %}
</ul>
{% endblock %}

2、在users.html中添加table

{% extends 'base.html' %}

{% block con %}
    <h4>用戶列表</h4>
    <a href="/users/add" class="btn btn-primary">添加用戶</a>
    <table class="table table-bordered table-striped">
        <thead>
            <tr>
                <th>序號</th>
                <th>姓名</th>
                <th>角色</th>
                <th>操做</th>
            </tr>
        </thead>
        <tbody>
            {% for user in user_list %}
                <tr>
                    <td>{{ forloop.counter }}</td>
                    <td>{{ user.name }}</td>
                    <td>
                        {% for role in user.roles.all %}
                            {{ role.title }}
                        {% endfor %}
                        
                    </td>
                    <td>
                        <a href="" class="btn btn-danger">刪除</a>
                        <a href="" class="btn btn-warning">編輯</a>
                    </td>
                </tr>
            {% endfor %}
            
        </tbody>
    </table>
{% endblock %}

  注意:django

(1)有一些用戶有多重角色,須要將這些角色拿到顯示在表格中的方法bootstrap

<td>
    {% for role in user.roles.all %}
        {{ role.title }}
    {% endfor %}
</td>

(2)django模板中的forloop模板變量:在每一個`` {% for %}``循環裏有一個稱爲`` forloop`` 的模板變量。這個變量有一些提示循環進度信息的屬性。數組

  forloop.counter 老是一個表示當前循環的執行次數的整數計數器。 這個計數器是從1開始的,因此在第一次循環時 forloop.counter 將會被設置爲1。瀏覽器

3、根據權限決定是否顯示按鈕

  在頁面每每有一些功能按鈕,若是該用戶沒有權限,就不將這個按鈕開放給當前用戶,這樣處理優於向用戶提示「沒有使用權限」。

一、在模板中權限按鈕控制的簡單形式

{# 根據是否有權限顯示添加用戶按鈕 #}
{% if "/users/add" in  permission_list %}
    <a href="/users/add" class="btn btn-primary">添加用戶</a>
{% endif %}

   處理帶有正則表達式的url:

<td>
    {% if '/users/delete/(d+)' in permission_list %}
        <a href="/users/delete/{{ user.pk }}" class="btn btn-danger">刪除</a>
    {% endif %}
    <a href="" class="btn btn-warning">編輯</a>
</td>

  這種方法是針對表作操做,根據表名去作判斷。

  若是但願if判斷時url裏面不帶有表名字。roles和users合併用一個視圖函數來處理。

二、admin修改顯示,頁面顯示更多內容

rbac/admin.py:

from django.contrib import admin
# Register your models here.
from .models import *

class PerConfig(admin.ModelAdmin):
    list_display = ["title", "url"]

admin.site.register(User)
admin.site.register(Role)
admin.site.register(Permission, PerConfig)

  注意:list_display = [] 。

  顯示效果:

  

三、修改數據結構

   添加一個權限組表。將每張表的增刪改查,劃到一個組裏面!不管多複雜的,最終必定是對數據庫的(增刪改查)。
  修改表結構,從新處理中間件,登陸頁面的目的:全是爲了按鈕的粒度,同一個模板,同一個視圖,
顯示不一樣的數據,權限。

(1)models.py代碼

from django.db import models
# Create your models here.

class User(models.Model):
    name = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    roles = models.ManyToManyField(to="Role")

    def __str__(self):
        return self.name

class Role(models.Model):
    title = models.CharField(max_length=32)
    permissions = models.ManyToManyField(to="Permission")

    def __str__(self):
        return self.title

class Permission(models.Model):
    title = models.CharField(max_length=32)
    url = models.CharField(max_length=32)
    # 操做
    action = models.CharField(max_length=32, default="")  # 默認值爲空
    # 分組
    group = models.ForeignKey("PermissionGroup", default=1, on_delete=True)

    def __str__(self):
        return self.title

class PermissionGroup(models.Model):
    title = models.CharField(max_length=32)

    def __str__(self):
        return self.title

  修改完後,在一次執行數據庫遷移。

(2)再一次修改rbac/admin.py:

from django.contrib import admin
# Register your models here.
from .models import *

class PerConfig(admin.ModelAdmin):
    list_display = ["title", "url", "group", "action"]

admin.site.register(User)
admin.site.register(Role)
admin.site.register(Permission, PerConfig)
admin.site.register(PermissionGroup)

(3)爲權限添加action:

  

  所有修改後:

  

  修改以後,GROUP描述是對哪張表進行操做,ACTION是描述對這個表作什麼操做

(4)修改rbac_permission表的group_id信息,將角色操做類別的group_id修改成2

  

四、重寫initial_session(user, request)函數

# -*- coding:utf-8 -*-
__author__ = 'Qiushi Huang'


def initial_session(user,request):
    """
    查看當前用戶全部的權限
    :param user:
    :param request:
    :return:
    """
    # 方案1:
    # permissions = user.roles.all().values("permissions__url").distinct()
    # print(permissions)  # <QuerySet [{'permissions__url': '/users/'}, {'permissions__url': '/users/add'}]>
    #
    # permission_list = []
    # for item in permissions:
    #     permission_list.append(item["permissions__url"])
    #
    # print(permission_list)
    #
    # request.session["permission_list"] = permission_list


    # 方案2:
    # 角色表跨到權限表查找
    permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action").distinct()
    print("permissions", permissions)  # 有一個權限QuerySet中就有一個字典
    """
    permissions <QuerySet [{'permissions__url': '/users/', 
                            'permissions__group_id': 1, 
                            'permissions__action': 'list'}]>
    """
    # 對上述數據進行處理: 以組爲鍵,以字典爲值
    permission_dict = {}
    for item in permissions:
        gid = item.get("permissions__group_id")

        if not gid in permission_dict:
            permission_dict[gid] = {
                "urls": [item["permissions__url"], ],
                "actions": [item["permissions__action"], ]

            }
        else:
            # 組id已經在字典中
            permission_dict[gid]["urls"].append(item["permissions__url"])
            permission_dict[gid]["actions"].append(item["permissions__action"])

    print(permission_dict)  # {1: {'urls': ['/users/', '/users/add', '/users/delete/(\\d+)', '/users/edit/(\\d+)'],
    #                              'actions': ['list', 'add', 'delete', 'edit']}}

    request.session['permission_dict']=permission_dict

注意:

 (1)在session中註冊權限字典

  前面是在session中註冊權限列表:

request.session['permission_list'] = permission_list

   如今須要在session中註冊的是權限字典:

request.session['permission_dict'] = permission_dict

(2)從角色表到權限表跨表查詢權限路徑、權限組ID、權限action

# 角色表跨到權限表查找
permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action").distinct()
print("permissions", permissions)  # 有一個權限QuerySet中就有一個字典
"""
permissions <QuerySet [{'permissions__url': '/users/', 
                        'permissions__group_id': 1, 
                        'permissions__action': 'list'}]>
"""

(3)對上述數據進行處理:以組爲鍵、以字典爲值

{
    1: {
        "url": ['/users/',],
        "actions": ['list',]
    },
}
若是用戶操做多個權限:
{
    1: {
        'urls': ['/users/', '/users/add/', '/users/delete/(\\d+)/', '/users/edit/(\\d+)/'], 
        'actions': ['list', 'add', 'delete', 'edit']
        }, 
}
若是除了有用戶操做權限還有角色操做權限:
{
    1: {
        'urls': ['/users/', '/users/add/', '/users/delete/(\\d+)/', '/users/edit/(\\d+)/'], 
        'actions': ['list', 'add', 'delete', 'edit']
        }, 
    2: {
        'urls': ['/roles/'], 
        'actions': ['list']
        }
}

五、改寫中間件rbac.py中的VaildPermission類

# -*- coding:utf-8 -*-
__author__ = 'Qiushi Huang'

import re
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse, redirect


class ValidPermission(MiddlewareMixin):

    def process_request(self, request):

        # 當前訪問路徑
        current_path = request.path_info  # 當前路徑的屬性

        ########### 檢查是否屬於白名單 #############
        valid_url_list = ['/login/', '/reg/', '/admin/.*']
        for valid_url in valid_url_list:
            ret = re.match(valid_url, current_path)
            if ret:
                return   # 等同於return none

        ############### 檢驗是否登陸 ##############
        user_id = request.session.get("user_id")

        if not user_id:
            return redirect("/login/")

        ################ 校驗權限1 #################
        # permission_list = request.session.get("permission_list")
        #
        # flag = False
        # for permission in permission_list:
        #     permission = "^%s$" % permission
        #     ret = re.match(permission, current_path)  # 第一個參數是匹配規則,第二個參數是匹配項
        #     if ret:
        #         flag = True
        #         break
        # if not flag:
        #     # 若是沒有訪問權限
        #     return HttpResponse("沒有訪問權限!")

        ################ 校驗權限2 #################
        permission_dict = request.session.get('permission_dict')

        for item in permission_dict.values():  # 循環只取字典的值
            urls = item["urls"]
            for reg in urls:
                reg = "^%s$" % reg
                ret = re.match(reg, current_path)
                if ret:
                    print("actions", item["actions"])
                    request.actions = item["actions"]
                    return None

        return HttpResponse("沒有訪問權限!")

 注意:

(1)中間件的request對象,給對象添加屬性actions,將來視圖中就能夠經過request.actions拿到當前用戶對這個表的全部操做權限。

request.actions = item["actions"]

(2)數據類型從數組變爲了字典,數據處理方式略有不一樣。

六、改寫users視圖,視圖添加Per類

class Per(object):
    def __init__(self, actions):
        self.actions = actions

    def add(self):
        return "add" in self.actions

    def delete(self):
        return "delete" in self.actions

    def edit(self):
        return "edit" in self.actions

    def list(self):
        return "list" in self.actions


def users(request):
    user_list = User.objects.all()
    permission_list = request.session.get("permission_list")
    print(permission_list)  # ['/users/', '/users/add', '/roles/', '/users/delete/(\\d+)', '/users/edit/(\\d+)']

    # 查詢當前登陸人的名字
    id = request.session.get("user_id")
    user = User.objects.filter(id=id).first()

    per = Per(request.actions)

    return render(request, "users.html", locals())

 注意:

  經過Per(request.actions)獲得per對象,傳到模板中能夠經過per.edit\per.list等方式來判斷是否擁有權限。增長閱讀性。

 七、users.html改寫

{% extends 'base.html' %}

{% block con %}
    <h4>用戶列表</h4>
    {# 根據是否有權限顯示添加用戶按鈕 #}
    {% if per.add %}
        <a href="/users/add" class="btn btn-primary">添加用戶</a>
    {% endif %}
    <table class="table table-bordered table-striped">
        <thead>
            <tr>
                <th>序號</th>
                <th>姓名</th>
                <th>角色</th>
                <th>操做</th>
            </tr>
        </thead>
        <tbody>
            {% for user in user_list %}
                <tr>
                    <td>{{ forloop.counter }}</td>
                    <td>{{ user.name }}</td>
                    <td>
                        {% for role in user.roles.all %}
                            {{ role.title }}
                        {% endfor %}
                    </td>
                    <td>
                        {% if per.delete %}
                            <a href="/users/delete/{{ user.pk }}" class="btn btn-danger">刪除</a>
                        {% endif %}
                        {% if per.edit %}
                            <a href="" class="btn btn-warning">編輯</a>
                        {% endif %}
                    </td>
                </tr>
            {% endfor %}
        </tbody>
    </table>
{% endblock %}

   顯示效果:

  

八、總結

一、權限粒度控制

簡單控制:
{% if "users/add" in permissions_list%}

二、更改數據庫結構

class Permission(models.Model):
    title = models.CharField(max_length=32)
    url = models.CharField(max_length=32)
    # 操做
    action = models.CharField(max_length=32, default="")  # 默認值爲空
    # 分組
    group = models.ForeignKey("PermissionGroup", default=1, on_delete=True)
def __str__(self): return self.title class PermissionGroup(models.Model): title = models.CharField(max_length=32)
def __str__(self): return self.title

三、登陸驗證

permissions = user.roles.all().values("permissions__url","permissions__group_id","permissions__action").distinct()

四、構建permission_dict

五、中間件校驗權限

permission_dict = request.session.get('permission_dict')

for item in permission_dict.values():  # 循環只取字典的值
    urls = item["urls"]
    for reg in urls:
        reg = "^%s$" % reg
        ret = re.match(reg, current_path)
        if ret:
            print("actions", item["actions"])
            request.actions = item["actions"]
            return None

return HttpResponse("沒有訪問權限!")

4、權限菜單顯示

一、用戶登陸在initial_session中註冊菜單權限並註冊到session中

def initial_session(user, request):
    """
    查看當前用戶全部的權限
    :param user:
    :param request:
    :return:
    """
    # 方案1:
    # permissions = user.roles.all().values("permissions__url").distinct()
    # print(permissions)  # <QuerySet [{'permissions__url': '/users/'}, {'permissions__url': '/users/add'}]>
    #
    # permission_list = []
    # for item in permissions:
    #     permission_list.append(item["permissions__url"])
    #
    # print(permission_list)
    #
    # request.session["permission_list"] = permission_list


    # 方案2:
    # 角色表跨到權限表查找
    permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action").distinct()
    print("permissions", permissions)  # 有一個權限QuerySet中就有一個字典
    """
    permissions <QuerySet [{'permissions__url': '/users/', 
                            'permissions__group_id': 1, 
                            'permissions__action': 'list'}]>
    """
    # 對上述數據進行處理: 以組爲鍵,以字典爲值
    permission_dict = {}
    for item in permissions:
        gid = item.get("permissions__group_id")

        if not gid in permission_dict:
            permission_dict[gid] = {
                "urls": [item["permissions__url"], ],
                "actions": [item["permissions__action"], ]

            }
        else:
            # 組id已經在字典中
            permission_dict[gid]["urls"].append(item["permissions__url"])
            permission_dict[gid]["actions"].append(item["permissions__action"])

    print(permission_dict)  # {1: {'urls': ['/users/', '/users/add', '/users/delete/(\\d+)', '/users/edit/(\\d+)'],
    #                              'actions': ['list', 'add', 'delete', 'edit']}}

    request.session['permission_dict'] = permission_dict

    # 註冊菜單權限
    permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action",
                                          "permissions__group__title").distinct()
    print("permissions", permissions)

    menu_permission_list = []   # 菜單欄中權限列表:空列表
    for item in permissions:
        # item是裏面的字典
        if item["permissions__action"] == "list":
            # 列表裏面套一個個的元組,每一個元組包含url和權限組title
            menu_permission_list.append((item["permissions__url"], item["permissions__group__title"]))

    print("menu_permission_list", menu_permission_list)

    # 註冊到session中
    request.session["menu_permission_list"] = menu_permission_list

  注意:

(1)註冊菜單權限:

# 註冊菜單權限
permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action",
                                   "permissions__group__title").distinct()

   其中permissions__group__title是跨三張表查詢。

(2)在菜單權限列表中添加元組,每一個元組包含url和權限組title信息。

menu_permission_list = []   # 菜單欄中權限列表:空列表
for item in permissions:
    # item是裏面的字典
    if item["permissions__action"] == "list":
        # 列表裏面套一個個的元組,每一個元組包含url和權限組title
        menu_permission_list.append((item["permissions__url"], item["permissions__group__title"]))

print("menu_permission_list", menu_permission_list)

 (3)將菜單權限列表註冊到session中:

# 註冊到session中
request.session["menu_permission_list"] = menu_permission_list

二、自定義標籤(inclusion_tag)

  由於模板繼承,只繼承樣式,不繼承數據!因此須要用到自定義標籤(inclusion_tag)

  在rbac項目下建立一個templatetags文夾。這個文件夾的名字必順是templatetags來命名的。而後在此文件夾下自定義一個my_tags.py文件。

from django import template

register = template.Library()

@register.inclusion_tag("menu.html")
def get_menu(request):
    # 獲取當前用戶應該放到菜單欄中的權限
    menu_permission_list = request.session["menu_permission_list"]

    return {"menu_permission_list": menu_permission_list}

  它會將返回數據傳遞給模板文件menu.html.

  建立menu.html模板:

<div>
    {% for item in menu_permission_list %}
        <p class="menu_btn"><a href="{{ item.0 }}">{{ item.1 }}</a></p>
    {% endfor %}
</div>

  修改base.html模板:

<body>

<div class="header">
    <p>{{ user.name }}</p>
</div>
<div class="contain">
    {% load my_tags %}
    <div class="menu">
        {% get_menu request %}
    </div>
    <div class="content">
        {% block con%}

        {% endblock %}
    </div>
</div>

</body>

三、模板遷移及模板渲染規則

  因爲rbac是可插拔組件,所以能夠將屬於權限的模板文件遷移到rbac的app中。

  建立rbac/templates文件夾,將users.html / roles.html / base.html / menu.html剪切到文件夾中。

  django的render去渲染 .html 時,先到項目的 templates 下找,若是找不到再到App下templates 下找,
最後找不到才報錯

(1)若是多個App的templates 下的.html重名怎麼辦?

  django 會根據註冊的順序顯示!
  解決辦法:項目/rbac/templates/rbac/xxx.html
  這時調用:return render(request, 'rbac/users.html', locals())

(2)templates 或者 templatetag 注意多個app下面 的文件名 有可能都會重名!

   辦法:就是 eg:/rbac/templates/rbac/xxx.html 或者不起重名

(3)同名.html文件查找順序?

  若是 base.html 在項目下有,在App下有,先找項目下的,找不到才找App,所以全局能夠覆蓋局部的!!

5、django路徑自動添加

一、django路徑添加現象及原理

知識點:路徑自動添加問題:
  http://127.0.0.1:8010/users
  http://127.0.0.1:8010/users/

  發如今瀏覽器瀏覽時,兩個路徑均可以正常訪問到頁面。這是由於瀏覽器發請求:django 發現以後,發了一個重定向的 url 加了一個 / 因此才能匹配上:path('users/', views.users),

  

二、路徑配置

  若是想讓django不給瀏覽器發重定向。能夠在setttings.py中添加:

APPEND_SLASH = False

  在不添加這個配置時,django默認APPEND_SLASH的值爲True,django會默認的加 / 發重定向。

  ajax中的url和這裏同理。

相關文章
相關標籤/搜索