權限
根據URL進行限制用戶可以訪問的資源
項目與應用的關系
項目可包含多個應用
應用可包含在多個項目中
RBAC:基於權限的管理系統
項目
先創建一個Django項目
Model
from django.db import models class UserInfo(models.Model): name = models.CharField(max_length=32) pwd = models.CharField(max_length=32,default=123) email = models.EmailField() 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): url = models.CharField(max_length=32) title = models.CharField(max_length=32) def __str__(self): return self.title
前端模板
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <form action="/login/" method="post"> {% csrf_token %} <p>用戶名<input type="text" name="user"></p> <p>密碼<input type="password" name="pwd"></p> <p><input type="submit" value="登錄"></p> </form> </body> </html>
URL
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/', views.login), url(r'^users/', views.user_list), url(r'^orders/', views.role_list), ]
后端
from django.shortcuts import render, HttpResponse, redirect from rbac.models import UserInfo, Role, Permission def login(request): if request.method == "GET": return render(request, "login.html") if request.method == "POST": username = request.POST.get("user") pwd = request.POST.get("pwd") user = UserInfo.objects.filter(name=username, pwd=pwd).first() if user: request.session["user_id"] = user.pk permission_list = user.roles.all().values("permissions__url", "permissions__title").distinct() temp = [] for per_url in permission_list: temp.append(per_url["permissions__url"]) request.session["permissions_list"] = temp print(temp) return HttpResponse("OK") else: return redirect('/login/') def user_list(request): return HttpResponse("用戶列表") def role_list(request): return HttpResponse("訂單列表")
后端有很多的視圖函數,如果編寫裝飾器進行判斷用戶是否有權限訪問,有三十個視圖函數,就需要在三十個視圖函數上添加裝飾器函數,因此裝飾器的方法不太妥當,取而代之的是中間件的方法
from django.utils.deprecation import MiddlewareMixin #注意 from django.shortcuts import render,redirect, HttpResponse from rbac.models import UserInfo import re #注意 class M1(MiddlewareMixin): def process_request(self,request): current_path = request.path_info permission_list = request.session.get("permissions_list") print(permission_list) valid_menu = ["/login/","/reg/","/admin/.*"] # 如果不設置白名單,admin的url也會被判為無權限,而且不需要驗證的函數少, 先設置白名單, # 如果用戶輸入的url在白名單中就會return None for valid_url in valid_menu: ret = re.match(valid_url,current_path) #注意 if ret: return None if not permission_list: return None Flage = False for per_url in permission_list: re_macth = re.match(per_url,current_path) if re_macth: Flage = True break if not Flage: return HttpResponse("無權限")
再次解耦后端
在rabc應用之service包intiale 模塊中創建一個inital_session函數,登錄后處理session
from rbac.service.initial import inital_session def login(request): if request.method == "GET": return render(request, "login.html") if request.method == "POST": username = request.POST.get("user") pwd = request.POST.get("pwd") user = UserInfo.objects.filter(name=username, pwd=pwd).first() if user: inital_session(request,user) return HttpResponse("OK") else: return redirect('/login/')
url_filter 模塊下的代碼
def inital_session(request,user): request.session["user_id"] = user.pk permission_list = user.roles.all().values("permissions__url", "permissions__title").distinct() temp = [] for per_url in permission_list: temp.append(per_url["permissions__url"]) request.session["permissions_list"] = temp
目錄結構如下圖

創建中間價的步驟
1、在項目中創建一個應用application,自己命名至於為什么?這是前面提到的:“一個應用可以包含在多個項目中”,方便以后的使用
2、在項目中創建一個文件夾service,
3、在service 中創建一個py文件,存放自己中間件類
4、創建一個類,必須繼承 MiddlewareMixin
5、該類中必須有一個函數,process_request
6、在該文件下面創建一個inital_session 的模塊,處理登錄后,session
做好以上步驟,效果如下圖

