今日内容:不同用户分权限(展示不同的菜单)
内容回顾:
settings的配置文件中的key都是大写
内容回顾: 1.使用别人源码,启动:解释器+工作目录 2. django请求生命周期 3. 配置文件 - key必须大写 - 导入配置 from django.conf import settings 3. 在模板中定义函数 - sample_tag - inclusion_tag 4. 寻找模板的顺序(静态文件) - 最外层templates目录 (static) - 去注册的app下的templates目录中找(按照app注册顺序)(static) 5. auto示例:所有用户登录后看到的菜单都一样。
今日内容:
1.回顾用户、角色、权限表的创建
2.设计含菜单的最终表结构:在权限表中加menu_id,(is_menu),pid
数据库设计 from django.db import models class Menu(models.Model): """ 菜单表 """ title = models.CharField(verbose_name='标题',max_length=32) icon = models.CharField(verbose_name='图标',max_length=32) class Permission(models.Model): """ 权限表 """ url = models.CharField(verbose_name='URL(含正则)', max_length=128) title = models.CharField(verbose_name='名称',max_length=32) name = models.CharField(verbose_name='别名',max_length=32,unique=True) menu = models.ForeignKey(verbose_name='管理菜单',to='Menu',to_field='id',null=True,blank=True) parent = models.ForeignKey(verbose_name='父菜单',to='Permission',null=True,blank=True) class Role(models.Model): """ 角色表 """ title = models.CharField(verbose_name='名称', max_length=32) permissions = models.ManyToManyField(verbose_name='关联权限',to='Permission') class UserInfo(models.Model): """ 用户表 """ username = models.CharField(verbose_name='用户名',max_length=32) password = models.CharField(verbose_name='密码',max_length=64) roles = models.ManyToManyField(verbose_name='关联角色',to='Role')
给url取别名,且别名不能重复
创建表时类别首字母大写
1对多时,将外键关联的字段写在多的表中;多对多时,写在常用查询的位置
3.写model中代码,数据填充
4.去掉web相关内容
上一节功能去掉 - 去掉web app - url.py urlpatterns = [ url(r'^admin/', admin.site.urls), # url(r'^web/', include('web.urls')), ] - settings.py 去掉 MENU_LIST 去掉注册的app: 'web.apps.WebConfig',
5.取数据(权限和菜单信息)
可以连续跨表
获取权限信息,获取可以做菜单的权限信息
6.复制service文件夹,完成权限初始化
复制中间件中权限 可以去写好的中间件中参考引入
注册中间件 设置默认选中菜单
权限控制
7.动态生成二级菜单
注意menu.html中去掉if is_menu判断
模板:权限校验rbac+菜单menu+模板样式
8.用户列表显示
如果没有权限,按钮不展示
---粒度控制到按钮级别(要有别名name)
自定义filter函数(因为在模板语言中不能用inclusion_tag)
注意使用:{% if 'xxx'|permission:request %} {% endif %}
操作、表格业没有也加if即可
9.应用-使用权限系统
通过别名反向生成url
共9步
1. 拷贝rbac应用 2. 删除rbac/migrations目录中除 __init__.py 以外的所有文件 3. 配置文件中注册 rbac的app INSTALLED_APPS = [ ... 'rbac.apps.RbacConfig', ] 4. 数据库迁移 python manage.py makemigrations python manage.py migrate 5. 编写测试系统的业务逻辑 如果使用rbac中的模板的话,需要先删除layout.html中的: <!-- 导入xxxxxxx模块 --> {% load rbac %} <!-- 执行get_menu函数并传递了一个参数 --> {% get_menu request %} 业务逻辑开发完毕.... 6. 设置权限相关的配置文件 # ############################ 权限+菜单相关配置 ############################# RBAC_PERMISSION_SESSION_KEY = "ijksdufwesdfs" RBAC_MENU_SESSION_KEY = "rtwsdfgwerffsd" VALID_LIST = [ '/api/login/', '/admin/.*' ] 7. 基于django admin 录入权限数据 - 菜单 - 权限 - 权限角色关系表 - 角色 - 用户角色关系表 - 用户 8. 权限和菜单信息的应用 - 用户登录:初始化权限和菜单信息 def login(request): """ 用户登录 :param request: :return: """ if request.method == "GET": return render(request, 'api/login.html') user = request.POST.get('user') pwd = request.POST.get('pwd') user = rbac_model.UserInfo.objects.filter(username=user, password=pwd).first() if not user: return render(request, 'api/login.html', {'msg': '用户名或密码错误'}) # ############ 看这里 ############ init_permission(user, request) return redirect('/api/app/list/') - 中间件:权限判断 settings.py MIDDLEWARE = [ ... 'rbac.middlewares.rbac.RBACMiddleware', ] - inclusion_tag:动态生成菜单 layout.html <div class="menu-body"> {% load rbac %} {% get_menu request %} </div> 9. 控制页面按钮 {% extends 'layout.html' %} {% load rbac %} {% block content %} <h1>应用列表</h1> {% if 'app_add'|permission:request %} <a class="btn btn-primary" href="{% url 'app_add' %}">添加</a> {% endif %} <table class="table table-bordered"> <thead> <tr> <th>ID</th> <th>姓名</th> {% if "app_edit"|permission:request or "app_del"|permission:request %} <th>操作</th> {% endif %} </tr> </thead> <tbody> {% for row in app_queryset %} <tr> <td>{{ row.id }}</td> <td>{{ row.title }}</td> {% if "app_edit"|permission:request or "app_del"|permission:request %} <td> {% if "app_edit"|permission:request %} <a href="{% url 'app_edit' row.id %}">编辑</a> {% endif %} {% if "app_del"|permission:request %} <a href="{% url 'app_del' row.id %}/">删除</a> {% endif %} </td> {% endif %} </tr> {% endfor %} </tbody> </table> {% endblock %}
权限和菜单的应用,按钮的控制引用filter
小结-------
权限可以被应用到任何一个系统
总结: 1. 保存的代码: - 上一节示例:auto - 7 - 静态的菜单示例(最终版).zip - 本节示例:nb_test.zip 2. 权限相关--记住 1. 权限系统是如何实现的? 基于角色的权限控制(rbac) 2. 权限系统中用了哪些表?表中都有哪些字段? --见上面 3. 你用中间件实现过什么?为什么使用中间件? rbac对权限的控制。 所有的请求都会走中间件,所以权限控制在中间件中。 4. 你认为哪里最难搞? - 动态二级菜单+默认选中 - 构建菜单和权限的数据结构时。 5. 其他 ...
流程总结:用户登录--将权限和菜单放入session中(定义一个init_permission),通过中间件进行权限的判断。通过inclusion_tag:动态生成菜单
控制页面按钮用filter实现
wsgi,中间件--描述清楚
初始化--中间件--inlcusiontag 三块代码
# ############################ 权限+菜单相关配置 ############################# RBAC_PERMISSION_SESSION_KEY = "ijksdufwesdfs" RBAC_MENU_SESSION_KEY = "rtwsdfgwerffsd" VALID_LIST = [ '/app01/login/', '/admin/.*' ]
import re from django.utils.deprecation import MiddlewareMixin from django.conf import settings from django.shortcuts import HttpResponse class RBACMiddleware(MiddlewareMixin): """ 用户权限校验的中间件 """ def process_request(self, request): """ 权限校验 1. 当前请求的URL 2. 去Session中获取当前用户拥有的所有的权限 3. 权限校验 :param request: :return: """ current_url = request.path_info # 1. 白名单处理 for valid in settings.VALID_LIST: if re.match(valid,current_url): return None # 2. 获取权限信息 permission_dict = request.session.get(settings.RBAC_PERMISSION_SESSION_KEY) if not permission_dict: return HttpResponse('当前用户无权限信息,请重新登录!') """ permission_dict = { 'user_list': {'url': '/app01/user/', 'menu_id': 1, 'parent_name': None}, 'user_add': {'url': '/app01/user/add/', 'menu_id': None, 'parent_name': 'user_list'}, 'user_edit': {'url': '/app01/user/edit/(\d+)', 'menu_id': None, 'parent_name': 'user_list'}, 'order': {'url': '/app01/order/', 'menu_id': 2, 'parent_name': None} } """ # 3. 权限匹配 match = False for k,v in permission_dict.items(): reg = "^%s$" % v['url'] if re.match(reg,current_url): # 用于以后生成菜单时候,设置默认选中的菜单。 if v['menu_id']: request.default_selected_menu_name = k else: request.default_selected_menu_name = v['parent_name'] match = True break if not match: return HttpResponse('无权访问')
from django.conf import settings def init_permission(user,request): """ 用户初始化,将权限信息和菜单信息放入session中。 :param user: 当前登录的用户对象 :param request: 请求相关的所有数据 :return: """ permission_menu_list = user.roles.filter(permissions__isnull=False).distinct().values( 'permissions__title', 'permissions__url', 'permissions__name', 'permissions__menu_id', # 菜单相关 'permissions__menu__title', 'permissions__menu__icon', 'permissions__parent_id', # 父权限相关 'permissions__parent__name' ) # 2.3 获取当前用户拥有的所有权限信息 + 获取当前用户拥有的所有权限信息 permission_dict = {} menu_dict = {} for item in permission_menu_list: # 添加权限字典中 name = item['permissions__name'] url = item['permissions__url'] menu_id = item['permissions__menu_id'] parent_name = item['permissions__parent__name'] permission_dict[name] = {'url': url, 'menu_id': menu_id, 'parent_name': parent_name} # 添加到菜单字典中(只要可以成为菜单的权限) if menu_id: menu_id = item['permissions__menu_id'] if menu_id in menu_dict: menu_dict[menu_id]['children'].append( {'title': item['permissions__title'], 'url': item['permissions__url'], 'name': item['permissions__name']}) else: menu_dict[menu_id] = { 'title': item['permissions__menu__title'], 'icon': item['permissions__menu__icon'], 'class': 'hide', 'children': [ {'title': item['permissions__title'], 'url': item['permissions__url'], 'name': item['permissions__name']} ] } request.session[settings.RBAC_PERMISSION_SESSION_KEY] = permission_dict request.session[settings.RBAC_MENU_SESSION_KEY] = menu_dict
menu-html <div class="multi-menu"> {% for item in menus %} <div class="item"> <div class="title"><span class="icon-wrap"> <i class="fa {{ item.icon }}"></i></span> {{ item.title }} </div> <div class="body {{ item.class }}"> {% for child in item.children %} <a class="{{ child.class }}" href="{{ child.url }}">{{ child.title }}</a> {% endfor %} </div> </div> {% endfor %} </div> layout.html {% load staticfiles %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>路飞学城</title> <link rel="shortcut icon" href="{% static 'rbac/imgs/luffy-study-logo.png' %} "> <link rel="stylesheet" href="{% static 'rbac/plugins/bootstrap/css/bootstrap.css' %} "/> <link rel="stylesheet" href="{% static 'rbac/plugins/font-awesome/css/font-awesome.css' %} "/> <link rel="stylesheet" href="{% static 'rbac/css/commons.css' %} "/> <link rel="stylesheet" href="{% static 'rbac/css/nav.css' %} "/> <link rel="stylesheet" href="{% static 'rbac/css/menu.css' %} "/> <style> body { margin: 0; } .no-radius { border-radius: 0; } .no-margin { margin: 0; } .pg-body > .left-menu { background-color: #EAEDF1; position: absolute; left: 1px; top: 48px; bottom: 0; 220px; border: 1px solid #EAEDF1; overflow: auto; } .pg-body > .right-body { position: absolute; left: 225px; right: 0; top: 48px; bottom: 0; overflow: scroll; border: 1px solid #ddd; border-top: 0; font-size: 13px; min- 755px; } .navbar-right { float: right !important; margin-right: -15px; } .luffy-container { padding: 15px; } </style> </head> <body> <div class="pg-header"> <div class="nav"> <div class="logo-area left"> <a href="#"> <img class="logo" src="{% static 'rbac/imgs/logo.svg' %}"> <span style="font-size: 18px;">路飞学城 </span> </a> </div> <div class="left-menu left"> <a class="menu-item">资产管理</a> <a class="menu-item">用户信息</a> <a class="menu-item">路飞管理</a> <div class="menu-item"> <span>使用说明</span> <i class="fa fa-caret-down" aria-hidden="true"></i> <div class="more-info"> <a href="#" class="more-item">管他什么菜单</a> <a href="#" class="more-item">实在是编不了</a> </div> </div> </div> <div class="right-menu right clearfix"> <div class="user-info right"> <a href="#" class="avatar"> <img class="img-circle" src="{% static 'rbac/imgs/default.png' %}"> </a> <div class="more-info"> <a href="#" class="more-item">个人信息</a> <a href="#" class="more-item">注销</a> </div> </div> <a class="user-menu right"> 消息 <i class="fa fa-commenting-o" aria-hidden="true"></i> <span class="badge bg-success">2</span> </a> <a class="user-menu right"> 通知 <i class="fa fa-envelope-o" aria-hidden="true"></i> <span class="badge bg-success">2</span> </a> <a class="user-menu right"> 任务 <i class="fa fa-bell-o" aria-hidden="true"></i> <span class="badge bg-danger">4</span> </a> </div> </div> </div> <div class="pg-body"> <div class="left-menu"> <div class="menu-body"> <!-- 导入xxxxxxx模块 --> {% load rbac %} <!-- 执行get_menu函数并传递了一个参数 --> {% get_menu request %} </div> </div> <div class="right-body"> <div> <ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;"> <li><a href="#">首页</a></li> <li class="active">客户管理</li> </ol> </div> {% block content %} {% endblock %} </div> </div> <script src="{% static 'rbac/js/jquery-3.3.1.min.js' %} "></script> <script src="{% static 'rbac/plugins/bootstrap/js/bootstrap.js' %} "></script> {% block js %} {% endblock %} <script> $(function () { $('.multi-menu .title').click(function () { $(this).next().toggleClass('hide'); }); }) </script> </body>. </html>
from django.template import Library from django.conf import settings register = Library() @register.filter def permission(name,request): if name in request.session.get(settings.RBAC_PERMISSION_SESSION_KEY): return True @register.inclusion_tag('rbac/menu.html') def get_menu(request): """ 动态生成二级菜单 :param request: :return: """ menu_dict = request.session.get(settings.RBAC_MENU_SESSION_KEY) """ { 1: { 'title': '用户管理', 'icon': 'fa-clipboard', 'class':'', 'children': [ {'title': '用户列表', 'url': '/app01/user/', 'name': 'user_list','class':'active'} ] }, 2: { 'title': '商品管理', 'icon': 'fa-clipboard', 'class':'hide', 'children': [ {'title': '订单列表', 'url': '/app01/order/', 'name': 'order'}, {'title': '个人中心', 'url': '/app01/certer/', 'name': 'center'} ] } } """ for k,v in menu_dict.items(): for child in v['children']: name = child['name'] if request.default_selected_menu_name == name: child['class'] = 'active' v['class'] = '' return {'menus': list(menu_dict.values()) }