Django:权限介绍 + url 权限 + 一级菜单权限动态显示
权限的介绍
说起权限我们大家都知道,不一样的角色会有不一样的权限。比如就像学生管理系统一样,管理员,老师,学生之间的权限都是不一样的,那么展示的页面也是不一样的。
这里我们介绍的是 web 网站的权限,对于web网站的权限来讲,一个含有正则表达式的 url 就是说一个权限。
这里有两种方案:
方案一:基于人进行分配,这样会造成空间的浪费

方案二:基于角色分配(RBAC:role based access control)
一般情况下,我们多采用的是第二种方案,将权限分别存于权限表中,其中用户表和角色表是多对多的关系,角色表和权限表也是多对多的关系。
url 权限的简单使用
创建权限的目标是为了创建组件,让其他的项目也能够使用
首先要创建一个Django项目,创建一个app01.
一、表结构的设计
根据方案二中的表结构在models中创建模型,然后迁移数据库:makemigrations migrate
# 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("Role") def __str__(self): return self.name class Role(models.Model): title = models.CharField(max_length=32) permissions = models.ManyToManyField("Permission") def __str__(self): return self.title class Permission(models.Model): title = models.CharField(max_length=32) url = models.CharField(max_length=32) def __str__(self): return self.title
然后进行数据库的迁移
makemigrations
migrate
二、利用Django中的 admin 录入权限数据
要创建超级用户才能登陆 admin
createsuperuser
在app01.admin.py中添加 register,将模型类添加register,在admin中就可以可视化录入数据
from django.contrib import admin # Register your models here. from app01.models import User,Role,Permission admin.site.register(User) #### class RoleConfig(admin.ModelAdmin): # 添加该类可以在admin中改变查看的界面更加方便 类名Config是 固定的 list_display=["title"] admin.site.register(Role,RoleConfig) ### class PermissionConfig(admin.ModelAdmin): list_display=["pk","title","url"] ordering = ["pk"] admin.site.register(Permission,PermissionConfig) ###
三、权限的注入
权限的注册就是在登录的时候,将登录人的权限加入到session中,登录(获取当前登录用户的权限)
思路:
- 编写登录
- 如果用户验证成功就设置session
- 先查出当前用户的所有的权限
- 从这些权限中找到所有的url,把这些url放到session中,
# views.py
这里只是注入了 url 权限,不涉及菜单权限
from django.shortcuts import render,HttpResponse,redirect from app01.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") user=User.objects.filter(name=user,pwd=pwd).first() # user是一个对象,一定要first(),否则后面没有 user.pk if user: # 登录成功 request.session["user_id"]=user.pk #保存当前登录用户状态信息 # 查询当前登录人的所有权限列表 # 查看当前登录人的所有角色 # ret=Role.objects.filter(user=user) permissions=Role.objects.filter(user=user).values("permissions__url").distinct() # distinct去重,user=user 前者是表明小写,表示记录(对象) 后者user本身就是对象 print(permissions) permission_list=[] for item in permissions: permission_list.append(item["permissions__url"]) # 双下划线 # 将当前登录人的权限列表注入session中 request.session["permission_list"]=permission_list # 注入的session可以是一个列表 return HttpResponse("登录成功")
# html 登录的一个form表单
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <form action="" method="post"> {% csrf_token %} 用户名<input type="text" name="user"> 密码<input type="text" name="pwd"> <input type="submit"> </form> </body> </html>
注意:
- session中可以保存很多的东西,并不一定是登录信息,比如之前学的验证码的保存,也用到了session
- permissions中获取的类型和内容:动态参数是 \d
<QuerySet [{'permissions__url': 'customers/edit/(\\d+)/'}, {'permissions__url': 'customers/delete/(\\d+)/'}, {'permissions__url': 'orders/edit/(\\d+)/'}, {'permissions__url': 'orders/delete/(\\d+)/'}]>
四、权限的校验
利用中间件进行校验
权限加入到session中,在每次访问请求 url 的时候,都要校验该 url 请求是否在session中,但是如果在每个视图函数中都要校验的话,会有代码的重复,因此我们可以在中间件中进行权限的校验。
# app01 ===> middlewares.py
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse,redirect import re class PermissionMiddleWare(MiddlewareMixin): #必须继承该类 def process_request(self,request): current_path = request.path # 当前路径 # 设置白名单,即使不在权限中,也会放行,注意必须用循环,因为 "/admin/*" 含有正则, # 如果直接用in的话,"/admin/*" 会被当做字符串 for path in ["/login/","/admin/*"]: # 将其作为规则符合当前路径,那么就放行 ret = re.search(path,current_path) if ret: return None # 返回空则放行 # 先校验是否登录 user_id = request.session.get("user_id") if not user_id: return redirect("/login/") # 再校验权限 # 同样的由于url中含有 \d 正则,因此需要使用循环,而不是in permission_list = request.session.get("permission_list") for item in permission_list: item="^%s$"%(item) #将规则加开头结尾,否则只要含有权限url中内容就会放行, ret = re.search(item,current_path) if ret: return None return HttpResponse("无权访问!")
# setting.py 添加中间件
MIDDLEWARE = [ 'app01.middlewares.PermissionMiddleWare' ]
注意:
- 在设置白名单或者校验的时候,如果路径中含有正则表达式的,不能直接用 in ,要用循环,循环白名单或校验的内容,将其作为规则,然后去校验当前路径,是否符合规则。
- 另外在校验权限的时候,要注意,规则一定要加开头和结尾,否则当前路径中,只要含有规则中内容,还有多余的内容,也会通过放行,例如:/customers/(规则)/customers/add/(当前路径)也会放行
五、权限的优化 ♥
写权限的目的是为了让其可以再其他项目中也能使用,否则每个项目都要写一套权限,因此可以把权限写成组件的形式,方便项目的调用。
组件实质上也是一个包,将写的权限都放入一个包内,供其他的项目调用。
1、创建一个关于权限的app(rbac):
python manage.py startapp rbac # 也可以在 tool run manage
2、在rbac中创建包 service, 将所有与权限相关的内容都放入该包中
- 与权限相关的表(模型类)放入rbac 的 models.py 中,利用 admin 进行数据的录入
- 与权限相关的注入session内容,放入rbac 中 service 包中的 rbac.py 中,封入一个函数之内 def initial_sesson(user,request) ,注意传值,user和request,以后项目中写登录的时候,登录成功即调用该方法,然后再进行登录成功后的重定向。
- 与权限相关的中间件校验内容,放入rbac 中 service 包中的 middlewares.py 中,不要忘记在setting中进行设置路径
# rbac ==> models.py
from django.db import models class Permission(models.Model): """ 权限表 """ title = models.CharField(max_length=32, verbose_name='标题') url = models.CharField(max_length=32, verbose_name='权限') 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 class Role(models.Model): name = models.CharField(max_length=32, verbose_name='角色名称') permissions = models.ManyToManyField(to='Permission', verbose_name='角色所拥有的权限', blank=True) def __str__(self): return self.name class User(models.Model): """ 用户表 """ name = models.CharField(max_length=32, verbose_name='用户名') password = models.CharField(max_length=32, verbose_name='密码') roles = models.ManyToManyField(to='Role', verbose_name='用户所拥有的角色', blank=True) def __str__(self): return self.name
# rbac ==> service ==> rbac.py
注意传值,user 和 request,登录成功后,要调用该方法 ♥
from rbac.models import Role def initial_sesson(user,request): """ 功能:将当前登录人的所有权限录入session中 :param user: 当前登录人 """ # 查询当前登录人的所有权限列表 # 查看当前登录人的所有角色 # ret=Role.objects.filter(user=user) permissions = Role.objects.filter(user=user).values("permissions__url", "permissions__is_menu", "permissions__title", "permissions__icon", ).distinct() permission_list = [] permission_menu_list = [] for item in permissions: # 构建权限列表 permission_list.append(item["permissions__url"]) # 构建菜单权限列表 这里后面会有详细的介绍。 if item["permissions__is_menu"]: permission_menu_list.append({ "title":item["permissions__title"], "icon":item["permissions__icon"], "url":item["permissions__url"], }) # 将当前登录人的权限列表注入session中 request.session["permission_list"] = permission_list # 将当前登录人的菜单权限列表注入session中 print("permission_menu_list",permission_menu_list) request.session["permission_menu_list"] = permission_menu_list
# rbac ==> service ==> middlewares.py
每次发送请求,都会通过中间件
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse,redirect import re class PermissionMiddleWare(MiddlewareMixin): def process_request(self,request): print("permission_list",request.session.get("permission_list")) current_path = request.path # 设置白名单放行 for reg in ["/login/","/admin/*"]: ret=re.search(reg,current_path) if ret: return None # /customers/edit/1 # 校验是否登录 user_id=request.session.get("user_id") if not user_id: return redirect("/login/") # 校验权限 涉及到正则要用循环 permission_list=request.session.get("permission_list") for reg in permission_list: reg="^%s$"%reg ret=re.search(reg,current_path) if ret: return None return HttpResponse("无访问权限!")
中间件要在 setting 中进行设置
MIDDLEWARE = ['rbac.service.middlewares.PermissionMiddleWare', #根据中间件所在的目录位置,来进行添加中间件,注意一定要写对,否则会报错。 ]
一级菜单权限
前面详细介绍了什么是权限,以及url 权限,并且将每个人的权限通过角色分配,在表结构中,进行了设计 ,但是这种情况下,我们只能通过地址栏输入url 来查看该用户是否具有访问权限,这种不方便用户的操作,我们希望用户能够登录成功之后,能够再左侧的菜单栏只显示自己又权限的功能,没有权限的则不显示,但是并不是所有的权限都要放入左侧的菜单栏中,比如添加、变价、删除,本身就是基于查看页面中的 a 标签发送请求的,因此,没有必要放在左侧的菜单栏处。
一、什么是菜单权限?
左侧菜单栏,有标题,图标,点击请求的路径

二、权限表,添加字段(is_menu icon)
因此在涉及权限表的时候,可以多涉及两个字段 is_menu 存储bool值,和 icon 存储图标,根据 is_menu 来判断是否是菜单权限来决定当前登录的用户菜单栏显示的内容。
具体的添加方式,查看上面优化中的 models.py 中已经添加。
三、在注入权限的时候,session中需要注入 权限列表 和 菜单权限
- 权限列表:8条所有权限的 url
- 菜单权限列表:可以当做左侧菜单栏的权限,包括菜单权限的 url、title、icon (因为左侧菜单栏,有标题,图标,点击请求的路径,渲染的时候需要用到)
详细的添加方式,查看上面优化中的 session 注入。
注意:session 中注入的是键值对,可以添加多个键值对
四、菜单权限的显示 ♥♥♥
由于左侧的菜单栏在每次发送请求的时候都会显示在页面,因此在每个请求的视图函数中都要传送 菜单权限列表 给前端数据来渲染,由于render 的时候是将 html 解析成一大堆的字符串,传递给前端浏览器,浏览器根据 html 的字符串渲染出页面,这就造成了 模板html 和 数据 是分离的,要求每个视图函数中都要传送数据,这样就造成了代码的重复。
如何解决以上问题?
我们可以使用模板语法中提供的自定义过滤标签传值的思想。
1、自定义的过滤器标签要默认放入 templatetags 包中,因为是根权限相关的,所以整个包可以放入 rbac (app) 中,
# rbac(app) / templatetags / web.py
其中,templatetags 是必须命名, web.py 是可以随便起名字的,rbac(app) 整个app项目也是可以随便起名字的,这里因为是跟全年相关的额,所以起名为 rbac
注意:添加 templatetags 以后需要重新启动项目
from django.template import Library import re register =Library() # 必须命名为 register # 该自定义过滤器标签不仅给menu.html中传值permission_menu_list, # 而且还根据列表中的url是否为当前路径,给其添加键值对class=acitve,给菜单列表添加样式
@register.inclusion_tag("rbac/menu.html") # 默认会找 templates 文件夹下,因此不需要写 templates 路径 def get_menu_styles(request): # 注意该方法需要传值 permission_menu_list = request.session.get("permission_menu_list") for item in permission_menu_list: if re.search("^{}$".format(item["url"]), request.path): item["class"] = "active" return {"permission_menu_list":permission_menu_list} # 会传值给上面的 rbac/menu.html中,此处类似于render的传值
2、编写,自定义过滤标签中的传入数据的 rbac.menu.html
# rabc(app) / templates / rbac / menu.html
根据 传入的 permission_menu_list 渲染菜单权限列表的标签, 用于需要的模板,通过load 和 调用该方法。
注意:模板变量有值则渲染,没值不渲染,不会对页面有任何的影响
<div class="static-menu"> {% for item in permission_menu_list %} <a href="{{ item.url }}" class="{{ item.class }}"> <span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span> {{ item.title }} </a> {% endfor %} </div>
3、在母版中 html 中渲染以上的 菜单标签 语法
找到要渲染该菜单标签的位置
load 会直接去找 templatetags 中的 web.py 文件
get_menu_styles 是过滤标签的方法,request 是该方法的传的参数,由于request 是全局变量,因此在 html 中是可直接使用的。
<div class="left-menu"> <div class="menu-body"> {% load web %} {% get_menu_styles request %} </div> </div>
关于权限的app(rbac)中目录解析:


浙公网安备 33010602011771号