权限
根据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>