权限组件(15):rbac的使用文档和在业务中的应用
这里用主机管理系统当做示例。
一、将rbac组件拷贝到项目中。
注意:
- rbac自己的静态文件、layout.html(被继承的模板)、bootstrap、fontsize、公共的css、jquery、layout里用到的图片也要拷贝进来
- 在settings.py注册rbac:
- 二级菜单和面包屑导航需要先注释掉
- 在settings中将LANGUAGE_CODE = 'en-us'改为LANGUAGE_CODE = 'zh-hans'
在layout.html里注释掉二级菜单和面包屑导航
<div class="pg-body"> <div class="left-menu"> <div class="menu-body"> {# {% multi_menu request %}#} </div> </div> <div class="right-body"> {# {% breadcrumb request %}#} {% block content %} {% endblock %} </div> </div>
在settings.py里注册rbac
INSTALLED_APPS = [ ... 'rbac.apps.RbacConfig' ... ]
二、将rbac/migrations目录中的数据库迁移记录删除(init.py不能删除)
三、业务系统中用户表结构的设计
业务表结构中的用户表需要和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
注意:
- 改成abstract后就不能在admin里注册了
- 多对多关联Role的时候不能加引号
- 业务/models.py
from rbac.models import UserInfo as RbacUserInfo class Host(models.Model): """主机表""" hostname = models.CharField(verbose_name='主机名', max_length=32) ip = models.GenericIPAddressField(verbose_name='IP', protocol='both') department = models.ForeignKey(verbose_name='归属部门', to='Department', on_delete=models.CASCADE) def __str__(self): return self.hostname class UserInfo(RbacUserInfo): """用户表""" phone = models.CharField(verbose_name='联系方式', max_length=32) T1 = 1 T2 = 2 T3 = 3 level_choices = ( (T1, 'T1'), (T2, 'T2'), (T3, 'T3') ) level = models.IntegerField(verbose_name='级别', choices=level_choices) department = models.ForeignKey(verbose_name='部门', to='Department', on_delete=models.CASCADE)
迁移数据库
./manage.py makemigrations
./manage.py migrate
四、将业务系统中的用户表的路径写到配置文件
settings.py
# 业务中的用户表
RBAC_USER_MODEL_CLASS = 'host.models.UserInfo' # 用于在rbac分配权限时,读取业务表中的用户信息。
host换成你的业务app
在权限组件的视图函数中将用户表改为新的,所有的UserInfo都要改成user_model_class
rbac/views/menu.py
... from django.conf import settings from django.utils.module_loading import import_string ... ... def distribute_permissions(request): """ 权限分配 :param request: :return: """ user_id = request.GET.get('uid') # user_object = models.UserInfo.objects.filter(id=user_id).first() # 之前的用户表,不要了 # 业务中的用户表 user_model_class = import_string(settings.RBAC_USER_MODEL_CLASS) # 自动根据字符串的形式,把这个类导入进来 user_object = user_model_class.objects.filter(id=user_id).first() ...
五、业务逻辑开发
1. 开发一个登陆功能
2.将所有的路由都设置一个name,用来反向生成url以及粒度控制到按钮级别的权限控制。如:
from django.contrib import admin from django.urls import path, re_path from host.views import account from host.views import user from host.views import host urlpatterns = [ re_path(r'^admin/', admin.site.urls), re_path(r'^login/$', account.login, name='login'), re_path(r'^logout/$', account.logout, name='logout'), re_path(r'^index/$', account.index, name='index'), re_path(r'^user/list/$', user.user_list, name='user_list'), re_path(r'^user/add/$', user.user_add, name='user_add'), re_path(r'^user/edit/(?P<pk>\d+)/$', user.user_edit, name='user_edit'), re_path(r'^user/delete/(?P<pk>\d+)/$', user.user_delete, name='user_delete'), re_path(r'^user/reset/pwd/(?P<pk>\d+)/$', user.user_reset_pwd, name='user_reset_pwd'), re_path(r'^host/list/$', host.host_list, name='host_list'), re_path(r'^host/add/$', host.host_add, name='host_add'), re_path(r'^host/edit/(?P<pk>\d+)/$', host.host_edit, name='host_edit'), re_path(r'^host/delete/(?P<pk>\d+)/$', host.host_delete, name='host_delete'), ]
六、权限信息的录入
在url中添加rbac的路由分发。注意:必须设置namesapce
项目/urls.py
... from django.urls import path, re_path, include ... urlpatterns = [ ... path('rbac/', include(('rbac.urls', 'rbac'))), ... ]
注意:rbac中的用户管理相关的URL配置要注释掉或删除掉
添加菜单、分配角色、录入权限
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/
1. 先添加一级菜单,然后批量录入二级菜单和权限
2. 添加完菜单后创建角色
3. 最后记得分配权限,否则后面加上中间件后会提示无权限访问
相关配置:自动发现URL时,排除的URL
AUTO_DISCOVER_EXCLUDE = [ '/admin/', '/login/', '/logout/', '/index/', ]
注意:如果排除的URL中,如admin、login等用的是django2.0新出的path方法,那么正则匹配的之后需要写成/admin.*,因为path会在admin后面加一个斜杠(\)。/admin/配置的话就会在自动发现URL中出现一推这样的URL:/admin/
七、编写用户登录的逻辑【进行权限的初始化】
def login(request): """ 登录 :param request: :return: """ if request.method == 'GET': return render(request, 'login.html') user = request.POST.get('username') pwd = request.POST.get('password') user_obj = models.UserInfo.objects.filter(name=user, password=pwd).first() if not user_obj: return render(request, 'login.html', {'error': '用户名或密码错误'}) # 用户权限信息的初始化 init_permission(user_obj, request) return redirect(reverse('index'))
相关配置:权限和菜单的session key:
PERMISSION_SESSION_KEY = 'permission_url_list_key' MENU_SESSION_KEY = 'permission_menu_key'
八、编写一个首页的逻辑
def index(request): return render(request, 'index.html')
相关配置:
# 需要登录,但无需权限的URL NO_PERMISSION_LIST = [ '/logout/', '/index/', ]
在中间件新增无需权限校验,但是需要登录才能访问的功能
... url_record = [ {'title': '首页', 'url': '#'} ] # 此处代码进行判断:/logout/,/index/ 无需权限校验,但是需要登录才能访问 for url in settings.NO_PERMISSION_LIST: if re.match(url, request.path_info): # 需要登录,但无需权限校验 request.current_selected_permission = 0 # 等于0就是没有默认选中,和菜单没有关联上,就不会做默认展开 request.breadcrumb = url_record return None has_permission = False ...
九、通过中间件进行权限的校验
rbac/middlewares/rabc.py
# 权限校验 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', ]
白名单
# 白名单,无需登录就可以访问 WHITE_LIST = ['/login/', '/admin/.*']
十、粒度到按钮级别的控制
host/templates/user_list.html
{% extends 'layout.html' %} {% load rbac %} {% block content %} <h1>用户列表</h1> <div class="custom-container"> <div class="btn-group" style="margin: 5px 0"> {% if request|has_permission:'user_add' %} <!-- 控制增加按钮 --> <a class="btn btn-default" href="{% memory_url request 'user_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>邮箱</th> <th>级别</th> <th>部门</th> <th>手机</th> {% if request|has_permission:'user_reset_pwd' %} <!-- 控制重置密码按钮 --> <th>重置密码</th> {% endif %} {% if request|has_permission:'user_edit' or 'user_delete' %} <th>操作</th> {% endif %} </tr> </thead> <tbody> {% for row in user_queryset %} <tr> <td>{{ row.name }}</td> <td>{{ row.email }}</td> <td>{{ row.get_level_display }}</td> <td>{{ row.department.title }}</td> <td>{{ row.phone }}</td> {% if request|has_permission:'user_reset_pwd' %} <td> <a href="{% memory_url request 'user_reset_pwd' pk=row.id %}">重置密码</a> </td> {% endif %} {% if request|has_permission:'user_edit' or 'user_delete' %} <!-- 控制编辑和删除按钮 --> <td> {% if request|has_permission:'user_edit' %} <a style="color: #333333; font-size:18px" href="{% memory_url request 'user_edit' pk=row.id %}"> <i class="fa fa-edit" aria-hidden="true"></i></a> {% endif %} {% if request|has_permission:'user_delete' %} <a style="color: red; font-size:18px" href="{% memory_url request 'user_delete' pk=row.id %}"> <i class="fa fa-trash-o" aria-hidden="true"></i></a> {% endif %} </td> {% endif %} </tr> {% endfor %} </tbody> </table> </div> {% endblock content %}
总结:目的是希望在任意系统中应用权限系统
-
用户登录 + 用户首页 + 用户注销 + 业务逻辑
-
业务逻辑开发。注意,开发时要灵活的去设计layout.html中的两个inclusion_tag
-
权限信息的录入
-
配置文件
INSTALLED_APPS = [ ... 'host.apps.HostConfig', 'rbac.apps.RbacConfig' ... ] MIDDLEWARE = [ ... 'rbac.middlewares.rbac.RbacMiddleware', ... ] # 业务中的用户表 RBAC_USER_MODEL_CLASS = 'host.models.UserInfo' # 权限在session中存储的key PERMISSION_SESSION_KEY = 'permission_url_list_key' # 菜单在Session中存储的key MENU_SESSION_KEY = 'permission_menu_key' # 白名单 WHITE_LIST = ['/login/', '/admin/.*'] # 自动发现路由中URL时,排除的URL AUTO_DISCOVER_EXCLUDE = [ '/admin.*', '/login.*', '/logout.*', '/index.*', ] # 需要登录,但无需权限的URL NO_PERMISSION_LIST = [ '/logout/', '/index/', ]
- 粒度到按钮级别的控制