CRM【第一篇】: 权限组件之权限控制

1. 问:为何程序须要权限控制?html

   答:生活中的权限限制,① 看灾难片电影《2012》中富人和权贵有权登上诺亚方舟,穷苦老百姓只有等着灾难的来临;② 屌丝们,有没有想过为何那些长得漂亮身材好的姑娘在你身边不存在呢?由于有钱人和漂亮姑娘都是珍贵稀有的,稀有的人在一块儿玩耍和解锁各类姿式。而你,无权拥有他们,只能本身玩本身了。
程序开发时的权限控制,对于不一样用户使用系统时候就应该有不一样的功能,如:python

  • 普通员工
  • 部门主管
  • 总监
  • 总裁

因此,只要有不一样角色的人员来使用系统,那么就确定须要权限系统。程序员

2. 问:为何要开发权限组件?web

   答:假设你今年25岁,从今天开始写代码到80岁,每一年写5个项目,那么你的一辈子就会写275个项目,保守估计其中应该有150+个都须要用到权限控制,为了之后再也不重复的写代码,因此就开发一个权限组件以便以后55年的岁月中使用。 亲,不要太较真哦,你以为程序员能到80岁么,哈哈哈哈哈哈哈 
偷偷告诉你:老程序员开发速度快,其中一个缘由是经验丰富,另一个就是他本身保留了不少组件,新系统开发时,只需把组件拼凑起来基本就能够完成。sql

3. 问:web开发中权限指的是什么?数据库

   答:web程序是经过 url 的切换来查看不一样的页面(功能),因此权限指的其实就是URL,对url控制就是对权限的控制。django

结论:一我的有多少个权限就取决于他有多少个URL的访问权限。bash

权限表结构设计:初版

问答环节中已得出权限就是URL的结论,那么就能够开始设计表结构了。session

  • 一个用户能够有多个权限。
  • 一个权限能够分配给多个用户。

你设计的表结构大概会是这个样子:app

如今,此时此刻是否是以为本身设计出的表结构棒棒哒!!!

But,不管是是否认可,你仍是too young too native,由于老汉腚眼一看就有问题....

问题:假设 “老男孩”和“Alex” 这俩货都是老板,老板的权限必定是很是多。那么试想,若是给这俩货分配权限时须要在【用户权限关系表中】添加好多条数据。假设再次须要对老板的权限进行修改时,又须要在【用户权限关系表】中找到这俩人全部的数据进行更新,太他妈烦了吧!!! 相似的,若是给其余相同角色的人来分配权限时,必然会很是繁琐。

权限表结构设计:第二版

聪明机智的必定在上述的表述中看出了写门道,若是对用户进行角色的划分,而后对角色进行权限的分配,这不就迎刃而解了么。

  • 一我的能够有多个角色。
  • 一个角色能够有多我的。
  • 一个角色能够有多个权限。
  • 一个权限能够分配给多个角色。

表结构设计:

 此次调整以后,由原来的【基于用户的权限控制】转换成【基于角色的权限控制】,之后再进行分配权限时只须要给指定角色分配一次权限,给众多用户再次分配指定角色便可。

from django.db import models class Permission(models.Model): """ 权限表 """ title = models.CharField(verbose_name='标题', max_length=32) url = models.CharField(verbose_name='含正则的URL', max_length=128) def __str__(self): return self.title class Role(models.Model): """ 角色 """ title = models.CharField(verbose_name='角色名称', max_length=32) permissions = models.ManyToManyField(verbose_name='拥有的全部权限', to='Permission', blank=True) def __str__(self): return self.title class UserInfo(models.Model): """ 用户表 """ name = models.CharField(verbose_name='用户名', max_length=32) password = models.CharField(verbose_name='密码', max_length=64) email = models.CharField(verbose_name='邮箱', max_length=32) roles = models.ManyToManyField(verbose_name='拥有的全部角色', to='Role', blank=True) def __str__(self): return self.name models.py 示例
models.py

小伙子,告诉你一个事实,不经意间,你竟然设计出了一个经典的权限访问控制系统:rbac(Role-Based Access Control)基于角色的权限访问控制。你这么优秀,为何不来老男孩IT教育?路飞学城也行呀! 哈哈哈哈。

