Django-權限管理與路徑導航


1、url權限管理

設計表

1、設計表
    系統一共有多少個路徑;
    有哪些用戶使用;
    用戶在公司的角色;
    對角色進行權限分配(什么樣的角色可以訪問什么樣的路徑);
2、往表中添加數據,分配角色權限
3、登錄成功,設置session,並將該用戶可以訪問的url從表中取出保存在session中,
4、設置中間件rocess_request,

設置白名單放行登錄和admin:

以admin開頭的所有路徑都放行

# 設置白名單放行
for i in ["/login/","/register/","/logout/","/code/","/admin/.*"]:
    # 請求的路徑能夠和i匹配
    # 匹配不到返回None
    print(request.path)
    ret = re.search(i,request.path)
    if ret:
       print(ret)
       return None

登錄認證:

# 登錄認證,判斷當前用戶是否攜帶session來訪問
if not request.user.username:
    return redirect("login")

權限認證:

session中的路徑是用戶可以訪問的路徑,

request.path是當前訪問路徑,與session中的路徑進行匹配,search成功return None

中間件繼續往下執行,search不到ret為None,如果當前訪問的路徑都不在這個列表中證明該用戶沒有訪問這個路徑的權限,返回對應的錯誤(權限不夠,無法訪問)

# 權限認證
for item in request.session["permisson_list"]:
     res = "^%s$"%item
     ret = re.search(res,request.path)
     if ret:
        return None
return HttpResponse("您的權限不夠不能訪問該頁面!!!")

2、左側菜單欄權限分配

動態顯示左側一級菜單欄

權限表模型

# 權限表
class Permission(models.Model):
    title = models.CharField(max_length=32)
    url = models.CharField(max_length=128)
    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

admin顯示:

展示的時候直接可以編輯:

from django.contrib import admin

# Register your models here.
from app01 import models
# 定義顯示字段
class PermissonAdmin(admin.ModelAdmin):
    list_display = ['pk','title','url','is_menu','icon']
    # 展示時直接就可以編輯的字段
    list_editable = ['is_menu','icon','url']
    # 排序
    ordering = ['pk',]

class RoleAdmin(admin.ModelAdmin):
    list_display = ['pk','title']
    ordering = ['pk',]

# 注冊表
admin.site.register(models.Customer)
admin.site.register(models.Campuses)
admin.site.register(models.ClassList)
admin.site.register(models.UserInfo)
admin.site.register(models.ConsultRecord)
admin.site.register(models.Role,RoleAdmin)
admin.site.register(models.Permission,PermissonAdmin)
admin文件中配置

顯示表的名字:

# 權限表
class Permission(models.Model):
    title = models.CharField(max_length=32)
    url = models.CharField(max_length=128)
    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
model中的verbose_name_plural

具體流程:

1、獲取登錄用戶擁有的所有權限,用戶菜單欄數據(用戶權限的url中有哪些是菜單欄);
2、將菜單欄的數據注入到session中;
3、不管是公有客戶還是私有客戶菜單欄都是通過繼承的模板,所以我們只需要在模板中動態生成菜單欄即可,哪些用戶有哪些菜單欄;
4、為了加強代碼的復用性,我們使用自定義標簽來生成左側菜單欄,,並給標簽添加選中樣式;
5、自定義標簽把我們事先保存在session中的菜單欄數據取出來渲染到指定頁面,這里我們用的inclusion_tag("result.html");
6、在后端給選中的標簽添加樣式,用request.path與我們取出的菜單欄數據的url進行匹配,匹配成功表示這個url就是我們點擊的標簽,對應的給它添加"class":"active";

用戶菜單欄數據注入:

# 判斷用戶輸入的用戶名和密碼與數據庫中的是否匹配
user_obj = auth.authenticate(username=username, password=password)
if user_obj and str_code.upper()==code.upper():
    # 登錄成功設置session
    auth.login(request, user_obj)
    # 獲取該用戶的所有權限並保存在session中,並去重
    permissions = models.Permission.objects.filter(role__userinfo__username=user_obj.username).distinct()
    # 將該用戶可以訪問的所有url和菜單欄數據都添加到session中
    permisson_list = []
    permisson_is_menu_list = []
    for item in permissions:
        # 用戶權限
        permisson_list.append(item.url)
     # 如果路徑是否是菜單欄
