CRM项目之RBAC权限组件

写在前面



    世间安得双全法 不负如来不负卿



 

  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天]
武Sir - 笔记

 

一、内容回顾

- .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/$",
]

  

 

转载于:https://www.cnblogs.com/standby/p/7801910.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值