注意:如今的设计还不是最终版,但以后的设计都是在此版本基础上扩增的,为了让你们可以更好的理解,咱们暂且再此基础上继续开发,直到遇到没法知足的状况,再进行整改。

源码示例猛击下载

客户管理之权限控制

学习知识最好的方式就是试错,坑踩多了那么学到的知识天然而然就多了,因此接下里下来咱们用《客户管理》系统为示例,提出功能并实现,而且随着功能愈来愈多,一点点来找出问题,并解决问题。

目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
luffy_permission/
├── db.sqlite3
├── luffy_permission
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
├── rbac             # 权限组件,便于之后应用到其余系统
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── templates
└── web             # 客户管理业务
     ├── __init__.py
     ├── admin.py
     ├── apps.py
     ├── models.py
     ├── tests.py
     └── views.py
from django.db import models class Permission(models.Model): """ 权限表 """ title = models.CharField(verbose_name='标题', max_length=32) url = models.CharField(verbose_name='含正则的URL', max_length=128) def __str__(self): return self.title class Role(models.Model): """ 角色 """ title = models.CharField(verbose_name='角色名称', max_length=32) permissions = models.ManyToManyField(verbose_name='拥有的全部权限', to='Permission', blank=True) def __str__(self): return self.title class UserInfo(models.Model): """ 用户表 """ name = models.CharField(verbose_name='用户名', max_length=32) password = models.CharField(verbose_name='密码', max_length=64) email = models.CharField(verbose_name='邮箱', max_length=32) roles = models.ManyToManyField(verbose_name='拥有的全部角色', to='Role', blank=True) def __str__(self): return self.name rbac/models.py
rbac/models.py
from django.db import models class Customer(models.Model): """ 客户表 """ name = models.CharField(verbose_name='姓名', max_length=32) age = models.CharField(verbose_name='年龄', max_length=32) email = models.EmailField(verbose_name='邮箱', max_length=32) company = models.CharField(verbose_name='公司', max_length=32) class Payment(models.Model): """ 付费记录 """ customer = models.ForeignKey(verbose_name='关联客户', to='Customer') money = models.IntegerField(verbose_name='付费金额') create_time = models.DateTimeField(verbose_name='付费时间', auto_now_add=True) web/models.py
web/models.py

《客户管理》系统截图:基本增删改查和Excel导入源码下载猛击这里

以上简易版客户管理系统中的URL有:

  • 客户管理
    • 客户列表:/customer/list/
    • 添加客户:/customer/add/
    • 删除客户:/customer/list/(?P<cid>\d+)/
    • 修改客户:/customer/edit/(?P<cid>\d+)/
    • 批量导入:/customer/import/
    • 下载模板:/customer/tpl/
  • 帐单管理
    • 帐单列表:/payment/list/
    • 添加帐单:/payment/add/
    • 删除帐单:/payment/del/(?P<pid>\d+)/
    • 修改帐单:/payment/edit/<?P<pid>\d+/

那么接下来,咱们就在权限组件中录入相关信息:

  • 录入权限
  • 建立用户
  • 建立角色
  • 用户分配角色
  • 角色分配权限

这么一来,用户登陆时,就能够根据本身的【用户】找到全部的角色,再根据角色找到全部的权限,再将权限信息放入session,之后每次访问时候须要先去session检查是否有权访问。

已录入权限数据源码下载猛击这里

含用户登陆权限源码下载:猛击这里(简易版)

含用户登陆权限源码下载猛击这里

至此,基本的权限控制已经完成,基本流程为:

  • 用户登陆,获取权限信息并放入session
  • 用户访问,在中间件从session中获取用户权限信息,并进行权限验证。

全部示例中的帐户信息:

1
2
3
4
5
6
7
帐户一:
     用户名:alex
        密码: 123
 
帐户二:
     用户名:wupeiqi
        密码: 123

客户管理之动态“一级”菜单

上述过程当中的菜单是在程序中定义好的,没法根据用户权限进行动态配置。

那么,接下来咱们来完成一个 “单级菜单”的功能:

from django.db import models class Permission(models.Model): """ 权限表 """ title = models.CharField(verbose_name='标题', max_length=32) url = models.CharField(verbose_name='含正则的URL', max_length=128) icon = models.CharField(verbose_name='图标', max_length=32, null=True, blank=True, help_text='菜单才设置图标') is_menu = models.BooleanField(verbose_name='是不是菜单', default=False) def __str__(self): return self.title class Role(models.Model): """ 角色 """ title = models.CharField(verbose_name='角色名称', max_length=32) permissions = models.ManyToManyField(verbose_name='拥有的全部权限', to='Permission', blank=True) def __str__(self): return self.title class UserInfo(models.Model): """ 用户表 """ name = models.CharField(verbose_name='用户名', max_length=32) password = models.CharField(verbose_name='密码', max_length=64) email = models.CharField(verbose_name='邮箱', max_length=32) roles = models.ManyToManyField(verbose_name='拥有的全部角色', to='Role', blank=True) def __str__(self): return self.name 表结构
表结构

这样能够开发出一个单级菜单的示例:

 

示例源码下载:猛击这里(无默认选中)

示例源码下载猛击这里(含默认选中)

客户管理之动态“二级”菜单

对于功能比较少的应用程序 “一级菜单” 基本能够知足需求,可是功能多的程序就须要 “二级菜单” 了,而且访问时候须要默认选中指定菜单。

from django.db import models class Menu(models.Model): """ 菜单 """ title = models.CharField(verbose_name='菜单', max_length=32) icon = models.CharField(verbose_name='图标', max_length=32) 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) menu = models.ForeignKey(verbose_name='菜单', to='Menu', null=True, blank=True, help_text='null表示非菜单') def __str__(self): return self.title class Role(models.Model): """ 角色 """ title = models.CharField(verbose_name='角色名称', max_length=32) permissions = models.ManyToManyField(verbose_name='拥有的全部权限', to='Permission', blank=True) def __str__(self): return self.title class UserInfo(models.Model): """ 用户表 """ name = models.CharField(verbose_name='用户名', max_length=32) password = models.CharField(verbose_name='密码', max_length=64) email = models.CharField(verbose_name='邮箱', max_length=32) roles = models.ManyToManyField(verbose_name='拥有的全部角色', to='Role', blank=True) def __str__(self): return self.name 表结构
表结构

示例效果:

 

示例源码下载猛击这里

示例源码下载猛击这里(路飞线上录制代码示例)

客户管理之默认展开非菜单URL

因为不少URL都是不能做为菜单,因此当点击该类功能时,是没法默认展开菜单的,如:

  • 删除
  • 修改
  • ...

此类页面只能经过菜单页面二次点击才能跳转,此时也应该为他们默认展开原菜单。

from django.db import models class Menu(models.Model): """ 菜单 """ title = models.CharField(verbose_name='菜单', max_length=32) icon = models.CharField(verbose_name='图标', max_length=32) 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) pid = models.ForeignKey(verbose_name='默认选中权限', to='Permission', related_name='ps', null=True, blank=True, help_text="对于没法做为菜单的URL,能够为其选择一个能够做为菜单的权限,那么访问时,则默认选中此权限", limit_choices_to={'menu__isnull': False}) menu = models.ForeignKey(verbose_name='菜单', to='Menu', null=True, blank=True, help_text='null表示非菜单') def __str__(self): return self.title class Role(models.Model): """ 角色 """ title = models.CharField(verbose_name='角色名称', max_length=32) permissions = models.ManyToManyField(verbose_name='拥有的全部权限', to='Permission', blank=True) def __str__(self): return self.title class UserInfo(models.Model): """ 用户表 """ name = models.CharField(verbose_name='用户名', max_length=32) password = models.CharField(verbose_name='密码', max_length=64) email = models.CharField(verbose_name='邮箱', max_length=32) roles = models.ManyToManyField(verbose_name='拥有的全部角色', to='Role', blank=True) def __str__(self): return self.name 表结构
表结构

功能完成后的示例以下:

示例源码下载猛击这里

示例源码下载猛击这里(路飞线上录制代码示例)

客户管理之访问路径导航

若是想要保留放的地址,那么就能够经过在权限配置中获取此功能,示例以下:

 

示例源码下载猛击这里

客户管理之 权限粒度控制按钮级别