上面的介紹如何使用中間件控制用戶的訪問那個函數,下面介紹,根據用戶的角色展示菜單
二級菜單
用戶登錄成功,在cookie中寫入用戶的權限
import re def inital_session(request, user): request.session["user_id"] = user.pk permission_info = user.roles.all().values( "permissions__url", # 權限url "permissions__code", # "permissions__title", "permissions__id", "permissions__permission_group_id", "permissions__parent", "permissions__parent_id", "permissions__permission_group__menu__caption", "permissions__permission_group__menu__id", ).distinct() print(permission_info) # 設置用戶權限 dic = {} for per_info in permission_info: gid = per_info["permissions__permission_group_id"] if gid not in dic: dic[gid] = { "urls": [per_info["permissions__url"]], "codes": [per_info["permissions__code"]] } else: dic[gid]["urls"].append(per_info["permissions__url"]) dic[gid]["codes"].append(per_info["permissions__code"]) request.session["permissions_dict"] = dic ## 設置用戶的菜單 permission_list = [] for permission_item in permission_info: temp = { "id":permission_item["permissions__id"], "title":permission_item["permissions__title"], "url":permission_item["permissions__url"], "pid":permission_item["permissions__parent_id"], "menu_name":permission_item["permissions__permission_group__menu__caption"], "menu_id":permission_item['permissions__permission_group__menu__id'], } permission_list.append(temp) request.session["permission_list"] = permission_list
由於菜單是通用,每個函數都是需要處理菜單的邏輯,所有單獨拿出來進行創建一個處理菜單的模塊,使用@register.inclusion_tag 標簽。
自定義register.inclusion_tag標簽
from django import template register=template.Library() @register.inclusion_tag("menu.html") def get_menu(request): permission_list = request.session["permission_list"] #############temp_dict:存儲所有放到菜單欄中的權限 temp_dict = {} print(permission_list) for item in permission_list: pid = item["pid"] if not pid: item["active"] = False temp_dict[item["id"]] = item #######將需要標中的active設置True # print(permission_list) current_path = request.path_info import re for item in permission_list: pid = item["pid"] url = "^%s$" % item["url"] if re.match(url, current_path): if pid: temp_dict[pid]["active"] = True else: item["active"] = True ########將temp_dict轉換為最終的menu_dict的數據格式 menu_dict = {} for item in temp_dict.values(): if item["menu_id"] in menu_dict: temp = {"title": item["title"], "url": item["url"], "active": item["active"]}, menu_dict[item["menu_id"]]["children"].append(temp) if item["active"]: menu_dict[item["menu_id"]]["active"] = True else: menu_dict[item["menu_id"]] = { "title": item["menu_name"], "active": item["active"], "children": [ {"title": item["title"], "url": item["url"], "active": item["active"]}, ] } print(menu_dict) return {"menu_dict":menu_dict}
def m1(request): # # menu_dict = { # # 1: { # # "title": "菜單一", # # "active": False, # # "children": [ # # {"title": "添加用戶", "url": "xxxxxxxxxxx", "active": False}, # # {"title": "查看用戶", "url": "xxxxxxxxxxx", "active": False}, # # # # ]}, # # # # 2: { # # "title": "菜單二", # # "active": True, # # "children": [ # # {"title": "添加用戶", "url": "xxxxxxxxxxx", "active": True}, # # {"title": "查看用戶", "url": "xxxxxxxxxxx", "active": True}, # # # # ] # # # # }} # premission_list = request.session["permission_list"] print(premission_list) #存儲放到菜單欄中的權限 temp_dict = {} for item in premission_list: if not item["pid"]: item["active"] = False #添加到菜單欄時,添加一個是否展開的標志 temp_dict[item["id"]]= item #將需要標中的active設置為True current_path = request.path_info import re for item in premission_list: pid = item["pid"] url = "%s$"%item["url"] if re.match(url,current_path): if pid: #判斷是不是二級菜單,如果是,就會把該菜單上一級設置為Ture temp_dict[pid]["active"]=True else: item["avtive"] = True #注意此時的item 和temp_dict 的數據同一條數據,這里修改了True,temp_dict 也會改為True print(temp_dict) #將數據最終構造成最終的menu_dict數據 menu_dict = {} for item in temp_dict.values(): if item["menu_id"] in menu_dict: temp = {"title":item["title","url":item["url"],"active":item["avtive"]]} #定義一個自己的字典結構體 menu_dict[item["menu_id"]]["children"].append(temp) #把菜單添加到一級菜單中 if item["active"] == True: #如果二級菜單是展開的,那么一級菜單也是展開的 menu_dict[item["menu_id"]]["active"] = True else: menu_dict[item["menu_id"]] = { "title":item["menu_name"], "active":False, "children":[ {"title":item["title"],"url":item["url"],"active":item["active"],} ] } print(menu_dict) return render(request, "m1.html")
前端頁面
{% load my_tags %}
<!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">
<title>Title</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
<style>
.header {
width: 100%;
height: 50px;
background-color: #336699;
}
.menu, .content {
float: left;
}
.menu {
width: 200px;
height: 600px;
background-color: darkgray;
}
.hide {
display: none;
}
.menu .title {
font-size: 16px;
color: #336699 !important;
margin: 20px 0;
}
.con a {
margin-left: 30px;
color: white;
}
.active {
color: red !important;
}
</style>
</head>
<body>
<div class="header"></div>
<div class="box">
{% mul 1 2 %}
{% get_menu request %}
<div class="content">
{% block con %}
{% endblock %}
</div>
</div>
</body>
</html>
