目標:生成一個獨立的組件,到哪都能用
一、先創建一個 項目,建一個app01和rbac的應用
二、表結構設計
1、先看配置文件合適不,給創建的rbac在配置文件里面設置一下
找到INSTALLED_APPS=['rbac']
配置靜態文件
2、設計表結構
models中創建類:五個類,七張表
角色表:
用戶表:
權限表:
權限組表:
菜單表:
角色表和權限表是多對多的關系(一個角色可以有多個權限,一個權限可以對應多個角色)
用戶表和角色表是多對多的關系(一個用戶可以有多個角色,一個角色有多個用戶)
所以有會多生成兩張關系表
一個菜單下面有多個組
一個組下面有多個菜單
一個菜單下面有多個權限
from django.db import models # Create your models here. class Role(models.Model): title = models.CharField(max_length=32,verbose_name="角色") permissions = models.ManyToManyField(to="Permission",verbose_name="擁有權限的角色",blank=True) #權限和角色是多對多的關系 def __str__(self): return self.title class Meta: verbose_name_plural = "角色表" class Permission(models.Model): title = models.CharField(max_length=32,verbose_name="權限名") url = models.CharField(max_length=32,verbose_name="帶正則的url") codes = models.CharField(max_length=32,verbose_name="代碼") group = models.ForeignKey(to="Group",verbose_name="所屬組",blank=True) #組和權限是一對多的關系,一個組有多個權限 menu_gp = models.ForeignKey(to='Permission',related_name='aaa',null=True,blank=True,verbose_name="組內菜單") def __str__(self): return self.title class Meta: verbose_name_plural = "權限表" class UserInfo(models.Model): name = models.CharField(max_length=32,verbose_name="姓名") password = models.CharField(max_length=64,verbose_name="密碼") email = models.CharField(max_length=32,verbose_name="郵箱") roles = models.ManyToManyField(to="Role",blank=True) #用戶和角色是多對多的關系 def __str__(self): return self.name class Meta: verbose_name_plural = "用戶表" class Group(models.Model): title = models.CharField(max_length=32,verbose_name="組名稱") menu = models.ForeignKey(to="Menu",verbose_name="組內菜單",blank=True) #一個組下有多個菜單 def __str__(self): return self.title class Meta: verbose_name_plural = "權限組" class Menu(models.Model): caption = models.CharField(max_length=32,verbose_name="菜單") def __str__(self): return self.caption class Meta: verbose_name_plural = "菜單表"
具體分析為什么要多加個code列和權限組表呢?
1、我們一般是先看到的是列表頁面,在這個頁面上是否顯示添加,是否顯示編輯,是否顯示刪除,都是需要判斷的
有無添加權限,有無刪除權限,有無編輯權限,我們可以給每一個url一個代號
dict = { 1:{ 代號 /userinfo/ list /userinfo/add/ add /userinfo/del(\d+)/ del /userinfo/edit(\d+)/ edit } }
不僅在列表頁面需要知道他有那些權限,在其他頁面也知道他有那些權限
所以上面的方案還是有點不好,那么我們采取下面的方案。將代號取出來放在一個列表里面
dict = { 1:{ "codes":["list","add","del","edit"] urls:[ "/userinfo/", "/userinfo/add"/, "/userinfo/del(\d+)/ ", "/userinfo/edit(\d+)/ ", ] } 2:{ "codes":{"list","add","del","edit"} urls:[ "/order", "/order/add"/, "/order/del(\d+)/ ", "/order/edit(\d+)/ ", ] } }
把這個字典存到session中
當你訪問頁面的時候我就知道你有什么權限
一個url對應一個code
多個url對應一個組
注意:
關聯字段 null = True 數據庫用的時候可以為空
關聯字段 blank = True admin用的時候可以為空
當出現這個錯誤的時候
解決辦法
python manage.py migrate --fake 廢棄
三、通過django-admin錄入權限數據
- 先創建一個超級用戶 python3 manage.py createsuperuser - 用戶名 root - 密碼 zhy123456 - 在admin.py 中 from rbac import models admin.site.register(models.Permission) admin.site.register(models.Role) admin.site.register(models.UserInfo) 這樣的話上去的是英文的,如果你想讓中文顯示就在類中加一個類 class Meta: verbose_name_plural = "權限表" - 當你給關聯字段錄入數據的時候會有錯誤提示,那么在類中你的那個關聯字段在加一個屬性blank = True 可以為空 permissions = models.ManyToManyField(to="Permission",verbose_name="具有的所有權限", blank=True)
四、編寫登錄
1.編寫登錄
2.如果用戶驗證成功就設置session
3.先查出當前用戶的所有的權限
4.從這些權限中找到所有的url,吧這些url放到session中
這些都是在rbac里面的操作,如果我們做一些復雜的操作,可能會有好多的代碼
我們寫rbac的目的是做成一個公共的組件,為了讓別人省事
我們在創建一個server的文件夾,里面創建一個init_permission的py文件。
結構化數據:方便以后做操作。
dict = { 1:{ "codes":["list","add","del","edit"] urls:[ "/userinfo/", "/userinfo/add"/, "/userinfo/del(\d+)/ ", "/userinfo/edit(\d+)/ ", ] } 2:{ "codes":{"list","add","del","edit"} urls:[ "/order", "/order/add"/, "/order/del(\d+)/ ", "/order/edit(\d+)/ ", ] } }
5.拿到用戶請求的url去session里面做驗證
獲取當前請求的url
獲取session中保存當前用戶的權限
然后開始驗證
如果匹配成功就有權訪問
如果匹配不成功就無權訪問
用re去匹配的時候,re.match(/userinfo/,/userinfo/add) #都能匹配到
那么要記得在匹配正則的時候加個起始符和終止符regex = "^{0}$".format(url)
def login(request):
.....
設置session
def index(request):
....
獲取session
def userinfo(request):
獲取session
這樣如果有好多個函數,就的重復好多代碼,我們可以用中間件來處理
中間件和裝飾器的區別:
中間件用來做批量處理
如果函數不多的話可以用加裝飾器的方法
五、中間件:獲取session,並且當用戶匹配成功的時候,先把code保存在request中,方便以后判斷
1、記得要配置白名單
2、必須繼承MiddlewareMixin這個類
class MiddlewareMixin(object): def __init__(self, get_response=None): self.get_response = get_response super(MiddlewareMixin, self).__init__() def __call__(self, request): response = None if hasattr(self, 'process_request'): response = self.process_request(request) if not response: response = self.get_response(request) if hasattr(self, 'process_response'): response = self.process_response(request, response) return response
六、設計權限管理-----問題:在訪問列表頁面時,是否需要判斷有無添加權限、有無刪除權限、有無編輯權限。
views.py
def userinfo(request): # 方式一 # Page_permission = request.permission_code_list # 方式二:實例化 page_permission = BasePagePermission(request.permission_code_list) print("page_permission",request.permission_code_list) data_list = [ {"id":1,"name":"xxx1"}, {"id":2,"name":"xxx2"}, {"id":3,"name":"xxx3"}, {"id":4,"name":"xxx4"}, {"id":5,"name":"xxx5"}, ] return render(request,"userinfo.html",{"data_list":data_list,"page_permission":page_permission})
在模板userinfo.html中:兩種使用方式
方式一:
<table> {% if "add" in Page_permission %} <a href="#">添加</a> {% endif %} {% for row in data_list %} <tr> <td>{{ row.id }}</td> <td>{{ row.name }}</td> {% if "edit" in Page_permission %} <td><a href="#">編輯</a></td> {% endif %} {% if "del" in Page_permission %} <td>{<a href="#">刪除</a></td> {% endif %} </tr> {% endfor %} </table>
如果不想像上面一樣每個都判斷,那么還有第二種方法,
方式二:
把permission_code_list處理一下 在views中定義一個類 class BasePagePermission(object): def __init__(self,code_list): self.code_list = code_list def has_add(self): if "add" in self.code_list: return True def has_del(self): if "del" in self.code_list: return True def has_edit(self): if "edit" in self.code_list: return True 實例化:page_permission = BasePagePermission(request.permission_code_list) 在模板中 <table> {% if page_permission.has_add %} <a href="#">添加</a> {% endif %} {% for row in data_list %} <tr> <td>{{ row.id }}</td> <td>{{ row.name }}</td> {% if page_permission.has_edit %} <td><a href="#">編輯</a></td> {% endif %} {% if page_permission.has_del %} <td>{<a href="#">刪除</a></td> {% endif %} </tr> {% endfor %} </table>
七、設計菜單管理-----問題:1、如何生成菜單
2、怎么讓這些菜單分級顯示並且如果當前訪問的url權限默認展開如果是組內菜單就加粗或者變紅
3、非菜單url,默認選中原菜單。(如果你是點擊用戶列表進來的,那么你看到頁面了,如果你點擊添加的時候,你的那個用戶列看不見了,這就不好了。所以要設計當你點擊添加按鈕的時候,那個用戶列表被默認選中)
菜單管理
菜單一
用戶管理
權限管理
菜單二
訂單管理
角色管理
分級做了菜單。這些菜單該顯示什么菜單?是當前用戶登錄之后從數據庫拿到這個用戶擁有的權限,然后把權限搞成菜單
在表里面設計了一個組內菜單(自關聯 ),當menu_gp_id為NULL就代表可以作為菜單
1、在初始化的時候,初始化權限信息,獲取權限信息並放置到session中
menu_list = [] for item in permission_list: tpl = { "id":item["permissions__id"], "title":item["permissions__title"], "url":item["permissions__url"], "menu_gp_id":item["permissions__menu_gp_id"], "menu_id":item["permissions__group__menu_id"], "menu_title":item["permissions__group__menu__caption"] } menu_list.append(tpl) request.session[settings.PERMISSION_MENU_KEY] = menu_list
因為是要在頁面上渲染,一般我們會在視圖函數的render里面加{"":變量}這樣渲染,
但是還有個更好用的方法:用自定義的標簽
具體操作:
1、找到app創建一個templatetags的文件夾
2、然后在里面隨便創建一個文件
3、導入form django.template import Library
register = Library()
方式一:
@register.simple_tag
def menu():
return "菜單" 這里返回啥頁面上就顯示啥
然后在母版里面導入mnue.html
{% load rbac %}
方式二:
@register.includsion_tag("xxx.html") #這里存放的是html文件,,,@register.includsion_tag("xxx.html") 自動會讀這個文件並且把返回值拿到在頁面上渲染
def menu():
return "菜單" 這里返回啥頁面上就顯示啥
“在母版中:{%menu_html request%} request是參數,記得要加上{% load rbac %}
4、注意:
如果有兩個文件夾同名,避免發生沖突:就再創建一個文件夾包起來
2、去Session中獲取菜單相關信息,匹配當前URL,生成菜單
①先把和菜單相關的所有字段取出來
menu_list = [ {'id': 1, 'title': '用戶列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜單二'}, {'id': 2, 'title': '添加用戶', 'url': '/userinfo/add/', 'menu_gp_id': 1, 'menu_id': 2, 'menu_title': '菜單二'}, {'id': 3, 'title': '刪除用戶', 'url': '/userinfo/del/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 2, 'menu_title': '菜單二'}, {'id': 4, 'title': '編輯用戶', 'url': '/userinfo/edit/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 2, 'menu_title': '菜單二'}, {'id': 5, 'title': '訂單列表','url': '/order/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜單一'}, {'id': 6, 'title': '添加訂單', 'url': '/order/add/', 'menu_gp_id': 2, 'menu_id': 1, 'menu_title': '菜單一'}, {'id': 7, 'title': '刪除訂單', 'url': '/order/del/(\\d+)/', 'menu_gp_id': 2, 'menu_id': 1, 'menu_title': '菜單一'}, {'id': 8, 'title': '編輯訂單', 'url': '/order/edit/(\\d+)/', 'menu_gp_id': 2, 'menu_id': 1, 'menu_title': '菜單一'} ]
②然后循環列表找出可以作為菜單的權限
{ 1: {'id': 1, 'title': '用戶列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜單二'}, 5: {'id': 5, 'title': '訂單列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜單一'} }
③再次循環列表向上邊的字典中添加active
{ 1: {'id': 1, 'title': '用戶列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜單二', 'active': True}, 5: {'id': 5, 'title': '訂單列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜單一'} }
④結構化數據(吧上面得到的數據化成下面這樣格式的,方便以后使用)
{ 1: { 'menu_id': 1, 'menu_title': '菜單一', 'active': None, 'children': [ {'title': '訂單列表', 'url': '/order/', 'active': None} ] } 2: { 'menu_id': 2, 'menu_title': '菜單二', 'active': True, 'children': [ {'title': '用戶列表', 'url': '/userinfo/', 'active': True} ] }, }