写在前面
世间安得双全法 不负如来不负卿
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 s17day26 CRM项目 2 3 项目概要:XX公司CRM 4 - 权限管理,公共组件,app ***** 5 - 熟悉增删改查,Low *** 6 - 增删改查组件,公共组件,app **** 7 8 内容回顾: 9 1. .all,values,values_list 10 models.xx.objects.all() 11 ---> [obj,obj,obj,] 12 models.xx.objects.values('id','name') 13 ---> [{"id":1,'name':'兴普'},{"id":2,'name':'兴普1'},] 14 models.xx.objects.values_list('id','name') 15 ---> [(1,'兴普'),(2,'记不住就别来了')] 16 17 2. 中间件是什么?有什么作用? 18 19 class Md1: 20 21 def process_request(self,request): 22 pass 23 24 class process_response(self,request,response): 25 return response 26 27 class Md2: 28 29 def process_request(self,request): 30 pass 31 32 class process_response(self,request,response): 33 return response 34 35 36 37 MIDDLEWARE = [ 38 39 "xxxxx.xxxx.Md1", 40 "xxxxx.xxxx.Md2" 41 ] 42 43 3. ORM创建表 44 45 FK常用操作 46 class A(Model): 47 name = models.CharField(...) 48 49 50 class B(Model): 51 name = models.CharField(...) 52 fk = models.FK(A) 53 54 a. B表中有几列 55 id 56 name 57 fk_id 58 59 b. 跨表操作 60 b_list = models.B.objects.all() 61 for item in b_list: 62 item.id 63 item.name 64 item.fk_id 65 item.fk 66 item.fk.name 67 item.fk.id 68 c. 跨表操作 69 b_list = models.B.objects.values('id','name','fk_id','fk__name') 70 for item in b_list: 71 item['id'] 72 item['name'] 73 item['fk_id'] 74 item['fk__name'] 75 76 d. 跨表操作 77 b_list = models.B.objects.vlaues_list('id','name','fk_id','fk__name') 78 for item in b_list: 79 item[0] 80 item[1] 81 item[2'] 82 item[3] 83 e. 找A名称=“吴一飞”所有B表中的数据 84 models.B.objects.filter(fk__name='吴一飞') 85 86 M2M常用操作: 87 88 class A(Model): 89 name = models.CharField(...) 90 91 92 class B(Model): 93 name = models.CharField(...) 94 m = models.M2M(A) 95 96 97 a. B表中有几列 98 id 99 name 100 101 PS: 自动生成一个第三张表;m用于间接的对第三张表进行操作 102 103 b. 在B表中插入三条数据;A表中插入2条数据 104 models.A.objects.create(name='赵峰分') 105 models.A.objects.create(name='王勇') 106 models.A.objects.create(name='刘宏伟') 107 108 109 models.B.objects.create(name='兴普') 110 models.B.objects.create(name='香姐') 111 models.B.objects.create(name='名扬') 112 113 c. 兴普和【赵峰分,王勇,刘宏伟】创建关系 114 115 obj = models.B.objects.create(name='兴普') 116 obj.m.add(1) 117 obj.m.add(2) 118 obj.m.add(3) 119 obj.m.add(*[1,2,3]) 120 121 d. 查找和兴普有关系的男人? 122 obj = models.B.objects.create(name='兴普') 123 obj.m.all() [A—obj,A—obj,A—obj] 124 125 4. Session是什么?和Cookie有什么区别? 126 127 5. 正则表达式 128 129 re.match() 130 131 ^ 132 133 $ 134 135 136 示例程序:pro_crm 137 员工模块: 138 权限,根据含有正则表达式URL进行分配 139 操作,通过配置文件进行定制 140 学生模块: 141 问卷 142 143 144 145 今日内容: 146 147 权限:授权 148 149 s17crm 150 - app01 151 - rbac 152 - models.py 153 154 第一版: 155 1. 权限表(含有正则表达式),permission 156 157 id url title 158 1 /userinfo/ 用户列表 159 2 /userinfo/(\d+)/delete/ 删除用户 160 3 /userinfo/(\d+)/change/ 修改用户 161 4 /userinfo/add/ 添加用户 162 163 164 2. 用户表,userinfo 165 id username password 166 1 yql 123 167 2 wxp 123 168 3 zmx 123 169 4 ll 123 170 171 172 3. 用户和权限关系表 173 id 用户ID 权限ID 174 1 1 175 2 1 176 2 2 177 2 3 178 2 4 179 3 1 180 4 1 181 4 2 182 4 3 183 4 4 184 问题:用户分配权限时,麻烦 185 186 第二版: 187 188 0. 权限组,group 189 id title 190 1 用户组 191 2 订单组 192 193 194 195 1. 权限表(含有正则表达式),permission 196 197 id url title code group_i1 is_menu 198 1 /userinfo/ 用户列表 list 1 true 199 2 /userinfo/(\d+)/delete/ 删除用户 del 1 false 200 3 /userinfo/(\d+)/change/ 修改用户 edit 1 false 201 4 /userinfo/add/ 添加用户 add 1 true 202 5 /order/add/ ... add 2 true 203 6 /order/(\d+)/delete/ del 2 false 204 7 /order/(\d+)/change/ edit 2 false 205 8 /order/ list 2 true 206 207 208 2. 用户表,userinfo 209 id username password 210 1 yql 123 211 2 wxp 123 212 3 zmx 123 213 4 ll 123 214 215 216 3. 角色表,role 217 id title 218 1 销售员 219 2 销售经理 220 3 市场专员 221 4 市场经理 222 5 IT 223 6 CTO 224 7 销售市场总监 225 8 CEO 226 227 4. 用户角色关系表 228 用户ID 角色ID 229 1 1 230 2 1 231 2 2 232 3 1 233 3 2 234 3 7 235 236 5. 角色和权限关系表 237 角色ID 权限ID 238 1 1 239 2 1 240 2 4 241 7 1 242 7 3 243 7 4 244 8 1 245 8 2 246 8 3 247 8 4 248 249 250 注意:根据用户找权限 251 - 用户具有的所有角色 252 - 再根据角色找所有权限(去重) 253 254 255 中午作业: 256 - 填充数据 257 - id=1的用户, 258 - 找具有所有角色 259 - 找到所有权限 260 - 找到所有权限(去重) 261 262 填充数据: 263 基于Django Admin数据填充 264 265 266 补充:不要相信你的眼睛 267 268 分配权限: 269 销售员:用户列表 270 孟祥杰 271 王勇 272 273 销售经理:用户列表,添加用户 274 银秋良 275 276 总监: 用户列表,添加用户,订单列表 277 兴普 278 279 CEO:.... 280 刘磊 281 282 283 284 注意:Djang Admin进行添加权限或授权 285 286 权限: 自动生成菜单 287 288 1. 获取数据 289 290 [ 291 {'menu_id': 4, 'menu_title': '用户管理菜单', 'title': '用户列表', 'url': '/userinfo/'}, 292 {'opened':True,'menu_id': 4, 'menu_title': '用户管理菜单', 'title': '订单列表', 'url': '/order/'}, 293 294 {'menu_id': 6, 'menu_title': '权限管理菜单', 'title': 'xxx列表', 'url': '/order/add/'} 295 {'menu_id': 6, 'menu_title': '权限管理菜单', 'title': 'xx列表', 'url': '/order/add/'} 296 ] 297 298 2. 根据数据创建两级菜单 299 用户管理菜单 300 用户列表! 301 订单列表 302 权限管理菜单 303 xxx列表 304 xx列表 305 306 307 思路: 308 page_menu = { 309 4:{'menu_id':4,'menu_title':'用户管理菜单','children': [{ 'title': '用户列表', 'url': '/userinfo/'},{'title': '订单列表', 'url': '/order/'} ]} 310 6: {'menu_id':4,'menu_title':'权限管理菜单','children': [{ 'title': '用户列表', 'url': '/userinfo/'},{'title': '订单列表', 'url': '/order/'} ]} 311 } 312 313 314 current_url = request.path_info 315 316 317 for item in permission_menu_list: 318 url = item['url'] 319 regex = xxx.format(url) 320 if re.match(regex,current_url): 321 item['opened'] = True 322 323 menu_id = item['menu_id'] 324 opened = "active" if item.get('opened') else "" 325 326 327 child = {'title':item['title'],'url':item['url'],'opened': opened} 328 if menu_id in page_menu: 329 page_menu[menu_id]['children'].append(child) 330 if opened: 331 page_menu[menu_id]['opened'] = opened 332 else: 333 page_menu[menu_id] = {'opened':opened, 'menu_title': item['menu_title'], 'children': [child, ]} 334 335 总结: 336 337 1. 表结构设计 338 339 2. 录入数据 340 341 3. 用户登录: 342 - 获取角色 343 - 获取权限 344 - 对权限去重 345 346 4. 结构,权限信息 347 { 348 1: { 349 'urls': ['/userinfo/', '/userinfo/add/', '/userinfo/(\\d+)/delete/', '/userinfo/(\\d+)/change/'], 350 'codes': ['list', 'add', 'del', 'edit'] 351 }, 352 2: { 353 'urls': ['/order/', '/order/add/', '/order/(\\d+)/delete/', '/order/(\\d+)/change/'], 354 'codes': ['list', 'add', 'del', 'edit'] 355 } 356 357 } 358 放入session中 359 5. 中间件 360 白名单 361 request.path_info 362 session获取权限,进行验证 363 364 6. 自动生成菜单 365 - inclusion_tag 366 - 母版 367 368 7. Django Admin 369 370 371 372 权限app中写代码: 373 - 中间件 374 - init_permission 375 - models 376 - admin 377 - templatetags 378 379 依赖配置文件: 380 XX = "permission_dict" 381 OO = "permission_menu_list" 382 RBAC_LOGIN_URL = "/login/" 383 URL_FORMAT = "^{0}$" 384 385 VALID_URL_LIST = [ 386 "^/login/$", 387 "^/admin.*", 388 ] 389 390 391 使用权限管理: 392 1. 创建cmdb project 393 2. rbac拷贝到项目中 394 3. 在settings.APP中将 rbac 注册 395 4. 在settings中设置 396 XX = "permission_dict" 397 OO = "permission_menu_list" 398 RBAC_LOGIN_URL = "/login/" 399 URL_FORMAT = "^{0}$" 400 401 VALID_URL_LIST = [ 402 "^/login/$", 403 "^/admin.*", 404 ] 405 5. 开发CMDB完成 406 407 6. 中间件中注册 408 7. 利用temaplatetags和母版获取动态菜单 409 410 8. 在Django Admin中操作权限信息 411 412 413 作业: 414 1. 权限系统:4天 415 2. 自己创建一个主机管理系统: 416 - 主机 417 - 主机组 418 - 部门 419 - 机房 420 3. 使用 权限系统[10天]
一、内容回顾
- .all .values .values_list
models.xxx.objects.all() --> [obj,obj,obj,...]
models.xxx.objects.values('id','name') --> [{'id':1,'name':'alex'},{'id':2,'name':'standby'},...]
models.xxx.objects.values_list('id','name') --> [(1,'alex'),(2,'standby',....)]
- 中间件是什么?有什么用?
可以拦截所有的请求和响应,进行一些处理
中间件执行时有序的,从上到下执行,如果当前中间件不允许过,那么就不会再执行后面的中间件
比如说:对用户的请求做下黑名单
process_request() 默认没有返回值,即返回None; 则继续往下执行
但是如果有返回值,则不再继续往下执行,直接返回;
每个中间件就是一个类:
class Md1:
def process_request(self,request):
pass
def process_response(self,request,response):
return response
class Md2:
def process_request(self,request):
pass
def process_response(self,request,response):
return response
settings.py里配置
MIDDLEWARE = [
"XXXXXX.XXXXX.Md1",
"XXXXXX.XXXXX.Md2",
]
- ORM创建表
- ForeignKey常用操作
class A(Model):
name = models.CharField(...)
class B(Model):
name = models.CharField(...)
fk = models.ForeignKey(A)
a. B表中有几列:
id
name
fk_id
b. 跨表操作
b_list = models.B.objects.all()
for item in b_list:
item.id
item.name
item.fk
item.fk_id
item.fk.id
item.fk.name
c. 跨表操作
b_list = models.B.objects.values('id','name','fk_id','fk__name')
for item in b_list:
item['id']
item['name']
item['fk_id']
item['fk__name']
d. 跨表操作
b_list = models.B.objects.vlaues_list('id','name','fk_id','fk__name')
for item in b_list:
item[0]
item[1]
item[2]
item[3]
e. 找A表名称等于"alex"的所有B表中的数据 *****
models.B.objects.filter(fk__name="alex") good
models.B.objects.filter(fk.name="alex") 这个是不可以,需要验证!!!!!!
- M2M常用操作
class A(Model):
name = models.CharField(...)
class B(Model):
name = models.CharField(...)
m = models.ManyToMany(A)
a. B表中有几列?
id
name
PS:ManyToMany 会自动生成第三张表,字段m用于间接的对第三张表进行操作
b. 在B表中插入3条数据,A表中插入2条数据
models.A.objects.create(name='alex1')
models.A.objects.create(name='alex2')
models.B.objects.create(name='egon1')
models.B.objects.create(name='egon2')
models.B.objects.create(name='egon3')
c. 让 egon1 和 [alex1,alex2] 创建关系
obj = models.B.objects.filter('name'='egon1')
obj.m.add(*[1,2])
d. 查找和egon1有关系的人
obj = models.B.objects.filter('name'='egon1')
obj.m.all() # 拿到了queryset对象集合,都是A表的对象
- Session是什么? 和cookie有什么区别?
- session依赖cookie而存在
- session是存储在服务端的键值对
- cookie是存储在客户端浏览器里的键值对
- 用户第一次来访问主页,服务端生成一个随机字符串通过cookie返回给客户端
同时服务端把这个字符串当做key存储在数据库表里(也可以存在其他地方),值可以为空
- 用户第二次发起登录请求则带上刚才的cookie和用户信息
服务端则把用户信息(比如用户名密码)用某种方式存储在刚才随机字符串对应的值里
这样就表示用户登录过了
二、CRM项目介绍
- CRM简介
xxx公司的CRM项目
- 权限管理 (写成一个公共的组件/app,适用于Django项目) *****
- low的增删改查 ***
- 增删改查的组件 (写成公共组件/app) ****
- 员工模块
- 权限:根据 含有正则表达式的url 进行分配的
- 操作:通过配置文件进行定制
- 学生模块
- 问卷
- RBAC简介
RBAC(Role-Based Access Control,基于角色的权限控制)
就是用户通过角色与权限进行关联。
简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。
这样,就构造成“用户-角色-权限”的授权模型。
在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。
三、表结构设计
- 第一版
- permission权限表(含有正则表达式的url)
id url title
1 /userinfo/ 用户列表
2 /userinfo/(\d+)/delete/ 删除用户
3 /userinfo/(\d+)/change/ 修改用户
4 /userinfo/add/ 添加用户
- userinfo用户表
id username password
1 alex 123
2 egon 123
3 standby 123
- 用户和权限的关系表
id user_id permission_id
1 1 1
2 1 2
3 2 1
4 3 1
5 3 2
6 3 3
问题:用户分配权限时,很麻烦,不方便后面的维护
- 第二版
- permission权限表(含有正则表达式的url)
id url title
1 /userinfo/ 用户列表
2 /userinfo/(\d+)/delete/ 删除用户
3 /userinfo/(\d+)/change/ 修改用户
4 /userinfo/add/ 添加用户
- userinfo用户表
id username password
1 alex 123
2 egon 123
3 standby 123
- role 角色表
id title
1 销售员
2 销售经理
3 市场专员
4 市场经理
5 IT
6 CTO
7 CEO
- 用户和角色关系表
id user_id role_id
1 1 1
2 2 1
3 2 2
4 3 1
5 3 2
6 3 7
- 角色和权限的关系表
id role_id permission_id
1 1 1
2 2 1
3 2 4
4 6 1
5 6 3
6 6 4
7 7 1
8 7 2
9 7 3
10 7 4
列出standby所具有的所有权限,可能会重复,所以需要去重;
注意:根据用户找权限
- 根据用户找角色
- 根据角色找权限
- 增加权限组表group
id title
1 用户组
2 订单组
- permission权限表 增加一个代号code字段、权限组字段以及 是否是菜单字段(涉及到在前端页面展示):
- permission权限表(含有正则表达式的url)
id url title code group_id is_menu
1 /userinfo/ 用户列表 list 1 true
2 /userinfo/(\d+)/delete/ 删除用户 del 1 false
3 /userinfo/(\d+)/change/ 修改用户 edit 1 false
4 /userinfo/add/ 添加用户 add 1 true
5 /order/ 订单列表 list 2 true
6 /order/(\d+)/delete/ 删除订单 del 2 false
7 /order/(\d+)/change/ 修改订单 edit 2 false
8 /order/add/ 添加订单 add 2 true
class Group(models.Model):
"""
权限组表
"""
title = models.CharField(max_length=32)
class Meta:
verbose_name_plural = "权限组表"
def __str__(self):
return self.title
class Permission(models.Model):
"""
权限表
"""
title = models.CharField(verbose_name='标题',max_length=32)
url = models.CharField(verbose_name="含正则的URL",max_length=128)
code = models.CharField(verbose_name="权限代号",max_length=16)
group = models.ForeignKey(verbose_name="权限组",to="Group") # 把具体的权限分组管理,类似省市县
is_menu = models.BooleanField(verbose_name="是否是菜单") # 用来筛选出可以在左侧菜单展示的选项
class Meta:
verbose_name_plural = "权限表"
def __str__(self):
return self.title
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",blank=True)
class Meta:
verbose_name_plural = "用户表"
def __str__(self):
return self.username
class Role(models.Model):
"""
角色表
"""
title = models.CharField(verbose_name='角色名称',max_length=32)
permissions = models.ManyToManyField(verbose_name="具有所有权限",to='Permission',blank=True)
class Meta:
verbose_name_plural = "角色表"
def __str__(self):
return self.title
让组和菜单创建关系 页面显示的时候是不显示组的
组的存在只是为了在分配权限的时候方便一些:一个组下面的增删改查
但是菜单的信息存储在哪呢?
- 可以把菜单和组都存储在 Group表里
- 增加parent字段,让某些组属于某个菜单(ORM里就是自关联,需要related_name字段)
- 然后再增加一个is_group字段用来区分组和菜单
补充:
- 具体的权限只能在 组 下面,不能直接挂载菜单下面
class Permission(models.Model):
"""
权限表
"""
title = models.CharField(verbose_name='标题',max_length=32)
url = models.CharField(verbose_name="含正则的URL",max_length=128)
code = models.CharField(verbose_name="权限代号",max_length=16)
# group = models.ForeignKey(verbose_name="权限组",to="Group") # 把具体的权限分组管理,类似省市县
group = models.ForeignKey(verbose_name="权限组",to="Group",limit_choices_to={'is_group':True})
is_menu = models.BooleanField(verbose_name="是否是菜单") # 用来筛选出可以在左侧菜单展示的选项
class Meta:
verbose_name_plural = "权限表"
def __str__(self):
return self.title
group = models.ForeignKey(verbose_name="权限组",to="Group")
group = models.ForeignKey(verbose_name="权限组",to="Group",limit_choices_to={'is_group':True})
就是说:只有是组的时候才能做 Permission表的外键
最终的表结构如下:
class Group(models.Model):
"""
权限组表
"""
title = models.CharField(max_length=32)
parent = models.ForeignKey(to="Group",related_name="xxx",null=True,blank=True)
is_group = models.BooleanField(verbose_name="是否是组",default=True)
class Meta:
verbose_name_plural = "权限组表"
def __str__(self):
return self.title
class Permission(models.Model):
"""
权限表
"""
title = models.CharField(verbose_name='标题',max_length=32)
url = models.CharField(verbose_name="含正则的URL",max_length=128)
code = models.CharField(verbose_name="权限代号",max_length=16)
# group = models.ForeignKey(verbose_name="权限组",to="Group") # 把具体的权限分组管理,类似省市县
group = models.ForeignKey(verbose_name="权限组",to="Group",limit_choices_to={'is_group':True})
is_menu = models.BooleanField(verbose_name="是否是菜单") # 用来筛选出可以在左侧菜单展示的选项
class Meta:
verbose_name_plural = "权限表"
def __str__(self):
return self.title
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",blank=True)
class Meta:
verbose_name_plural = "用户表"
def __str__(self):
return self.username
class Role(models.Model):
"""
角色表
"""
title = models.CharField(verbose_name='角色名称',max_length=32)
permissions = models.ManyToManyField(verbose_name="具有所有权限",to='Permission',blank=True)
class Meta:
verbose_name_plural = "角色表"
def __str__(self):
return self.title
四、需求分析
# 几点注意事项
- 双下划线跨表取值
- 外键和多对多 都可以用 __(双下划线) 跨表获取对应字段
- user -> 多个角色roles -> 根据角色的permissions字段加 '__' 跨到权限表
```
permission_list = user.roles.filter(permissions__id__isnull=False).values(
'permissions__id',
'permissions__title',
'permissions__url',
'permissions__code',
'permissions__group',
'permissions__is_menu'
).distinct()
```
- 列表distinct()去重
- [].distinct() 进行去重
- 自定义配置
- 在settings.py里定义好 (变量名一定要大写)
- 在代码里引入
Django的所有配置(既包含自定义的也包含Django内置的):
from django.conf import settings
- 找到某个用户所具有的权限
- 先查找所具有的角色
- 再查找所具有的权限
- 最后把权限去重
- 自动生成权限菜单
- 获取数据
- 创建两级菜单
- 两级菜单生成
- 在视图函数中拼接好返回给前端
- @register.simple_tag()
- @register.inclusion_rag("menu_tpl.html")
- 请求url验证中间件
- 要在settings.py里维护一份白名单
- 请求验证中间件依赖于session,所以自定义中间件的位置应该放在session中间件(SessionMiddleware)之后
- 对用户请求的url和该用户所具有的权限要做正则匹配:re.match(patten, current_request_url)
# 1/2/3 是组ID
{
1: {
'urls': ['/userinfo/', '/userinfo/add/'],
'codes': ['list', 'add']
},
2: {
'urls': ['/order/'],
'codes': ['list']
},
3: {
'urls': ['/index/'],
'codes': ['index']
}
}
[
{'menu_id': 4, 'menu_title': '用户管理菜单', 'permission_url': '/userinfo/', 'permission_title': '用户列表'},
{'menu_id': 4, 'menu_title': '用户管理菜单', 'permission_url': '/userinfo/add/', 'permission_title': '添加用户'},
{'menu_id': 6, 'menu_title': '权限管理菜单', 'permission_url': '/permissioninfo/', 'permission_title': '权限列表'},
{'menu_id': 6, 'menu_title': '权限管理菜单', 'permission_url': '/permissioninfo/add', 'permission_title': '添加权限'}
]
# 4/6 是菜单ID
# 菜单和组都在同一张表里,有父子关系,最后的目的是构造菜单和is_menu=True的权限item的两级菜单
{
4:{
'opened':False,
'menu_title':'用户管理菜单',
'children': [
{'title': '用户列表', 'url': '/userinfo/', 'opened':False},
{'title': '订单列表', 'url': '/order/', 'opened':False,}
]
},
6: {
'opened':False,
'menu_title':'权限管理菜单',
'children': [
{'title': '权限列表', 'url': '/userinfo/', 'opened':False},
{'title': '添加权限', 'url': '/order/', 'opened':False,}
]
}
}
总结:
- 表的设计(很重要)
4个类 6张表
- Django admin 录入数据
- 用户登录处理
- 获取角色
- 获取权限
- 对权限去重
- 然后把权限数据存储到session
- 登录中间件
- 白名单
- request.path_info
- 从session获取权限,进行验证
- 自动生成菜单
- 跨表取出相关数据,转换字典结构
- 视图中生成好字典返回给前端
- 前端模板渲染
- simple_tag
- inclusion_rag
- 模板layout.html
- 权限app包含哪些内容
- 中间件
- init_permission
- models
- admin
- templatetags
依赖配置文件
PERMISSION_DICT = "permission_dict"
URL_FORMAT = "^{0}$"
RBAC_LOGIN_URL = "/login/"
VALID_URL_LIST = [
"^/login/$",
"^/admin.*",
]
- 使用rbac权限管理
- 创建CMDB项目
- 把rbac拷贝到项目中
- 在settings.APP中注册 rbac
- 在settings中配置相关变量:
PERMISSION_DICT = "permission_dict"
URL_FORMAT = "^{0}$"
RBAC_LOGIN_URL = "/login/"
VALID_URL_LIST = [
"^/login/$",
"^/admin.*",
]
- 开发CMDB
- 开启中间件验证
- 利用tmplatetags和模板动态生成菜单
- 在Django admin中操作权限信息
练习
- 权限系统补充完整,搞清楚每一行代码 [4天]
- 自己创建project,比如主机管理系统
- 主机
- 主机组
- 部门
- 机房
- ...
- 使用rbac权限系统 [10天]
五、代码实现
表结构如上所述,此处略过...
# 项目结构
D:\soft\work\Python_17\day26\homework\s17crm>tree /F
卷 NewDisk 的文件夹 PATH 列表
卷序列号为 2E8B-8205
D:.
│ db.sqlite3
│ manage.py
│
│
├─app01
│ │ admin.py
│ │ apps.py
│ │ models.py
│ │ tests.py
│ │ views.py
│ └─ __init__.py
│
│
├─rbac
│ │ admin.py
│ │ apps.py
│ │ models.py
│ │ tests.py
│ │ views.py
│ │ __init__.py
│ │
│ ├─middleware
│ │ └─ rbac.py
│ │
│ │
│ ├─service
│ │ │ init_permission.py
│ │ │
│ │ └─__pycache__
│ │ init_permission.cpython-35.pyc
│ │
│ ├─templatetags
│ │ menu_gennerator.py
│ └─ __init__.py
│
│
├─s17crm
│ │ settings.py
│ │ urls.py
│ │ wsgi.py
│ └─ __init__.py
│
├─statics
│ ├─css
│ │ bbs.css
│ │ bootstrap-select.min.css
│ │ bootstrap-theme.css
│ │ bootstrap-theme.css.map
│ │ bootstrap-theme.min.css
│ │ bootstrap-theme.min.css.map
│ │ bootstrap.css
│ │ bootstrap.css.map
│ │ bootstrap.min.css
│ │ bootstrap.min.css.map
│ │ bootstrapValidator.min.css
│ │
│ ├─image
│ │ header.png
│ │
│ └─js
│ bootstrap-select.js.map
│ bootstrap-select.min.js
│ bootstrap.js
│ bootstrap.min.js
│ bootstrapValidator.min.js
│ city_info.js
│ jquery-3.2.1.js
│ jquery-3.2.1.min.js
│ jquery.cookie.js
│ npm.js
│
└─templates
index.html
login.html
menu_tpl.html
order.html
order_add.html
tpl.html
user.html
user_add.html
D:\soft\work\Python_17\day26\homework\s17crm>
# s17crm/app01/views.py
from django.shortcuts import render,HttpResponse,redirect
from django.forms import Form
from django.forms import fields
from django.forms import widgets
from rbac import models
from rbac.service.init_permission import init_permission
from django.conf import settings
class LoginForm(Form):
username = fields.CharField(required=True,error_messages={'required':"用户名不能为空"})
password = fields.CharField(required=True,error_messages={'required':"密码不能为空"})
def login(request):
if "GET" == request.method:
form = LoginForm()
return render(request,'login.html',{'form':form})
else:
form = LoginForm(data=request.POST)
if form.is_valid():
# 格式验证通过
# 注意:只有当 LoginForm 里面变量名称和models表字段名称一致才能使用 **form.cleaned_data
user = models.UserInfo.objects.filter(**form.cleaned_data).first()
if user:
# 获取当前用户的权限信息做格式化然后存储到session中
init_permission(request,user)
print(request.session.get(settings.PERMISSION_DICT))
print(request.session.get(settings.PERMISSION_MENU_LIST))
return redirect('/index/')
else:
from django.core.exceptions import ValidationError
form.add_error('password',ValidationError("用户名或密码错误!!!"))
return render(request,'login.html',{'form':form})
def index(request):
# 通过 @register.inclusion_tag('menu_tpl.html') 构造菜单结构并渲染
# 所以这里不用做处理
return render(request,'index.html')
def clear(request):
# 手动清空session
request.session.clear()
return HttpResponse("已清除")
def order(request):
return render(request,'order.html')
def order_add(request):
return render(request,'order_add.html')
def user(request):
return render(request,'user.html')
def user_add(request):
return render(request,'user_add.html')
# s17crm\rbac\middleware\rbac.py
# 用户访问鉴权中间件
#!/usr/bin/python
# -*- coding:utf-8 -*-
# 这是页面权限验证的中间件
from django.shortcuts import HttpResponse,redirect
from django.conf import settings
import re
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
class RbacMiddleware(MiddlewareMixin):
def process_request(self,request):
# 1. 获取当前请求的 uri
current_request_url = request.path_info
# 2. 判断是否在白名单里,在则不进行验证,直接放行
for url in settings.VALID_URL_LIST:
if re.match(url, current_request_url):
return None
# 3. 验证用户是否有访问权限
flag = False
permission_dict = request.session.get(settings.PERMISSION_DICT)
print(permission_dict)
# 如果没有登录过就直接跳转到登录页面
if not permission_dict:
return redirect(settings.RBAC_LOGIN_URL)
"""
{
1: {
'codes': ['list', 'add'],
'urls': ['/userinfo/', '/userinfo/add/']
},
2: {
'codes': ['list'],
'urls': ['/order/']
}
}
"""
for group_id, values in permission_dict.items():
for url in values['urls']:
# 必须精确匹配 URL : "^{0}$"
patten = settings.URL_FORMAT.format(url)
if re.match(patten, current_request_url):
flag = True
break
if flag:
break
if not flag:
return HttpResponse("无权访问")
# s17crm\rbac\service\init_permission.py
# 这是用户登录后初始化用户权限信息的模块
#!/usr/bin/python
# -*- coding:utf-8 -*-
from django.conf import settings
def init_permission(request,user):
# 获取当前用户的权限信息
"""
外键和多对多 都可以用 __(双下划线) 跨表获取对应字段
[].distinct() 进行去重
"""
permission_list = user.roles.filter(permissions__id__isnull=False).values(
'permissions__id',
'permissions__title',
'permissions__url',
'permissions__code',
'permissions__group',
'permissions__is_menu',
'permissions__group__parent_id', # 即:当前权限所在组的所属菜单ID
'permissions__group__parent__title', # 即:当前权限所在组的所属菜单名称
).distinct()
# 格式化当前用户的权限信息
"""
{
1: {
'codes': ['list', 'add'],
'urls': ['/userinfo/', '/userinfo/add/']
},
2: {
'codes': ['list'],
'urls': ['/order/']
}
}
"""
permission_dict = {}
for item in permission_list:
url = item.get('permissions__url')
code = item.get('permissions__code')
group_id = item.get('permissions__group')
if group_id in permission_dict:
permission_dict[group_id]['urls'].append(url)
permission_dict[group_id]['codes'].append(code)
else:
permission_dict[group_id] = {'codes': [code, ], 'urls': [url, ]}
# 把当前用户权限信息存储到session中
request.session[settings.PERMISSION_DICT] = permission_dict
# 格式化当前用户的菜单列表信息
# 最终的目的就是要把菜单和具体的is_menu=True的权限item关联起来并在前端构成 两级菜单
'''
[
{'menu_id': 4, 'menu_title': '用户管理菜单', 'permission_url': '/userinfo/', 'permission_title': '用户列表'},
{'menu_id': 4, 'menu_title': '用户管理菜单', 'permission_url': '/userinfo/add/', 'permission_title': '添加用户'},
{'menu_id': 4, 'menu_title': '用户管理菜单', 'permission_url': '/order/', 'permission_title': '订单列表'}
]
'''
permission_menu_list = []
for item in permission_list:
if item['permissions__is_menu']:
tmp = {
'menu_title':item['permissions__group__parent__title'],
'menu_id':item['permissions__group__parent_id'],
'permission_url':item['permissions__url'],
'permission_title':item['permissions__title'],
}
permission_menu_list.append(tmp)
# 把当前用户菜单信息存储到session中
request.session[settings.PERMISSION_MENU_LIST] = permission_menu_list
# 设置session的超时时间:30min
request.session.set_expiry(1800)
# 为了要在前端构造出两级菜单,还需要对上面拿到的菜单列表做一下格式转换
'''
{
4:{
'opened':False,
'menu_title':'用户管理菜单',
'children': [
{'title': '用户列表', 'url': '/userinfo/', 'opened':False},
{'title': '订单列表', 'url': '/order/', 'opened':False,}
]
},
6: {
'opened':False,
'menu_title':'权限管理菜单',
'children': [
{'title': '权限列表', 'url': '/xxxinfo/', 'opened':False},
{'title': '分类列表', 'url': '/xxxxxxxx/', 'opened':False,}
]
}
}
'''
# s17crm\rbac\templatetags\menu_gennerator.py
# 使用inclusion_tag渲染菜单显示在前端页面
#!/usr/bin/python
# -*- coding:utf-8 -*-
from django import template
register = template.Library()
import re
from django.conf import settings
'''
[
{'menu_id': 4, 'menu_title': '用户管理菜单', 'permission_url': '/userinfo/', 'permission_title': '用户列表'},
{'menu_id': 4, 'menu_title': '用户管理菜单', 'permission_url': '/userinfo/add/', 'permission_title': '添加用户'},
{'menu_id': 4, 'menu_title': '用户管理菜单', 'permission_url': '/order/', 'permission_title': '订单列表'},
...
]
转换成下面的结构,然后再前端进行展示
{
4:{
'opened':False,
'menu_title':'用户管理菜单',
'children': [
{'title': '用户列表', 'url': '/userinfo/', 'opened':False},
{'title': '订单列表', 'url': '/order/', 'opened':False,}
]
},
6: {
'opened':False,
'menu_title':'权限管理菜单',
'children': [
{'title': '权限列表', 'url': '/xxxinfo/', 'opened':False},
{'title': '分类列表', 'url': '/xxxxxxxx/', 'opened':False,}
]
},
...
}
'''
@register.inclusion_tag('menu_tpl.html')
def menu_show(request):
permission_menu_dict = {}
current_request_url = request.path_info
permission_menu_list = request.session.get(settings.PERMISSION_MENU_LIST)
for item in permission_menu_list:
url = item['permission_url']
patten = settings.URL_FORMAT.format(url)
# 首先遍历所有的具体权限,与当前请求url做比较,匹配则把 opened置为 active,用于配合前端的 .active 属性
opened = "active" if re.match(patten, current_request_url) else ""
menu_id = item['menu_id']
child = {'opened': opened, 'title': item['permission_title'], 'url': item['permission_url']}
if menu_id in permission_menu_dict:
# 更新已有menu
permission_menu_dict[menu_id]['children'].append(child)
if opened:
permission_menu_dict[menu_id]['opened'] = opened
else:
# 初始化一个menu
permission_menu_dict[menu_id] = {
'opened': opened,
'menu_title': item['menu_title'],
'children': [child,]
}
return {'permission_menu_dict':permission_menu_dict}
# s17crm\templates\menu_tpl.html
{% for menu_id,item_dict in permission_menu_dict.items %}
<div class="item">
<div class="menu_title">
<a href="#" class="{{ item_dict.opened }}">{{ item_dict.menu_title }}</a>
</div>
<div class="menu_items {{ item_dict.opened }}">
{% for permission in item_dict.children %}
<a href={{ permission.url }} class="{{ permission.opened }}">{{ permission.title }}</a>
{% endfor %}
</div>
</div>
{% endfor %}
# s17crm\templates\login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post" novalidate>
{% csrf_token %}
<P>
{{ form.username }} {{ form.errors.username.0 }}
</P>
<p>
{{ form.password }} {{ form.errors.password.0 }}
</p>
<input type="submit" value="提交">
</form>
</body>
</html>
# s17crm\templates\tpl.html
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>###CRM</title>
<link rel="Shortcut Icon" href="{% static "image/header.png" %}">
<link rel="stylesheet" href="{% static "css/bootstrap.css" %}">
<script src="{% static "js/jquery-3.2.1.min.js" %}"></script>
<script src="{% static "js/bootstrap.js" %}"></script>
<style>
body {
margin: 0;
padding: 0;
}
.header {
height: 40px;
background-color: lightgreen;
text-align: center;
line-height: 40px;
}
.menu {
float: left;
width: 16%;
background-color: wheat;
min-height: 500px
}
.right_body {
float: left;
width: 84%;
min-height: 500px;
}
.item .menu_title {
font-size: 20px;
}
.item .menu_title a.active {
color: green;
}
.item .menu_items {
display: none;
}
.item .menu_items.active {
display: block !important;
}
.item .menu_items a {
font-size: 15px;
display: block;
padding-left: 20px;
}
.item .menu_items a.active {
color: red;
}
</style>
</head>
<body>
<div class="header">
<h2>头部文字水平垂直居中显示...</h2>
</div>
{% load menu_gennerator %}
<div class="content">
<div class="menu">
{% menu_show request %}
</div>
{% block content %}
<h1>这个是模板页面...</h1>
{% endblock %}
</div>
</body>
<script>
$(function () {
menu_swith();
});
function menu_swith() {
$(".menu_title").click(function () {
if($(this).children().hasClass("active")) {
$(this).next().removeClass("active");
$(this).children().removeClass("active");
}else {
$(this).children().addClass("active");
$(this).next().addClass("active");
}
})
}
</script>
</html>
# s17crm\templates\index.html
{% extends "tpl.html" %}
{% block content %}
<div class="right_body">
<h1>这是首页...</h1>
</div>
{% endblock %}
# s17crm\templates\order.html
{% extends "tpl.html" %}
{% block content %}
<div class="right_body">
<h1>订单列表...</h1>
</div>
{% endblock %}
# s17crm\templates\order_add.html
{% extends "tpl.html" %}
{% block content %}
<div class="right_body">
<h1>新增订单...</h1>
</div>
{% endblock %}
...
# s17crm\s17crm\urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login/', views.login),
url(r'^index/', views.index),
url(r'^clear/', views.clear),
url(r'^userinfo/$', views.user),
url(r'^userinfo/add/$', views.user_add),
url(r'^order/$', views.order),
url(r'^order/add/$', views.order_add),
]
# \s17crm\s17crm\settings.py
"""
Django settings for s17crm project.
Generated by 'django-admin startproject' using Django 1.11.3.
For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '-dulev94(7c=_e_s!@ww937pu3rqiiszw1y(eyl3*mp$&5h_e2'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01',
'rbac',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'rbac.middleware.rbac.RbacMiddleware'
]
ROOT_URLCONF = 's17crm.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 's17crm.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_URL = '/static/' #引用名
# 配置STATICFILES_DIRS,指定额外的静态文件存储位置
STATICFILES_DIRS = (
os.path.join(BASE_DIR,"statics"), #实际名 ,即实际文件夹的名字
)
########################## 自定义权限相关配置 ##################################
PERMISSION_DICT = "permission_dict"
PERMISSION_MENU_LIST = "permission_menu_list"
URL_FORMAT = "^{0}$"
RBAC_LOGIN_URL = "/login/"
VALID_URL_LIST = [
"^/login/$",
"^/admin.*",
"^/clear/$",
]