if item.is_menu: # 用戶菜單欄數據(用戶權限的url中有哪些是菜單欄) permisson_is_menu_list.append({ "title": item.title, "url": item.url, "icon":item.icon, }) request.session["permisson_list"] = permisson_list request.session["permisson_is_menu_list"] = permisson_is_menu_list print("permisson_list-->",permisson_list) print("permisson_is_menu_list-->",permisson_is_menu_list) # permisson_list--> ['/home/', '/customers/'] # permisson_is_menu_list--> [{'title': '首頁', 'url': '/home/', 'icon': 'fa fa-link'}, {'title': '公共客戶信息', 'url': '/customers/', 'icon': 'fa fa-link'}]

自定義標簽:

from django import template
register = template.Library()
import re
@register.inclusion_tag("result.html")
def create_label(request):
    menu_list = request.session.get("permisson_is_menu_list")
    # 給點擊的標簽添加選中樣式
    for item in menu_list:
        # 請求路徑與當前用戶的菜單欄數據中的url匹配成功說明這個路徑就是當前用戶點擊的a標簽發起的請求
        if re.match("^{}$".format(item["url"]),request.path):
            # match 匹配開頭的,匹配失敗返回None,匹配成功可通過group(0)返回匹配成功的字符串
            # setch  掃描整個字符串,匹配不到返回None
            item["class"]="active"
            break
    # 將菜單欄的數據交給result頁面去渲染,選然后將結果當作組件返回給調用create_label的頁面
    return {"menu_list":menu_list}

inclusion_tag指定的result頁面:

{% for foo in menu_list %}
     <li class="{{ foo.class }}" ><a href="{{ foo.url }}" ><i class="{{ foo.icon }}"></i>
         <span>{{ foo.title }}</span></a></li>
{% endfor %}

模板中引入自定義標簽:

<ul class="sidebar-menu" data-widget="tree">
    <li class="header">HEADER</li>
    <!-- Optionally, you can add icons to the links -->

        {% load mytag %}            <!--引用自定義標簽-->
        {% create_label request %}  <!--調用自定義標簽,並將request傳過去-->

</ul>

3、二級菜單

二級菜單和一級菜單不同的是:

1、設計表結構添加數據

一級菜單與權限表一對多關聯,權限表設置自關聯

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

# 權限表
class Permission(models.Model):
    title = models.CharField(max_length=32)
    url = models.CharField(max_length=128)
    menu = models.ForeignKey("Menu",null=True)
    pid = models.ForeignKey("self",null=True)     # 自關聯
    class Meta:
        verbose_name_plural="權限表"
        verbose_name = '權限表'
    def __str__(self):
        return self.title

2、登陸成功后獲取當前登錄用戶的所有權限,將用戶所有可以訪問的url和菜單欄數據保存在session中

# 獲取該用戶的所有權限並保存在session中,並去重
permissions = models.Permission.objects.filter(role__userinfo__username=user_obj.username).values("title","url","pk","pid","menu__title","menu__icon","menu_id").distinct()
# 將該用戶可以訪問的所有url和菜單欄數據都添加到session中
input_permission(request, permissions)
def input_permission(request, permissions):
    # 將該用戶可以訪問的所有url和菜單欄數據都添加到session中
    permisson_list = []
    permisson_is_menu_dict = {}
    for item in permissions:
        # 用戶權限
        permisson_list.append({
            "pk":item["pk"],
            "url":item["url"],
            "pid":item["pid"],
        })
        # 判斷是否是一級菜單
        if item["menu_id"]:
            # menu_id同屬於一個 一級菜單(下一個a標簽的menu_id也是當前的一級標簽)
            if item["menu_id"] in permisson_is_menu_dict:
                permisson_is_menu_dict[item["menu_id"]]["children"].append({
                    "pk":item["pk"],"title": item["title"], "url": item["url"]
                })
            else:
                # 設計菜單欄數據結構(方便前端渲染)
                permisson_is_menu_dict[item["menu_id"]] = {
                    "title": item["menu__title"],
                    "icon": item["menu__icon"],
                    "children": [
                        { "pk":item["pk"],"title": item["title"], "url": item["url"]},
                    ]
                }

    request.session["permisson_list"] = permisson_list
    request.session["permisson_is_menu_dict"] = permisson_is_menu_dict
