Django权限:权限移植
权限移植步骤
本博文以 luffy_permission 项目中写好的权限,移植到 CRM项目中为例
一、将 rbac 移植到新项目中
直接将 rbac 整个文件夹复制到 CRM 项目中
注意:
1)rbac 中的 url 的路径,需要在整个大 url 中做路径的分发后,才能有效
path(r'^rbac/',include("rbac.urls"))
2)由于版本1和2 的区别,url 与 path 的应用要对齐
二、settings 中 install_app 配置 rbac 的app
注入app的目的:做数据库迁移,或找templates、templatetags 能够找到
# settings
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config',
'rbac.apps.RbacConfig'
]
三、项目中的 UserInfo 表与 rbac 中的 User 表做一对一的关联
将项目中的用户表和 rbac 中的 user 表做一对一的绑定
# app01 / models.py
from rbac.models import User # 引表 注意引用表的时候,如果和其他的表名重复要给个别名
class UserInfo(AbstractUser): tel = models.CharField(max_length=32, null=True, blank=True) gender = models.IntegerField(choices=((1, "男"), (2, "女")), default=1) depart = models.ForeignKey("Department", on_delete=models.CASCADE, default=1) user = models.OneToOneField(User, null=True,on_delete=models.CASCADE) # 与rbac中的User表进行一对一关联,由于跨app,因此需要引用过来
# 记住,此处千万不要用 default="" ,应该用 null = True 否则数据库迁移的时候会报错,严重要删库
四、数据库迁移
然后做数据库的迁移,数据库迁移之后,迁移的只是权限中的表结构,没有记录,因为我们移植的是model模型类而不是数据库。
python manage.py #cmd中才使用
makemigrations
migrate
五、admin 录入数据
查看的功能一般作为菜单权限,增删改类的不是菜单权限
菜单权限没有父权限pid,因为它自己本身就是父权限
1、自定义的在admin中输入,
- 首先录入权限(rbac)中相关的表:
1)菜单表
2)权限表
3)角色表
4)用户表(名称和密码)
- app01中的 user 表分配角色:
1)userinfo表,添加角色
注意:一定要录入userinfo表中的角色,进行关联,
2、另外,权限管理相关的业务完善以后
利用完整版得权限管理,来对权限相关的表进行添加等操作,并为用户分配权限
六、修改登录函数(登录成功后权限注入session)
from rbac.service.rbac import init_permission # 引用将权限注入session的函数 from rbac.models import User def login(request): if request.method == "GET": return render(request, "login.html") else: user = request.POST.get("user") pwd = request.POST.get("pwd") validcode = request.POST.get("validcode") res = {"user": None, "error_msg": ""} if validcode.upper() == request.session.get("keep_str").upper(): # user_obj = auth.authenticate(username=user, password=pwd) # 校验数据 user_obj = User.objects.filter(name=user,password=pwd).first() if user_obj: # auth.login(request, user_obj) # auth用户认证 一定要注释掉,否则登录时没反应
res["user"] = user # 此处不能够是user_obj ,不能是对象,可以是字符串,是对象的话,点击登录没有反应
#注入session权限 init_permission(request,user_obj) request.session["user_id"]=user_obj.pk # 登录认证用session else: res["error_msg"] = "用户名或密码错误!" else: res["error_msg"] = "验证码错误" return JsonResponse(res)
1、登录时校验用户名和密码的时候是用权限中的 User 表,还是用项目中的 UserInfo 表?
需要用权限中的 User 表来进行校验用户名和密码,
因为注入函数,init_permission(request,user),中 user 对象是权限表中的User记录,因为该对象才有与roles绑定好的角色,如下图:

