Day 75 CRM业务——rbac组件
CRM业务
ModelForm
生成 ModelForm 对象
from django import forms from rbac import model class User_add_list(forms.modelfrom): class Meta: model = model.Menu fields = ('user','create_time') # 获取 # exclude = ('user') # 排除 # widgets = {} # 自定义修饰 form 组件 # error_messages # 重写错误信息
ModelForm 自定义字段
from django import forms form rbac import model class User_add_list(forms.modelfrom): # 自定义字段 Confirm_password = forms.CharField(label='确认密码') class Meta: model = model.Menu # 加入自定义字段 fields = ('Confirm_password')
forms中钩子的运用
# 在 Meta 类下,创建 clean 开头的函数 def clean_confirm_password(self): pass = self.clean_data('pawd') con_pass = self.clean_data('Confirm_password') if not pass == con_pass: raise ValidationError('两次密码输入错误') return con_pass
重写 ModelForm 中的 __init__ 方法
from django import forms form rbac import model class User_add_list(forms.modelfrom): class Meta: pass def __init__(self,*args,**kwargs): super(User_add_list,self).__init__(*args,**kwargs) # 统一加上样式 for name,field in self.fields.item(): field.widget.attrs['class'] = 'form-control'
ModelForm定制 radio
from django import forms from rbac import models from django.utils.safestring import mark_safe icon_list = [ ['fa-calendar', '<i class="fa fa-calendar" aria-hidden="true"></i>'], ['fa-calculator', '<i class="fa fa-calculator" aria-hidden="true"></i>'], ['fa-bus', '<i class="fa fa-bus" aria-hidden="true"></i>'], ['fa-calendar-check-o', '<i class="fa fa-calendar-check-o" aria-hidden="true"></i>'] ] icon_safe = [] for i in icon_list: i[1] = mark_safe(i[1]) class MenuModelForms(forms.ModelForm): class Meta: model = models.Menu fields = ('title', 'icon') widgets = { 'title': forms.TextInput(attrs={'class': 'form-control'}), 'icon': forms.RadioSelect( choices=icon_list, attrs={'class': 'clearfix'} ) }
ModelForm显示默认值
from rbac.form.base import WidegetClass class SecondMenuModelForms(WidegetClass): class Meta: model = models.Permission fields = ('title', 'name', 'url', 'menu') from django.shortcuts import render, redirect, reverse, HttpResponse from rbac import models from rbac.form.menu import MenuModelForms, SecondMenuModelForms, ThreeMenuModelForms from rbac.server.urls import memory_reverse def second_menu_add(request, menu_id): ''' 添加二级菜单 :param request: :param menu_id: :return: ''' menu_obj = models.Menu.objects.filter(pk=menu_id).first() if request.method == "GET": # initial={"menu": menu_obj} 输入要显示的默认值 forms = SecondMenuModelForms(initial={"menu": menu_obj}) return render(request, 'role/changes.html', locals())
ModelForm——save之前对其 instance 进行修改
from django import forms form rbac import model from rbac.form.base import WidegetClass class ThreeMenuModelForms(WidegetClass): class Meta: model = models.Permission fields = ('title','name','url') def three_menu_add(request, menu_id): ''' 权限菜单 :param requset: :param menu_id: :return: ''' if request.method == 'GET': forms = ThreeMenuModelForms() return render(request, 'role/changes.html', locals()) forms = ThreeMenuModelForms(data=request.POST) if forms.is_valid(): # 在保存前存入二级目录id num_s_obj = models.Permission.objects.filter(pk=menu_id).first() if not num_s_obj: return HttpResponse('二级目录不存在,请重新选择') forms.instance.pid = num_s_obj forms.save() return redirect(memory_reverse(request, 'rbac:menu_list')) return render(request, 'role/changes.html', locals())
报错中文显示
setting.py 文件 # LANGUAGE_CODE = 'en-us' # 英文 LANGUAGE_CODE = 'zh-hans' # 中文
反向解析
# url:方法 # rbac:url分发名称,有就加上 # user_add:自定义 url 的名称空间 # pk:自定义 url 分组名称 {{ url ''rbac:user_add" pk='id' }}
模板查询顺序
# 根据 INSTALLED_APPS 的注册顺序来查找模板 INSTALLED_APPS = [ 'django.contrib.staticfiles', 'rbac.apps.RbacConfig', 'web.apps.WebConfig' ]
菜单跳转并保留原搜索条件
from django.shortcuts import reverse from django.http import QueryDict # 跳转菜单后 保留原搜索条件,生成带原搜索条件的 url def memory_url(request,name,*args,**kwargs): # 反向生成基本的url basic_url = reverse(name,args=args,kwargs=kwargs) if not basic_url: return basic_url queru_dict = QueryDict(mutable=True) queru_dict['_filter'] = request.GET.urlencode() # 获取 get 值并放入字典 get_value = queru_dict.urlencode() # 打包 转义 return "%s?%s" % (basic_url, get_value) # 返回生成带有 get 值的 url def memory_reverse(request, name, *args, **kwargs): ''' 返回生成带有 get 值的 url http://127.0.0.1:8000/rbac/menu/edit/2/?_filter=num%3D2 1.在 url 中将原来搜索条件,如 filter 后的值 2.reverse 圣桦城原来的 url,如: 3./menu/list/?num%3D2 :return: ''' url = reverse(name, args=args, kwargs=kwargs) origin_params = request.GET.get('_filter') if origin_params: url = '%s?%s' % (url, origin_params) return url
HTML模板中过滤器的使用
# 模板中过滤器的使用 {{obj|filter__name:param}} {{ 1|safe }} # int ---> string { value|date:"Y-m-d" }} # 格式化时间
RBAC组件的使用文档
#!/usr/bin/env python # -*- coding:utf-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/', ] - 粒度到按钮级别的控制 """