View Code

中間件取提取權限列表進行驗證,並將當前訪問路徑的pid封裝在request中

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

class PermissonMiddleware(MiddlewareMixin):
    def process_request(self,request):
        # 設置白名單放行
        for i in ["/login/","/register/","/logout/","/code/","/admin/.*"]:
            # 請求的路徑能夠和i匹配
            # 匹配不到返回None
            ret = re.search(i,request.path)
            if ret:
                return None
        # 登錄認證,判斷當前用戶是否攜帶session來訪問
        if not request.user.username:
            return redirect("login")

        # 權限認證
        for item in request.session["permisson_list"]:
            res = "^%s$"%item["url"]
            ret = re.search(res,request.path)
            if ret:
                # 當前訪問路徑的pid 給request對象動態添加了個屬性
                request.show_id = item["pid"]
                return None
        return HttpResponse("您的權限不夠不能訪問該頁面!!!")
View Code

3、菜單欄的數據我們通過自定義標簽來渲染

根據我們構建的數據格式在前端進行渲染

    '1': {
        'title': '信息管理',
        'icon': 'fa fa-link',
        'children': [{
            'pk': 5,
            'title': '首頁',
            'url': '/home/'
        }, {
            'pk': 6,
            'title': '公共客戶信息',
            'url': '/customers/'
        }, {
            'pk': 7,
            'title': '我的客戶',
            'url': '/my_customers/'
        }]
    },

自定義標簽

from django import template
register = template.Library()

@register.inclusion_tag("result.html")
def create_label(request):
    menu_dict = request.session.get("permisson_is_menu_dict")
    for key, item in menu_dict.items():
        # 默認給二級標簽添加隱藏樣式
        item["class"] = "hide"
        for child in item["children"]:
            # if re.match("^{}$".format(child["url"]), request.path):
            # 當前訪問路徑的pid如果等於它二級菜單的id證明他倆是屬於同一個一級菜單,
            # 如果是屬於同一個一級菜單,我們就讓這個二級菜單一直顯示,並給它添加選中樣式;
            if request.show_id == child["pk"]:
                item["class"] = ""
                child["class"] = "active"
                break
    return  {"menu_dict" :menu_dict }

我們通過權限表自關聯的方式來區分他們的從屬關系;

bug:通過正則匹配無法確定從屬關系

 

那么我們為什么要區分他們的從屬關系呢:
        默認二級標簽是隱藏狀態的,我們為了在點擊我的客戶或當前一級標簽的其他二級標簽時都是展開的狀態,我們通過正則當前請求的路徑request.path與二級菜單進行匹配,匹配成功證明當前用戶請求的url在二級菜單中,我們設置它父級標簽的class屬性值為空,讓其展開;
    
        當我們點擊我的客戶中的其他功能時,因為這些功能的路徑都不在二級菜單中,所以無法通過正則的方式進行匹配,也就是說我們在點擊這些功能時左側的菜單欄會隱藏起來,為了避免這種bug出現我們擴展了權限表添加了pid字段,並通過自關聯的方式解決了bug

當前請求路徑的pid與菜單欄id進行判斷:

result.html生成二級標簽

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

4、按鈕權限分配

用戶有沒有這個按鈕就看看他有沒有這個按鈕的權限

通過過濾器判斷請求的url值否在權限列表里