2、点击登录按钮,没反应的两种可能错误
- res["user"] = user (此处user指的是name,非对象) 改变 res 字典返回值的时候,里面的user必须赋值是字符串形式的,不能够是user_obj对象,否则点击没反应
- auth.login(request,user_obj) 该方法是auth 组件进行登录认证注入session的,,这里需要注释掉,因为该登陆是基于权限表中的 user 表校验用户名,直接用 request.session["user_id"] = user_obj.pk
七、在setting中配置中间件(关于权限校验的中间件)
根据中间件的路径进行配置
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'rbac.service.middlewares.PermissionMiddleWare' # 中间件路径配置要到类名,根据自己的中间件路径来进行配置,本权限组件的路径就是此路径 ]
八、中间件添加白名单
1、将一些任何人都能访问的 url 添加到白名单中
2、"/rbac/permissions_tree/"
本文的权限分配中的权限表的渲染是基于jquery操作渲染的,因此ajax发送的请求,也要添加到白名单中,这样任何时候都能访问了,但是分配权限的 url 不用添加到白名单。
3、注意 ajax 发送的请求,任何人都可以访问的也要放到白名单中
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse,redirect import re from rbac.models import Permission class PermissionMiddleWare(MiddlewareMixin): def process_request(self,request): current_path = request.path # 设置白名单 ,"/rbac/distribute/permissions/" for reg in ["/login/","/admin*/","/rbac/permissions_tree/","get_valid_img","/index/"]: # 设置/admin*/ 为了使用admin后台录入数据,
ret = re.search(reg,current_path) if ret: request.show_id=0 # 只要渲染页面的时候用到母版的就会用到show_id变量,因为白名单中的路径不会走下面设置show_id,因此此处最好给设置一个值,不要为id值即可,此处只是暂时添加,等数据库中的权限表完善好了就可以不用添加, return None # 验证登录 user_id = request.session.get("user_id") if not user_id: return redirect("/login/") # 校验权限 # 1、校验权限 2、添加导航列表(做面包细) 3、添加show_id(用于判断当前url,与菜单权限的pk比较,菜单栏相应的二级菜单展开) permission_list = request.session.get("permission_list") # 路径导航列表(面包细)将其存放到request内的添加属性breadcrumb(可以随便起名)中,用于在母版layout.html中渲染 request.breadcrumb=[ { "title":"首页", "url":"/" } ] for item in permission_list: reg = "^%s$"%(item["url"]) ret = re.search(reg,current_path) if ret: show_id = item["pid"] or item["id"] # 有item["pid"](子权限url的pid值)等于item["pid"],没有值则等于item["id"](父权限的pk) request.show_id = show_id # 当前路径url的 pid ,show_id ,添加到request中临时属性中,便于后面进行比较 if item["pid"]: # 请求为子权限时 p_permission = Permission.objects.filter(pk=item["pid"]).first() # extend 是追加多个,需要放到列表中 request.breadcrumb.extend([ # 父权限字典 { "title":p_permission.title, "url":p_permission.url }, # 子权限字典 { "title":item["title"], # "url":item["url"] #由于数据库中存放的是正则的字符串,不能用item["url"],可以直接使用当前路径 "url":request.path } ]) else: # 请求为菜单父权限时,直接加入到request.breadcrumb属性中 request.breadcrumb.append({ "title": item["title"], "url": item["url"] }) return None return HttpResponse("无权访问该网页!")
九、在项目 base.html 母版中引入左侧菜单栏样式,渲染显示
1、左侧菜单中原来是在母版中写死的,需要注释或删掉,
2、在母版中使用 模板语法来进行渲染左侧菜单栏的样式

{% load rbac %}
{% get_menu request %}
分析:
关于该语法调用的是 templatestags / rbac / get_menu(request) 方法,该方法的用处在博文 二级菜单权限 中有详细的解释,该方法会序渲染 menu.html
from django import template register = template.Library() # 命名必须为 register from django.conf import settings import re # 将菜单权限字典 默认传给 templates/rbac/menu.html中 @register.inclusion_tag("rbac/menu.html") #templates是默认的不需要写 def get_menu(request): permission_menu_dict = request.session.get("permission_menu_dict") # 二级菜单发送请求的时候(或点击对应的添加、编辑等非菜单权限),该二级菜单仍然是展开的,其他一级菜单时收起的 for val in permission_menu_dict.values(): for item in val["children"]: val["class"]="hide" # ret = re.search("^%s$"%(item["url"]),request.path) # 之前是只判断当前路径跟菜单权限一样展开,但是对应的添加和编辑时就不展开了,不符合需求 print(request.show_id, item["pk"]) # 发现这里如果一级菜单下有两个二级菜单,则不符合需求,仍然需要修改 if request.show_id==item["pk"]: # 当前请求url的show_id 与菜单权限的pk一样时,那么该二级菜单权限是展开的 val["class"]="" return {"permission_menu_dict":permission_menu_dict}
3、menu.html 需要在母版 base.html 中引入和更改(结合当前模板背景)
需要渲染的 menu.html 虽然是写好的,但是样式不一定符合我们的要求,因此需要对样式进行引入和按照当前的模板颜色进行更改

