今日內容:不一樣用戶分權限(展現不一樣的菜單)
內容回顧:
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; width: 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-width: 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()) }