一、模板繼承
知識點:
users.html / roles.html 繼承自 base.html
頁面滾動時,固定
.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:
<!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:
{% extends 'base.html' %}
{% block con %}
<h4>用戶列表</h4>
{% for user in user_list %}
<p>{{ user }}</p>
{% endfor %}
{% endblock con%}
roles.html:
{% extends 'base.html' %}
{% block con %}
<h4>角色列表</h4>
<ul>
{% for role in role_list %}
<p>{{ role }}</p>
{% endfor %}
</ul>
{% endblock %}
二、在users.html中添加table
{% 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)有一些用戶有多重角色,需要將這些角色拿到顯示在表格中的方法
<td>
{% for role in user.roles.all %}
{{ role.title }}
{% endfor %}
</td>
(2)django模板中的forloop模板變量:在每個`` {% for %}``循環里有一個稱為`` forloop`` 的模板變量。這個變量有一些提示循環進度信息的屬性。
forloop.counter 總是一個表示當前循環的執行次數的整數計數器。 這個計數器是從1開始的,所以在第一次循環時 forloop.counter 將會被設置為1。
三、根據權限決定是否顯示按鈕
在頁面往往有一些功能按鈕,如果該用戶沒有權限,就不將這個按鈕開放給當前用戶,這樣處理優於向用戶提示“沒有使用權限”。
1、在模板中權限按鈕控制的簡單形式
{# 根據是否有權限顯示添加用戶按鈕 #}
{% if "/users/add" in permission_list %}
<a href="/users/add" class="btn btn-primary">添加用戶</a>
{% endif %}
處理帶有正則表達式的url:
<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:
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代碼
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:
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)函數
# -*- 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中注冊權限列表:
request.session['permission_list'] = permission_list
現在需要在session中注冊的是權限字典:
request.session['permission_dict'] = permission_dict
(2)從角色表到權限表跨表查詢權限路徑、權限組ID、權限action
# 角色表跨到權限表查找
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: {
"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類
# -*- 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拿到當前用戶對這個表的所有操作權限。
request.actions = item["actions"]
(2)數據類型從數組變為了字典,數據處理方式略有不同。
6、改寫users視圖,視圖添加Per類
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改寫
{% 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、權限粒度控制
簡單控制:
{% if "users/add" in permissions_list%}
2、更改數據庫結構
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
3、登錄驗證
permissions = user.roles.all().values("permissions__url","permissions__group_id","permissions__action").distinct()
4、構建permission_dict
5、中間件校驗權限
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中
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)注冊菜單權限:
# 注冊菜單權限
permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action",
"permissions__group__title").distinct()
其中permissions__group__title是跨三張表查詢。
(2)在菜單權限列表中添加元組,每個元組包含url和權限組title信息。
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中:
# 注冊到session中 request.session["menu_permission_list"] = menu_permission_list
2、自定義標簽(inclusion_tag)
因為模板繼承,只繼承樣式,不繼承數據!所以需要用到自定義標簽(inclusion_tag)。
在rbac項目下創建一個templatetags文夾。這個文件夾的名字必順是templatetags來命名的。然后在此文件夾下自定義一個my_tags.py文件。
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模板:
<div>
{% for item in menu_permission_list %}
<p class="menu_btn"><a href="{{ item.0 }}">{{ item.1 }}</a></p>
{% endfor %}
</div>
修改base.html模板:
<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中添加:
APPEND_SLASH = False
在不添加這個配置時,django默認APPEND_SLASH的值為True,django會默認的加 / 發重定向。
ajax中的url和這里同理。