1)在base.html母版中引入,由于css和js在rbac(app)下的静态文件中,这里直接使用static开始即可。
<link rel="stylesheet" href="/static/css/menu.css"> # 可能会票黄,但是不影响 ... ... ... <script src="/static/js/menu.js"></script>
补充:静态文件配置
2)修改css样式,据自己项目中的颜色对 menu.css 文件进行修改
修改后的 menu.html(黑色背景):
.static-menu .icon-wrap { width: 20px; display: inline-block; text-align: center; } .static-menu a { text-decoration: none; padding: 8px 15px; border-bottom: 1px solid #ccc; color: #333; display: block; background: #efefef; background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa)); background: -ms-linear-gradient(bottom, #efefef, #fafafa); background: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%); background: -o-linear-gradient(bottom, #efefef, #fafafa); filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')"; box-shadow: inset 0px 1px 1px white; } .static-menu a:hover { color: #2F72AB; border-left: 2px solid #2F72AB; } .static-menu a.active { color: #2F72AB; border-left: 2px solid #2F72AB; } .multi-menu .item { /*background-color: white;*/ background-color: #222d32; } .multi-menu .item > .title { padding: 10px 5px; /*border-bottom: 1px solid #dddddd;*/ cursor: pointer; /*color: #333;*/ color: #b8c7ce; display: block; /*background: #efefef;*/ /*background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1,#fafafa));*/ /*background: -ms-linear-gradient(bottom, #efefef, #fafafa);*/ /*background: -o-linear-gradient(bottom, #efefef, #fafafa);*/ /*filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff');*/ -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')"; /*box-shadow: inset 0 1px 1px white;*/ } /*新添加的*/ .multi-menu .item > .title:hover{ color: #fff; } .multi-menu .item > .body { /*border-bottom: 1px solid #dddddd;*/ background-color: #2c3b41; } .multi-menu .item > .body a { display: block; padding: 5px 20px; text-decoration: none; border-left: 2px solid transparent; font-size: 13px; } .multi-menu .item > .body a:hover { border-left: 2px solid #2F72AB; color: #fff; } .multi-menu .item > .body a.active { border-left: 2px solid #2F72AB; }
menu.js 通过一级菜单控制二级菜单的显示和隐藏
$('.item .title').click(function () { $(this).next().toggleClass('hide'); $(this).parent().siblings().children(".body").addClass("hide") });
补充:修改前的 menu.html(白色背景):这里可以供以后别的白色系项目模板使用
.static-menu .icon-wrap { width: 20px; display: inline-block; text-align: center; } .static-menu a { text-decoration: none; padding: 8px 15px; border-bottom: 1px solid #ccc; color: #333; display: block; background: #efefef; background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa)); background: -ms-linear-gradient(bottom, #efefef, #fafafa); background: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%); background: -o-linear-gradient(bottom, #efefef, #fafafa); filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')"; box-shadow: inset 0px 1px 1px white; } .static-menu a:hover { color: #2F72AB; border-left: 2px solid #2F72AB; } .static-menu a.active { color: #2F72AB; border-left: 2px solid #2F72AB; } .multi-menu .item { background-color: white; } .multi-menu .item > .title { padding: 10px 5px; border-bottom: 1px solid #dddddd; cursor: pointer; color: #333; display: block; background: #efefef; background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa)); background: -ms-linear-gradient(bottom, #efefef, #fafafa); background: -o-linear-gradient(bottom, #efefef, #fafafa); filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')"; box-shadow: inset 0 1px 1px white; } .multi-menu .item > .body { border-bottom: 1px solid #dddddd; } .multi-menu .item > .body a { display: block; padding: 5px 20px; text-decoration: none; border-left: 2px solid transparent; font-size: 13px; } .multi-menu .item > .body a:hover { border-left: 2px solid #2F72AB; } .multi-menu .item > .body a.active { border-left: 2px solid #2F72AB; }
十、在母版(base.html)中渲染菜单路径导航 (面包屑)
在母版中添加一下代码,由于该代码是在内容区域 ,因此最好添加在 {% block content %}的上方:
<div class="content-wrapper"> <!-- 添加面包屑 --> <div> <ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;"> {% for item in request.breadcrumb %} <li><a href="{{ item.url }}">{{ item.title }}</a></li> {% endfor %} </ol> </div>
<!-- 面包屑结束 -->
<br> {% block content %} ... {% endblock %} </div>

浙公网安备 33010602011771号