不一样用户登陆系统时候,根据权限不一样来控制是否限制指定按钮,如:

示例源码下载猛击这里 

示例源码下载猛击这里(路飞线上录制代码示例)

客户管理之 编辑权限和分配权限

给用户进行权限的分配。

1. 用户和角色管理

 

示例源码下载猛击这里(用户和角色管理)

2. 一级菜单

 

示例源码下载猛击这里(菜单和权限管理之一级菜单)

3. 二级菜单管理

 

示例源码下载猛击这里(菜单和权限管理之二级菜单)

4. 三级菜单管理(权限管理)

示例源码下载猛击这里(菜单和权限管理之三级菜单)

5. django formset示例

源码示例下载猛击这里(django formset实现批量添加和编辑)

6. 批量操做权限的显示

源码示例下载猛击这里(批量操做权限的显示)

7. 批量操做权限:添加、删除、更新

  

源码示例下载猛击这里(权限的批量增删改) 

8. 权限分配

源码示例下载猛击这里(权限分配)

RBAC组件应用及文档

""" 1. 将rbac组件拷贝项目。 2. 将rbac/migrations目录中的数据库迁移记录删除 3. 业务系统中用户表结构的设计 业务表结构中的用户表须要和rbac中的用户有继承关系,如: rbac/models.py class UserInfo(models.Model): # 用户表 name = models.CharField(verbose_name='用户名', max_length=32) password = models.CharField(verbose_name='密码', max_length=64) email = models.CharField(verbose_name='邮箱', max_length=32) roles = models.ManyToManyField(verbose_name='拥有的全部角色', to=Role, blank=True) 严重提醒 Role 不要加引号 def __str__(self): return self.name class Meta: # django之后再作数据库迁移时,再也不为UserInfo类建立相关的表以及表结构了。 # 此类能够当作"父类",被其余Model类继承。 abstract = True 业务/models.py class UserInfo(RbacUserInfo): phone = models.CharField(verbose_name='联系方式', max_length=32) level_choices = ( (1, 'T1'), (2, 'T2'), (3, 'T3'), ) level = models.IntegerField(verbose_name='级别', choices=level_choices) depart = models.ForeignKey(verbose_name='部门', to='Department') 4. 讲业务系统中的用户表的路径写到配置文件。 # 业务中的用户表 RBAC_USER_MODLE_CLASS = "app01.models.UserInfo" 用于在rbac分配权限时,读取业务表中的用户信息。 5. 业务逻辑开发 将全部的路由都设置一个name,如: url(r'^login/$', account.login, name='login'), url(r'^logout/$', account.logout, name='logout'), url(r'^index/$', account.index, name='index'), url(r'^user/list/$', user.user_list, name='user_list'), url(r'^user/add/$', user.user_add, name='user_add'), url(r'^user/edit/(?P<pk>\d+)/$', user.user_edit, name='user_edit'), url(r'^user/del/(?P<pk>\d+)/$', user.user_del, name='user_del'), url(r'^user/reset/password/(?P<pk>\d+)/$', user.user_reset_pwd, name='user_reset_pwd'), url(r'^host/list/$', host.host_list, name='host_list'), url(r'^host/add/$', host.host_add, name='host_add'), url(r'^host/edit/(?P<pk>\d+)/$', host.host_edit, name='host_edit'), url(r'^host/del/(?P<pk>\d+)/$', host.host_del, name='host_del'), 用于反向生成URL以及粒度控制到按钮级别的权限控制。 6. 权限信息录入 - 在url中添加rbac的路由分发,注意:必须设置namespace urlpatterns = [ ... url(r'^rbac/', include('rbac.urls', namespace='rbac')), ] - rbac提供的地址进行操做 - http://127.0.0.1:8000/rbac/menu/list/ - http://127.0.0.1:8000/rbac/role/list/ - http://127.0.0.1:8000/rbac/distribute/permissions/ 相关配置:自动发现URL时,排除的URL: # 自动化发现路由中URL时,排除的URL AUTO_DISCOVER_EXCLUDE = [ '/admin/.*', '/login/', '/logout/', '/index/', ] 7. 编写用户登陆的逻辑【进行权限初始化】 from django.shortcuts import render, redirect from app01 import models from rbac.service.init_permission import init_permission def login(request): if request.method == 'GET': return render(request, 'login.html') user = request.POST.get('username') pwd = request.POST.get('password') user_object = models.UserInfo.objects.filter(name=user, password=pwd).first() if not user_object: return render(request, 'login.html', {'error': '用户名或密码错误'}) # 用户权限信息的初始化 init_permission(user_object, request) return redirect('/index/') 相关配置: 权限和菜单的session key: setting.py PERMISSION_SESSION_KEY = "luffy_permission_url_list_key" MENU_SESSION_KEY = "luffy_permission_menu_key" 8. 编写一个首页的逻辑 def index(request): return render(request, 'index.html') 相关配置:须要登陆但无需权限的URL # 须要登陆但无需权限的URL NO_PERMISSION_LIST = [ '/index/', '/logout/', ] 9. 经过中间件进行权限校验 # 权限校验 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.middlewares.rbac.RbacMiddleware', ] # 白名单,无需登陆就能够访问 VALID_URL_LIST = [ '/login/', '/admin/.*' ] 10. 粒度到按钮级别的控制 {% extends 'layout.html' %} {% load rbac %} {% block content %} <div class="luffy-container"> <div class="btn-group" style="margin: 5px 0"> {% if request|has_permission:'host_add' %} <a class="btn btn-default" href="{% memory_url request 'host_add' %}"> <i class="fa fa-plus-square" aria-hidden="true"></i> 添加主机 </a> {% endif %} </div> <table class="table table-bordered table-hover"> <thead> <tr> <th>主机名</th> <th>IP</th> <th>部门</th> {% if request|has_permission:'host_edit' or request|has_permission:'host_del' %} <th>操做</th> {% endif %} </tr> </thead> <tbody> {% for row in host_queryset %} <tr> <td>{{ row.hostname }}</td> <td>{{ row.ip }}</td> <td>{{ row.depart.title }}</td> {% if request|has_permission:'host_edit' or request|has_permission:'host_del' %} <td> {% if request|has_permission:'host_edit' %} <a style="color: #333333;" href="{% memory_url request 'host_edit' pk=row.id %}"> <i class="fa fa-edit" aria-hidden="true"></i></a> {% endif %} {% if request|has_permission:'host_del' %} <a style="color: #d9534f;" href="{% memory_url request 'host_del' pk=row.id %}"><i class="fa fa-trash-o"></i></a> {% endif %} </td> {% endif %} </tr> {% endfor %} </tbody> </table> </div> {% endblock %} 总结,目的是但愿在任意系统中应用权限系统。 - 用户登陆 + 用户首页 + 用户注销 业务逻辑 - 项目业务逻辑开发 注意:开发时候灵活的去设置layout.html中的两个inclusion_tag <div class="pg-body"> <div class="left-menu"> <div class="menu-body"> {% multi_menu request %} # 开发时,去掉;上下时,取回。 </div> </div> <div class="right-body"> <div> {% breadcrumb request %} # 开发时,去掉;上下时,取回。 </div> {% block content %} {% endblock %} </div> </div> - 权限信息的录入 - 配置文件 # 注册APP INSTALLED_APPS = [ ... 'rbac.apps.RbacConfig' ] # 应用中间件 MIDDLEWARE = [ ... 'rbac.middlewares.rbac.RbacMiddleware', ] # 业务中的用户表 RBAC_USER_MODLE_CLASS = "app01.models.UserInfo" # 权限在Session中存储的key PERMISSION_SESSION_KEY = "luffy_permission_url_list_key" # 菜单在Session中存储的key MENU_SESSION_KEY = "luffy_permission_menu_key" # 白名单 VALID_URL_LIST = [ '/login/', '/admin/.*' ] # 须要登陆但无需权限的URL NO_PERMISSION_LIST = [ '/index/', '/logout/', ] # 自动化发现路由中URL时,排除的URL AUTO_DISCOVER_EXCLUDE = [ '/admin/.*', '/login/', '/logout/', '/index/', ] - 粒度到按钮级别的控制 """
View Code

最终版组件下载:rbac.zip 

源码示例下载rbac组件应用之主机管理系统【auto_luffy.zip】

相关文章
相关标签/搜索