权限组件(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('未获取权限,请先获取权限')

 

思路: 

  1. 先检查是不是在白名单里,是的话直接返回,就不往下运行了
  2. 获取session里面储存的权限,如果没有的话就说明没有登录,提示用户登录
  3. 检查当前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>

 

posted @ 2019-03-07 15:49  梁少华  阅读(694)  评论(0编辑  收藏  举报