写在前面
世间安得双全法 不负如来不负卿

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:softworkPython_17day26homeworks17crm>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:softworkPython_17day26homeworks17crm>
# 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 bacmiddleware bac.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 bacserviceinit_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 bac emplatetagsmenu_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 emplatesmenu_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 emplateslogin.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 emplates pl.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; 16%; background-color: wheat; min-height: 500px } .right_body { float: left; 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 emplatesindex.html {% extends "tpl.html" %} {% block content %} <div class="right_body"> <h1>这是首页...</h1> </div> {% endblock %} # s17crm emplatesorder.html {% extends "tpl.html" %} {% block content %} <div class="right_body"> <h1>订单列表...</h1> </div> {% endblock %} # s17crm emplatesorder_add.html {% extends "tpl.html" %} {% block content %} <div class="right_body"> <h1>新增订单...</h1> </div> {% endblock %} ...
# s17crms17crmurls.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), ]
# s17crms17crmsettings.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/$", ]