权限组件(1):一级菜单
一级菜单效果图
权限表:
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) is_menu = models.BooleanField(verbose_name='是否可以做菜单', default=False) icon = models.CharField(verbose_name='图标', max_length=32, null=True, blank=True) def __str__(self): return self.title class Role(models.Model): """ 角色表 """ name = models.CharField(verbose_name='角色名称', max_length=32) permissions = models.ManyToManyField(verbose_name='所拥有的权限', to='Permission', blank=True) 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.username
一、 先登录,访问login
业务app/views/accounts.py
from django.shortcuts import render, redirect from web.forms.accounts import LoginForm from rbac import models from rbac.service.init_permission import init_permission def login(request): if request.method == 'GET': forms = LoginForm() return render(request, 'login.html', {'forms': forms}) forms = LoginForm(data=request.POST) errors = forms.errors.get('__all__') if forms.is_valid(): username = request.POST.get('name') password = request.POST.get('password') current_user = models.UserInfo.objects.filter(name=username, password=password).first() init_permission(current_user, request) return redirect('/customer/list/') context = { 'forms': forms, 'errors': errors } return render(request, 'login.html', context)
业务app/forms/accounts.py login的form表单验证。用户名和密码是否正确也在这里验证。
from django import forms from rbac.models import UserInfo class BaseBootStrapForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(BaseBootStrapForm, self).__init__(*args, **kwargs) for name, field in self.fields.items(): field.widget.attrs['class'] = 'form-control' class LoginForm(BaseBootStrapForm): class Meta: model = UserInfo fields = ('name', 'password',) label = { 'name': '用户名', 'password': '密码', } def clean(self): input_username = self.cleaned_data.get('name') input_password = self.cleaned_data.get('password') if input_username and input_password: user_obj = UserInfo.objects.filter(name=input_username, password=input_password).first() print(user_obj) if not user_obj: raise forms.ValidationError('用户名或密码错误') else: return self.cleaned_data
二、登录成功后初始化用户权限
rbac/service/init_permission.py
from django.conf import settings def init_permission(current_user, request): """ 用户权限的初始化 :param current_user: 当前登录用户 :param request: :return: """ permission_queryset = current_user.roles.filter(permissions__isnull=False).values( 'permissions__id', 'permissions__title', 'permissions__is_menu', 'permissions__icon', 'permissions__url' ).distinct() # 获取权限 + 菜单信息 menu_list = [] permission_list = [] for item in permission_queryset: permission_list.append(item['permissions__url']) # 权限 if item['permissions__is_menu']: menu_info = { 'title': item['permissions__title'], 'icon': item['permissions__icon'], 'url': item['permissions__url'], } menu_list.append(menu_info) # 菜单 # 将权限信息和菜单信息放入到session中 request.session[settings.PERMISSION_SESSION_KEY] = permission_list request.session[settings.MENU_SESSION_KEY] = menu_list
settings.py 中关于权限的配置
... ########### 权限相关 ############## PERMISSION_SESSION_KEY = 'permission_url_list_key' WHITE_LIST = ['/login/', '/admin/.*'] # 一级菜单 MENU_SESSION_KEY = 'permission_menu_key' ...
三、中间件验证权限
rbac/middlewares/rbac.py
import re from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse from django.conf import settings class RbacMiddleware(MiddlewareMixin): def process_request(self, request): white_list = settings.WHITE_LIST current_path = request.path_info for valid_url in white_list: if re.match(valid_url, current_path): return None permission_list = request.session.get(settings.PERMISSION_SESSION_KEY) if not permission_list: return HttpResponse('请先登录') has_permission = False for url in permission_list: reg = '^%s$' % url if re.match(reg, current_path): has_permission = True break if not has_permission: return HttpResponse('未获取权限,请先获取权限')
思路:
- 先检查是不是在白名单里,是的话直接返回,就不往下运行了
- 获取session里面储存的权限,如果没有的话就说明没有登录,提示用户登录
- 检查当前url是否在session里,如果不是的话说明用户没有权限,提示用户先获取权限
注册middleware
MIDDLEWARE = [ ... 'rbac.middlewares.rbac.RbacMiddleware' ... ]
四、渲染到模板
现在settings里注册自定义的标签templatetags
INSTALLED_APPS = [ 'django.contrib.staticfiles', # 静态文件 'rbac.templatetags', # 自定义标签 ]
rbac/templatetags/rbac.py
import re from django.template import Library from django.conf import settings register = Library() @register.inclusion_tag('rbac/menu.html') def menu(request): """ 创建一级菜单 :param request: :return: """ menu_list = request.session[settings.MENU_SESSION_KEY] current_path = request.path for item in menu_list: reg = '^%s$' % item['url'] if re.match(reg, current_path): item['class'] = 'active' # menu函数里面的返回值是返回到 rbac/templates/rbac/menu.html # menu函数里面的返回值也可以说是返回到inclusion_tag所指向的模版路径 context = { 'menu_list': menu_list, } return context
思路:
1. 在session中获取一级菜单列表
2. 循环菜单,让菜单里的url,和当前用户点击的url进行匹配,如果匹配上的话给用户点击的url加上样式
3. 最后返回一级菜单列表给模板用
rbac/templates/rbac/menu.html
<div class="static-menu"> {% for menu in menu_list %} <a href="{{ menu.url }}" class="{{ menu.class }}"> <span class="icon-wrap"><i class="fa {{ menu.icon }}"></i></span>{{ menu.title }} </a> {% endfor %} </div>
在业务app/templates/layout.html引入rbac/templates/rbac/menu.html
{% load staticfiles %} {% load rbac %} {# 程序去加载自定义变迁,系统会默认找到templatetags的文件下面的rbac模块 #} <!DOCTYPE html> <html lang="en"> <head> ....... </head> <body> <div class="pg-header"> <div class="nav"> <div class="logo-area left"> <a href="#"> <img class="logo" src="{% static 'imgs/logo.svg' %}"> <span style="font-size: 18px;">路飞学城 </span> </a> </div> <div class="left-menu left"> <a class="menu-item">资产管理</a> <a class="menu-item">用户信息</a> <a class="menu-item">路飞管理</a> <div class="menu-item"> <span>使用说明</span> <i class="fa fa-caret-down" aria-hidden="true"></i> <div class="more-info"> <a href="#" class="more-item">管他什么菜单</a> <a href="#" class="more-item">实在是编不了</a> </div> </div> </div> <div class="right-menu right clearfix"> <div class="user-info right"> <a href="#" class="avatar"> <img class="img-circle" src="{% static 'imgs/default.png' %}"> </a> <div class="more-info"> <a href="#" class="more-item">个人信息</a> <a href="#" class="more-item">注销</a> </div> </div> <a class="user-menu right"> 消息 <i class="fa fa-commenting-o" aria-hidden="true"></i> <span class="badge bg-success">2</span> </a> <a class="user-menu right"> 通知 <i class="fa fa-envelope-o" aria-hidden="true"></i> <span class="badge bg-success">2</span> </a> <a class="user-menu right"> 任务 <i class="fa fa-bell-o" aria-hidden="true"></i> <span class="badge bg-danger">4</span> </a> </div> </div> </div> <div class="pg-body"> <div class="left-menu"> <div class="menu-body"> {% menu request %} {# request 是传递给menu的参数 #} {# menu 是templatetags 里面的函数 #} </div> </div> <div class="right-body"> <div> <ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;"> <li><a href="#">首页</a></li> <li class="active">客户管理</li> </ol> </div> {% block content %} {% endblock %} </div> </div> <script src="{% static 'js/jquery-3.3.1.min.js' %} "></script> <script src="{% static 'plugins/bootstrap/js/bootstrap.js' %} "></script> {% block js %} {% endblock %} </body> </html> </html>