rbac——界面、权限
一、模板继承
知识点:
users.html / roles.html 继承自 base.html
页面滚动时,固定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | .menu { background-color : bisque; position : fixed ; top : 60px ; bottom : 0px ; left : 0px ; width : 200px ; } .content { position : fixed ; top : 60px ; bottom : 0 ; right : 0 ; left : 200px ; overflow : auto ; /* 滚动条 */ } |
base.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <! DOCTYPE html> < html lang="en"> < head > < meta charset="UTF-8"> < title >Title</ title > <!-- 引入 Bootstrap 核心 CSS 文件 --> < link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> < style > .header { width: 100%; height: 60px; background-color: #336699; } .menu { background-color: bisque; position: fixed; top: 60px; bottom: 0px; left: 0px; width: 200px; } .content { position: fixed; top: 60px; bottom: 0; right: 0; left: 200px; overflow: auto; /* 滚动条 */ } </ style > </ head > < body > < div class="header"> {{ user.name }} </ div > < div class="contain"> < div class="menu"> menu </ div > < div class="content"> {% block con%} {% endblock %} </ div > </ div > </ body > </ html > |
users.html:
1 2 3 4 5 6 7 8 9 | {% extends 'base.html' %} {% block con %} < h4 >用户列表</ h4 > {% for user in user_list %} < p >{{ user }}</ p > {% endfor %} {% endblock con%} |
roles.html:
1 2 3 4 5 6 7 8 9 10 | {% extends 'base.html' %} {% block con %} < h4 >角色列表</ h4 > < ul > {% for role in role_list %} < p >{{ role }}</ p > {% endfor %} </ ul > {% endblock %} |
二、在users.html中添加table
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | {% extends 'base.html' %} {% block con %} < h4 >用户列表</ h4 > < a href="/users/add" class="btn btn-primary">添加用户</ a > < table class="table table-bordered table-striped"> < thead > < tr > < th >序号</ th > < th >姓名</ th > < th >角色</ th > < th >操作</ th > </ tr > </ thead > < tbody > {% for user in user_list %} < tr > < td >{{ forloop.counter }}</ td > < td >{{ user.name }}</ td > < td > {% for role in user.roles.all %} {{ role.title }} {% endfor %} </ td > < td > < a href="" class="btn btn-danger">删除</ a > < a href="" class="btn btn-warning">编辑</ a > </ td > </ tr > {% endfor %} </ tbody > </ table > {% endblock %} |
注意:
(1)有一些用户有多重角色,需要将这些角色拿到显示在表格中的方法
1 2 3 4 5 | < td > {% for role in user.roles.all %} {{ role.title }} {% endfor %} </ td > |
(2)django模板中的forloop模板变量:在每个`` {% for %}``循环里有一个称为`` forloop`` 的模板变量。这个变量有一些提示循环进度信息的属性。
forloop.counter 总是一个表示当前循环的执行次数的整数计数器。 这个计数器是从1开始的,所以在第一次循环时 forloop.counter 将会被设置为1。
三、根据权限决定是否显示按钮
在页面往往有一些功能按钮,如果该用户没有权限,就不将这个按钮开放给当前用户,这样处理优于向用户提示“没有使用权限”。
1、在模板中权限按钮控制的简单形式
1 2 3 4 | {# 根据是否有权限显示添加用户按钮 #} {% if "/users/add" in permission_list %} < a href="/users/add" class="btn btn-primary">添加用户</ a > {% endif %} |
处理带有正则表达式的url:
1 2 3 4 5 6 | < td > {% if '/users/delete/(d+)' in permission_list %} < a href="/users/delete/{{ user.pk }}" class="btn btn-danger">删除</ a > {% endif %} < a href="" class="btn btn-warning">编辑</ a > </ td > |
这种方法是针对表做操作,根据表名去做判断。
如果希望if判断时url里面不带有表名字。roles和users合并用一个视图函数来处理。
2、admin修改显示,页面显示更多内容
rbac/admin.py:
1 2 3 4 5 6 7 8 9 10 | from django.contrib import admin # Register your models here. from .models import * class PerConfig(admin.ModelAdmin): list_display = [ "title" , "url" ] admin.site.register(User) admin.site.register(Role) admin.site.register(Permission, PerConfig) |
注意:list_display = [] 。
显示效果:
3、修改数据结构
添加一个权限组表。将每张表的增删改查,划到一个组里面!无论多复杂的,最终一定是对数据库的(增删改查)。
修改表结构,重新处理中间件,登录页面的目的:全是为了按钮的粒度,同一个模板,同一个视图,
显示不同的数据,权限。
(1)models.py代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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(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): title = models.CharField(max_length = 32 ) url = models.CharField(max_length = 32 ) # 操作 action = models.CharField(max_length = 32 , default = "") # 默认值为空 # 分组 group = models.ForeignKey( "PermissionGroup" , default = 1 , on_delete = True ) def __str__( self ): return self .title class PermissionGroup(models.Model): title = models.CharField(max_length = 32 ) def __str__( self ): return self .title |
修改完后,在一次执行数据库迁移。
(2)再一次修改rbac/admin.py:
1 2 3 4 5 6 7 8 9 10 11 | from django.contrib import admin # Register your models here. from .models import * class PerConfig(admin.ModelAdmin): list_display = [ "title" , "url" , "group" , "action" ] admin.site.register(User) admin.site.register(Role) admin.site.register(Permission, PerConfig) admin.site.register(PermissionGroup) |
(3)为权限添加action:
全部修改后:
修改之后,GROUP描述是对哪张表进行操作,ACTION是描述对这个表做什么操作。
(4)修改rbac_permission表的group_id信息,将角色操作类别的group_id修改为2
4、重写initial_session(user, request)函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | # -*- coding:utf-8 -*- __author__ = 'Qiushi Huang' def initial_session(user,request): """ 查看当前用户所有的权限 :param user: :param request: :return: """ # 方案1: # permissions = user.roles.all().values("permissions__url").distinct() # print(permissions) # <QuerySet [{'permissions__url': '/users/'}, {'permissions__url': '/users/add'}]> # # permission_list = [] # for item in permissions: # permission_list.append(item["permissions__url"]) # # print(permission_list) # # request.session["permission_list"] = permission_list # 方案2: # 角色表跨到权限表查找 permissions = user.roles. all ().values( "permissions__url" , "permissions__group_id" , "permissions__action" ).distinct() print ( "permissions" , permissions) # 有一个权限QuerySet中就有一个字典 """ permissions <QuerySet [{'permissions__url': '/users/', 'permissions__group_id': 1, 'permissions__action': 'list'}]> """ # 对上述数据进行处理: 以组为键,以字典为值 permission_dict = {} for item in permissions: gid = item.get( "permissions__group_id" ) if not gid in permission_dict: permission_dict[gid] = { "urls" : [item[ "permissions__url" ], ], "actions" : [item[ "permissions__action" ], ] } else : # 组id已经在字典中 permission_dict[gid][ "urls" ].append(item[ "permissions__url" ]) permission_dict[gid][ "actions" ].append(item[ "permissions__action" ]) print (permission_dict) # {1: {'urls': ['/users/', '/users/add', '/users/delete/(\\d+)', '/users/edit/(\\d+)'], # 'actions': ['list', 'add', 'delete', 'edit']}} request.session[ 'permission_dict' ] = permission_dict |
注意:
(1)在session中注册权限字典
前面是在session中注册权限列表:
1 | request.session[ 'permission_list' ] = permission_list |
现在需要在session中注册的是权限字典:
1 | request.session[ 'permission_dict' ] = permission_dict |
(2)从角色表到权限表跨表查询权限路径、权限组ID、权限action
1 2 3 4 5 6 7 8 | # 角色表跨到权限表查找 permissions = user.roles. all ().values( "permissions__url" , "permissions__group_id" , "permissions__action" ).distinct() print ( "permissions" , permissions) # 有一个权限QuerySet中就有一个字典 """ permissions <QuerySet [{'permissions__url': '/users/', 'permissions__group_id': 1, 'permissions__action': 'list'}]> """ |
(3)对上述数据进行处理:以组为键、以字典为值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | { 1 : { "url" : [ '/users/' ,], "actions" : [ 'list' ,] }, } 如果用户操作多个权限: { 1 : { 'urls' : [ '/users/' , '/users/add/' , '/users/delete/(\\d+)/' , '/users/edit/(\\d+)/' ], 'actions' : [ 'list' , 'add' , 'delete' , 'edit' ] }, } 如果除了有用户操作权限还有角色操作权限: { 1 : { 'urls' : [ '/users/' , '/users/add/' , '/users/delete/(\\d+)/' , '/users/edit/(\\d+)/' ], 'actions' : [ 'list' , 'add' , 'delete' , 'edit' ] }, 2 : { 'urls' : [ '/roles/' ], 'actions' : [ 'list' ] } } |
5、改写中间件rbac.py中的VaildPermission类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | # -*- coding:utf-8 -*- __author__ = 'Qiushi Huang' import re from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse, redirect class ValidPermission(MiddlewareMixin): def process_request( self , request): # 当前访问路径 current_path = request.path_info # 当前路径的属性 ########### 检查是否属于白名单 ############# valid_url_list = [ '/login/' , '/reg/' , '/admin/.*' ] for valid_url in valid_url_list: ret = re.match(valid_url, current_path) if ret: return # 等同于return none ############### 检验是否登录 ############## user_id = request.session.get( "user_id" ) if not user_id: return redirect( "/login/" ) ################ 校验权限1 ################# # permission_list = request.session.get("permission_list") # # flag = False # for permission in permission_list: # permission = "^%s$" % permission # ret = re.match(permission, current_path) # 第一个参数是匹配规则,第二个参数是匹配项 # if ret: # flag = True # break # if not flag: # # 如果没有访问权限 # return HttpResponse("没有访问权限!") ################ 校验权限2 ################# permission_dict = request.session.get( 'permission_dict' ) for item in permission_dict.values(): # 循环只取字典的值 urls = item[ "urls" ] for reg in urls: reg = "^%s$" % reg ret = re.match(reg, current_path) if ret: print ( "actions" , item[ "actions" ]) request.actions = item[ "actions" ] return None return HttpResponse( "没有访问权限!" ) |
注意:
(1)中间件的request对象,给对象添加属性actions,未来视图中就可以通过request.actions拿到当前用户对这个表的所有操作权限。
1 | request.actions = item[ "actions" ] |
(2)数据类型从数组变为了字典,数据处理方式略有不同。
6、改写users视图,视图添加Per类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | class Per( object ): def __init__( self , actions): self .actions = actions def add( self ): return "add" in self .actions def delete( self ): return "delete" in self .actions def edit( self ): return "edit" in self .actions def list ( self ): return "list" in self .actions def users(request): user_list = User.objects. all () permission_list = request.session.get( "permission_list" ) print (permission_list) # ['/users/', '/users/add', '/roles/', '/users/delete/(\\d+)', '/users/edit/(\\d+)'] # 查询当前登录人的名字 id = request.session.get( "user_id" ) user = User.objects. filter ( id = id ).first() per = Per(request.actions) return render(request, "users.html" , locals ()) |
注意:
通过Per(request.actions)得到per对象,传到模板中可以通过per.edit\per.list等方式来判断是否拥有权限。增加阅读性。
7、users.html改写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | { % extends 'base.html' % } { % block con % } <h4>用户列表< / h4> { # 根据是否有权限显示添加用户按钮 #} { % if per.add % } <a href = "/users/add" class = "btn btn-primary" >添加用户< / a> { % endif % } <table class = "table table-bordered table-striped" > <thead> <tr> <th>序号< / th> <th>姓名< / th> <th>角色< / th> <th>操作< / th> < / tr> < / thead> <tbody> { % for user in user_list % } <tr> <td>{{ forloop.counter }}< / td> <td>{{ user.name }}< / td> <td> { % for role in user.roles. all % } {{ role.title }} { % endfor % } < / td> <td> { % if per.delete % } <a href = "/users/delete/{{ user.pk }}" class = "btn btn-danger" >删除< / a> { % endif % } { % if per.edit % } <a href = " " class=" btn btn - warning">编辑< / a> { % endif % } < / td> < / tr> { % endfor % } < / tbody> < / table> { % endblock % } |
显示效果:
8、总结
1、权限粒度控制
1 2 | 简单控制: { % if "users/add" in permissions_list % } |
2、更改数据库结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Permission(models.Model): title = models.CharField(max_length = 32 ) url = models.CharField(max_length = 32 ) # 操作 action = models.CharField(max_length = 32 , default = "") # 默认值为空 # 分组 group = models.ForeignKey( "PermissionGroup" , default = 1 , on_delete = True )<br> def __str__( self ): return self .title class PermissionGroup(models.Model): title = models.CharField(max_length = 32 )<br> def __str__( self ): return self .title |
3、登录验证
1 | permissions = user.roles. all ().values( "permissions__url" , "permissions__group_id" , "permissions__action" ).distinct() |
4、构建permission_dict
5、中间件校验权限
1 2 3 4 5 6 7 8 9 10 11 12 13 | permission_dict = request.session.get( 'permission_dict' ) for item in permission_dict.values(): # 循环只取字典的值 urls = item[ "urls" ] for reg in urls: reg = "^%s$" % reg ret = re.match(reg, current_path) if ret: print ( "actions" , item[ "actions" ]) request.actions = item[ "actions" ] return None return HttpResponse( "没有访问权限!" ) |
四、权限菜单显示
1、用户登录在initial_session中注册菜单权限并注册到session中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | def initial_session(user, request): """ 查看当前用户所有的权限 :param user: :param request: :return: """ # 方案1: # permissions = user.roles.all().values("permissions__url").distinct() # print(permissions) # <QuerySet [{'permissions__url': '/users/'}, {'permissions__url': '/users/add'}]> # # permission_list = [] # for item in permissions: # permission_list.append(item["permissions__url"]) # # print(permission_list) # # request.session["permission_list"] = permission_list # 方案2: # 角色表跨到权限表查找 permissions = user.roles. all ().values( "permissions__url" , "permissions__group_id" , "permissions__action" ).distinct() print ( "permissions" , permissions) # 有一个权限QuerySet中就有一个字典 """ permissions <QuerySet [{'permissions__url': '/users/', 'permissions__group_id': 1, 'permissions__action': 'list'}]> """ # 对上述数据进行处理: 以组为键,以字典为值 permission_dict = {} for item in permissions: gid = item.get( "permissions__group_id" ) if not gid in permission_dict: permission_dict[gid] = { "urls" : [item[ "permissions__url" ], ], "actions" : [item[ "permissions__action" ], ] } else : # 组id已经在字典中 permission_dict[gid][ "urls" ].append(item[ "permissions__url" ]) permission_dict[gid][ "actions" ].append(item[ "permissions__action" ]) print (permission_dict) # {1: {'urls': ['/users/', '/users/add', '/users/delete/(\\d+)', '/users/edit/(\\d+)'], # 'actions': ['list', 'add', 'delete', 'edit']}} request.session[ 'permission_dict' ] = permission_dict # 注册菜单权限 permissions = user.roles. all ().values( "permissions__url" , "permissions__group_id" , "permissions__action" , "permissions__group__title" ).distinct() print ( "permissions" , permissions) menu_permission_list = [] # 菜单栏中权限列表:空列表 for item in permissions: # item是里面的字典 if item[ "permissions__action" ] = = "list" : # 列表里面套一个个的元组,每个元组包含url和权限组title menu_permission_list.append((item[ "permissions__url" ], item[ "permissions__group__title" ])) print ( "menu_permission_list" , menu_permission_list) # 注册到session中 request.session[ "menu_permission_list" ] = menu_permission_list |
注意:
(1)注册菜单权限:
1 2 3 | # 注册菜单权限 permissions = user.roles. all ().values( "permissions__url" , "permissions__group_id" , "permissions__action" , "permissions__group__title" ).distinct() |
其中permissions__group__title是跨三张表查询。
(2)在菜单权限列表中添加元组,每个元组包含url和权限组title信息。
1 2 3 4 5 6 7 8 | menu_permission_list = [] # 菜单栏中权限列表:空列表 for item in permissions: # item是里面的字典 if item[ "permissions__action" ] = = "list" : # 列表里面套一个个的元组,每个元组包含url和权限组title menu_permission_list.append((item[ "permissions__url" ], item[ "permissions__group__title" ])) print ( "menu_permission_list" , menu_permission_list) |
(3)将菜单权限列表注册到session中:
1 2 | # 注册到session中 request.session[ "menu_permission_list" ] = menu_permission_list |
2、自定义标签(inclusion_tag)
因为模板继承,只继承样式,不继承数据!所以需要用到自定义标签(inclusion_tag)。
在rbac项目下创建一个templatetags文夹。这个文件夹的名字必顺是templatetags来命名的。然后在此文件夹下自定义一个my_tags.py文件。
1 2 3 4 5 6 7 8 9 10 | from django import template register = template.Library() @register .inclusion_tag( "menu.html" ) def get_menu(request): # 获取当前用户应该放到菜单栏中的权限 menu_permission_list = request.session[ "menu_permission_list" ] return { "menu_permission_list" : menu_permission_list} |
它会将返回数据传递给模板文件menu.html.
创建menu.html模板:
1 2 3 4 5 | < div > {% for item in menu_permission_list %} < p class="menu_btn">< a href="{{ item.0 }}">{{ item.1 }}</ a ></ p > {% endfor %} </ div > |
修改base.html模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | < body > < div class="header"> < p >{{ user.name }}</ p > </ div > < div class="contain"> {% load my_tags %} < div class="menu"> {% get_menu request %} </ div > < div class="content"> {% block con%} {% endblock %} </ div > </ div > </ body > |
3、模板迁移及模板渲染规则
由于rbac是可插拔组件,因此可以将属于权限的模板文件迁移到rbac的app中。
创建rbac/templates文件夹,将users.html / roles.html / base.html / menu.html剪切到文件夹中。
django的render去渲染 .html 时,先到项目的 templates 下找,如果找不到再到App下templates 下找,
最后找不到才报错。
(1)如果多个App的templates 下的.html重名怎么办?
django 会根据注册的顺序显示!
解决办法:项目/rbac/templates/rbac/xxx.html
这时调用:return render(request, 'rbac/users.html', locals())
(2)templates 或者 templatetag 注意多个app下面 的文件名 有可能都会重名!
办法:就是 eg:/rbac/templates/rbac/xxx.html 或者不起重名
(3)同名.html文件查找顺序?
如果 base.html 在项目下有,在App下有,先找项目下的,找不到才找App,因此全局可以覆盖局部的!!
五、django路径自动添加
1、django路径添加现象及原理
知识点:路径自动添加问题:
http://127.0.0.1:8010/users
http://127.0.0.1:8010/users/
发现在浏览器浏览时,两个路径都可以正常访问到页面。这是因为浏览器发请求:django 发现之后,发了一个重定向的 url 加了一个 / 所以才能匹配上:path('users/', views.users),
2、路径配置
如果想让django不给浏览器发重定向。可以在setttings.py中添加:
1 | APPEND_SLASH = False |
在不添加这个配置时,django默认APPEND_SLASH的值为True,django会默认的加 / 发重定向。
ajax中的url和这里同理。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术