{% load mytag %}
{% if "/add_customers/"|haspermission:request %}
      <a href="{% url "add_customers" %}" class="btn btn-primary pull-right">添加</a>
{% endif %}
@register.filter
# 分配按鈕權限
def haspermission(base_url,request):
    for item in request.session["permisson_list"]:
        ret = "^%s$"%item["url"]
        res = re.search(ret,base_url)
        if res:
            return True
    return False

通過過濾器判斷用戶權限的url中有沒有該按鈕的url,有就說明該用戶有這個按鈕的權限,讓其顯示;反之隱藏

{% if "/edit_customers/1/"|haspermission:request or "/delete_customers/1/"|haspermission:request %}
    <td>
        {% if "/edit_customers/1/"|haspermission:request %}
             <a href="{% url "edit_customers" customers.pk %}"
           class="btn btn-primary btn-sm">編輯</a>
        {% endif %}
        {% if "/delete_customers/1/"|haspermission:request %}
            <a href="{% url "delete_customers" customers.pk %}"
           class="btn btn-danger btn-sm">刪除</a>
        {% endif %}

    </td>
{% endif %}

5、路徑導航

頁面效果:

點擊我的客戶頁面中的其他功能:基於我的客戶后面添加

1、首頁是默認的,無論點擊哪個菜單路徑導航第一個都是首頁;
2、點擊左側菜單路徑導航顯示對應的名稱,
3、點擊左側菜單欄頁面中對應的功能時路徑導航基於菜單欄的路徑繼續往后添加;

實現思路:

我們只需要判斷請求的路徑是不是二級菜單:
如果是二級菜單取出對應的路徑title和url在前端直接渲染即可;
如果不是二級菜單那么就是二級菜單的功能請求,我們取出功能請求的title和url並需要根據已知條件查出它父級菜單的title和url,在前端渲染時放在自己的前面;

代碼:

每次請求都會走一遍中間件,權限認證通過之后判斷是二級菜單還是二級菜單中的功能請求;

# -*- coding: utf-8 -*-
# @Time    : 2019/6/14 20:22
import re
from django.shortcuts import HttpResponse,redirect,render
from django.utils.deprecation import MiddlewareMixin
from app01 import models
class PermissonMiddleware(MiddlewareMixin):
    def process_request(self,request):
        # 設置白名單放行
        for i in ["/login/","/register/","/logout/","/code/","/admin/.*"]:
            # 請求的路徑能夠和i匹配
            # 匹配不到返回None
            ret = re.search(i,request.path)
            if ret:
                return None
        # 登錄認證,判斷當前用戶是否攜帶session來訪問
        if not request.user.username:
            return redirect("login")
        
        # 路徑導航
        request.path_navigation = [{
            "title":"首頁",
            "url":"/home/",
        }]
        # 權限認證
        for item in request.session["permisson_list"]:

            res = "^%s$"%item["url"]
            ret = re.search(res,request.path)
            if ret:
                # 當前訪問路徑的pid 給request對象動態添加了個屬性
                request.show_id = item["pid"]
                
                # pid=pk 就是菜單欄請求的url
                if item["pk"]==item["pid"]:
                    request.path_navigation.append({
                        "title":item["title"],
                        "url":request.path,
                    })
                else:
                    # 通過pid找到二級菜單的對象
                    obj = models.Permission.objects.filter(pk=item["pid"]).first()
                    li = [
                        # 父級權限
                        {
                            "title":obj.title,
                            "url":obj.url,
                        },
                        # 子權限
                        {
                            "title": item["title"],
                            "url": request.path,
                        }
                    ]
                    # 迭代添加到path_navigation中
                    request.path_navigation.extend(li)
                return None
        return HttpResponse("您的權限不夠不能訪問該頁面!!!")
在中間件中,權限認證通過之后

 用bootstrap給最后一層添加灰色樣式

<ol class="breadcrumb">
    {% for foo in  request.path_navigation %}
        {% if forloop.last %}
            <li class="active">{{ foo.title }}</li>
        {% else %}
            <li><a href="{{ foo.url }}">{{ foo.title }}</a></li>
        {% endif %}

    {% endfor %}

</ol>

 


免責聲明!

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



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