zoukankan      html  css  js  c++  java
  • Django

    第一版

    表的设计

    from django.db import models
    
    
    class Permission(models.Model):
        title = models.CharField(max_length=32, verbose_name='标题')
        url = models.CharField(max_length=32, verbose_name='权限')
    
        def __str__(self):
            return self.title
    
    
    class Role(models.Model):
        name = models.CharField(max_length=32, verbose_name='角色')
        permissions = models.ManyToManyField(to='Permission', verbose_name='角色所拥有的权限',blank=True)
    
        def __str__(self):
            return self.name
    
    
    class User(models.Model):
        name = models.CharField(max_length=32, verbose_name='用户名')
        password = models.CharField(max_length=32, verbose_name='密码')
        roles = models.ManyToManyField(to='Role', verbose_name='用户所拥有的角色',blank=True)
    
        def __str__(self):
            return self.name
    models.py

    使用admin录入权限,角色,用户

    from django.contrib import admin
    from rbac import models
    
    
    # Register your models here.
    
    class PermissionAdmin(admin.ModelAdmin):
        list_display = ['title', 'url']  # 可查看
        list_editable = ['url']  # 可做编辑
    
    
    admin.site.register(models.Permission, PermissionAdmin)
    admin.site.register(models.Role)
    admin.site.register(models.User)
    admin.py

    登录相关视图

    from django.shortcuts import render, HttpResponse, redirect, reverse
    from rbac import models
    from django.conf import settings
    
    
    def login(request):
        if request.method == 'POST':
            username = request.POST.get('username')
            pwd = request.POST.get('pwd')
    
            user = models.User.objects.filter(name=username, password=pwd).first()
    
            if not user:
                err_msg = '用户名或密码错误'
                return render(request, 'login.html', {'err_msg': err_msg})
    
            # 登录成功
            # 将权限信息写入到session
    
            # 1. 查当前登录用户拥有的权限
            # values_list内部转为元组
            permission_list = user.roles.filter(permissions__url__isnull=False).values_list(
                'permissions__url').distinct()
            for i in permission_list:
                print(i)
    
            # 2. 将权限信息写入到session
            # 方法一
            # request.session['permissions'] = list(permission_list)  # [(),] 写入到session,session内部会序列化[[],]
    
            print("permission_list", permission_list)
            print("permission_list_1", list(permission_list))
    
            # 方法二 写成可配置的
            request.session[settings.PERMISSION_SESSION_KEY] = list(permission_list)
    
            return redirect(reverse('customer'))
    
        return render(request, 'login.html')
    account.py

    settings配置权限相关信息和白名单

    # 中间件注册
    MIDDLEWARE = [
        'web.middlewares.rbac.PermissionMiddleware',
    ]
    
    #  ###### 权限相关的配置 ######
    PERMISSION_SESSION_KEY = 'permissions'
    ### 这里需要用到正则
    WHITE_URL_LIST = [
        r'^/login/$',
        r'^/logout/$',
        r'^/reg/$',
        r'^/admin/.*',
    ]
    settings.py

    中间件对权限的校验以及白名单的判断

    from django.utils.deprecation import MiddlewareMixin
    from django.conf import settings
    from django.shortcuts import HttpResponse
    import re
    
    
    class PermissionMiddleware(MiddlewareMixin):
        def process_request(self, request):
            # 对权限进行校验
            # 1. 当前访问的URL
            current_url = request.path_info
            
            # 白名单的判断
            for i in settings.WHITE_URL_LIST:
                if re.match(i,current_url):
                    return
            
            # 2. 获取当前用户的所有权限信息
            
            permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
            # 3. 权限的校验
            print(current_url)
            
            for item in permission_list:
                url = item[0]
                if re.match("^{}$".format(url), current_url):
                    return
            else:
                return HttpResponse('没有权限')
    rbac.py

    业务相关views和templates

    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)
    
        def __str__(self):
            return self.name
    
    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)
    models.py
    from django.conf.urls import url
    from web.views import customer
    from web.views import payment
    from web.views import account
    
    urlpatterns = [
    
        url(r'^customer/list/$', customer.customer_list,name='customer'),
        url(r'^customer/add/$', customer.customer_add),
        url(r'^customer/edit/(?P<cid>d+)/$', customer.customer_edit),
        url(r'^customer/del/(?P<cid>d+)/$', customer.customer_del),
    
        url(r'^payment/list/$', payment.payment_list),
        url(r'^payment/add/$', payment.payment_add),
        url(r'^payment/edit/(?P<pid>d+)/$', payment.payment_edit),
        url(r'^payment/del/(?P<pid>d+)/$', payment.payment_del),
        
        
        url(r'^login/$',account.login)
    ]
    urls.py

    客户相关view

    import os
    import mimetypes
    from django.shortcuts import render, redirect
    from django.http import FileResponse
    from django.conf import settings
    # import xlrd
    
    from web import models
    from web.forms.customer import CustomerForm
    
    
    def customer_list(request):
        """
        客户列表
        :return:
        """
        data_list = models.Customer.objects.all()
    
        return render(request, 'customer_list.html', {'data_list': data_list})
    
    
    def customer_add(request):
        """
        编辑客户
        :return:
        """
        if request.method == 'GET':
            form = CustomerForm()
            return render(request, 'customer_edit.html', {'form': form})
        form = CustomerForm(data=request.POST)
        if form.is_valid():
            form.save()
            return redirect('/customer/list/')
        return render(request, 'customer_edit.html', {'form': form})
    
    
    def customer_edit(request, cid):
        """
        新增客户
        :return:
        """
        obj = models.Customer.objects.get(id=cid)
        if request.method == 'GET':
            form = CustomerForm(instance=obj)
            return render(request, 'customer_add.html', {'form': form})
        form = CustomerForm(data=request.POST, instance=obj)
        if form.is_valid():
            form.save()
            return redirect('/customer/list/')
        return render(request, 'customer_add.html', {'form': form})
    
    
    def customer_del(request, cid):
        """
        删除客户
        :param request:
        :param cid:
        :return:
        """
        models.Customer.objects.filter(id=cid).delete()
        return redirect('/customer/list/')
    customer.py

    缴费相关view

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from django.shortcuts import render, redirect
    
    from web import models
    from web.forms.payment import PaymentForm, PaymentUserForm
    
    
    def payment_list(request):
        """
        付费列表
        :return:
        """
        data_list = models.Payment.objects.all()
        return render(request, 'payment_list.html', {'data_list': data_list})
    
    
    def payment_add(request):
        """
        编辑付费记录
        :return:
        """
        if request.method == 'GET':
            form = PaymentForm()
            return render(request, 'payment_edit.html', {'form': form})
        form = PaymentForm(data=request.POST)
        if form.is_valid():
            form.save()
            return redirect('/payment/list/')
        return render(request, 'payment_edit.html', {'form': form})
    
    
    def payment_edit(request, pid):
        """
        新增付费记录
        :return:
        """
        obj = models.Payment.objects.get(id=pid)
        if request.method == 'GET':
            form = PaymentForm(instance=obj)
            return render(request, 'payment_add.html', {'form': form})
        form = PaymentForm(data=request.POST, instance=obj)
        if form.is_valid():
            form.save()
            return redirect('/payment/list/')
        return render(request, 'payment_add.html', {'form': form})
    
    
    def payment_del(request, pid):
        """
        删除付费记录
        :param request:
        :param cid:
        :return:
        """
        models.Payment.objects.filter(id=pid).delete()
        return redirect('/payment/list/')
    payment.py

    templates

    {% load staticfiles %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>爱软测</title>
        <link rel="shortcut icon" href="{% static 'imgs/luffy-study-logo.png' %} ">
        <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/>
        <link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/commons.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/nav.css' %} "/>
        <style>
            body {
                margin: 0;
            }
    
            .no-radius {
                border-radius: 0;
            }
    
            .no-margin {
                margin: 0;
            }
    
            .pg-body > .left-menu {
                background-color: #EAEDF1;
                position: absolute;
                left: 1px;
                top: 48px;
                bottom: 0;
                 220px;
                border: 1px solid #EAEDF1;
                overflow: auto;
            }
    
            .pg-body > .right-body {
                position: absolute;
                left: 225px;
                right: 0;
                top: 48px;
                bottom: 0;
                overflow: scroll;
                border: 1px solid #ddd;
                border-top: 0;
                font-size: 13px;
                min- 755px;
            }
    
            .navbar-right {
                float: right !important;
                margin-right: -15px;
            }
    
            .luffy-container {
                padding: 15px;
            }
    
            .left-menu .menu-body .static-menu {
    
            }
    
            .left-menu .menu-body .static-menu .icon-wrap {
                 20px;
                display: inline-block;
                text-align: center;
            }
    
            .left-menu .menu-body .static-menu a {
                text-decoration: none;
                padding: 8px 15px;
                border-bottom: 1px solid #ccc;
                color: #333;
                display: block;
                background: #efefef;
                background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
                background: -ms-linear-gradient(bottom, #efefef, #fafafa);
                background: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%);
                background: -o-linear-gradient(bottom, #efefef, #fafafa);
                filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff');
                -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')";
                box-shadow: inset 0px 1px 1px white;
            }
    
            .left-menu .menu-body .static-menu a:hover {
                color: #2F72AB;
                border-left: 2px solid #2F72AB;
            }
    
            .left-menu .menu-body .static-menu a.active {
                color: #2F72AB;
                border-left: 2px solid #2F72AB;
            }
        </style>
    </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">
                <div class="static-menu">
                    <a href="/customer/list/" class="active">
                        <span class="icon-wrap"><i class="fa fa-connectdevelop"></i></span> 客户管理</a>
                    <a href="/payment/list/">
                        <span class="icon-wrap"><i class="fa fa-code-fork"></i></span> 账单管理</a>
                </div>
    
            </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>
    layout.html
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta http-equiv="content-Type" charset="UTF-8">
        <meta http-equiv="x-ua-compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Title</title>
    </head>
    <body>
    <form action="" method="post">
        {% csrf_token %}
        <p>
            用户名:<input type="text" name="username">
        </p>
        <p>
            密码:<input type="password" name="pwd">
        </p>
        <button>登录</button>
        <span style="color: red">{{ err_msg }}</span>
    </form>
    
    </body>
    </html>
    login.html
    {% extends 'layout.html' %}
    
    {% block content %}
    
        <div class="luffy-container">
            <div class="btn-group" style="margin: 5px 0">
                <a class="btn btn-default" href="/customer/add/">
                    <i class="fa fa-plus-square" aria-hidden="true"></i> 添加客户
                </a>
            </div>
            <table class="table table-bordered table-hover">
                <thead>
                <tr>
                    <th>ID</th>
                    <th>客户姓名</th>
                    <th>年龄</th>
                    <th>邮箱</th>
                    <th>公司</th>
                    <th>选项</th>
                </tr>
                </thead>
                <tbody>
                {% for row in data_list %}
                    <tr>
                        <td>{{ row.id }}</td>
                        <td>{{ row.name }}</td>
                        <td>{{ row.age }}</td>
                        <td>{{ row.email }}</td>
                        <td>{{ row.company }}</td>
                        <td>
                            <a style="color: #333333;" href="/customer/edit/{{ row.id }}/">
                                <i class="fa fa-edit" aria-hidden="true"></i></a>
                            |
                            <a style="color: #d9534f;" href="/customer/del/{{ row.id }}/"><i class="fa fa-trash-o"></i></a>
                        </td>
    
                    </tr>
                {% endfor %}
                </tbody>
            </table>
        </div>
    {% endblock %}
    customer_list.html
    {% extends 'layout.html' %}
    
    {% block content %}
        <div class="luffy-container">
            <form class="form-horizontal clearfix" method="post" novalidate>
                {% csrf_token %}
    
                {% for field in form %}
                    <div class="form-group col-sm-6 clearfix">
                        <label class="col-sm-3 control-label">{{ field.label }}</label>
                        <div class="col-sm-9">
                            {{ field }} <span style="color:firebrick;">{{ field.errors.0 }}</span>
                        </div>
                    </div>
                {% endfor %}
                <div class="form-group col-sm-12">
                    <div class="col-sm-6">
                        <div class="col-sm-offset-3">
                            <button type="submit" class="btn btn-primary">提 交</button>
                        </div>
                    </div>
                </div>
            </form>
        </div>
    {% endblock %}
    customer_add.html
    {% extends 'layout.html' %}
    
    {% block content %}
        <div class="luffy-container">
            <form class="form-horizontal clearfix" method="post" novalidate>
                {% csrf_token %}
    
                {% for field in form %}
                    <div class="form-group col-sm-6 clearfix">
                        <label class="col-sm-3 control-label">{{ field.label }}</label>
                        <div class="col-sm-9">
                            {{ field }} <span style="color:firebrick;">{{ field.errors.0 }}</span>
                        </div>
                    </div>
                {% endfor %}
                <div class="form-group col-sm-12">
                    <div class="col-sm-6">
                        <div class="col-sm-offset-3">
                            <button type="submit" class="btn btn-primary">提 交</button>
                        </div>
                    </div>
                </div>
            </form>
        </div>
    {% endblock %}
    customer_edit.html
    {% extends 'layout.html' %}
    
    {% block content %}
    
        <div class="luffy-container">
            <form method="post" enctype="multipart/form-data">
                {% csrf_token %}
                <div class="form-group">
                    <div
                            style="position: relative;display: inline-block;height: 50px;min- 300px;overflow: hidden;">
                        <div style="position: absolute;top: 0;left: 0;right: 0;bottom: 0;z-index: 1000;border: 1px dotted #9d9d9d;color: #9d9d9d;line-height: 50px;padding-left: 15px;">
                            <i class="fa fa-cloud-upload" aria-hidden="true"></i>
                            <span>点击上传Excel文件</span>
                        </div>
                        <input name="customer_excel" type="file" id="excelFile"
                               style="position: absolute;top: 0;left: 0;right: 0;bottom: 0;background-color: #333333;z-index: 1001;opacity: 0;filter:alpha(opacity=0);">
                    </div>
                    <p class="help-block">注意:批量导入的Excel需使用规定格式模板. <a href="/customer/tpl/">下载模板</a></p>
                </div>
                <button type="submit" class="btn btn-primary">上传</button>
                {% if status %}
                    <span style="color: green;">{{ msg }}</span>
                {% else %}
                    <span style="color: red;">{{ msg }}</span>
                {% endif %}
            </form>
        </div>
    {% endblock %}
    
    {% block js %}
        <script>
            $(function () {
                $('#excelFile').change(function (e) {
                    var fileName = e.currentTarget.files[0].name;
                    $(this).prev().find('span').text(fileName);
                })
            })
        </script>
    {% endblock %}
    customer_import.html
    {% extends 'layout.html' %}
    
    {% block content %}
    
        <div class="luffy-container">
            <div style="margin: 5px 0;">
                <a class="btn btn-success" href="/payment/add/">
                    <i class="fa fa-plus-square" aria-hidden="true"></i> 添加缴费记录
                </a>
            </div>
            <table class="table table-bordered table-hover">
                <thead>
                <tr>
                    <th>ID</th>
                    <th>客户姓名</th>
                    <th>金额</th>
                    <th>付费时间</th>
                    <th>选项</th>
                </tr>
                </thead>
                <tbody>
                {% for row in data_list %}
                    <tr>
                        <td>{{ row.id }}</td>
                        <td>{{ row.customer.name }}</td>
                        <td>{{ row.money }}</td>
                        <td>{{ row.create_time|date:"Y-m-d H:i:s" }}</td>
                        <td>
                            <a style="color: #333333;" href="/payment/edit/{{ row.id }}/">
                                <i class="fa fa-edit" aria-hidden="true"></i></a>
                            |
                            <a style="color: #d9534f;" href="/payment/del/{{ row.id }}/"><i class="fa fa-trash-o"></i></a>
                        </td>
    
                    </tr>
                {% endfor %}
                </tbody>
            </table>
        </div>
    {% endblock %}
    payment_list.html
    {% extends 'layout.html' %}
    
    {% block content %}
        <div class="luffy-container">
            <form class="form-horizontal clearfix" method="post" novalidate>
                {% csrf_token %}
    
                {% for field in form %}
                    <div class="form-group col-sm-6 clearfix">
                        <label class="col-sm-3 control-label">{{ field.label }}</label>
                        <div class="col-sm-9">
                            {{ field }} <span style="color:firebrick;">{{ field.errors.0 }}</span>
                        </div>
                    </div>
                {% endfor %}
                <div class="form-group col-sm-12">
                    <div class="col-sm-6">
                        <div class="col-sm-offset-3">
                            <button type="submit" class="btn btn-primary">提 交</button>
                        </div>
                    </div>
                </div>
            </form>
        </div>
    {% endblock %}
    payment_add.html
    {% extends 'layout.html' %}
    
    {% block content %}
    
        <div class="luffy-container">
            <form class="form-horizontal clearfix" method="post" novalidate>
                {% csrf_token %}
    
                {% for field in form %}
                    <div class="form-group col-sm-6 clearfix">
                        <label class="col-sm-3 control-label">{{ field.label }}</label>
                        <div class="col-sm-9">
                            {{ field }} <span style="color:firebrick;">{{ field.errors.0 }}</span>
                        </div>
                    </div>
                {% endfor %}
                <div class="form-group col-sm-12">
                    <div class="col-sm-6">
                        <div class="col-sm-offset-3">
                            <button type="submit" class="btn btn-primary">提 交</button>
                        </div>
                    </div>
                </div>
            </form>
        </div>
    {% endblock %}
    payment_edit.html

    第二版:动态生成一级菜单

    表结构的设计改动,增加菜单和图标标识

    from django.db import models
    
    
    class Permission(models.Model):
        """
        权限表
        """
        title = models.CharField(max_length=32, verbose_name='标题')
        url = models.CharField(max_length=32, verbose_name='权限')
        
        is_menu = models.BooleanField(default=False, verbose_name='是否是菜单')
        icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)
        
        class Meta:
            verbose_name_plural = '权限表'
            verbose_name = '权限表'
        
        def __str__(self):
            return self.title
    
    
    class Role(models.Model):
        name = models.CharField(max_length=32, verbose_name='角色名称')
        permissions = models.ManyToManyField(to='Permission', verbose_name='角色所拥有的权限', blank=True)
        
        def __str__(self):
            return self.name
    
    
    class User(models.Model):
        """
        用户表
        """
        name = models.CharField(max_length=32, verbose_name='用户名')
        password = models.CharField(max_length=32, verbose_name='密码')
        roles = models.ManyToManyField(to='Role', verbose_name='用户所拥有的角色', blank=True)
        
        def __str__(self):
            return self.name
    models.py

    admin的增加

    from django.contrib import admin
    from rbac import models
    
    
    class PermissionAdmin(admin.ModelAdmin):
        list_display = ['title', 'url', 'is_menu', 'icon']
        list_editable = ['url', 'is_menu', 'icon']
    
    
    admin.site.register(models.Permission, PermissionAdmin)
    admin.site.register(models.Role)
    admin.site.register(models.User)
    admin.py

    登录

    from django.shortcuts import render, HttpResponse, redirect, reverse
    from rbac import models
    from rbac.server.init_permission import init_permission
    import copy
    
    
    def login(request):
        if request.method == 'POST':
            username = request.POST.get('username')
            pwd = request.POST.get('pwd')
            
            user = models.User.objects.filter(name=username, password=pwd).first()
            
            if not user:
                err_msg = '用户名或密码错误'
                return render(request, 'login.html', {'err_msg': err_msg})
            
            # 登录成功
            # 将权限信息写入到session
            init_permission(request,user)
            
            return redirect(reverse('customer'))
        
        return render(request, 'login.html')
    account.py

    权限的初始化

    from django.conf import settings
    
    
    def init_permission(request, user):
        # 1. 查当前登录用户拥有的权限,values 返回{}
        permission_query = user.roles.filter(permissions__url__isnull=False).values(
            'permissions__url',
            'permissions__is_menu',
            'permissions__icon',
            'permissions__title').distinct()
        
        # 存放权限信息
        permission_list = []
        
        # 存放菜单信息
        
        menu_list = []
        
        for item in permission_query:
            permission_list.append({'url': item['permissions__url']})
            
            if item.get('permissions__is_menu'):
                menu_list.append({'url': item['permissions__url'], 'icon': item['permissions__icon'],
                                  'title': item['permissions__title']})
        
        # 2. 将权限信息写入到session
        request.session[settings.PERMISSION_SESSION_KEY] = permission_list
        
        # 将菜单信息写入到session
        request.session[settings.MENU_SESSION_KEY] = menu_list
    init_permission.py

    中间件的校验

    from django.utils.deprecation import MiddlewareMixin
    from django.conf import settings
    from django.shortcuts import HttpResponse
    import re
    
    
    class PermissionMiddleware(MiddlewareMixin):
        def process_request(self, request):
            # 对权限进行校验
            # 1. 当前访问的URL
            current_url = request.path_info
            
            # 白名单的判断
            for i in settings.WHITE_URL_LIST:
                if re.match(i,current_url):
                    return
            
            # 2. 获取当前用户的所有权限信息
            
            permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
            # 3. 权限的校验
            print(current_url)
            
            for item in permission_list:
                url = item['url']
                if re.match("^{}$".format(url), current_url):
                    return
            else:
                return HttpResponse('没有权限')
    middlewares bac.py

    菜单展示逻辑判断,写到自定义inclusion_tag里面,在传到menu.html文件中

    from django import template
    
    register = template.Library()
    
    from django.conf import settings
    import re
    
    
    @register.inclusion_tag('rbac/menu.html')
    def menu(request):
        menu_list = request.session.get(settings.MENU_SESSION_KEY)
        for item in menu_list:
            url = item['url']
            if re.match('^{}$'.format(url), request.path_info):
                item['class'] = 'active'
                break
        
        return {"menu_list": menu_list}
    templatetags/rbac.py
    <div class="static-menu">
    
        {% for item in menu_list %}
            <a href="{{ item.url }}" class="{{ item.class }}">
                <span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span> {{ item.title }}</a>
    
        {% endfor %}
    menu.html

    母版中引用menu.html

    {% load staticfiles %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>爱软测</title>
        <link rel="shortcut icon" href="{% static 'imgs/luffy-study-logo.png' %} ">
        <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/>
        <link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/commons.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/nav.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/menu.css' %}">
        <style>
            body {
                margin: 0;
            }
    
            .no-radius {
                border-radius: 0;
            }
    
            .no-margin {
                margin: 0;
            }
    
            .pg-body > .left-menu {
                background-color: #EAEDF1;
                position: absolute;
                left: 1px;
                top: 48px;
                bottom: 0;
                 220px;
                border: 1px solid #EAEDF1;
                overflow: auto;
            }
    
            .pg-body > .right-body {
                position: absolute;
                left: 225px;
                right: 0;
                top: 48px;
                bottom: 0;
                overflow: scroll;
                border: 1px solid #ddd;
                border-top: 0;
                font-size: 13px;
                min- 755px;
            }
    
            .navbar-right {
                float: right !important;
                margin-right: -15px;
            }
    
            .luffy-container {
                padding: 15px;
            }
    
            .left-menu .menu-body .static-menu {
    
            }
    
    
        </style>
    </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">
                {% load rbac %}
                {% menu request %}
    
            </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>
    layout.html

     应用到项目中

    应用rbac组件
            1. 拷贝rbac组件 到 新的项目中 并注册APP
            
            2. 配置权限的相关信息
                #  ###### 权限相关的配置 ######
                PERMISSION_SESSION_KEY = 'permissions'
                MENU_SESSION_KEY = 'menus'
                WHITE_URL_LIST = [
                    r'^/login/$',
                    r'^/logout/$',
                    r'^/reg/$',
                    r'^/admin/.*',
                ]
                
            3. 创建跟权限相关的表(删除之前的迁移文件的记录)
                执行命令:
                    python manage.py makemigrations
                    python manage.py migrate
    
            4. 录入权限信息
                创建超级用户
                录入所有权限信息
                创建角色  给角色分权限
                创建用户  给用户分角色
                
            5. 在登录成功之后 写入权限和菜单的信息 到session中
            
            6. 配置上中间件 进行权限的校验
            
            7. 使用动态的菜单
                    导入静态文件
                    <link rel="stylesheet" href="{% static 'css/menu.css' %}">
                    
                    使用inclusion_tag 
                    <div class="left-menu">
                        <div class="menu-body">
                            {% load rbac %}
                            {% menu request %}
                        </div>
                    </div>

    第三版:动态生成二级菜单

     表的设计

    from django.db import models
    
    
    class Menu(models.Model):
        """
        一级菜单
        """
        title = models.CharField(max_length=32, unique=True)
        icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)
    
        class Meta:
            verbose_name_plural = '菜单表'
            verbose_name = '菜单表'
    
        def __str__(self):
            return self.title
        
    
    class Permission(models.Model):
        """
        权限表
        有关联Menu的是二级菜单
        没有关联Menu的不是二级菜单,是不可以做菜单的权限
        
        
        """
        title = models.CharField(max_length=32, verbose_name='标题')
        url = models.CharField(max_length=32, verbose_name='权限')
        menu = models.ForeignKey('Menu', null=True, blank=True)
        
        class Meta:
            verbose_name_plural = '权限表'
            verbose_name = '权限表'
        
        def __str__(self):
            return self.title
    
    
    class Role(models.Model):
        name = models.CharField(max_length=32, verbose_name='角色名称')
        permissions = models.ManyToManyField(to='Permission', verbose_name='角色所拥有的权限', blank=True)
        
        def __str__(self):
            return self.name
    
    
    class User(models.Model):
        """
        用户表
        """
        name = models.CharField(max_length=32, verbose_name='用户名')
        password = models.CharField(max_length=32, verbose_name='密码')
        roles = models.ManyToManyField(to='Role', verbose_name='用户所拥有的角色', blank=True)
        
        def __str__(self):
            return self.name
    models.py

    使用admin录入一级和二级菜单

    from django.contrib import admin
    from rbac import models
    
    
    class PermissionAdmin(admin.ModelAdmin):
        list_display = ['title', 'url', ]
        list_editable = ['url', ]
    
    
    admin.site.register(models.Permission, PermissionAdmin)
    admin.site.register(models.Role)
    admin.site.register(models.User)
    admin.site.register(models.Menu)
    admin.py

    菜单的数据结构设计

    data = [
        {
            'permissions__url': '/customer/list/',
            'permissions__title': '客户列表',
            'permissions__menu_id': 1,
            'permissions__menu__title': '信息管理',
            'permissions__menu__icon': 'fa-clipboard'
        }, {
            'permissions__url': '/customer/add/',
            'permissions__title': '添加客户',
            'permissions__menu_id': None,
            'permissions__menu__title': None,
            'permissions__menu__icon': None
        }, {
            'permissions__url': '/customer/edit/(?P<cid>\d+)/',
            'permissions__title': '编辑客户',
            'permissions__menu_id': None,
            'permissions__menu__title': None,
            'permissions__menu__icon': None
        },
        {
            'permissions__url': '/payment/list/',
            'permissions__title': '缴费列表',
            'permissions__menu_id': 1,
            'permissions__menu__title': '信息管理',
            'permissions__menu__icon': 'fa-clipboard'
        },
    ]
    
    menu_dict = {}
    
    for item in data:
        
        menu_id = item.get('permissions__menu_id')
        
        if not menu_id:
            continue
        
        if menu_id not in menu_dict:
            menu_dict[menu_id] = {
                'title': item['permissions__menu__title'],
                'icon': item['permissions__menu__icon'],
                'children': [
                    {'title': item['permissions__title'], 'url': item['permissions__url']}
                ]
            }
        else:
            menu_dict[menu_id]['children'].append({'title': item['permissions__title'], 'url': item['permissions__url']})
    
    print(menu_dict)
    
    """
    
    {
        1 : {
            'title' : '信息管理',
            'icon'  : 'fa-clipboard',
            'children': [
                {'title':'客户列表','url':'/customer/list/'}
            ]
        },
        2 : {
            'title' : '财务管理',
            'icon'  : 'fa-clipboard',
            'children': [
                {'title':'客户列表','url':'/customer/list/'}
            ]
        }
    }
    
    """
    菜单的数据结构.py

    权限的初始化

    from django.conf import settings
    
    
    def init_permission(request, user):
        # 1. 查当前登录用户拥有的权限
        permission_query = user.roles.filter(permissions__url__isnull=False).values(
            'permissions__url',
            'permissions__title',
            'permissions__menu_id',
            'permissions__menu__title',
            'permissions__menu__icon',
        ).distinct()
        
        # 存放权限信息
        permission_list = []
        
        # 存放菜单信息
        
        menu_dict = {}
        
        for item in permission_query:
            permission_list.append({'url': item['permissions__url']})
    
            menu_id = item.get('permissions__menu_id')
    
            if not menu_id:
                continue
    
            if menu_id not in menu_dict:
                menu_dict[menu_id] = {
                    'title': item['permissions__menu__title'],
                    'icon': item['permissions__menu__icon'],
                    'children': [
                        {'title': item['permissions__title'], 'url': item['permissions__url']}
                    ]
                }
            else:
                menu_dict[menu_id]['children'].append(
                    {'title': item['permissions__title'], 'url': item['permissions__url']})
        
        
    
        
    
        # # 2. 将权限信息写入到session
        request.session[settings.PERMISSION_SESSION_KEY] = permission_list
    
        # 将菜单信息写入到session
        request.session[settings.MENU_SESSION_KEY] = menu_dict
    init_permission.py

    菜单展示逻辑判断,写到自定义inclusion_tag里面,在传到menu.html文件中

    from django import template
    
    register = template.Library()
    
    from django.conf import settings
    import re
    
    
    @register.inclusion_tag('rbac/menu.html')
    def menu(request):
        menu_list = request.session.get(settings.MENU_SESSION_KEY)
        
        return {"menu_list": menu_list}
    templatetags/rbac.py
    {#<div class="static-menu">#}
    {##}
    {#    {% for item in menu_list %}#}
    {#        <a href="{{ item.url }}" class="{{ item.class }}">#}
    {#            <span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span> {{ item.title }}</a>#}
    {##}
    {#    {% endfor %}#}
    {##}
    {#</div>#}
    
    <div class="multi-menu">
        {% for item in menu_list.values %}
            <div class="item">
                <div class="title"><i class="fa {{ item.icon }}"></i> {{ item.title }}</div>
                <div class="body hide">
                    {% for child in item.children %}
                        <a href="{{ child.url }}">{{ child.title }}</a>
                    {% endfor %}
    
                </div>
            </div>
        {% endfor %}
    
    
    </div>
    menu.html

    菜单根据权重排序,二级菜单默认选中及展开

    表字段的增加

    from django.db import models
    
    
    class Menu(models.Model):
        """
        一级菜单
        """
        title = models.CharField(max_length=32, unique=True)
        icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)
        weight = models.IntegerField(default=1)
        
        class Meta:
            verbose_name_plural = '菜单表'
            verbose_name = '菜单表'
        
        def __str__(self):
            return self.title
    
    
    class Permission(models.Model):
        """
        权限表
        有关联Menu的是二级菜单
        没有关联Menu的不是二级菜单,是不可以做菜单的权限
        
        
        """
        title = models.CharField(max_length=32, verbose_name='标题')
        url = models.CharField(max_length=32, verbose_name='权限')
        menu = models.ForeignKey('Menu', null=True, blank=True)
        
        parent = models.ForeignKey('Permission', null=True, blank=True)
        
        class Meta:
            verbose_name_plural = '权限表'
            verbose_name = '权限表'
        
        def __str__(self):
            return self.title
    
    
    class Role(models.Model):
        name = models.CharField(max_length=32, verbose_name='角色名称')
        permissions = models.ManyToManyField(to='Permission', verbose_name='角色所拥有的权限', blank=True)
        
        def __str__(self):
            return self.name
    
    
    class User(models.Model):
        """
        用户表
        """
        name = models.CharField(max_length=32, verbose_name='用户名')
        password = models.CharField(max_length=32, verbose_name='密码')
        roles = models.ManyToManyField(to='Role', verbose_name='用户所拥有的角色', blank=True)
        
        def __str__(self):
            return self.name
    models.py

    menu.js

    $('.item .title').click(function () {
        // $(this).next().toggleClass('hide')
        $(this).next().removeClass('hide');
        $(this).parent().siblings().find('.body').addClass('hide');
    
    });
    menu.js

    权限的初始化

    from django.conf import settings
    
    
    def init_permission(request, user):
        # 1. 查当前登录用户拥有的权限
        permission_query = user.roles.filter(permissions__url__isnull=False).values(
            'permissions__url',
            'permissions__title',
            'permissions__menu_id',
            'permissions__menu__title',
            'permissions__menu__icon',
            'permissions__menu__weight',
        ).distinct()
        
        # 存放权限信息
        permission_list = []
        
        # 存放菜单信息
        
        menu_dict = {}
        
        for item in permission_query:
            permission_list.append({'url': item['permissions__url']})
            
            menu_id = item.get('permissions__menu_id')
            
            if not menu_id:
                continue
            
            if menu_id not in menu_dict:
                menu_dict[menu_id] = {
                    'title': item['permissions__menu__title'],
                    'icon': item['permissions__menu__icon'],
                    'weight': item['permissions__menu__weight'],
                    'children': [
                        {'title': item['permissions__title'], 'url': item['permissions__url']}
                    ]
                }
            else:
                menu_dict[menu_id]['children'].append(
                    {'title': item['permissions__title'], 'url': item['permissions__url']})
        
        # # 2. 将权限信息写入到session
        request.session[settings.PERMISSION_SESSION_KEY] = permission_list
        
        # 将菜单信息写入到session
        request.session[settings.MENU_SESSION_KEY] = menu_dict
    init_permission.py

    自定义inclusion_tag

    from django import template
    
    register = template.Library()
    
    from django.conf import settings
    import re
    from collections import OrderedDict
    
    
    @register.inclusion_tag('rbac/menu.html')
    def menu(request):
        menu_list = request.session.get(settings.MENU_SESSION_KEY)
        
        order_dict = OrderedDict() #python3.7以下是无序字典,使用OrderedDict
        
        # for i in sorted(menu_list, key=lambda x: menu_list[x]['weight'],reverse=True):
        #     order_dict[i] = menu_list[i]
        #
        # for item in order_dict.values():
        #     item['class'] = 'hide'
        #
        #     for i in item['children']:
        #
        #         if re.match("^{}$".format(i['url']), request.path_info):
        #             i['class'] = 'active'
        #             item['class'] = ''
        
        for key in sorted(menu_list, key=lambda x: menu_list[x]['weight'], reverse=True):
            order_dict[key] = menu_list[key]
            
            item = order_dict[key]
            
            item['class'] = 'hide'
            
            for i in item['children']:
                
                if re.match("^{}$".format(i['url']), request.path_info):
                    i['class'] = 'active'
                    item['class'] = ''
        
        return {"menu_list": order_dict}
    templatetags/rbac.py
    {#<div class="static-menu">#}
    {##}
    {#    {% for item in menu_list %}#}
    {#        <a href="{{ item.url }}" class="{{ item.class }}">#}
    {#            <span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span> {{ item.title }}</a>#}
    {##}
    {#    {% endfor %}#}
    {##}
    {#</div>#}
    
    <div class="multi-menu">
        {% for item in menu_list.values %}
            <div class="item">
                <div class="title"><i class="fa {{ item.icon }}"></i> {{ item.title }}</div>
                <div class="body {{ item.class }}">
                    {% for child in item.children %}
                        <a href="{{ child.url }}" class="{{ child.class }}">{{ child.title }}</a>
                    {% endfor %}
    
                </div>
            </div>
        {% endfor %}
    
    
    </div>
    menu.html

    第四版:三级菜单

    访问子权限时父权限展开

     表的设计

    from django.db import models
    
    
    class Menu(models.Model):
        """
        一级菜单
        """
        title = models.CharField(max_length=32, unique=True)
        icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)
        weight = models.IntegerField(default=1)
        
        class Meta:
            verbose_name_plural = '菜单表'
            verbose_name = '菜单表'
        
        def __str__(self):
            return self.title
    
    
    class Permission(models.Model):
        """
        权限表
        有关联Menu的是二级菜单
        没有关联Menu的不是二级菜单,是不可以做菜单的权限
        
        
        """
        title = models.CharField(max_length=32, verbose_name='标题')
        url = models.CharField(max_length=32, verbose_name='权限')
        menu = models.ForeignKey('Menu', null=True, blank=True)
        
        parent = models.ForeignKey('Permission', null=True, blank=True)
        
        class Meta:
            verbose_name_plural = '权限表'
            verbose_name = '权限表'
        
        def __str__(self):
            return self.title
    
    
    class Role(models.Model):
        name = models.CharField(max_length=32, verbose_name='角色名称')
        permissions = models.ManyToManyField(to='Permission', verbose_name='角色所拥有的权限', blank=True)
        
        def __str__(self):
            return self.name
    
    
    class User(models.Model):
        """
        用户表
        """
        name = models.CharField(max_length=32, verbose_name='用户名')
        password = models.CharField(max_length=32, verbose_name='密码')
        roles = models.ManyToManyField(to='Role', verbose_name='用户所拥有的角色', blank=True)
        
        def __str__(self):
            return self.name
    models.py

    权限的初始化

    from django.conf import settings
    
    
    def init_permission(request, user):
        # 1. 查当前登录用户拥有的权限
        permission_query = user.roles.filter(permissions__url__isnull=False).values(
            'permissions__url',
            'permissions__title',
            'permissions__id',
            'permissions__parent_id',
            'permissions__menu_id',
            'permissions__menu__title',
            'permissions__menu__icon',
            'permissions__menu__weight',
        ).distinct()
        
        # 存放权限信息
        permission_list = []
        
        # 存放菜单信息
        
        menu_dict = {}
        
        for item in permission_query:
            permission_list.append(
                {'url': item['permissions__url'], 'id': item['permissions__id'], 'pid': item['permissions__parent_id']})
            
            menu_id = item.get('permissions__menu_id')
            
            if not menu_id:
                continue
            
            if menu_id not in menu_dict:
                menu_dict[menu_id] = {
                    'title': item['permissions__menu__title'],
                    'icon': item['permissions__menu__icon'],
                    'weight': item['permissions__menu__weight'],
                    'children': [
                        {'title': item['permissions__title'], 'url': item['permissions__url'],
                         'id': item['permissions__id'], 'pid': item['permissions__parent_id']}
                    ]
                }
            else:
                menu_dict[menu_id]['children'].append(
                    {'title': item['permissions__title'], 'url': item['permissions__url'], 'id': item['permissions__id'],
                     'pid': item['permissions__parent_id']})
        
        # # 2. 将权限信息写入到session
        request.session[settings.PERMISSION_SESSION_KEY] = permission_list
        
        # 将菜单信息写入到session
        request.session[settings.MENU_SESSION_KEY] = menu_dict
    init_permission.py

    中间件校验

    from django.utils.deprecation import MiddlewareMixin
    from django.conf import settings
    from django.shortcuts import HttpResponse
    import re
    
    
    class PermissionMiddleware(MiddlewareMixin):
        def process_request(self, request):
            # 对权限进行校验
            # 1. 当前访问的URL
            current_url = request.path_info
            
            # 白名单的判断
            for i in settings.WHITE_URL_LIST:
                if re.match(i, current_url):
                    return
            
            # 2. 获取当前用户的所有权限信息
            
            permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
            # 3. 权限的校验
            print(current_url)
            
            for item in permission_list:
                url = item['url']
                if re.match("^{}$".format(url), current_url):
                    pid = item['pid']
                    id = item['id']
                    if pid:
                        # 表示当前权限是子权限,让父权限是展开
                        request.current_menu_id = pid
                        print()
                    else:
                        # 表示当前权限是父权限,要展开的二级菜单
                        request.current_menu_id = id
                    return
            
            else:
                return HttpResponse('没有权限')
    middlewares bac.py

    自定义inclusion_tag

    from django import template
    
    register = template.Library()
    
    from django.conf import settings
    import re
    from collections import OrderedDict
    '''实现了对字典对象中元素的排序'''
    
    
    @register.inclusion_tag('rbac/menu.html')
    def menu(request):
        menu_list = request.session.get(settings.MENU_SESSION_KEY)
        
        order_dict = OrderedDict()  #有序字典
        
        
        for key in sorted(menu_list, key=lambda x: menu_list[x]['weight'], reverse=True):
            order_dict[key] = menu_list[key]
            
            item = order_dict[key]
            
            item['class'] = 'hide'
            
            for i in item['children']:
                
                if i['id'] == request.current_menu_id: #当前二级菜单的id等于中间件初始化传过来的id或pid
                    i['class'] = 'active'
                    item['class'] = ''
        
        return {"menu_list": order_dict}
    templatetags/rbac.py

     路径导航

     中间件校验

    from django.utils.deprecation import MiddlewareMixin
    from django.conf import settings
    from django.shortcuts import HttpResponse
    import re
    
    
    class PermissionMiddleware(MiddlewareMixin):
        def process_request(self, request):
            # 对权限进行校验
            # 1. 当前访问的URL
            current_url = request.path_info
            
            # 白名单的判断
            for i in settings.WHITE_URL_LIST:
                if re.match(i, current_url):
                    return
            
            # 2. 获取当前用户的所有权限信息
            
            permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY)
            
            request.breadcrumb_list = [
                {"title": '首页', 'url': '#'},
            ]
            
            # 3. 权限的校验
            print(permission_dict)
            for item in permission_dict.values():
                url = item['url']
                if re.match("^{}$".format(url), current_url):
                    pid = item['pid']
                    id = item['id']
                    if pid:
                        # 表示当前权限是子权限,让父权限是展开
                        request.current_menu_id = pid
                        request.breadcrumb_list.extend(
                            [{"title": permission_dict[str(pid)]['title'], 'url': permission_dict[str(pid)]['url']},
                             {"title": item['title'], 'url': item['url']}]
                        )
                    
                    else:
                        # 表示当前权限是父权限,要展开的二级菜单
                        request.current_menu_id = id
                        # 添加面包屑导航
                        request.breadcrumb_list.append({"title": item['title'], 'url': item['url']})
                    
                    return
            
            else:
                return HttpResponse('没有权限')
    middlewares bac.py

    权限数据结构更改

    import json
    data = {
        1: {'url': '/customer/list/', 'id': 1, 'pid': None, 'title': '客户列表'},
        2: {'url': '/customer/add/', 'id': 2, 'pid': 1, 'title': '添加客户'},
        3: {'url': '/customer/edit/(?P<cid>\d+)/', 'id': 3, 'pid': 1, 'title': '编辑客户'},
        4: {'url': '/customer/del/(?P<cid>\d+)/', 'id': 4, 'pid': 1, 'title': '删除客户'},
        5: {'url': '/payment/list/', 'id': 5, 'pid': None, 'title': '缴费列表'},
        6: {'url': '/payment/add/', 'id': 6, 'pid': 5, 'title': '添加缴费记录'},
        7: {'url': '/payment/edit/(?P<pid>\d+)/', 'id': 7, 'pid': 5, 'title': '编辑缴费记录'},
        8: {'url': '/payment/del/(?P<pid>\d+)/', 'id': 8, 'pid': 5, 'title': '删除缴费记录'}}
    
    # data2 = {'1': {'url': '/customer/list/', 'id': 1, 'pid': None, 'title': '客户列表'},
    #          '2': {'url': '/customer/add/', 'id': 2, 'pid': 1, 'title': '添加客户'},
    #          '3': {'url': '/customer/edit/(?P<cid>\d+)/', 'id': 3, 'pid': 1, 'title': '编辑客户'},
    #          '4': {'url': '/customer/del/(?P<cid>\d+)/', 'id': 4, 'pid': 1, 'title': '删除客户'},
    #          '5': {'url': '/payment/list/', 'id': 5, 'pid': None, 'title': '缴费列表'},
    #          '6': {'url': '/payment/add/', 'id': 6, 'pid': 5, 'title': '添加缴费记录'},
    #          '7': {'url': '/payment/edit/(?P<pid>\d+)/', 'id': 7, 'pid': 5, 'title': '编辑缴费记录'},
    #          '8': {'url': '/payment/del/(?P<pid>\d+)/', 'id': 8, 'pid': 5, 'title': '删除缴费记录'}}
    
    
    ret = json.dumps(data)
    
    print(json.loads(ret))
    权限数据结构

    权限的初始化

    from django.conf import settings
    
    
    def init_permission(request, user):
        # 1. 查当前登录用户拥有的权限
        permission_query = user.roles.filter(permissions__url__isnull=False).values(
            'permissions__url',
            'permissions__title',
            'permissions__id',
            'permissions__parent_id',
            'permissions__menu_id',
            'permissions__menu__title',
            'permissions__menu__icon',
            'permissions__menu__weight',
        ).distinct()
        
        # 存放权限信息
        permission_dict = {}
        
        # 存放菜单信息
        
        menu_dict = {}
        
        for item in permission_query:
            permission_dict[item['permissions__id']] = {'url': item['permissions__url'],
                                                        'id': item['permissions__id'],
                                                        'pid': item['permissions__parent_id'],
                                                        'title': item['permissions__title']}
            
            menu_id = item.get('permissions__menu_id')
            
            if not menu_id:
                continue
            
            if menu_id not in menu_dict:
                menu_dict[menu_id] = {
                    'title': item['permissions__menu__title'],
                    'icon': item['permissions__menu__icon'],
                    'weight': item['permissions__menu__weight'],
                    'children': [
                        {'title': item['permissions__title'], 'url': item['permissions__url'],
                         'id': item['permissions__id'], 'pid': item['permissions__parent_id']}
                    ]
                }
            else:
                menu_dict[menu_id]['children'].append(
                    {'title': item['permissions__title'], 'url': item['permissions__url'], 'id': item['permissions__id'],
                     'pid': item['permissions__parent_id']})
        
        # # 2. 将权限信息写入到session
        request.session[settings.PERMISSION_SESSION_KEY] = permission_dict
        
        # 将菜单信息写入到session
        request.session[settings.MENU_SESSION_KEY] = menu_dict
    init_permission.py

    自定义inclusion_tag

    from django import template
    
    register = template.Library()
    
    from django.conf import settings
    import re
    from collections import OrderedDict
    
    
    @register.inclusion_tag('rbac/menu.html')
    def menu(request):
        menu_dict = request.session.get(settings.MENU_SESSION_KEY)
        
        order_dict = OrderedDict()
        
        for key in sorted(menu_dict, key=lambda x: menu_dict[x]['weight'], reverse=True):
            order_dict[key] = menu_dict[key]
            
            item = order_dict[key]
            
            item['class'] = 'hide'
            
            for i in item['children']:
                
                if i['id'] == request.current_menu_id:
                    i['class'] = 'active'
                    item['class'] = ''
        
        return {"menu_list": order_dict}
    
    @register.inclusion_tag('rbac/breadcrumb.html')
    def breadcrumb(request):
        
        return {'breadcrumb_list':request.breadcrumb_list}
    templatetags/rbac.py
    <ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">
    
        {% for li in breadcrumb_list %}
            {% if forloop.last %}
                <li>{{ li.title }}</li>
            {% else %}
                <li><a href="{{ li.url }}">{{ li.title }}</a></li>
            {% endif %}
    
        {% endfor %}
    
    </ol>
    breadcrumb.html
    {% load staticfiles %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>爱软测</title>
        <link rel="shortcut icon" href="{% static 'imgs/luffy-study-logo.png' %} ">
        <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/>
        <link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/commons.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/nav.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/menu.css' %}">
        <style>
            body {
                margin: 0;
            }
    
            .no-radius {
                border-radius: 0;
            }
    
            .no-margin {
                margin: 0;
            }
    
            .pg-body > .left-menu {
                background-color: #EAEDF1;
                position: absolute;
                left: 1px;
                top: 48px;
                bottom: 0;
                 220px;
                border: 1px solid #EAEDF1;
                overflow: auto;
            }
    
            .pg-body > .right-body {
                position: absolute;
                left: 225px;
                right: 0;
                top: 48px;
                bottom: 0;
                overflow: scroll;
                border: 1px solid #ddd;
                border-top: 0;
                font-size: 13px;
                min- 755px;
            }
    
            .navbar-right {
                float: right !important;
                margin-right: -15px;
            }
    
            .luffy-container {
                padding: 15px;
            }
    
            .left-menu .menu-body .static-menu {
    
            }
    
    
        </style>
    </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">
                {% load rbac %}
                {% menu request %}
    
            </div>
        </div>
        <div class="right-body">
            <div>
                {% breadcrumb request %}
            </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>
    <script src="{% static 'js/menu.js' %} "></script>
    
    {% block js %} {% endblock %}
    </body>
    </html>
    layout.html

     权限控制到按钮级别

    表的设计

    from django.db import models
    
    
    class Menu(models.Model):
        """
        一级菜单
        """
        title = models.CharField(max_length=32, unique=True)
        icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)
        weight = models.IntegerField(default=1)
        
        class Meta:
            verbose_name_plural = '菜单表'
            verbose_name = '菜单表'
        
        def __str__(self):
            return self.title
    
    
    class Permission(models.Model):
        """
        权限表
        有关联Menu的是二级菜单
        没有关联Menu的不是二级菜单,是不可以做菜单的权限
        
        
        """
        title = models.CharField(max_length=32, verbose_name='标题')
        url = models.CharField(max_length=32, verbose_name='权限')
        menu = models.ForeignKey('Menu', null=True, blank=True)
        
        parent = models.ForeignKey('Permission', null=True, blank=True)
        name = models.CharField(max_length=32, null=True, blank=True, unique=True)
        
        class Meta:
            verbose_name_plural = '权限表'
            verbose_name = '权限表'
        
        def __str__(self):
            return self.title
    
    
    class Role(models.Model):
        name = models.CharField(max_length=32, verbose_name='角色名称')
        permissions = models.ManyToManyField(to='Permission', verbose_name='角色所拥有的权限', blank=True)
        
        def __str__(self):
            return self.name
    
    
    class User(models.Model):
        """
        用户表
        """
        name = models.CharField(max_length=32, verbose_name='用户名')
        password = models.CharField(max_length=32, verbose_name='密码')
        roles = models.ManyToManyField(to='Role', verbose_name='用户所拥有的角色', blank=True)
        
        def __str__(self):
            return self.name
    models.py

    权限的初始化

    from django.conf import settings
    
    
    def init_permission(request, user):
        # 1. 查当前登录用户拥有的权限
        permission_query = user.roles.filter(permissions__url__isnull=False).values(
            'permissions__url',
            'permissions__title',
            'permissions__id',
            'permissions__name',
            'permissions__parent_id',
            'permissions__parent__name',
            'permissions__menu_id',
            'permissions__menu__title',
            'permissions__menu__icon',
            'permissions__menu__weight',
        ).distinct()
        
        # 存放权限信息
        permission_dict = {}
        
        # 存放菜单信息
        
        menu_dict = {}
        
        for item in permission_query:
            permission_dict[item['permissions__name']] = {'url': item['permissions__url'],
                                                          'id': item['permissions__id'],
                                                          'pid': item['permissions__parent_id'],
                                                          'pname': item['permissions__parent__name'],
                                                          'title': item['permissions__title']}
            
            menu_id = item.get('permissions__menu_id')
            
            if not menu_id:
                continue
            
            if menu_id not in menu_dict:
                menu_dict[menu_id] = {
                    'title': item['permissions__menu__title'],
                    'icon': item['permissions__menu__icon'],
                    'weight': item['permissions__menu__weight'],
                    'children': [
                        {'title': item['permissions__title'], 'url': item['permissions__url'],
                         'id': item['permissions__id'], 'pid': item['permissions__parent_id']}
                    ]
                }
            else:
                menu_dict[menu_id]['children'].append(
                    {'title': item['permissions__title'], 'url': item['permissions__url'], 'id': item['permissions__id'],
                     'pid': item['permissions__parent_id']})
        
        # # 2. 将权限信息写入到session
        request.session[settings.PERMISSION_SESSION_KEY] = permission_dict
        
        # 将菜单信息写入到session
        request.session[settings.MENU_SESSION_KEY] = menu_dict
    init_permission.py

    中间件的校验

    from django.utils.deprecation import MiddlewareMixin
    from django.conf import settings
    from django.shortcuts import HttpResponse
    import re
    
    
    class PermissionMiddleware(MiddlewareMixin):
        def process_request(self, request):
            # 对权限进行校验
            # 1. 当前访问的URL
            current_url = request.path_info
            
            # 白名单的判断
            for i in settings.WHITE_URL_LIST:
                if re.match(i, current_url):
                    return
            
            # 2. 获取当前用户的所有权限信息
            
            permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY)
            
            request.breadcrumb_list = [
                {"title": '首页', 'url': '#'},
            ]
            
            # 3. 权限的校验
            print(permission_dict)
            for item in permission_dict.values():
                url = item['url']
                if re.match("^{}$".format(url), current_url):
                    pid = item['pid']
                    id = item['id']
                    pname = item['pname']
                    if pid:
                        # 表示当前权限是子权限,让父权限是展开
                        request.current_menu_id = pid
                        request.breadcrumb_list.extend(
                            [{"title": permission_dict[pname]['title'], 'url': permission_dict[pname]['url']},
                             {"title": item['title'], 'url': item['url']}]
                        )
                    
                    else:
                        # 表示当前权限是父权限,要展开的二级菜单
                        request.current_menu_id = id
                        # 添加面包屑导航
                        request.breadcrumb_list.append({"title": item['title'], 'url': item['url']})
                    
                    return
            
            else:
                return HttpResponse('没有权限')
    middlewares bac.py

    自定义inclusion_tag

    from django import template
    
    register = template.Library()
    
    from django.conf import settings
    import re
    from collections import OrderedDict
    
    
    @register.inclusion_tag('rbac/menu.html')
    def menu(request):
        menu_dict = request.session.get(settings.MENU_SESSION_KEY)
        
        order_dict = OrderedDict()
        
        for key in sorted(menu_dict, key=lambda x: menu_dict[x]['weight'], reverse=True):
            order_dict[key] = menu_dict[key]
            
            item = order_dict[key]
            
            item['class'] = 'hide'
            
            for i in item['children']:
                
                if i['id'] == request.current_menu_id:
                    i['class'] = 'active'
                    item['class'] = ''
        
        return {"menu_list": order_dict}
    
    
    @register.inclusion_tag('rbac/breadcrumb.html')
    def breadcrumb(request):
        return {'breadcrumb_list': request.breadcrumb_list}
    
    
    @register.filter
    def has_permission(request, permission):
        if permission in request.session.get(settings.PERMISSION_SESSION_KEY):
            return True
    templatetags/rbac.py
    {% extends 'layout.html' %}
    
    {% block content %}
        {% load rbac %}
        <div class="luffy-container">
            <div class="btn-group" style="margin: 5px 0">
    
    
                {% if request|has_permission:'web:customer_add' %}
                    <a class="btn btn-default" href="{% url 'web:customer_add' %}">
                        <i class="fa fa-plus-square" aria-hidden="true"></i> 添加客户
                    </a>
                {% endif %}
    
    
            </div>
            <table class="table table-bordered table-hover">
                <thead>
                <tr>
                    <th>ID</th>
                    <th>客户姓名</th>
                    <th>年龄</th>
                    <th>邮箱</th>
                    <th>公司</th>
                    {% if request|has_permission:'web:customer_edit' or request|has_permission:'web:customer_del' %}
                        {#           自定义filter过滤器        #}
                        <th>选项</th>
                    {% endif %}
                </tr>
                </thead>
                <tbody>
                {% for row in data_list %}
                    <tr>
                        <td>{{ row.id }}</td>
                        <td>{{ row.name }}</td>
                        <td>{{ row.age }}</td>
                        <td>{{ row.email }}</td>
                        <td>{{ row.company }}</td>
                        {% if request|has_permission:'web:customer_edit' or request|has_permission:'web:customer_del' %}
                            <td>
                                {% if request|has_permission:'web:customer_edit' %}
                                    <a style="color: #333333;" href="{% url "web:customer_edit" row.id %}">
                                        <i class="fa fa-edit" aria-hidden="true"></i></a>
                                {% endif %}
    
                                {% if request|has_permission:'web:customer_del' %}
                                    <a style="color: #d9534f;" href="{% url "web:customer_del" row.id %}"><i
                                            class="fa fa-trash-o"></i></a>
                                {% endif %}
    
                            </td>
                        {% endif %}
    
                    </tr>
                {% endfor %}
                </tbody>
            </table>
        </div>
    {% endblock %}
    customer_list.html

    第五版:权限管理页面展示与配置

    首先注释权限中间件,母版相关权限信息,然后进行开发 

    """
    Django settings for luffy_permission project.
    
    Generated by 'django-admin startproject' using Django 1.11.7.
    
    For more information on this file, see
    https://docs.djangoproject.com/en/1.11/topics/settings/
    
    For the full list of settings and their values, see
    https://docs.djangoproject.com/en/1.11/ref/settings/
    """
    
    import os
    
    # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    
    # Quick-start development settings - unsuitable for production
    # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
    
    # SECURITY WARNING: keep the secret key used in production secret!
    SECRET_KEY = '-t5hehq#zmk=_m)!6pm(c8_s-ycack)$dpppm7ws!&0#eljwzs'
    
    # SECURITY WARNING: don't run with debug turned on in production!
    DEBUG = True
    
    ALLOWED_HOSTS = []
    
    # Application definition
    
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'web.apps.WebConfig',
        'rbac.apps.RbacConfig'
    ]
    
    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.PermissionMiddleware',
    ]
    
    ROOT_URLCONF = 'luffy_permission.urls'
    
    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR, 'templates')]
            ,
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    
    WSGI_APPLICATION = 'luffy_permission.wsgi.application'
    
    # Database
    # https://docs.djangoproject.com/en/1.11/ref/settings/#databases
    
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        }
    }
    
    # Password validation
    # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
    
    AUTH_PASSWORD_VALIDATORS = [
        {
            'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
        },
    ]
    
    # Internationalization
    # https://docs.djangoproject.com/en/1.11/topics/i18n/
    
    LANGUAGE_CODE = 'en-us'
    
    TIME_ZONE = 'UTC'
    
    USE_I18N = True
    
    USE_L10N = True
    
    USE_TZ = True
    
    # Static files (CSS, JavaScript, Images)
    # https://docs.djangoproject.com/en/1.11/howto/static-files/
    
    STATIC_URL = '/static/'
    
    #  ###### 权限相关的配置 ######
    PERMISSION_SESSION_KEY = 'permissions'
    MENU_SESSION_KEY = 'menus'
    WHITE_URL_LIST = [
        r'/login/$',
        r'^/logout/$',
        r'^/reg/$',
        r'^/admin/.*',
    ]
    settings.pys
    {% load staticfiles %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>路飞学城</title>
        <link rel="shortcut icon" href="{% static 'imgs/luffy-study-logo.png' %} ">
        <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/>
        <link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/commons.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/nav.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/menu.css' %}">
        <style>
            body {
                margin: 0;
            }
    
            .no-radius {
                border-radius: 0;
            }
    
            .no-margin {
                margin: 0;
            }
    
            .pg-body > .left-menu {
                background-color: #EAEDF1;
                position: absolute;
                left: 1px;
                top: 48px;
                bottom: 0;
                 220px;
                border: 1px solid #EAEDF1;
                overflow: auto;
            }
    
            .pg-body > .right-body {
                position: absolute;
                left: 225px;
                right: 0;
                top: 48px;
                bottom: 0;
                overflow: scroll;
                border: 1px solid #ddd;
                border-top: 0;
                font-size: 13px;
                min- 755px;
            }
    
            .navbar-right {
                float: right !important;
                margin-right: -15px;
            }
    
            .luffy-container {
                padding: 15px;
            }
    
            .left-menu .menu-body .static-menu {
    
            }
    
    
        </style>
    
        {% block css %}
    
        {% endblock %}
    </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">
                {% load rbac %}
                {#            {% menu request %}#}
    
            </div>
        </div>
        <div class="right-body">
            <div>
                {#            {% breadcrumb request %}#}
            </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>
    <script src="{% static 'js/menu.js' %} "></script>
    
    {% block js %} {% endblock %}
    </body>
    </html>
    layout.html

    权限之角色管理,菜单管理

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from django.utils.safestring import mark_safe
    import requests
    from bs4 import BeautifulSoup
    
    response = requests.get(
        url='http://fontawesome.dashgame.com/',
    )
    response.encoding = 'utf-8'
    
    soup = BeautifulSoup(response.text, 'html.parser')
    web = soup.find(attrs={'id': 'web-application'})
    
    icon_list = []
    
    for item in web.find_all(attrs={'class': 'fa-hover'}):
        tag = item.find('i')
        class_name = tag.get('class')[1]
        icon_list.append([class_name, str(tag)])
    
    print(icon_list)
    icon爬虫.py
    from django.conf.urls import url
    from rbac import views
    
    urlpatterns = [
        # /app01/role/list/    # rbac:role_list
        url(r'^role/list/$', views.role_list, name='role_list'),
        url(r'^role/add/$', views.role, name='role_add'),
        url(r'^role/edit/(d+)$', views.role, name='role_edit'),
        url(r'^role/del/(d+)$', views.del_role, name='role_del'),
        
        url(r'^menu/list/$', views.menu_list, name='menu_list'),
        
        url(r'^menu/add/$', views.menu, name='menu_add'),
        url(r'^menu/edit/(d+)$', views.menu, name='menu_edit'),
    ]
    urls.py
    from django.shortcuts import render, HttpResponse, redirect, reverse
    from rbac import models
    from rbac.forms import *
    
    
    def role_list(request):
        all_roles = models.Role.objects.all()
        return render(request, 'rbac/role_list.html', {"all_roles": all_roles})
    
    
    def role(request, edit_id=None):
        obj = models.Role.objects.filter(id=edit_id).first()
        form_obj = RoleForm(instance=obj)
        if request.method == 'POST':
            form_obj = RoleForm(request.POST, instance=obj)
            if form_obj.is_valid():
                form_obj.save()
                return redirect(reverse('rbac:role_list'))
        
        return render(request, 'rbac/form.html', {'form_obj': form_obj})
    
    
    def del_role(request, del_id):
        models.Role.objects.filter(id=del_id).delete()
        return redirect(reverse('rbac:role_list'))
    
    
    # 菜单信息  权限信息
    def menu_list(request):
        all_menu = models.Menu.objects.all()
        all_permission = models.Permission.objects.all()
        
        return render(request, 'rbac/menu_list.html', {"all_menu": all_menu, 'all_permission': all_permission})
    
    
    def menu(request, edit_id=None):
        obj = models.Menu.objects.filter(id=edit_id).first()
        form_obj = MenuForm(instance=obj)
        if request.method == 'POST':
            form_obj = MenuForm(request.POST, instance=obj)
            if form_obj.is_valid():
                form_obj.save()
                return redirect(reverse('rbac:menu_list'))
        
        return render(request, 'rbac/form.html', {'form_obj': form_obj})
    View.py
    from django import forms
    from rbac import models
    from django.utils.safestring import mark_safe
    
    
    # 角色的Form
    class RoleForm(forms.ModelForm):
        class Meta:
            model = models.Role
            fields = ['name']
            
            widgets = {
                'name': forms.widgets.Input(attrs={"class": 'form-control'})
            }
    
    
    ICON_LIST = [[i[0], mark_safe(i[1])] for i in [
        ['fa-address-book', '<i aria-hidden="true" class="fa fa-address-book"></i>'],
        ['fa-address-book-o', '<i aria-hidden="true" class="fa fa-address-book-o"></i>'],
        ['fa-address-card', '<i aria-hidden="true" class="fa fa-address-card"></i>'],
        ['fa-address-card-o', '<i aria-hidden="true" class="fa fa-address-card-o"></i>'],
        ['fa-adjust', '<i aria-hidden="true" class="fa fa-adjust"></i>'],
        ['fa-american-sign-language-interpreting',
         '<i aria-hidden="true" class="fa fa-american-sign-language-interpreting"></i>'],
        ['fa-anchor', '<i aria-hidden="true" class="fa fa-anchor"></i>'],
        ['fa-archive', '<i aria-hidden="true" class="fa fa-archive"></i>'],
        ['fa-area-chart', '<i aria-hidden="true" class="fa fa-area-chart"></i>'],
        ['fa-arrows', '<i aria-hidden="true" class="fa fa-arrows"></i>'],
        ['fa-arrows-h', '<i aria-hidden="true" class="fa fa-arrows-h"></i>'],
        ['fa-arrows-v', '<i aria-hidden="true" class="fa fa-arrows-v"></i>'],
        ['fa-asl-interpreting', '<i aria-hidden="true" class="fa fa-asl-interpreting"></i>'],
        ['fa-assistive-listening-systems', '<i aria-hidden="true" class="fa fa-assistive-listening-systems"></i>'],
        ['fa-asterisk', '<i aria-hidden="true" class="fa fa-asterisk"></i>'],
        ['fa-at', '<i aria-hidden="true" class="fa fa-at"></i>'],
        ['fa-audio-description', '<i aria-hidden="true" class="fa fa-audio-description"></i>'],
        ['fa-automobile', '<i aria-hidden="true" class="fa fa-automobile"></i>'],
        ['fa-balance-scale', '<i aria-hidden="true" class="fa fa-balance-scale"></i>'],
        ['fa-ban', '<i aria-hidden="true" class="fa fa-ban"></i>'],
        ['fa-bank', '<i aria-hidden="true" class="fa fa-bank"></i>'],
        ['fa-bar-chart', '<i aria-hidden="true" class="fa fa-bar-chart"></i>'],
        ['fa-bar-chart-o', '<i aria-hidden="true" class="fa fa-bar-chart-o"></i>'],
        ['fa-barcode', '<i aria-hidden="true" class="fa fa-barcode"></i>'],
        ['fa-bars', '<i aria-hidden="true" class="fa fa-bars"></i>'],
        ['fa-bath', '<i aria-hidden="true" class="fa fa-bath"></i>'],
        ['fa-bathtub', '<i aria-hidden="true" class="fa fa-bathtub"></i>'],
        ['fa-battery', '<i aria-hidden="true" class="fa fa-battery"></i>'],
        ['fa-battery-0', '<i aria-hidden="true" class="fa fa-battery-0"></i>'],
        ['fa-battery-1', '<i aria-hidden="true" class="fa fa-battery-1"></i>'],
        ['fa-battery-2', '<i aria-hidden="true" class="fa fa-battery-2"></i>'],
        ['fa-battery-3', '<i aria-hidden="true" class="fa fa-battery-3"></i>'],
        ['fa-battery-4', '<i aria-hidden="true" class="fa fa-battery-4"></i>'],
        ['fa-battery-empty', '<i aria-hidden="true" class="fa fa-battery-empty"></i>'],
        ['fa-battery-full', '<i aria-hidden="true" class="fa fa-battery-full"></i>'],
        ['fa-battery-half', '<i aria-hidden="true" class="fa fa-battery-half"></i>'],
        ['fa-battery-quarter', '<i aria-hidden="true" class="fa fa-battery-quarter"></i>'],
        ['fa-battery-three-quarters', '<i aria-hidden="true" class="fa fa-battery-three-quarters"></i>'],
        ['fa-bed', '<i aria-hidden="true" class="fa fa-bed"></i>'],
        ['fa-beer', '<i aria-hidden="true" class="fa fa-beer"></i>'],
        ['fa-bell', '<i aria-hidden="true" class="fa fa-bell"></i>'],
        ['fa-bell-o', '<i aria-hidden="true" class="fa fa-bell-o"></i>'],
        ['fa-bell-slash', '<i aria-hidden="true" class="fa fa-bell-slash"></i>'],
        ['fa-bell-slash-o', '<i aria-hidden="true" class="fa fa-bell-slash-o"></i>'],
        ['fa-bicycle', '<i aria-hidden="true" class="fa fa-bicycle"></i>'],
        ['fa-binoculars', '<i aria-hidden="true" class="fa fa-binoculars"></i>'],
        ['fa-birthday-cake', '<i aria-hidden="true" class="fa fa-birthday-cake"></i>'],
        ['fa-blind', '<i aria-hidden="true" class="fa fa-blind"></i>'],
        ['fa-bluetooth', '<i aria-hidden="true" class="fa fa-bluetooth"></i>'],
        ['fa-bluetooth-b', '<i aria-hidden="true" class="fa fa-bluetooth-b"></i>'],
        ['fa-bolt', '<i aria-hidden="true" class="fa fa-bolt"></i>'],
        ['fa-bomb', '<i aria-hidden="true" class="fa fa-bomb"></i>'],
        ['fa-book', '<i aria-hidden="true" class="fa fa-book"></i>'],
        ['fa-bookmark', '<i aria-hidden="true" class="fa fa-bookmark"></i>'],
        ['fa-bookmark-o', '<i aria-hidden="true" class="fa fa-bookmark-o"></i>'],
        ['fa-braille', '<i aria-hidden="true" class="fa fa-braille"></i>'],
        ['fa-briefcase', '<i aria-hidden="true" class="fa fa-briefcase"></i>'],
        ['fa-bug', '<i aria-hidden="true" class="fa fa-bug"></i>'],
        ['fa-building', '<i aria-hidden="true" class="fa fa-building"></i>'],
        ['fa-building-o', '<i aria-hidden="true" class="fa fa-building-o"></i>'],
        ['fa-bullhorn', '<i aria-hidden="true" class="fa fa-bullhorn"></i>'],
        ['fa-bullseye', '<i aria-hidden="true" class="fa fa-bullseye"></i>'],
        ['fa-bus', '<i aria-hidden="true" class="fa fa-bus"></i>'],
        ['fa-cab', '<i aria-hidden="true" class="fa fa-cab"></i>'],
        ['fa-calculator', '<i aria-hidden="true" class="fa fa-calculator"></i>'],
        ['fa-calendar', '<i aria-hidden="true" class="fa fa-calendar"></i>'],
        ['fa-calendar-check-o', '<i aria-hidden="true" class="fa fa-calendar-check-o"></i>'],
        ['fa-calendar-minus-o', '<i aria-hidden="true" class="fa fa-calendar-minus-o"></i>'],
        ['fa-calendar-o', '<i aria-hidden="true" class="fa fa-calendar-o"></i>'],
        ['fa-calendar-plus-o', '<i aria-hidden="true" class="fa fa-calendar-plus-o"></i>'],
        ['fa-calendar-times-o', '<i aria-hidden="true" class="fa fa-calendar-times-o"></i>'],
        ['fa-camera', '<i aria-hidden="true" class="fa fa-camera"></i>'],
        ['fa-camera-retro', '<i aria-hidden="true" class="fa fa-camera-retro"></i>'],
        ['fa-car', '<i aria-hidden="true" class="fa fa-car"></i>'],
        ['fa-caret-square-o-down', '<i aria-hidden="true" class="fa fa-caret-square-o-down"></i>'],
        ['fa-caret-square-o-left', '<i aria-hidden="true" class="fa fa-caret-square-o-left"></i>'],
        ['fa-caret-square-o-right', '<i aria-hidden="true" class="fa fa-caret-square-o-right"></i>'],
        ['fa-caret-square-o-up', '<i aria-hidden="true" class="fa fa-caret-square-o-up"></i>'],
        ['fa-cart-arrow-down', '<i aria-hidden="true" class="fa fa-cart-arrow-down"></i>'],
        ['fa-cart-plus', '<i aria-hidden="true" class="fa fa-cart-plus"></i>'],
        ['fa-cc', '<i aria-hidden="true" class="fa fa-cc"></i>'],
        ['fa-certificate', '<i aria-hidden="true" class="fa fa-certificate"></i>'],
        ['fa-check', '<i aria-hidden="true" class="fa fa-check"></i>'],
        ['fa-check-circle', '<i aria-hidden="true" class="fa fa-check-circle"></i>'],
        ['fa-check-circle-o', '<i aria-hidden="true" class="fa fa-check-circle-o"></i>'],
        ['fa-check-square', '<i aria-hidden="true" class="fa fa-check-square"></i>'],
        ['fa-check-square-o', '<i aria-hidden="true" class="fa fa-check-square-o"></i>'],
        ['fa-child', '<i aria-hidden="true" class="fa fa-child"></i>'],
        ['fa-circle', '<i aria-hidden="true" class="fa fa-circle"></i>'],
        ['fa-circle-o', '<i aria-hidden="true" class="fa fa-circle-o"></i>'],
        ['fa-circle-o-notch', '<i aria-hidden="true" class="fa fa-circle-o-notch"></i>'],
        ['fa-circle-thin', '<i aria-hidden="true" class="fa fa-circle-thin"></i>'],
        ['fa-clock-o', '<i aria-hidden="true" class="fa fa-clock-o"></i>'],
        ['fa-clone', '<i aria-hidden="true" class="fa fa-clone"></i>'],
        ['fa-close', '<i aria-hidden="true" class="fa fa-close"></i>'],
        ['fa-cloud', '<i aria-hidden="true" class="fa fa-cloud"></i>'],
        ['fa-cloud-download', '<i aria-hidden="true" class="fa fa-cloud-download"></i>'],
        ['fa-cloud-upload', '<i aria-hidden="true" class="fa fa-cloud-upload"></i>'],
        ['fa-code', '<i aria-hidden="true" class="fa fa-code"></i>'],
        ['fa-code-fork', '<i aria-hidden="true" class="fa fa-code-fork"></i>'],
        ['fa-coffee', '<i aria-hidden="true" class="fa fa-coffee"></i>'],
        ['fa-cog', '<i aria-hidden="true" class="fa fa-cog"></i>'],
        ['fa-cogs', '<i aria-hidden="true" class="fa fa-cogs"></i>'],
        ['fa-comment', '<i aria-hidden="true" class="fa fa-comment"></i>'],
        ['fa-comment-o', '<i aria-hidden="true" class="fa fa-comment-o"></i>'],
        ['fa-commenting', '<i aria-hidden="true" class="fa fa-commenting"></i>'],
        ['fa-commenting-o', '<i aria-hidden="true" class="fa fa-commenting-o"></i>'],
        ['fa-comments', '<i aria-hidden="true" class="fa fa-comments"></i>'],
        ['fa-comments-o', '<i aria-hidden="true" class="fa fa-comments-o"></i>'],
        ['fa-compass', '<i aria-hidden="true" class="fa fa-compass"></i>'],
        ['fa-copyright', '<i aria-hidden="true" class="fa fa-copyright"></i>'],
        ['fa-creative-commons', '<i aria-hidden="true" class="fa fa-creative-commons"></i>'],
        ['fa-credit-card', '<i aria-hidden="true" class="fa fa-credit-card"></i>'],
        ['fa-credit-card-alt', '<i aria-hidden="true" class="fa fa-credit-card-alt"></i>'],
        ['fa-crop', '<i aria-hidden="true" class="fa fa-crop"></i>'],
        ['fa-crosshairs', '<i aria-hidden="true" class="fa fa-crosshairs"></i>'],
        ['fa-cube', '<i aria-hidden="true" class="fa fa-cube"></i>'],
        ['fa-cubes', '<i aria-hidden="true" class="fa fa-cubes"></i>'],
        ['fa-cutlery', '<i aria-hidden="true" class="fa fa-cutlery"></i>'],
        ['fa-dashboard', '<i aria-hidden="true" class="fa fa-dashboard"></i>'],
        ['fa-database', '<i aria-hidden="true" class="fa fa-database"></i>'],
        ['fa-deaf', '<i aria-hidden="true" class="fa fa-deaf"></i>'],
        ['fa-deafness', '<i aria-hidden="true" class="fa fa-deafness"></i>'],
        ['fa-desktop', '<i aria-hidden="true" class="fa fa-desktop"></i>'],
        ['fa-diamond', '<i aria-hidden="true" class="fa fa-diamond"></i>'],
        ['fa-dot-circle-o', '<i aria-hidden="true" class="fa fa-dot-circle-o"></i>'],
        ['fa-download', '<i aria-hidden="true" class="fa fa-download"></i>'],
        ['fa-drivers-license', '<i aria-hidden="true" class="fa fa-drivers-license"></i>'],
        ['fa-drivers-license-o', '<i aria-hidden="true" class="fa fa-drivers-license-o"></i>'],
        ['fa-edit', '<i aria-hidden="true" class="fa fa-edit"></i>'],
        ['fa-ellipsis-h', '<i aria-hidden="true" class="fa fa-ellipsis-h"></i>'],
        ['fa-ellipsis-v', '<i aria-hidden="true" class="fa fa-ellipsis-v"></i>'],
        ['fa-envelope', '<i aria-hidden="true" class="fa fa-envelope"></i>'],
        ['fa-envelope-o', '<i aria-hidden="true" class="fa fa-envelope-o"></i>'],
        ['fa-envelope-open', '<i aria-hidden="true" class="fa fa-envelope-open"></i>'],
        ['fa-envelope-open-o', '<i aria-hidden="true" class="fa fa-envelope-open-o"></i>'],
        ['fa-envelope-square', '<i aria-hidden="true" class="fa fa-envelope-square"></i>'],
        ['fa-eraser', '<i aria-hidden="true" class="fa fa-eraser"></i>'],
        ['fa-exchange', '<i aria-hidden="true" class="fa fa-exchange"></i>'],
        ['fa-exclamation', '<i aria-hidden="true" class="fa fa-exclamation"></i>'],
        ['fa-exclamation-circle', '<i aria-hidden="true" class="fa fa-exclamation-circle"></i>'],
        ['fa-exclamation-triangle', '<i aria-hidden="true" class="fa fa-exclamation-triangle"></i>'],
        ['fa-external-link', '<i aria-hidden="true" class="fa fa-external-link"></i>'],
        ['fa-external-link-square', '<i aria-hidden="true" class="fa fa-external-link-square"></i>'],
        ['fa-eye', '<i aria-hidden="true" class="fa fa-eye"></i>'],
        ['fa-eye-slash', '<i aria-hidden="true" class="fa fa-eye-slash"></i>'],
        ['fa-eyedropper', '<i aria-hidden="true" class="fa fa-eyedropper"></i>'],
        ['fa-fax', '<i aria-hidden="true" class="fa fa-fax"></i>'],
        ['fa-feed', '<i aria-hidden="true" class="fa fa-feed"></i>'],
        ['fa-female', '<i aria-hidden="true" class="fa fa-female"></i>'],
        ['fa-fighter-jet', '<i aria-hidden="true" class="fa fa-fighter-jet"></i>'],
        ['fa-file-archive-o', '<i aria-hidden="true" class="fa fa-file-archive-o"></i>'],
        ['fa-file-audio-o', '<i aria-hidden="true" class="fa fa-file-audio-o"></i>'],
        ['fa-file-code-o', '<i aria-hidden="true" class="fa fa-file-code-o"></i>'],
        ['fa-file-excel-o', '<i aria-hidden="true" class="fa fa-file-excel-o"></i>'],
        ['fa-file-image-o', '<i aria-hidden="true" class="fa fa-file-image-o"></i>'],
        ['fa-file-movie-o', '<i aria-hidden="true" class="fa fa-file-movie-o"></i>'],
        ['fa-file-pdf-o', '<i aria-hidden="true" class="fa fa-file-pdf-o"></i>'],
        ['fa-file-photo-o', '<i aria-hidden="true" class="fa fa-file-photo-o"></i>'],
        ['fa-file-picture-o', '<i aria-hidden="true" class="fa fa-file-picture-o"></i>'],
        ['fa-file-powerpoint-o', '<i aria-hidden="true" class="fa fa-file-powerpoint-o"></i>'],
        ['fa-file-sound-o', '<i aria-hidden="true" class="fa fa-file-sound-o"></i>'],
        ['fa-file-video-o', '<i aria-hidden="true" class="fa fa-file-video-o"></i>'],
        ['fa-file-word-o', '<i aria-hidden="true" class="fa fa-file-word-o"></i>'],
        ['fa-file-zip-o', '<i aria-hidden="true" class="fa fa-file-zip-o"></i>'],
        ['fa-film', '<i aria-hidden="true" class="fa fa-film"></i>'],
        ['fa-filter', '<i aria-hidden="true" class="fa fa-filter"></i>'],
        ['fa-fire', '<i aria-hidden="true" class="fa fa-fire"></i>'],
        ['fa-fire-extinguisher', '<i aria-hidden="true" class="fa fa-fire-extinguisher"></i>'],
        ['fa-flag', '<i aria-hidden="true" class="fa fa-flag"></i>'],
        ['fa-flag-checkered', '<i aria-hidden="true" class="fa fa-flag-checkered"></i>'],
        ['fa-flag-o', '<i aria-hidden="true" class="fa fa-flag-o"></i>'],
        ['fa-flash', '<i aria-hidden="true" class="fa fa-flash"></i>'],
        ['fa-flask', '<i aria-hidden="true" class="fa fa-flask"></i>'],
        ['fa-folder', '<i aria-hidden="true" class="fa fa-folder"></i>'],
        ['fa-folder-o', '<i aria-hidden="true" class="fa fa-folder-o"></i>'],
        ['fa-folder-open', '<i aria-hidden="true" class="fa fa-folder-open"></i>'],
        ['fa-folder-open-o', '<i aria-hidden="true" class="fa fa-folder-open-o"></i>'],
        ['fa-frown-o', '<i aria-hidden="true" class="fa fa-frown-o"></i>'],
        ['fa-futbol-o', '<i aria-hidden="true" class="fa fa-futbol-o"></i>'],
        ['fa-gamepad', '<i aria-hidden="true" class="fa fa-gamepad"></i>'],
        ['fa-gavel', '<i aria-hidden="true" class="fa fa-gavel"></i>'],
        ['fa-gear', '<i aria-hidden="true" class="fa fa-gear"></i>'],
        ['fa-gears', '<i aria-hidden="true" class="fa fa-gears"></i>'],
        ['fa-gift', '<i aria-hidden="true" class="fa fa-gift"></i>'],
        ['fa-glass', '<i aria-hidden="true" class="fa fa-glass"></i>'],
        ['fa-globe', '<i aria-hidden="true" class="fa fa-globe"></i>'],
        ['fa-graduation-cap', '<i aria-hidden="true" class="fa fa-graduation-cap"></i>'],
        ['fa-group', '<i aria-hidden="true" class="fa fa-group"></i>'],
        ['fa-hand-grab-o', '<i aria-hidden="true" class="fa fa-hand-grab-o"></i>'],
        ['fa-hand-lizard-o', '<i aria-hidden="true" class="fa fa-hand-lizard-o"></i>'],
        ['fa-hand-paper-o', '<i aria-hidden="true" class="fa fa-hand-paper-o"></i>'],
        ['fa-hand-peace-o', '<i aria-hidden="true" class="fa fa-hand-peace-o"></i>'],
        ['fa-hand-pointer-o', '<i aria-hidden="true" class="fa fa-hand-pointer-o"></i>'],
        ['fa-hand-rock-o', '<i aria-hidden="true" class="fa fa-hand-rock-o"></i>'],
        ['fa-hand-scissors-o', '<i aria-hidden="true" class="fa fa-hand-scissors-o"></i>'],
        ['fa-hand-spock-o', '<i aria-hidden="true" class="fa fa-hand-spock-o"></i>'],
        ['fa-hand-stop-o', '<i aria-hidden="true" class="fa fa-hand-stop-o"></i>'],
        ['fa-handshake-o', '<i aria-hidden="true" class="fa fa-handshake-o"></i>'],
        ['fa-hard-of-hearing', '<i aria-hidden="true" class="fa fa-hard-of-hearing"></i>'],
        ['fa-hashtag', '<i aria-hidden="true" class="fa fa-hashtag"></i>'],
        ['fa-hdd-o', '<i aria-hidden="true" class="fa fa-hdd-o"></i>'],
        ['fa-headphones', '<i aria-hidden="true" class="fa fa-headphones"></i>'],
        ['fa-heart', '<i aria-hidden="true" class="fa fa-heart"></i>'],
        ['fa-heart-o', '<i aria-hidden="true" class="fa fa-heart-o"></i>'],
        ['fa-heartbeat', '<i aria-hidden="true" class="fa fa-heartbeat"></i>'],
        ['fa-history', '<i aria-hidden="true" class="fa fa-history"></i>'],
        ['fa-home', '<i aria-hidden="true" class="fa fa-home"></i>'],
        ['fa-hotel', '<i aria-hidden="true" class="fa fa-hotel"></i>'],
        ['fa-hourglass', '<i aria-hidden="true" class="fa fa-hourglass"></i>'],
        ['fa-hourglass-1', '<i aria-hidden="true" class="fa fa-hourglass-1"></i>'],
        ['fa-hourglass-2', '<i aria-hidden="true" class="fa fa-hourglass-2"></i>'],
        ['fa-hourglass-3', '<i aria-hidden="true" class="fa fa-hourglass-3"></i>'],
        ['fa-hourglass-end', '<i aria-hidden="true" class="fa fa-hourglass-end"></i>'],
        ['fa-hourglass-half', '<i aria-hidden="true" class="fa fa-hourglass-half"></i>'],
        ['fa-hourglass-o', '<i aria-hidden="true" class="fa fa-hourglass-o"></i>'],
        ['fa-hourglass-start', '<i aria-hidden="true" class="fa fa-hourglass-start"></i>'],
        ['fa-i-cursor', '<i aria-hidden="true" class="fa fa-i-cursor"></i>'],
        ['fa-id-badge', '<i aria-hidden="true" class="fa fa-id-badge"></i>'],
        ['fa-id-card', '<i aria-hidden="true" class="fa fa-id-card"></i>'],
        ['fa-id-card-o', '<i aria-hidden="true" class="fa fa-id-card-o"></i>'],
        ['fa-image', '<i aria-hidden="true" class="fa fa-image"></i>'],
        ['fa-inbox', '<i aria-hidden="true" class="fa fa-inbox"></i>'],
        ['fa-industry', '<i aria-hidden="true" class="fa fa-industry"></i>'],
        ['fa-info', '<i aria-hidden="true" class="fa fa-info"></i>'],
        ['fa-info-circle', '<i aria-hidden="true" class="fa fa-info-circle"></i>'],
        ['fa-institution', '<i aria-hidden="true" class="fa fa-institution"></i>'],
        ['fa-key', '<i aria-hidden="true" class="fa fa-key"></i>'],
        ['fa-keyboard-o', '<i aria-hidden="true" class="fa fa-keyboard-o"></i>'],
        ['fa-language', '<i aria-hidden="true" class="fa fa-language"></i>'],
        ['fa-laptop', '<i aria-hidden="true" class="fa fa-laptop"></i>'],
        ['fa-leaf', '<i aria-hidden="true" class="fa fa-leaf"></i>'],
        ['fa-legal', '<i aria-hidden="true" class="fa fa-legal"></i>'],
        ['fa-lemon-o', '<i aria-hidden="true" class="fa fa-lemon-o"></i>'],
        ['fa-level-down', '<i aria-hidden="true" class="fa fa-level-down"></i>'],
        ['fa-level-up', '<i aria-hidden="true" class="fa fa-level-up"></i>'],
        ['fa-life-bouy', '<i aria-hidden="true" class="fa fa-life-bouy"></i>'],
        ['fa-life-buoy', '<i aria-hidden="true" class="fa fa-life-buoy"></i>'],
        ['fa-life-ring', '<i aria-hidden="true" class="fa fa-life-ring"></i>'],
        ['fa-life-saver', '<i aria-hidden="true" class="fa fa-life-saver"></i>'],
        ['fa-lightbulb-o', '<i aria-hidden="true" class="fa fa-lightbulb-o"></i>'],
        ['fa-line-chart', '<i aria-hidden="true" class="fa fa-line-chart"></i>'],
        ['fa-location-arrow', '<i aria-hidden="true" class="fa fa-location-arrow"></i>'],
        ['fa-lock', '<i aria-hidden="true" class="fa fa-lock"></i>'],
        ['fa-low-vision', '<i aria-hidden="true" class="fa fa-low-vision"></i>'],
        ['fa-magic', '<i aria-hidden="true" class="fa fa-magic"></i>'],
        ['fa-magnet', '<i aria-hidden="true" class="fa fa-magnet"></i>'],
        ['fa-mail-forward', '<i aria-hidden="true" class="fa fa-mail-forward"></i>'],
        ['fa-mail-reply', '<i aria-hidden="true" class="fa fa-mail-reply"></i>'],
        ['fa-mail-reply-all', '<i aria-hidden="true" class="fa fa-mail-reply-all"></i>'],
        ['fa-male', '<i aria-hidden="true" class="fa fa-male"></i>'],
        ['fa-map', '<i aria-hidden="true" class="fa fa-map"></i>'],
        ['fa-map-marker', '<i aria-hidden="true" class="fa fa-map-marker"></i>'],
        ['fa-map-o', '<i aria-hidden="true" class="fa fa-map-o"></i>'],
        ['fa-map-pin', '<i aria-hidden="true" class="fa fa-map-pin"></i>'],
        ['fa-map-signs', '<i aria-hidden="true" class="fa fa-map-signs"></i>'],
        ['fa-meh-o', '<i aria-hidden="true" class="fa fa-meh-o"></i>'],
        ['fa-microchip', '<i aria-hidden="true" class="fa fa-microchip"></i>'],
        ['fa-microphone', '<i aria-hidden="true" class="fa fa-microphone"></i>'],
        ['fa-microphone-slash', '<i aria-hidden="true" class="fa fa-microphone-slash"></i>'],
        ['fa-minus', '<i aria-hidden="true" class="fa fa-minus"></i>'],
        ['fa-minus-circle', '<i aria-hidden="true" class="fa fa-minus-circle"></i>'],
        ['fa-minus-square', '<i aria-hidden="true" class="fa fa-minus-square"></i>'],
        ['fa-minus-square-o', '<i aria-hidden="true" class="fa fa-minus-square-o"></i>'],
        ['fa-mobile', '<i aria-hidden="true" class="fa fa-mobile"></i>'],
        ['fa-mobile-phone', '<i aria-hidden="true" class="fa fa-mobile-phone"></i>'],
        ['fa-money', '<i aria-hidden="true" class="fa fa-money"></i>'],
        ['fa-moon-o', '<i aria-hidden="true" class="fa fa-moon-o"></i>'],
        ['fa-mortar-board', '<i aria-hidden="true" class="fa fa-mortar-board"></i>'],
        ['fa-motorcycle', '<i aria-hidden="true" class="fa fa-motorcycle"></i>'],
        ['fa-mouse-pointer', '<i aria-hidden="true" class="fa fa-mouse-pointer"></i>'],
        ['fa-music', '<i aria-hidden="true" class="fa fa-music"></i>'],
        ['fa-navicon', '<i aria-hidden="true" class="fa fa-navicon"></i>'],
        ['fa-newspaper-o', '<i aria-hidden="true" class="fa fa-newspaper-o"></i>'],
        ['fa-object-group', '<i aria-hidden="true" class="fa fa-object-group"></i>'],
        ['fa-object-ungroup', '<i aria-hidden="true" class="fa fa-object-ungroup"></i>'],
        ['fa-paint-brush', '<i aria-hidden="true" class="fa fa-paint-brush"></i>'],
        ['fa-paper-plane', '<i aria-hidden="true" class="fa fa-paper-plane"></i>'],
        ['fa-paper-plane-o', '<i aria-hidden="true" class="fa fa-paper-plane-o"></i>'],
        ['fa-paw', '<i aria-hidden="true" class="fa fa-paw"></i>'],
        ['fa-pencil', '<i aria-hidden="true" class="fa fa-pencil"></i>'],
        ['fa-pencil-square', '<i aria-hidden="true" class="fa fa-pencil-square"></i>'],
        ['fa-pencil-square-o', '<i aria-hidden="true" class="fa fa-pencil-square-o"></i>'],
        ['fa-percent', '<i aria-hidden="true" class="fa fa-percent"></i>'],
        ['fa-phone', '<i aria-hidden="true" class="fa fa-phone"></i>'],
        ['fa-phone-square', '<i aria-hidden="true" class="fa fa-phone-square"></i>'],
        ['fa-photo', '<i aria-hidden="true" class="fa fa-photo"></i>'],
        ['fa-picture-o', '<i aria-hidden="true" class="fa fa-picture-o"></i>'],
        ['fa-pie-chart', '<i aria-hidden="true" class="fa fa-pie-chart"></i>'],
        ['fa-plane', '<i aria-hidden="true" class="fa fa-plane"></i>'],
        ['fa-plug', '<i aria-hidden="true" class="fa fa-plug"></i>'],
        ['fa-plus', '<i aria-hidden="true" class="fa fa-plus"></i>'],
        ['fa-plus-circle', '<i aria-hidden="true" class="fa fa-plus-circle"></i>'],
        ['fa-plus-square', '<i aria-hidden="true" class="fa fa-plus-square"></i>'],
        ['fa-plus-square-o', '<i aria-hidden="true" class="fa fa-plus-square-o"></i>'],
        ['fa-podcast', '<i aria-hidden="true" class="fa fa-podcast"></i>'],
        ['fa-power-off', '<i aria-hidden="true" class="fa fa-power-off"></i>'],
        ['fa-print', '<i aria-hidden="true" class="fa fa-print"></i>'],
        ['fa-puzzle-piece', '<i aria-hidden="true" class="fa fa-puzzle-piece"></i>'],
        ['fa-qrcode', '<i aria-hidden="true" class="fa fa-qrcode"></i>'],
        ['fa-question', '<i aria-hidden="true" class="fa fa-question"></i>'],
        ['fa-question-circle', '<i aria-hidden="true" class="fa fa-question-circle"></i>'],
        ['fa-question-circle-o', '<i aria-hidden="true" class="fa fa-question-circle-o"></i>'],
        ['fa-quote-left', '<i aria-hidden="true" class="fa fa-quote-left"></i>'],
        ['fa-quote-right', '<i aria-hidden="true" class="fa fa-quote-right"></i>'],
        ['fa-random', '<i aria-hidden="true" class="fa fa-random"></i>'],
        ['fa-recycle', '<i aria-hidden="true" class="fa fa-recycle"></i>'],
        ['fa-refresh', '<i aria-hidden="true" class="fa fa-refresh"></i>'],
        ['fa-registered', '<i aria-hidden="true" class="fa fa-registered"></i>'],
        ['fa-remove', '<i aria-hidden="true" class="fa fa-remove"></i>'],
        ['fa-reorder', '<i aria-hidden="true" class="fa fa-reorder"></i>'],
        ['fa-reply', '<i aria-hidden="true" class="fa fa-reply"></i>'],
        ['fa-reply-all', '<i aria-hidden="true" class="fa fa-reply-all"></i>'],
        ['fa-retweet', '<i aria-hidden="true" class="fa fa-retweet"></i>'],
        ['fa-road', '<i aria-hidden="true" class="fa fa-road"></i>'],
        ['fa-rocket', '<i aria-hidden="true" class="fa fa-rocket"></i>'],
        ['fa-rss', '<i aria-hidden="true" class="fa fa-rss"></i>'],
        ['fa-rss-square', '<i aria-hidden="true" class="fa fa-rss-square"></i>'],
        ['fa-s15', '<i aria-hidden="true" class="fa fa-s15"></i>'],
        ['fa-search', '<i aria-hidden="true" class="fa fa-search"></i>'],
        ['fa-search-minus', '<i aria-hidden="true" class="fa fa-search-minus"></i>'],
        ['fa-search-plus', '<i aria-hidden="true" class="fa fa-search-plus"></i>'],
        ['fa-send', '<i aria-hidden="true" class="fa fa-send"></i>'],
        ['fa-send-o', '<i aria-hidden="true" class="fa fa-send-o"></i>'],
        ['fa-server', '<i aria-hidden="true" class="fa fa-server"></i>'],
        ['fa-share', '<i aria-hidden="true" class="fa fa-share"></i>'],
        ['fa-share-alt', '<i aria-hidden="true" class="fa fa-share-alt"></i>'],
        ['fa-share-alt-square', '<i aria-hidden="true" class="fa fa-share-alt-square"></i>'],
        ['fa-share-square', '<i aria-hidden="true" class="fa fa-share-square"></i>'],
        ['fa-share-square-o', '<i aria-hidden="true" class="fa fa-share-square-o"></i>'],
        ['fa-shield', '<i aria-hidden="true" class="fa fa-shield"></i>'],
        ['fa-ship', '<i aria-hidden="true" class="fa fa-ship"></i>'],
        ['fa-shopping-bag', '<i aria-hidden="true" class="fa fa-shopping-bag"></i>'],
        ['fa-shopping-basket', '<i aria-hidden="true" class="fa fa-shopping-basket"></i>'],
        ['fa-shopping-cart', '<i aria-hidden="true" class="fa fa-shopping-cart"></i>'],
        ['fa-shower', '<i aria-hidden="true" class="fa fa-shower"></i>'],
        ['fa-sign-in', '<i aria-hidden="true" class="fa fa-sign-in"></i>'],
        ['fa-sign-language', '<i aria-hidden="true" class="fa fa-sign-language"></i>'],
        ['fa-sign-out', '<i aria-hidden="true" class="fa fa-sign-out"></i>'],
        ['fa-signal', '<i aria-hidden="true" class="fa fa-signal"></i>'],
        ['fa-signing', '<i aria-hidden="true" class="fa fa-signing"></i>'],
        ['fa-sitemap', '<i aria-hidden="true" class="fa fa-sitemap"></i>'],
        ['fa-sliders', '<i aria-hidden="true" class="fa fa-sliders"></i>'],
        ['fa-smile-o', '<i aria-hidden="true" class="fa fa-smile-o"></i>'],
        ['fa-snowflake-o', '<i aria-hidden="true" class="fa fa-snowflake-o"></i>'],
        ['fa-soccer-ball-o', '<i aria-hidden="true" class="fa fa-soccer-ball-o"></i>'],
        ['fa-sort', '<i aria-hidden="true" class="fa fa-sort"></i>'],
        ['fa-sort-alpha-asc', '<i aria-hidden="true" class="fa fa-sort-alpha-asc"></i>'],
        ['fa-sort-alpha-desc', '<i aria-hidden="true" class="fa fa-sort-alpha-desc"></i>'],
        ['fa-sort-amount-asc', '<i aria-hidden="true" class="fa fa-sort-amount-asc"></i>'],
        ['fa-sort-amount-desc', '<i aria-hidden="true" class="fa fa-sort-amount-desc"></i>'],
        ['fa-sort-asc', '<i aria-hidden="true" class="fa fa-sort-asc"></i>'],
        ['fa-sort-desc', '<i aria-hidden="true" class="fa fa-sort-desc"></i>'],
        ['fa-sort-down', '<i aria-hidden="true" class="fa fa-sort-down"></i>'],
        ['fa-sort-numeric-asc', '<i aria-hidden="true" class="fa fa-sort-numeric-asc"></i>'],
        ['fa-sort-numeric-desc', '<i aria-hidden="true" class="fa fa-sort-numeric-desc"></i>'],
        ['fa-sort-up', '<i aria-hidden="true" class="fa fa-sort-up"></i>'],
        ['fa-space-shuttle', '<i aria-hidden="true" class="fa fa-space-shuttle"></i>'],
        ['fa-spinner', '<i aria-hidden="true" class="fa fa-spinner"></i>'],
        ['fa-spoon', '<i aria-hidden="true" class="fa fa-spoon"></i>'],
        ['fa-square', '<i aria-hidden="true" class="fa fa-square"></i>'],
        ['fa-square-o', '<i aria-hidden="true" class="fa fa-square-o"></i>'],
        ['fa-star', '<i aria-hidden="true" class="fa fa-star"></i>'],
        ['fa-star-half', '<i aria-hidden="true" class="fa fa-star-half"></i>'],
        ['fa-star-half-empty', '<i aria-hidden="true" class="fa fa-star-half-empty"></i>'],
        ['fa-star-half-full', '<i aria-hidden="true" class="fa fa-star-half-full"></i>'],
        ['fa-star-half-o', '<i aria-hidden="true" class="fa fa-star-half-o"></i>'],
        ['fa-star-o', '<i aria-hidden="true" class="fa fa-star-o"></i>'],
        ['fa-sticky-note', '<i aria-hidden="true" class="fa fa-sticky-note"></i>'],
        ['fa-sticky-note-o', '<i aria-hidden="true" class="fa fa-sticky-note-o"></i>'],
        ['fa-street-view', '<i aria-hidden="true" class="fa fa-street-view"></i>'],
        ['fa-suitcase', '<i aria-hidden="true" class="fa fa-suitcase"></i>'],
        ['fa-sun-o', '<i aria-hidden="true" class="fa fa-sun-o"></i>'],
        ['fa-support', '<i aria-hidden="true" class="fa fa-support"></i>'],
        ['fa-tablet', '<i aria-hidden="true" class="fa fa-tablet"></i>'],
        ['fa-tachometer', '<i aria-hidden="true" class="fa fa-tachometer"></i>'],
        ['fa-tag', '<i aria-hidden="true" class="fa fa-tag"></i>'],
        ['fa-tags', '<i aria-hidden="true" class="fa fa-tags"></i>'],
        ['fa-tasks', '<i aria-hidden="true" class="fa fa-tasks"></i>'],
        ['fa-taxi', '<i aria-hidden="true" class="fa fa-taxi"></i>'],
        ['fa-television', '<i aria-hidden="true" class="fa fa-television"></i>'],
        ['fa-terminal', '<i aria-hidden="true" class="fa fa-terminal"></i>'],
        ['fa-thermometer', '<i aria-hidden="true" class="fa fa-thermometer"></i>'],
        ['fa-thermometer-0', '<i aria-hidden="true" class="fa fa-thermometer-0"></i>'],
        ['fa-thermometer-1', '<i aria-hidden="true" class="fa fa-thermometer-1"></i>'],
        ['fa-thermometer-2', '<i aria-hidden="true" class="fa fa-thermometer-2"></i>'],
        ['fa-thermometer-3', '<i aria-hidden="true" class="fa fa-thermometer-3"></i>'],
        ['fa-thermometer-4', '<i aria-hidden="true" class="fa fa-thermometer-4"></i>'],
        ['fa-thermometer-empty', '<i aria-hidden="true" class="fa fa-thermometer-empty"></i>'],
        ['fa-thermometer-full', '<i aria-hidden="true" class="fa fa-thermometer-full"></i>'],
        ['fa-thermometer-half', '<i aria-hidden="true" class="fa fa-thermometer-half"></i>'],
        ['fa-thermometer-quarter', '<i aria-hidden="true" class="fa fa-thermometer-quarter"></i>'],
        ['fa-thermometer-three-quarters', '<i aria-hidden="true" class="fa fa-thermometer-three-quarters"></i>'],
        ['fa-thumb-tack', '<i aria-hidden="true" class="fa fa-thumb-tack"></i>'],
        ['fa-thumbs-down', '<i aria-hidden="true" class="fa fa-thumbs-down"></i>'],
        ['fa-thumbs-o-down', '<i aria-hidden="true" class="fa fa-thumbs-o-down"></i>'],
        ['fa-thumbs-o-up', '<i aria-hidden="true" class="fa fa-thumbs-o-up"></i>'],
        ['fa-thumbs-up', '<i aria-hidden="true" class="fa fa-thumbs-up"></i>'],
        ['fa-ticket', '<i aria-hidden="true" class="fa fa-ticket"></i>'],
        ['fa-times', '<i aria-hidden="true" class="fa fa-times"></i>'],
        ['fa-times-circle', '<i aria-hidden="true" class="fa fa-times-circle"></i>'],
        ['fa-times-circle-o', '<i aria-hidden="true" class="fa fa-times-circle-o"></i>'],
        ['fa-times-rectangle', '<i aria-hidden="true" class="fa fa-times-rectangle"></i>'],
        ['fa-times-rectangle-o', '<i aria-hidden="true" class="fa fa-times-rectangle-o"></i>'],
        ['fa-tint', '<i aria-hidden="true" class="fa fa-tint"></i>'],
        ['fa-toggle-down', '<i aria-hidden="true" class="fa fa-toggle-down"></i>'],
        ['fa-toggle-left', '<i aria-hidden="true" class="fa fa-toggle-left"></i>'],
        ['fa-toggle-off', '<i aria-hidden="true" class="fa fa-toggle-off"></i>'],
        ['fa-toggle-on', '<i aria-hidden="true" class="fa fa-toggle-on"></i>'],
        ['fa-toggle-right', '<i aria-hidden="true" class="fa fa-toggle-right"></i>'],
        ['fa-toggle-up', '<i aria-hidden="true" class="fa fa-toggle-up"></i>'],
        ['fa-trademark', '<i aria-hidden="true" class="fa fa-trademark"></i>'],
        ['fa-trash', '<i aria-hidden="true" class="fa fa-trash"></i>'],
        ['fa-trash-o', '<i aria-hidden="true" class="fa fa-trash-o"></i>'],
        ['fa-tree', '<i aria-hidden="true" class="fa fa-tree"></i>'],
        ['fa-trophy', '<i aria-hidden="true" class="fa fa-trophy"></i>'],
        ['fa-truck', '<i aria-hidden="true" class="fa fa-truck"></i>'],
        ['fa-tty', '<i aria-hidden="true" class="fa fa-tty"></i>'],
        ['fa-tv', '<i aria-hidden="true" class="fa fa-tv"></i>'],
        ['fa-umbrella', '<i aria-hidden="true" class="fa fa-umbrella"></i>'],
        ['fa-universal-access', '<i aria-hidden="true" class="fa fa-universal-access"></i>'],
        ['fa-university', '<i aria-hidden="true" class="fa fa-university"></i>'],
        ['fa-unlock', '<i aria-hidden="true" class="fa fa-unlock"></i>'],
        ['fa-unlock-alt', '<i aria-hidden="true" class="fa fa-unlock-alt"></i>'],
        ['fa-unsorted', '<i aria-hidden="true" class="fa fa-unsorted"></i>'],
        ['fa-upload', '<i aria-hidden="true" class="fa fa-upload"></i>'],
        ['fa-user', '<i aria-hidden="true" class="fa fa-user"></i>'],
        ['fa-user-circle', '<i aria-hidden="true" class="fa fa-user-circle"></i>'],
        ['fa-user-circle-o', '<i aria-hidden="true" class="fa fa-user-circle-o"></i>'],
        ['fa-user-o', '<i aria-hidden="true" class="fa fa-user-o"></i>'],
        ['fa-user-plus', '<i aria-hidden="true" class="fa fa-user-plus"></i>'],
        ['fa-user-secret', '<i aria-hidden="true" class="fa fa-user-secret"></i>'],
        ['fa-user-times', '<i aria-hidden="true" class="fa fa-user-times"></i>'],
        ['fa-users', '<i aria-hidden="true" class="fa fa-users"></i>'],
        ['fa-vcard', '<i aria-hidden="true" class="fa fa-vcard"></i>'],
        ['fa-vcard-o', '<i aria-hidden="true" class="fa fa-vcard-o"></i>'],
        ['fa-video-camera', '<i aria-hidden="true" class="fa fa-video-camera"></i>'],
        ['fa-volume-control-phone', '<i aria-hidden="true" class="fa fa-volume-control-phone"></i>'],
        ['fa-volume-down', '<i aria-hidden="true" class="fa fa-volume-down"></i>'],
        ['fa-volume-off', '<i aria-hidden="true" class="fa fa-volume-off"></i>'],
        ['fa-volume-up', '<i aria-hidden="true" class="fa fa-volume-up"></i>'],
        ['fa-warning', '<i aria-hidden="true" class="fa fa-warning"></i>'],
        ['fa-wheelchair', '<i aria-hidden="true" class="fa fa-wheelchair"></i>'],
        ['fa-wheelchair-alt', '<i aria-hidden="true" class="fa fa-wheelchair-alt"></i>'],
        ['fa-wifi', '<i aria-hidden="true" class="fa fa-wifi"></i>'],
        ['fa-window-close', '<i aria-hidden="true" class="fa fa-window-close"></i>'],
        ['fa-window-close-o', '<i aria-hidden="true" class="fa fa-window-close-o"></i>'],
        ['fa-window-maximize', '<i aria-hidden="true" class="fa fa-window-maximize"></i>'],
        ['fa-window-minimize', '<i aria-hidden="true" class="fa fa-window-minimize"></i>'],
        ['fa-window-restore', '<i aria-hidden="true" class="fa fa-window-restore"></i>'],
        ['fa-wrench', '<i aria-hidden="true" class="fa fa-wrench"></i>']
    ]]
    
    
    # 菜单的Form
    class MenuForm(forms.ModelForm):
        class Meta:
            model = models.Menu
            fields = ['title', 'weight', 'icon', ]
            
            widgets = {
                'title': forms.widgets.Input(attrs={"class": 'form-control'}),
                'weight': forms.widgets.Input(attrs={"class": 'form-control'}),
                'icon': forms.widgets.RadioSelect(choices=ICON_LIST),
            }
    forms.py

    templates

    {% extends 'layout.html' %}
    
    {% block content %}
    
        <div style="margin: 20px">
            <h1>角色管理</h1>
    
            <a href="{% url "rbac:role_add" %}" class="btn  btn-success">添加</a>
    
            <table class="table table-bordered table-hover" style="margin-top: 5px">
                <thead>
                <tr>
                    <th>序号</th>
                    <th>名称</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                {% for role in all_roles %}
                    <tr>
                        <td>{{ forloop.counter }}</td>
                        <td>{{ role.name }}</td>
                        <td>
                            <a href="{% url 'rbac:role_edit' role.id %}"> <i class="fa fa-edit"></i> </a>
                            <a href="{% url 'rbac:role_del' role.id %}"> <i class="fa fa-trash-o"></i> </a>
                        </td>
                    </tr>
    
                {% endfor %}
    
                </tbody>
            </table>
        </div>
    
    
    {% endblock %}
    role_list.html
    {% extends 'layout.html' %}
    
    
    {% block content %}
        <div style="margin: 20px">
            <div class="col-sm-3">
                <div class="panel panel-default">
                    <!-- Default panel contents -->
                    <div class="panel-heading"><i class="fa fa-book"></i> 菜单管理
                        <a href="{% url 'rbac:menu_add' %}" class="btn btn-success btn-sm pull-right " style="padding: 2px 8px;margin: -3px;"> <i
                                class="fa fa-plus"></i> 新建</a>
                    </div>
    
                    <!-- Table -->
                    <table class="table">
                        <thead>
                        <tr>
                            <th>名称</th>
                            <th>图标</th>
                            <th>操作</th>
                        </tr>
                        </thead>
                        <tbody>
                        {% for menu in all_menu %}
                            <tr>
                                <td>{{ menu.title }}</td>
                                <td> <i class="fa {{ menu.icon }} "></i> </td>
                                <td>
                                    <a href="{% url 'rbac:menu_edit' menu.id %}"> <i class="fa fa-edit"></i> </a>
                                    <a href=""> <i class="fa fa-trash-o"></i> </a>
                                </td>
                            </tr>
    
                        {% endfor %}
    
                        </tbody>
    
                    </table>
                </div>
            </div>
            <div class="col-sm-9">
                <div class="panel panel-default">
                    <!-- Default panel contents -->
                    <div class="panel-heading"><i class="fa fa-cubes"></i> 权限管理</div>
    
                    <!-- Table -->
                    <table class="table">
                       <thead>
                        <tr>
                            <th>名称</th>
                            <th>URL</th>
                            <th>URL别名</th>
                            <th>操作</th>
                        </tr>
                        </thead>
                        <tbody>
                        {% for permission in all_permission %}
                            <tr>
                                <td>{{ permission.title }}</td>
                                <td>{{ permission.url }}</td>
                                <td>{{ permission.name }}</td>
    
                                <td>
                                    <a href=""> <i class="fa fa-edit"></i> </a>
                                    <a href=""> <i class="fa fa-trash-o"></i> </a>
                                </td>
                            </tr>
    
                        {% endfor %}
    
                        </tbody>
    
                    </table>
                </div>
            </div>
        </div>
    
    
    {% endblock %}
    menu_list.html
    {% extends 'layout.html' %}
    
    
    {% block css %}
        <style>
            ul {
                list-style-type: none;
                padding: 0;
            }
    
            ul li {
                float: left;
                padding: 10px;
                padding-left: 0;
                 80px;
            }
    
            ul li i {
                font-size: 18px;
                margin-left: 5px;
                color: #6d6565;
            }
    
        </style>
    {% endblock %}
    
    {% block content %}
    
        <form class="form-horizontal" novalidate method="post" action="" style="margin-top: 50px">
            {% csrf_token %}
    
            {% for field in form_obj %}
    
                <div class="form-group {% if field.errors %}has-error{% endif %} ">
                    <label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
                    <div class="col-sm-6">
                        {{ field }}
                    </div>
                    <span class="help-block">{{ field.errors.0 }}</span>
                </div>
            {% endfor %}
    
    
            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-6">
                    <button type="submit" class="btn btn-default">提交</button>
                </div>
            </div>
        </form>
    {% endblock %}
    form.html

    权限信息展示

    from django.shortcuts import render, HttpResponse, redirect, reverse
    from rbac import models
    from rbac.forms import *
    from django.db.models import Q
    
    
    def role_list(request):
        all_roles = models.Role.objects.all()
        return render(request, 'rbac/role_list.html', {"all_roles": all_roles})
    
    
    def role(request, edit_id=None):
        obj = models.Role.objects.filter(id=edit_id).first()
        form_obj = RoleForm(instance=obj)
        if request.method == 'POST':
            form_obj = RoleForm(request.POST, instance=obj)
            if form_obj.is_valid():
                form_obj.save()
                return redirect(reverse('rbac:role_list'))
        
        return render(request, 'rbac/form.html', {'form_obj': form_obj})
    
    
    def del_role(request, del_id):
        models.Role.objects.filter(id=del_id).delete()
        return redirect(reverse('rbac:role_list'))
    
    
    # 菜单信息  权限信息
    def menu_list(request):
        all_menu = models.Menu.objects.all()
        
        mid = request.GET.get('mid')
        
        if mid:
            permission_query = models.Permission.objects.filter(Q(menu_id=mid) | Q(parent__menu_id=mid))
        else:
            permission_query = models.Permission.objects.all()
        
        all_permission = permission_query.values('id', 'url', 'title', 'name', 'menu_id', 'parent_id','menu__title')
        
        all_permission_dict = {}
        
        for item in all_permission:
            menu_id = item.get('menu_id')
            if menu_id:
                item['children'] = []
                all_permission_dict[item['id']] = item
        
        for item in all_permission:
            pid = item.get('parent_id')
            
            if pid:
                all_permission_dict[pid]['children'].append(item)
        
        print(all_permission_dict)
        
        return render(request, 'rbac/menu_list.html',
                      {"all_menu": all_menu, 'all_permission_dict': all_permission_dict, 'mid': mid})
    
    
    def menu(request, edit_id=None):
        obj = models.Menu.objects.filter(id=edit_id).first()
        form_obj = MenuForm(instance=obj)
        if request.method == 'POST':
            form_obj = MenuForm(request.POST, instance=obj)
            if form_obj.is_valid():
                form_obj.save()
                return redirect(reverse('rbac:menu_list'))
        
        return render(request, 'rbac/form.html', {'form_obj': form_obj})
    views.py
    {% extends 'layout.html' %}
    
    
    {% block css %}
        <style>
            .permission-area tr.parent {
                background-color: #cae7fd;;
            }
    
            .menu-body tr.active {
                background-color: #f1f7fd;
                border-left: 3px solid #fdc00f;
            }
    
    
        </style>
    {% endblock %}
    
    {% block content %}
        <div style="margin: 20px">
            <div class="col-sm-3">
                <div class="panel panel-default">
                    <!-- Default panel contents -->
                    <div class="panel-heading"><i class="fa fa-book"></i> 菜单管理
                        <a href="{% url 'rbac:menu_add' %}" class="btn btn-success btn-sm pull-right "
                           style="padding: 2px 8px;margin: -3px;"> <i
                                class="fa fa-plus"></i> 新建</a>
                    </div>
    
                    <!-- Table -->
                    <table class="table">
                        <thead>
                        <tr>
                            <th>名称</th>
                            <th>图标</th>
                            <th>操作</th>
                        </tr>
                        </thead>
                        <tbody class="menu-body">
                        {% for menu in all_menu %}
                            <tr class=" {% if menu.id|safe == mid %} active {% endif %} ">
                                <td><a href="?mid={{ menu.id }}">{{ menu.title }}</a></td>
                                <td><i class="fa {{ menu.icon }} "></i></td>
                                <td>
                                    <a href="{% url 'rbac:menu_edit' menu.id %}"> <i class="fa fa-edit"></i> </a>
                                    <a href=""> <i class="fa fa-trash-o"></i> </a>
                                </td>
                            </tr>
    
                        {% endfor %}
    
                        </tbody>
    
                    </table>
                </div>
            </div>
            <div class="col-sm-9">
                <div class="panel panel-default">
                    <!-- Default panel contents -->
                    <div class="panel-heading"><i class="fa fa-cubes"></i> 权限管理</div>
    
                    <!-- Table -->
                    <table class="table">
                        <thead>
                        <tr>
                            <th>名称</th>
                            <th>URL</th>
                            <th>URL别名</th>
                            <th>菜单</th>
                            <th>所属菜单</th>
                            <th>操作</th>
                        </tr>
                        </thead>
                        <tbody class="permission-area">
                        {% for p_permission in all_permission_dict.values %}
                            <tr class="parent" id="{{ p_permission.id }}">
                                <td class="title">
                                    <i class="fa fa-caret-down"></i>
                                    {{ p_permission.title }}
                                </td>
                                <td>{{ p_permission.url }}</td>
                                <td>{{ p_permission.name }}</td>
                                <td></td>
                                <td>
                                    {{ p_permission.menu__title }}
                                </td>
    
                                <td>
                                    <a href=""> <i class="fa fa-edit"></i> </a>
                                    <a href=""> <i class="fa fa-trash-o"></i> </a>
                                </td>
                            </tr>
                            {% for c_permission in p_permission.children %}
                                <tr pid="{{ c_permission.parent_id }}">
                                    <td>{{ c_permission.title }}</td>
                                    <td>{{ c_permission.url }}</td>
                                    <td>{{ c_permission.name }}</td>
                                    <td></td>
                                    <td></td>
    
                                    <td>
                                        <a href=""> <i class="fa fa-edit"></i> </a>
                                        <a href=""> <i class="fa fa-trash-o"></i> </a>
                                    </td>
                                </tr>
                            {% endfor %}
    
    
                        {% endfor %}
    
                        </tbody>
    
                    </table>
                </div>
            </div>
        </div>
    
    
    {% endblock %}
    
    {% block js %}
        <script>
    
    
            $('.permission-area').on('click', '.parent .title', function () {
                var caret = $(this).find('i');
                var id = $(this).parent().attr('id');
                if (caret.hasClass('fa-caret-right')) {
                    caret.removeClass('fa-caret-right').addClass('fa-caret-down');
                    $(this).parent().nextAll('tr[pid="' + id + '"]').removeClass('hide');
                } else {
                    caret.removeClass('fa-caret-down').addClass('fa-caret-right');
                    $(this).parent().nextAll('tr[pid="' + id + '"]').addClass('hide');
    
                }
            })
    
    
        </script>
    {% endblock %}
    menu_list.html

    最终版:权限表增加,编辑,批量操作,权限分配

    from django.db import models
    
    
    class Menu(models.Model):
        """
        一级菜单
        """
        title = models.CharField(max_length=32, unique=True,verbose_name='标题')
        icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)
        weight = models.IntegerField(default=1,verbose_name='权重')
        
        class Meta:
            verbose_name_plural = '菜单表'
            verbose_name = '菜单表'
        
        def __str__(self):
            return self.title
    
    
    class Permission(models.Model):
        """
        权限表
        有关联Menu的是二级菜单
        没有关联Menu的不是二级菜单,是不可以做菜单的权限
        
        
        """
        title = models.CharField(max_length=32, verbose_name='标题')
        url = models.CharField(max_length=32, verbose_name='权限')
        menu = models.ForeignKey('Menu', null=True, blank=True,verbose_name='菜单')
        
        parent = models.ForeignKey('Permission', null=True, blank=True,verbose_name='父权限')
        name = models.CharField(max_length=32, null=True, blank=True, unique=True,verbose_name='URL别名')
        
        class Meta:
            verbose_name_plural = '权限表'
            verbose_name = '权限表'
        
        def __str__(self):
            return self.title
    
    
    class Role(models.Model):
        name = models.CharField(max_length=32, verbose_name='角色名称')
        permissions = models.ManyToManyField(to='Permission', verbose_name='角色所拥有的权限', blank=True)
        
        def __str__(self):
            return self.name
    
    
    class User(models.Model):
        """
        用户表
        """
        name = models.CharField(max_length=32, verbose_name='用户名')
        password = models.CharField(max_length=32, verbose_name='密码')
        roles = models.ManyToManyField(to='Role', verbose_name='用户所拥有的角色', blank=True)
        
        def __str__(self):
            return self.name
    models.py
    from django.conf.urls import url
    from rbac import views
    
    urlpatterns = [
        # /app01/role/list/    # rbac:role_list
        url(r'^role/list/$', views.role_list, name='role_list'),
        url(r'^role/add/$', views.role, name='role_add'),
        url(r'^role/edit/(d+)$', views.role, name='role_edit'),
        url(r'^role/del/(d+)$', views.del_role, name='role_del'),
        
        url(r'^menu/list/$', views.menu_list, name='menu_list'),
        url(r'^menu/add/$', views.menu, name='menu_add'),
        url(r'^menu/edit/(d+)$', views.menu, name='menu_edit'),
        
        url(r'^permission/add/$', views.permission, name='permission_add'),
        url(r'^permission/edit/(d+)$', views.permission, name='permission_edit'),
        url(r'^permission/del/(d+)$', views.del_permission, name='permission_del'),
        
        url(r'^multi/permissions/$', views.multi_permissions, name='multi_permissions'),
        
        url(r'^distribute/permissions/$', views.distribute_permissions, name='distribute_permissions'),
    
    ]
    urls.py
    from django import forms
    from rbac import models
    from django.utils.safestring import mark_safe
    
    
    # 角色的Form
    class RoleForm(forms.ModelForm):
        class Meta:
            model = models.Role
            fields = ['name']
            
            widgets = {
                'name': forms.widgets.Input(attrs={"class": 'form-control'})
            }
    
    
    ICON_LIST = [[i[0], mark_safe(i[1])] for i in [
        ['fa-address-book', '<i aria-hidden="true" class="fa fa-address-book"></i>'],
        ['fa-address-book-o', '<i aria-hidden="true" class="fa fa-address-book-o"></i>'],
        ['fa-address-card', '<i aria-hidden="true" class="fa fa-address-card"></i>'],
        ['fa-address-card-o', '<i aria-hidden="true" class="fa fa-address-card-o"></i>'],
        ['fa-adjust', '<i aria-hidden="true" class="fa fa-adjust"></i>'],
        ['fa-american-sign-language-interpreting',
         '<i aria-hidden="true" class="fa fa-american-sign-language-interpreting"></i>'],
        ['fa-anchor', '<i aria-hidden="true" class="fa fa-anchor"></i>'],
        ['fa-archive', '<i aria-hidden="true" class="fa fa-archive"></i>'],
        ['fa-area-chart', '<i aria-hidden="true" class="fa fa-area-chart"></i>'],
        ['fa-arrows', '<i aria-hidden="true" class="fa fa-arrows"></i>'],
        ['fa-arrows-h', '<i aria-hidden="true" class="fa fa-arrows-h"></i>'],
        ['fa-arrows-v', '<i aria-hidden="true" class="fa fa-arrows-v"></i>'],
        ['fa-asl-interpreting', '<i aria-hidden="true" class="fa fa-asl-interpreting"></i>'],
        ['fa-assistive-listening-systems', '<i aria-hidden="true" class="fa fa-assistive-listening-systems"></i>'],
        ['fa-asterisk', '<i aria-hidden="true" class="fa fa-asterisk"></i>'],
        ['fa-at', '<i aria-hidden="true" class="fa fa-at"></i>'],
        ['fa-audio-description', '<i aria-hidden="true" class="fa fa-audio-description"></i>'],
        ['fa-automobile', '<i aria-hidden="true" class="fa fa-automobile"></i>'],
        ['fa-balance-scale', '<i aria-hidden="true" class="fa fa-balance-scale"></i>'],
        ['fa-ban', '<i aria-hidden="true" class="fa fa-ban"></i>'],
        ['fa-bank', '<i aria-hidden="true" class="fa fa-bank"></i>'],
        ['fa-bar-chart', '<i aria-hidden="true" class="fa fa-bar-chart"></i>'],
        ['fa-bar-chart-o', '<i aria-hidden="true" class="fa fa-bar-chart-o"></i>'],
        ['fa-barcode', '<i aria-hidden="true" class="fa fa-barcode"></i>'],
        ['fa-bars', '<i aria-hidden="true" class="fa fa-bars"></i>'],
        ['fa-bath', '<i aria-hidden="true" class="fa fa-bath"></i>'],
        ['fa-bathtub', '<i aria-hidden="true" class="fa fa-bathtub"></i>'],
        ['fa-battery', '<i aria-hidden="true" class="fa fa-battery"></i>'],
        ['fa-battery-0', '<i aria-hidden="true" class="fa fa-battery-0"></i>'],
        ['fa-battery-1', '<i aria-hidden="true" class="fa fa-battery-1"></i>'],
        ['fa-battery-2', '<i aria-hidden="true" class="fa fa-battery-2"></i>'],
        ['fa-battery-3', '<i aria-hidden="true" class="fa fa-battery-3"></i>'],
        ['fa-battery-4', '<i aria-hidden="true" class="fa fa-battery-4"></i>'],
        ['fa-battery-empty', '<i aria-hidden="true" class="fa fa-battery-empty"></i>'],
        ['fa-battery-full', '<i aria-hidden="true" class="fa fa-battery-full"></i>'],
        ['fa-battery-half', '<i aria-hidden="true" class="fa fa-battery-half"></i>'],
        ['fa-battery-quarter', '<i aria-hidden="true" class="fa fa-battery-quarter"></i>'],
        ['fa-battery-three-quarters', '<i aria-hidden="true" class="fa fa-battery-three-quarters"></i>'],
        ['fa-bed', '<i aria-hidden="true" class="fa fa-bed"></i>'],
        ['fa-beer', '<i aria-hidden="true" class="fa fa-beer"></i>'],
        ['fa-bell', '<i aria-hidden="true" class="fa fa-bell"></i>'],
        ['fa-bell-o', '<i aria-hidden="true" class="fa fa-bell-o"></i>'],
        ['fa-bell-slash', '<i aria-hidden="true" class="fa fa-bell-slash"></i>'],
        ['fa-bell-slash-o', '<i aria-hidden="true" class="fa fa-bell-slash-o"></i>'],
        ['fa-bicycle', '<i aria-hidden="true" class="fa fa-bicycle"></i>'],
        ['fa-binoculars', '<i aria-hidden="true" class="fa fa-binoculars"></i>'],
        ['fa-birthday-cake', '<i aria-hidden="true" class="fa fa-birthday-cake"></i>'],
        ['fa-blind', '<i aria-hidden="true" class="fa fa-blind"></i>'],
        ['fa-bluetooth', '<i aria-hidden="true" class="fa fa-bluetooth"></i>'],
        ['fa-bluetooth-b', '<i aria-hidden="true" class="fa fa-bluetooth-b"></i>'],
        ['fa-bolt', '<i aria-hidden="true" class="fa fa-bolt"></i>'],
        ['fa-bomb', '<i aria-hidden="true" class="fa fa-bomb"></i>'],
        ['fa-book', '<i aria-hidden="true" class="fa fa-book"></i>'],
        ['fa-bookmark', '<i aria-hidden="true" class="fa fa-bookmark"></i>'],
        ['fa-bookmark-o', '<i aria-hidden="true" class="fa fa-bookmark-o"></i>'],
        ['fa-braille', '<i aria-hidden="true" class="fa fa-braille"></i>'],
        ['fa-briefcase', '<i aria-hidden="true" class="fa fa-briefcase"></i>'],
        ['fa-bug', '<i aria-hidden="true" class="fa fa-bug"></i>'],
        ['fa-building', '<i aria-hidden="true" class="fa fa-building"></i>'],
        ['fa-building-o', '<i aria-hidden="true" class="fa fa-building-o"></i>'],
        ['fa-bullhorn', '<i aria-hidden="true" class="fa fa-bullhorn"></i>'],
        ['fa-bullseye', '<i aria-hidden="true" class="fa fa-bullseye"></i>'],
        ['fa-bus', '<i aria-hidden="true" class="fa fa-bus"></i>'],
        ['fa-cab', '<i aria-hidden="true" class="fa fa-cab"></i>'],
        ['fa-calculator', '<i aria-hidden="true" class="fa fa-calculator"></i>'],
        ['fa-calendar', '<i aria-hidden="true" class="fa fa-calendar"></i>'],
        ['fa-calendar-check-o', '<i aria-hidden="true" class="fa fa-calendar-check-o"></i>'],
        ['fa-calendar-minus-o', '<i aria-hidden="true" class="fa fa-calendar-minus-o"></i>'],
        ['fa-calendar-o', '<i aria-hidden="true" class="fa fa-calendar-o"></i>'],
        ['fa-calendar-plus-o', '<i aria-hidden="true" class="fa fa-calendar-plus-o"></i>'],
        ['fa-calendar-times-o', '<i aria-hidden="true" class="fa fa-calendar-times-o"></i>'],
        ['fa-camera', '<i aria-hidden="true" class="fa fa-camera"></i>'],
        ['fa-camera-retro', '<i aria-hidden="true" class="fa fa-camera-retro"></i>'],
        ['fa-car', '<i aria-hidden="true" class="fa fa-car"></i>'],
        ['fa-caret-square-o-down', '<i aria-hidden="true" class="fa fa-caret-square-o-down"></i>'],
        ['fa-caret-square-o-left', '<i aria-hidden="true" class="fa fa-caret-square-o-left"></i>'],
        ['fa-caret-square-o-right', '<i aria-hidden="true" class="fa fa-caret-square-o-right"></i>'],
        ['fa-caret-square-o-up', '<i aria-hidden="true" class="fa fa-caret-square-o-up"></i>'],
        ['fa-cart-arrow-down', '<i aria-hidden="true" class="fa fa-cart-arrow-down"></i>'],
        ['fa-cart-plus', '<i aria-hidden="true" class="fa fa-cart-plus"></i>'],
        ['fa-cc', '<i aria-hidden="true" class="fa fa-cc"></i>'],
        ['fa-certificate', '<i aria-hidden="true" class="fa fa-certificate"></i>'],
        ['fa-check', '<i aria-hidden="true" class="fa fa-check"></i>'],
        ['fa-check-circle', '<i aria-hidden="true" class="fa fa-check-circle"></i>'],
        ['fa-check-circle-o', '<i aria-hidden="true" class="fa fa-check-circle-o"></i>'],
        ['fa-check-square', '<i aria-hidden="true" class="fa fa-check-square"></i>'],
        ['fa-check-square-o', '<i aria-hidden="true" class="fa fa-check-square-o"></i>'],
        ['fa-child', '<i aria-hidden="true" class="fa fa-child"></i>'],
        ['fa-circle', '<i aria-hidden="true" class="fa fa-circle"></i>'],
        ['fa-circle-o', '<i aria-hidden="true" class="fa fa-circle-o"></i>'],
        ['fa-circle-o-notch', '<i aria-hidden="true" class="fa fa-circle-o-notch"></i>'],
        ['fa-circle-thin', '<i aria-hidden="true" class="fa fa-circle-thin"></i>'],
        ['fa-clock-o', '<i aria-hidden="true" class="fa fa-clock-o"></i>'],
        ['fa-clone', '<i aria-hidden="true" class="fa fa-clone"></i>'],
        ['fa-close', '<i aria-hidden="true" class="fa fa-close"></i>'],
        ['fa-cloud', '<i aria-hidden="true" class="fa fa-cloud"></i>'],
        ['fa-cloud-download', '<i aria-hidden="true" class="fa fa-cloud-download"></i>'],
        ['fa-cloud-upload', '<i aria-hidden="true" class="fa fa-cloud-upload"></i>'],
        ['fa-code', '<i aria-hidden="true" class="fa fa-code"></i>'],
        ['fa-code-fork', '<i aria-hidden="true" class="fa fa-code-fork"></i>'],
        ['fa-coffee', '<i aria-hidden="true" class="fa fa-coffee"></i>'],
        ['fa-cog', '<i aria-hidden="true" class="fa fa-cog"></i>'],
        ['fa-cogs', '<i aria-hidden="true" class="fa fa-cogs"></i>'],
        ['fa-comment', '<i aria-hidden="true" class="fa fa-comment"></i>'],
        ['fa-comment-o', '<i aria-hidden="true" class="fa fa-comment-o"></i>'],
        ['fa-commenting', '<i aria-hidden="true" class="fa fa-commenting"></i>'],
        ['fa-commenting-o', '<i aria-hidden="true" class="fa fa-commenting-o"></i>'],
        ['fa-comments', '<i aria-hidden="true" class="fa fa-comments"></i>'],
        ['fa-comments-o', '<i aria-hidden="true" class="fa fa-comments-o"></i>'],
        ['fa-compass', '<i aria-hidden="true" class="fa fa-compass"></i>'],
        ['fa-copyright', '<i aria-hidden="true" class="fa fa-copyright"></i>'],
        ['fa-creative-commons', '<i aria-hidden="true" class="fa fa-creative-commons"></i>'],
        ['fa-credit-card', '<i aria-hidden="true" class="fa fa-credit-card"></i>'],
        ['fa-credit-card-alt', '<i aria-hidden="true" class="fa fa-credit-card-alt"></i>'],
        ['fa-crop', '<i aria-hidden="true" class="fa fa-crop"></i>'],
        ['fa-crosshairs', '<i aria-hidden="true" class="fa fa-crosshairs"></i>'],
        ['fa-cube', '<i aria-hidden="true" class="fa fa-cube"></i>'],
        ['fa-cubes', '<i aria-hidden="true" class="fa fa-cubes"></i>'],
        ['fa-cutlery', '<i aria-hidden="true" class="fa fa-cutlery"></i>'],
        ['fa-dashboard', '<i aria-hidden="true" class="fa fa-dashboard"></i>'],
        ['fa-database', '<i aria-hidden="true" class="fa fa-database"></i>'],
        ['fa-deaf', '<i aria-hidden="true" class="fa fa-deaf"></i>'],
        ['fa-deafness', '<i aria-hidden="true" class="fa fa-deafness"></i>'],
        ['fa-desktop', '<i aria-hidden="true" class="fa fa-desktop"></i>'],
        ['fa-diamond', '<i aria-hidden="true" class="fa fa-diamond"></i>'],
        ['fa-dot-circle-o', '<i aria-hidden="true" class="fa fa-dot-circle-o"></i>'],
        ['fa-download', '<i aria-hidden="true" class="fa fa-download"></i>'],
        ['fa-drivers-license', '<i aria-hidden="true" class="fa fa-drivers-license"></i>'],
        ['fa-drivers-license-o', '<i aria-hidden="true" class="fa fa-drivers-license-o"></i>'],
        ['fa-edit', '<i aria-hidden="true" class="fa fa-edit"></i>'],
        ['fa-ellipsis-h', '<i aria-hidden="true" class="fa fa-ellipsis-h"></i>'],
        ['fa-ellipsis-v', '<i aria-hidden="true" class="fa fa-ellipsis-v"></i>'],
        ['fa-envelope', '<i aria-hidden="true" class="fa fa-envelope"></i>'],
        ['fa-envelope-o', '<i aria-hidden="true" class="fa fa-envelope-o"></i>'],
        ['fa-envelope-open', '<i aria-hidden="true" class="fa fa-envelope-open"></i>'],
        ['fa-envelope-open-o', '<i aria-hidden="true" class="fa fa-envelope-open-o"></i>'],
        ['fa-envelope-square', '<i aria-hidden="true" class="fa fa-envelope-square"></i>'],
        ['fa-eraser', '<i aria-hidden="true" class="fa fa-eraser"></i>'],
        ['fa-exchange', '<i aria-hidden="true" class="fa fa-exchange"></i>'],
        ['fa-exclamation', '<i aria-hidden="true" class="fa fa-exclamation"></i>'],
        ['fa-exclamation-circle', '<i aria-hidden="true" class="fa fa-exclamation-circle"></i>'],
        ['fa-exclamation-triangle', '<i aria-hidden="true" class="fa fa-exclamation-triangle"></i>'],
        ['fa-external-link', '<i aria-hidden="true" class="fa fa-external-link"></i>'],
        ['fa-external-link-square', '<i aria-hidden="true" class="fa fa-external-link-square"></i>'],
        ['fa-eye', '<i aria-hidden="true" class="fa fa-eye"></i>'],
        ['fa-eye-slash', '<i aria-hidden="true" class="fa fa-eye-slash"></i>'],
        ['fa-eyedropper', '<i aria-hidden="true" class="fa fa-eyedropper"></i>'],
        ['fa-fax', '<i aria-hidden="true" class="fa fa-fax"></i>'],
        ['fa-feed', '<i aria-hidden="true" class="fa fa-feed"></i>'],
        ['fa-female', '<i aria-hidden="true" class="fa fa-female"></i>'],
        ['fa-fighter-jet', '<i aria-hidden="true" class="fa fa-fighter-jet"></i>'],
        ['fa-file-archive-o', '<i aria-hidden="true" class="fa fa-file-archive-o"></i>'],
        ['fa-file-audio-o', '<i aria-hidden="true" class="fa fa-file-audio-o"></i>'],
        ['fa-file-code-o', '<i aria-hidden="true" class="fa fa-file-code-o"></i>'],
        ['fa-file-excel-o', '<i aria-hidden="true" class="fa fa-file-excel-o"></i>'],
        ['fa-file-image-o', '<i aria-hidden="true" class="fa fa-file-image-o"></i>'],
        ['fa-file-movie-o', '<i aria-hidden="true" class="fa fa-file-movie-o"></i>'],
        ['fa-file-pdf-o', '<i aria-hidden="true" class="fa fa-file-pdf-o"></i>'],
        ['fa-file-photo-o', '<i aria-hidden="true" class="fa fa-file-photo-o"></i>'],
        ['fa-file-picture-o', '<i aria-hidden="true" class="fa fa-file-picture-o"></i>'],
        ['fa-file-powerpoint-o', '<i aria-hidden="true" class="fa fa-file-powerpoint-o"></i>'],
        ['fa-file-sound-o', '<i aria-hidden="true" class="fa fa-file-sound-o"></i>'],
        ['fa-file-video-o', '<i aria-hidden="true" class="fa fa-file-video-o"></i>'],
        ['fa-file-word-o', '<i aria-hidden="true" class="fa fa-file-word-o"></i>'],
        ['fa-file-zip-o', '<i aria-hidden="true" class="fa fa-file-zip-o"></i>'],
        ['fa-film', '<i aria-hidden="true" class="fa fa-film"></i>'],
        ['fa-filter', '<i aria-hidden="true" class="fa fa-filter"></i>'],
        ['fa-fire', '<i aria-hidden="true" class="fa fa-fire"></i>'],
        ['fa-fire-extinguisher', '<i aria-hidden="true" class="fa fa-fire-extinguisher"></i>'],
        ['fa-flag', '<i aria-hidden="true" class="fa fa-flag"></i>'],
        ['fa-flag-checkered', '<i aria-hidden="true" class="fa fa-flag-checkered"></i>'],
        ['fa-flag-o', '<i aria-hidden="true" class="fa fa-flag-o"></i>'],
        ['fa-flash', '<i aria-hidden="true" class="fa fa-flash"></i>'],
        ['fa-flask', '<i aria-hidden="true" class="fa fa-flask"></i>'],
        ['fa-folder', '<i aria-hidden="true" class="fa fa-folder"></i>'],
        ['fa-folder-o', '<i aria-hidden="true" class="fa fa-folder-o"></i>'],
        ['fa-folder-open', '<i aria-hidden="true" class="fa fa-folder-open"></i>'],
        ['fa-folder-open-o', '<i aria-hidden="true" class="fa fa-folder-open-o"></i>'],
        ['fa-frown-o', '<i aria-hidden="true" class="fa fa-frown-o"></i>'],
        ['fa-futbol-o', '<i aria-hidden="true" class="fa fa-futbol-o"></i>'],
        ['fa-gamepad', '<i aria-hidden="true" class="fa fa-gamepad"></i>'],
        ['fa-gavel', '<i aria-hidden="true" class="fa fa-gavel"></i>'],
        ['fa-gear', '<i aria-hidden="true" class="fa fa-gear"></i>'],
        ['fa-gears', '<i aria-hidden="true" class="fa fa-gears"></i>'],
        ['fa-gift', '<i aria-hidden="true" class="fa fa-gift"></i>'],
        ['fa-glass', '<i aria-hidden="true" class="fa fa-glass"></i>'],
        ['fa-globe', '<i aria-hidden="true" class="fa fa-globe"></i>'],
        ['fa-graduation-cap', '<i aria-hidden="true" class="fa fa-graduation-cap"></i>'],
        ['fa-group', '<i aria-hidden="true" class="fa fa-group"></i>'],
        ['fa-hand-grab-o', '<i aria-hidden="true" class="fa fa-hand-grab-o"></i>'],
        ['fa-hand-lizard-o', '<i aria-hidden="true" class="fa fa-hand-lizard-o"></i>'],
        ['fa-hand-paper-o', '<i aria-hidden="true" class="fa fa-hand-paper-o"></i>'],
        ['fa-hand-peace-o', '<i aria-hidden="true" class="fa fa-hand-peace-o"></i>'],
        ['fa-hand-pointer-o', '<i aria-hidden="true" class="fa fa-hand-pointer-o"></i>'],
        ['fa-hand-rock-o', '<i aria-hidden="true" class="fa fa-hand-rock-o"></i>'],
        ['fa-hand-scissors-o', '<i aria-hidden="true" class="fa fa-hand-scissors-o"></i>'],
        ['fa-hand-spock-o', '<i aria-hidden="true" class="fa fa-hand-spock-o"></i>'],
        ['fa-hand-stop-o', '<i aria-hidden="true" class="fa fa-hand-stop-o"></i>'],
        ['fa-handshake-o', '<i aria-hidden="true" class="fa fa-handshake-o"></i>'],
        ['fa-hard-of-hearing', '<i aria-hidden="true" class="fa fa-hard-of-hearing"></i>'],
        ['fa-hashtag', '<i aria-hidden="true" class="fa fa-hashtag"></i>'],
        ['fa-hdd-o', '<i aria-hidden="true" class="fa fa-hdd-o"></i>'],
        ['fa-headphones', '<i aria-hidden="true" class="fa fa-headphones"></i>'],
        ['fa-heart', '<i aria-hidden="true" class="fa fa-heart"></i>'],
        ['fa-heart-o', '<i aria-hidden="true" class="fa fa-heart-o"></i>'],
        ['fa-heartbeat', '<i aria-hidden="true" class="fa fa-heartbeat"></i>'],
        ['fa-history', '<i aria-hidden="true" class="fa fa-history"></i>'],
        ['fa-home', '<i aria-hidden="true" class="fa fa-home"></i>'],
        ['fa-hotel', '<i aria-hidden="true" class="fa fa-hotel"></i>'],
        ['fa-hourglass', '<i aria-hidden="true" class="fa fa-hourglass"></i>'],
        ['fa-hourglass-1', '<i aria-hidden="true" class="fa fa-hourglass-1"></i>'],
        ['fa-hourglass-2', '<i aria-hidden="true" class="fa fa-hourglass-2"></i>'],
        ['fa-hourglass-3', '<i aria-hidden="true" class="fa fa-hourglass-3"></i>'],
        ['fa-hourglass-end', '<i aria-hidden="true" class="fa fa-hourglass-end"></i>'],
        ['fa-hourglass-half', '<i aria-hidden="true" class="fa fa-hourglass-half"></i>'],
        ['fa-hourglass-o', '<i aria-hidden="true" class="fa fa-hourglass-o"></i>'],
        ['fa-hourglass-start', '<i aria-hidden="true" class="fa fa-hourglass-start"></i>'],
        ['fa-i-cursor', '<i aria-hidden="true" class="fa fa-i-cursor"></i>'],
        ['fa-id-badge', '<i aria-hidden="true" class="fa fa-id-badge"></i>'],
        ['fa-id-card', '<i aria-hidden="true" class="fa fa-id-card"></i>'],
        ['fa-id-card-o', '<i aria-hidden="true" class="fa fa-id-card-o"></i>'],
        ['fa-image', '<i aria-hidden="true" class="fa fa-image"></i>'],
        ['fa-inbox', '<i aria-hidden="true" class="fa fa-inbox"></i>'],
        ['fa-industry', '<i aria-hidden="true" class="fa fa-industry"></i>'],
        ['fa-info', '<i aria-hidden="true" class="fa fa-info"></i>'],
        ['fa-info-circle', '<i aria-hidden="true" class="fa fa-info-circle"></i>'],
        ['fa-institution', '<i aria-hidden="true" class="fa fa-institution"></i>'],
        ['fa-key', '<i aria-hidden="true" class="fa fa-key"></i>'],
        ['fa-keyboard-o', '<i aria-hidden="true" class="fa fa-keyboard-o"></i>'],
        ['fa-language', '<i aria-hidden="true" class="fa fa-language"></i>'],
        ['fa-laptop', '<i aria-hidden="true" class="fa fa-laptop"></i>'],
        ['fa-leaf', '<i aria-hidden="true" class="fa fa-leaf"></i>'],
        ['fa-legal', '<i aria-hidden="true" class="fa fa-legal"></i>'],
        ['fa-lemon-o', '<i aria-hidden="true" class="fa fa-lemon-o"></i>'],
        ['fa-level-down', '<i aria-hidden="true" class="fa fa-level-down"></i>'],
        ['fa-level-up', '<i aria-hidden="true" class="fa fa-level-up"></i>'],
        ['fa-life-bouy', '<i aria-hidden="true" class="fa fa-life-bouy"></i>'],
        ['fa-life-buoy', '<i aria-hidden="true" class="fa fa-life-buoy"></i>'],
        ['fa-life-ring', '<i aria-hidden="true" class="fa fa-life-ring"></i>'],
        ['fa-life-saver', '<i aria-hidden="true" class="fa fa-life-saver"></i>'],
        ['fa-lightbulb-o', '<i aria-hidden="true" class="fa fa-lightbulb-o"></i>'],
        ['fa-line-chart', '<i aria-hidden="true" class="fa fa-line-chart"></i>'],
        ['fa-location-arrow', '<i aria-hidden="true" class="fa fa-location-arrow"></i>'],
        ['fa-lock', '<i aria-hidden="true" class="fa fa-lock"></i>'],
        ['fa-low-vision', '<i aria-hidden="true" class="fa fa-low-vision"></i>'],
        ['fa-magic', '<i aria-hidden="true" class="fa fa-magic"></i>'],
        ['fa-magnet', '<i aria-hidden="true" class="fa fa-magnet"></i>'],
        ['fa-mail-forward', '<i aria-hidden="true" class="fa fa-mail-forward"></i>'],
        ['fa-mail-reply', '<i aria-hidden="true" class="fa fa-mail-reply"></i>'],
        ['fa-mail-reply-all', '<i aria-hidden="true" class="fa fa-mail-reply-all"></i>'],
        ['fa-male', '<i aria-hidden="true" class="fa fa-male"></i>'],
        ['fa-map', '<i aria-hidden="true" class="fa fa-map"></i>'],
        ['fa-map-marker', '<i aria-hidden="true" class="fa fa-map-marker"></i>'],
        ['fa-map-o', '<i aria-hidden="true" class="fa fa-map-o"></i>'],
        ['fa-map-pin', '<i aria-hidden="true" class="fa fa-map-pin"></i>'],
        ['fa-map-signs', '<i aria-hidden="true" class="fa fa-map-signs"></i>'],
        ['fa-meh-o', '<i aria-hidden="true" class="fa fa-meh-o"></i>'],
        ['fa-microchip', '<i aria-hidden="true" class="fa fa-microchip"></i>'],
        ['fa-microphone', '<i aria-hidden="true" class="fa fa-microphone"></i>'],
        ['fa-microphone-slash', '<i aria-hidden="true" class="fa fa-microphone-slash"></i>'],
        ['fa-minus', '<i aria-hidden="true" class="fa fa-minus"></i>'],
        ['fa-minus-circle', '<i aria-hidden="true" class="fa fa-minus-circle"></i>'],
        ['fa-minus-square', '<i aria-hidden="true" class="fa fa-minus-square"></i>'],
        ['fa-minus-square-o', '<i aria-hidden="true" class="fa fa-minus-square-o"></i>'],
        ['fa-mobile', '<i aria-hidden="true" class="fa fa-mobile"></i>'],
        ['fa-mobile-phone', '<i aria-hidden="true" class="fa fa-mobile-phone"></i>'],
        ['fa-money', '<i aria-hidden="true" class="fa fa-money"></i>'],
        ['fa-moon-o', '<i aria-hidden="true" class="fa fa-moon-o"></i>'],
        ['fa-mortar-board', '<i aria-hidden="true" class="fa fa-mortar-board"></i>'],
        ['fa-motorcycle', '<i aria-hidden="true" class="fa fa-motorcycle"></i>'],
        ['fa-mouse-pointer', '<i aria-hidden="true" class="fa fa-mouse-pointer"></i>'],
        ['fa-music', '<i aria-hidden="true" class="fa fa-music"></i>'],
        ['fa-navicon', '<i aria-hidden="true" class="fa fa-navicon"></i>'],
        ['fa-newspaper-o', '<i aria-hidden="true" class="fa fa-newspaper-o"></i>'],
        ['fa-object-group', '<i aria-hidden="true" class="fa fa-object-group"></i>'],
        ['fa-object-ungroup', '<i aria-hidden="true" class="fa fa-object-ungroup"></i>'],
        ['fa-paint-brush', '<i aria-hidden="true" class="fa fa-paint-brush"></i>'],
        ['fa-paper-plane', '<i aria-hidden="true" class="fa fa-paper-plane"></i>'],
        ['fa-paper-plane-o', '<i aria-hidden="true" class="fa fa-paper-plane-o"></i>'],
        ['fa-paw', '<i aria-hidden="true" class="fa fa-paw"></i>'],
        ['fa-pencil', '<i aria-hidden="true" class="fa fa-pencil"></i>'],
        ['fa-pencil-square', '<i aria-hidden="true" class="fa fa-pencil-square"></i>'],
        ['fa-pencil-square-o', '<i aria-hidden="true" class="fa fa-pencil-square-o"></i>'],
        ['fa-percent', '<i aria-hidden="true" class="fa fa-percent"></i>'],
        ['fa-phone', '<i aria-hidden="true" class="fa fa-phone"></i>'],
        ['fa-phone-square', '<i aria-hidden="true" class="fa fa-phone-square"></i>'],
        ['fa-photo', '<i aria-hidden="true" class="fa fa-photo"></i>'],
        ['fa-picture-o', '<i aria-hidden="true" class="fa fa-picture-o"></i>'],
        ['fa-pie-chart', '<i aria-hidden="true" class="fa fa-pie-chart"></i>'],
        ['fa-plane', '<i aria-hidden="true" class="fa fa-plane"></i>'],
        ['fa-plug', '<i aria-hidden="true" class="fa fa-plug"></i>'],
        ['fa-plus', '<i aria-hidden="true" class="fa fa-plus"></i>'],
        ['fa-plus-circle', '<i aria-hidden="true" class="fa fa-plus-circle"></i>'],
        ['fa-plus-square', '<i aria-hidden="true" class="fa fa-plus-square"></i>'],
        ['fa-plus-square-o', '<i aria-hidden="true" class="fa fa-plus-square-o"></i>'],
        ['fa-podcast', '<i aria-hidden="true" class="fa fa-podcast"></i>'],
        ['fa-power-off', '<i aria-hidden="true" class="fa fa-power-off"></i>'],
        ['fa-print', '<i aria-hidden="true" class="fa fa-print"></i>'],
        ['fa-puzzle-piece', '<i aria-hidden="true" class="fa fa-puzzle-piece"></i>'],
        ['fa-qrcode', '<i aria-hidden="true" class="fa fa-qrcode"></i>'],
        ['fa-question', '<i aria-hidden="true" class="fa fa-question"></i>'],
        ['fa-question-circle', '<i aria-hidden="true" class="fa fa-question-circle"></i>'],
        ['fa-question-circle-o', '<i aria-hidden="true" class="fa fa-question-circle-o"></i>'],
        ['fa-quote-left', '<i aria-hidden="true" class="fa fa-quote-left"></i>'],
        ['fa-quote-right', '<i aria-hidden="true" class="fa fa-quote-right"></i>'],
        ['fa-random', '<i aria-hidden="true" class="fa fa-random"></i>'],
        ['fa-recycle', '<i aria-hidden="true" class="fa fa-recycle"></i>'],
        ['fa-refresh', '<i aria-hidden="true" class="fa fa-refresh"></i>'],
        ['fa-registered', '<i aria-hidden="true" class="fa fa-registered"></i>'],
        ['fa-remove', '<i aria-hidden="true" class="fa fa-remove"></i>'],
        ['fa-reorder', '<i aria-hidden="true" class="fa fa-reorder"></i>'],
        ['fa-reply', '<i aria-hidden="true" class="fa fa-reply"></i>'],
        ['fa-reply-all', '<i aria-hidden="true" class="fa fa-reply-all"></i>'],
        ['fa-retweet', '<i aria-hidden="true" class="fa fa-retweet"></i>'],
        ['fa-road', '<i aria-hidden="true" class="fa fa-road"></i>'],
        ['fa-rocket', '<i aria-hidden="true" class="fa fa-rocket"></i>'],
        ['fa-rss', '<i aria-hidden="true" class="fa fa-rss"></i>'],
        ['fa-rss-square', '<i aria-hidden="true" class="fa fa-rss-square"></i>'],
        ['fa-s15', '<i aria-hidden="true" class="fa fa-s15"></i>'],
        ['fa-search', '<i aria-hidden="true" class="fa fa-search"></i>'],
        ['fa-search-minus', '<i aria-hidden="true" class="fa fa-search-minus"></i>'],
        ['fa-search-plus', '<i aria-hidden="true" class="fa fa-search-plus"></i>'],
        ['fa-send', '<i aria-hidden="true" class="fa fa-send"></i>'],
        ['fa-send-o', '<i aria-hidden="true" class="fa fa-send-o"></i>'],
        ['fa-server', '<i aria-hidden="true" class="fa fa-server"></i>'],
        ['fa-share', '<i aria-hidden="true" class="fa fa-share"></i>'],
        ['fa-share-alt', '<i aria-hidden="true" class="fa fa-share-alt"></i>'],
        ['fa-share-alt-square', '<i aria-hidden="true" class="fa fa-share-alt-square"></i>'],
        ['fa-share-square', '<i aria-hidden="true" class="fa fa-share-square"></i>'],
        ['fa-share-square-o', '<i aria-hidden="true" class="fa fa-share-square-o"></i>'],
        ['fa-shield', '<i aria-hidden="true" class="fa fa-shield"></i>'],
        ['fa-ship', '<i aria-hidden="true" class="fa fa-ship"></i>'],
        ['fa-shopping-bag', '<i aria-hidden="true" class="fa fa-shopping-bag"></i>'],
        ['fa-shopping-basket', '<i aria-hidden="true" class="fa fa-shopping-basket"></i>'],
        ['fa-shopping-cart', '<i aria-hidden="true" class="fa fa-shopping-cart"></i>'],
        ['fa-shower', '<i aria-hidden="true" class="fa fa-shower"></i>'],
        ['fa-sign-in', '<i aria-hidden="true" class="fa fa-sign-in"></i>'],
        ['fa-sign-language', '<i aria-hidden="true" class="fa fa-sign-language"></i>'],
        ['fa-sign-out', '<i aria-hidden="true" class="fa fa-sign-out"></i>'],
        ['fa-signal', '<i aria-hidden="true" class="fa fa-signal"></i>'],
        ['fa-signing', '<i aria-hidden="true" class="fa fa-signing"></i>'],
        ['fa-sitemap', '<i aria-hidden="true" class="fa fa-sitemap"></i>'],
        ['fa-sliders', '<i aria-hidden="true" class="fa fa-sliders"></i>'],
        ['fa-smile-o', '<i aria-hidden="true" class="fa fa-smile-o"></i>'],
        ['fa-snowflake-o', '<i aria-hidden="true" class="fa fa-snowflake-o"></i>'],
        ['fa-soccer-ball-o', '<i aria-hidden="true" class="fa fa-soccer-ball-o"></i>'],
        ['fa-sort', '<i aria-hidden="true" class="fa fa-sort"></i>'],
        ['fa-sort-alpha-asc', '<i aria-hidden="true" class="fa fa-sort-alpha-asc"></i>'],
        ['fa-sort-alpha-desc', '<i aria-hidden="true" class="fa fa-sort-alpha-desc"></i>'],
        ['fa-sort-amount-asc', '<i aria-hidden="true" class="fa fa-sort-amount-asc"></i>'],
        ['fa-sort-amount-desc', '<i aria-hidden="true" class="fa fa-sort-amount-desc"></i>'],
        ['fa-sort-asc', '<i aria-hidden="true" class="fa fa-sort-asc"></i>'],
        ['fa-sort-desc', '<i aria-hidden="true" class="fa fa-sort-desc"></i>'],
        ['fa-sort-down', '<i aria-hidden="true" class="fa fa-sort-down"></i>'],
        ['fa-sort-numeric-asc', '<i aria-hidden="true" class="fa fa-sort-numeric-asc"></i>'],
        ['fa-sort-numeric-desc', '<i aria-hidden="true" class="fa fa-sort-numeric-desc"></i>'],
        ['fa-sort-up', '<i aria-hidden="true" class="fa fa-sort-up"></i>'],
        ['fa-space-shuttle', '<i aria-hidden="true" class="fa fa-space-shuttle"></i>'],
        ['fa-spinner', '<i aria-hidden="true" class="fa fa-spinner"></i>'],
        ['fa-spoon', '<i aria-hidden="true" class="fa fa-spoon"></i>'],
        ['fa-square', '<i aria-hidden="true" class="fa fa-square"></i>'],
        ['fa-square-o', '<i aria-hidden="true" class="fa fa-square-o"></i>'],
        ['fa-star', '<i aria-hidden="true" class="fa fa-star"></i>'],
        ['fa-star-half', '<i aria-hidden="true" class="fa fa-star-half"></i>'],
        ['fa-star-half-empty', '<i aria-hidden="true" class="fa fa-star-half-empty"></i>'],
        ['fa-star-half-full', '<i aria-hidden="true" class="fa fa-star-half-full"></i>'],
        ['fa-star-half-o', '<i aria-hidden="true" class="fa fa-star-half-o"></i>'],
        ['fa-star-o', '<i aria-hidden="true" class="fa fa-star-o"></i>'],
        ['fa-sticky-note', '<i aria-hidden="true" class="fa fa-sticky-note"></i>'],
        ['fa-sticky-note-o', '<i aria-hidden="true" class="fa fa-sticky-note-o"></i>'],
        ['fa-street-view', '<i aria-hidden="true" class="fa fa-street-view"></i>'],
        ['fa-suitcase', '<i aria-hidden="true" class="fa fa-suitcase"></i>'],
        ['fa-sun-o', '<i aria-hidden="true" class="fa fa-sun-o"></i>'],
        ['fa-support', '<i aria-hidden="true" class="fa fa-support"></i>'],
        ['fa-tablet', '<i aria-hidden="true" class="fa fa-tablet"></i>'],
        ['fa-tachometer', '<i aria-hidden="true" class="fa fa-tachometer"></i>'],
        ['fa-tag', '<i aria-hidden="true" class="fa fa-tag"></i>'],
        ['fa-tags', '<i aria-hidden="true" class="fa fa-tags"></i>'],
        ['fa-tasks', '<i aria-hidden="true" class="fa fa-tasks"></i>'],
        ['fa-taxi', '<i aria-hidden="true" class="fa fa-taxi"></i>'],
        ['fa-television', '<i aria-hidden="true" class="fa fa-television"></i>'],
        ['fa-terminal', '<i aria-hidden="true" class="fa fa-terminal"></i>'],
        ['fa-thermometer', '<i aria-hidden="true" class="fa fa-thermometer"></i>'],
        ['fa-thermometer-0', '<i aria-hidden="true" class="fa fa-thermometer-0"></i>'],
        ['fa-thermometer-1', '<i aria-hidden="true" class="fa fa-thermometer-1"></i>'],
        ['fa-thermometer-2', '<i aria-hidden="true" class="fa fa-thermometer-2"></i>'],
        ['fa-thermometer-3', '<i aria-hidden="true" class="fa fa-thermometer-3"></i>'],
        ['fa-thermometer-4', '<i aria-hidden="true" class="fa fa-thermometer-4"></i>'],
        ['fa-thermometer-empty', '<i aria-hidden="true" class="fa fa-thermometer-empty"></i>'],
        ['fa-thermometer-full', '<i aria-hidden="true" class="fa fa-thermometer-full"></i>'],
        ['fa-thermometer-half', '<i aria-hidden="true" class="fa fa-thermometer-half"></i>'],
        ['fa-thermometer-quarter', '<i aria-hidden="true" class="fa fa-thermometer-quarter"></i>'],
        ['fa-thermometer-three-quarters', '<i aria-hidden="true" class="fa fa-thermometer-three-quarters"></i>'],
        ['fa-thumb-tack', '<i aria-hidden="true" class="fa fa-thumb-tack"></i>'],
        ['fa-thumbs-down', '<i aria-hidden="true" class="fa fa-thumbs-down"></i>'],
        ['fa-thumbs-o-down', '<i aria-hidden="true" class="fa fa-thumbs-o-down"></i>'],
        ['fa-thumbs-o-up', '<i aria-hidden="true" class="fa fa-thumbs-o-up"></i>'],
        ['fa-thumbs-up', '<i aria-hidden="true" class="fa fa-thumbs-up"></i>'],
        ['fa-ticket', '<i aria-hidden="true" class="fa fa-ticket"></i>'],
        ['fa-times', '<i aria-hidden="true" class="fa fa-times"></i>'],
        ['fa-times-circle', '<i aria-hidden="true" class="fa fa-times-circle"></i>'],
        ['fa-times-circle-o', '<i aria-hidden="true" class="fa fa-times-circle-o"></i>'],
        ['fa-times-rectangle', '<i aria-hidden="true" class="fa fa-times-rectangle"></i>'],
        ['fa-times-rectangle-o', '<i aria-hidden="true" class="fa fa-times-rectangle-o"></i>'],
        ['fa-tint', '<i aria-hidden="true" class="fa fa-tint"></i>'],
        ['fa-toggle-down', '<i aria-hidden="true" class="fa fa-toggle-down"></i>'],
        ['fa-toggle-left', '<i aria-hidden="true" class="fa fa-toggle-left"></i>'],
        ['fa-toggle-off', '<i aria-hidden="true" class="fa fa-toggle-off"></i>'],
        ['fa-toggle-on', '<i aria-hidden="true" class="fa fa-toggle-on"></i>'],
        ['fa-toggle-right', '<i aria-hidden="true" class="fa fa-toggle-right"></i>'],
        ['fa-toggle-up', '<i aria-hidden="true" class="fa fa-toggle-up"></i>'],
        ['fa-trademark', '<i aria-hidden="true" class="fa fa-trademark"></i>'],
        ['fa-trash', '<i aria-hidden="true" class="fa fa-trash"></i>'],
        ['fa-trash-o', '<i aria-hidden="true" class="fa fa-trash-o"></i>'],
        ['fa-tree', '<i aria-hidden="true" class="fa fa-tree"></i>'],
        ['fa-trophy', '<i aria-hidden="true" class="fa fa-trophy"></i>'],
        ['fa-truck', '<i aria-hidden="true" class="fa fa-truck"></i>'],
        ['fa-tty', '<i aria-hidden="true" class="fa fa-tty"></i>'],
        ['fa-tv', '<i aria-hidden="true" class="fa fa-tv"></i>'],
        ['fa-umbrella', '<i aria-hidden="true" class="fa fa-umbrella"></i>'],
        ['fa-universal-access', '<i aria-hidden="true" class="fa fa-universal-access"></i>'],
        ['fa-university', '<i aria-hidden="true" class="fa fa-university"></i>'],
        ['fa-unlock', '<i aria-hidden="true" class="fa fa-unlock"></i>'],
        ['fa-unlock-alt', '<i aria-hidden="true" class="fa fa-unlock-alt"></i>'],
        ['fa-unsorted', '<i aria-hidden="true" class="fa fa-unsorted"></i>'],
        ['fa-upload', '<i aria-hidden="true" class="fa fa-upload"></i>'],
        ['fa-user', '<i aria-hidden="true" class="fa fa-user"></i>'],
        ['fa-user-circle', '<i aria-hidden="true" class="fa fa-user-circle"></i>'],
        ['fa-user-circle-o', '<i aria-hidden="true" class="fa fa-user-circle-o"></i>'],
        ['fa-user-o', '<i aria-hidden="true" class="fa fa-user-o"></i>'],
        ['fa-user-plus', '<i aria-hidden="true" class="fa fa-user-plus"></i>'],
        ['fa-user-secret', '<i aria-hidden="true" class="fa fa-user-secret"></i>'],
        ['fa-user-times', '<i aria-hidden="true" class="fa fa-user-times"></i>'],
        ['fa-users', '<i aria-hidden="true" class="fa fa-users"></i>'],
        ['fa-vcard', '<i aria-hidden="true" class="fa fa-vcard"></i>'],
        ['fa-vcard-o', '<i aria-hidden="true" class="fa fa-vcard-o"></i>'],
        ['fa-video-camera', '<i aria-hidden="true" class="fa fa-video-camera"></i>'],
        ['fa-volume-control-phone', '<i aria-hidden="true" class="fa fa-volume-control-phone"></i>'],
        ['fa-volume-down', '<i aria-hidden="true" class="fa fa-volume-down"></i>'],
        ['fa-volume-off', '<i aria-hidden="true" class="fa fa-volume-off"></i>'],
        ['fa-volume-up', '<i aria-hidden="true" class="fa fa-volume-up"></i>'],
        ['fa-warning', '<i aria-hidden="true" class="fa fa-warning"></i>'],
        ['fa-wheelchair', '<i aria-hidden="true" class="fa fa-wheelchair"></i>'],
        ['fa-wheelchair-alt', '<i aria-hidden="true" class="fa fa-wheelchair-alt"></i>'],
        ['fa-wifi', '<i aria-hidden="true" class="fa fa-wifi"></i>'],
        ['fa-window-close', '<i aria-hidden="true" class="fa fa-window-close"></i>'],
        ['fa-window-close-o', '<i aria-hidden="true" class="fa fa-window-close-o"></i>'],
        ['fa-window-maximize', '<i aria-hidden="true" class="fa fa-window-maximize"></i>'],
        ['fa-window-minimize', '<i aria-hidden="true" class="fa fa-window-minimize"></i>'],
        ['fa-window-restore', '<i aria-hidden="true" class="fa fa-window-restore"></i>'],
        ['fa-wrench', '<i aria-hidden="true" class="fa fa-wrench"></i>']
    ]]
    
    
    # 菜单的Form
    class MenuForm(forms.ModelForm):
        class Meta:
            model = models.Menu
            fields = ['title', 'weight', 'icon', ]
            
            widgets = {
                'title': forms.widgets.Input(attrs={"class": 'form-control'}),
                'weight': forms.widgets.Input(attrs={"class": 'form-control'}),
                'icon': forms.widgets.RadioSelect(choices=ICON_LIST),
            }
    
    
    class PermissionForm(forms.ModelForm):
        class Meta:
            model = models.Permission
            # fields = '__all__'
            fields = ['title', 'url', 'name', 'parent', 'menu']
        
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for field in self.fields.values():
                field.widget.attrs.update({'class': 'form-control'})
    
    
    # 批量权限使用的form
    class MultiPermissionForm(forms.ModelForm):
        class Meta:
            model = models.Permission
            fields = ['title', 'url', 'name', 'parent', 'menu']
        
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for field in self.fields:
                self.fields[field].widget.attrs.update({"class": "form-control"})
            self.fields['parent'].choices = [(None, '-------')] + list(
                models.Permission.objects.filter(parent__isnull=True).exclude(
                    menu__isnull=True).values_list('id', 'title'))
        
        def clean(self):
            menu = self.cleaned_data.get('menu')
            pid = self.cleaned_data.get('parent')
            
            if menu and pid:
                raise forms.ValidationError('菜单和根权限同时只能选择一个')
            return self.cleaned_data
    forms.py
    from django.shortcuts import render, HttpResponse, redirect, reverse
    from rbac import models
    from rbac.forms import *
    from django.db.models import Q
    from rbac.server.routes import get_all_url_dict
    
    
    def role_list(request):
        all_roles = models.Role.objects.all()
        return render(request, 'rbac/role_list.html', {"all_roles": all_roles})
    
    
    def role(request, edit_id=None):
        obj = models.Role.objects.filter(id=edit_id).first()
        form_obj = RoleForm(instance=obj)
        if request.method == 'POST':
            form_obj = RoleForm(request.POST, instance=obj)
            if form_obj.is_valid():
                form_obj.save()
                return redirect(reverse('rbac:role_list'))
        
        return render(request, 'rbac/form.html', {'form_obj': form_obj})
    
    
    def del_role(request, del_id):
        models.Role.objects.filter(id=del_id).delete()
        return redirect(reverse('rbac:role_list'))
    
    
    # 菜单信息  权限信息
    def menu_list(request):
        all_menu = models.Menu.objects.all()
        
        mid = request.GET.get('mid')
        
        if mid:
            permission_query = models.Permission.objects.filter(Q(menu_id=mid) | Q(parent__menu_id=mid))
        else:
            permission_query = models.Permission.objects.all()
        
        all_permission = permission_query.values('id', 'url', 'title', 'name', 'menu_id', 'parent_id', 'menu__title')
        
        all_permission_dict = {}
        
        for item in all_permission:
            menu_id = item.get('menu_id')
            if menu_id:
                item['children'] = []
                all_permission_dict[item['id']] = item
        
        for item in all_permission:
            pid = item.get('parent_id')
            
            if pid:
                all_permission_dict[pid]['children'].append(item)
        
        print(all_permission_dict)
        
        return render(request, 'rbac/menu_list.html',
                      {"all_menu": all_menu, 'all_permission_dict': all_permission_dict, 'mid': mid})
    
    
    def menu(request, edit_id=None):
        obj = models.Menu.objects.filter(id=edit_id).first()
        form_obj = MenuForm(instance=obj)
        if request.method == 'POST':
            form_obj = MenuForm(request.POST, instance=obj)
            if form_obj.is_valid():
                form_obj.save()
                return redirect(reverse('rbac:menu_list'))
        
        return render(request, 'rbac/form.html', {'form_obj': form_obj})
    
    
    def permission(request, edit_id=None):
        obj = models.Permission.objects.filter(id=edit_id).first()
        form_obj = PermissionForm(instance=obj)
        if request.method == 'POST':
            form_obj = PermissionForm(request.POST, instance=obj)
            if form_obj.is_valid():
                form_obj.save()
                return redirect(reverse('rbac:menu_list'))
        
        return render(request, 'rbac/form.html', {'form_obj': form_obj})
    
    
    def del_permission(request, del_id):
        models.Permission.objects.filter(id=del_id).delete()
        return redirect(reverse('rbac:menu_list'))
    
    
    from django.forms import modelformset_factory, formset_factory
    
    
    def multi_permissions(request):
        """
        批量操作权限
        :param request:
        :return:
        """
        
        post_type = request.GET.get('type')
        
        # 更新和编辑用的
        FormSet = modelformset_factory(models.Permission, MultiPermissionForm, extra=0)
        # 增加用的
        AddFormSet = formset_factory(MultiPermissionForm, extra=0)
        
        permissions = models.Permission.objects.all()
        
        # 获取路由系统中所有URL
        router_dict = get_all_url_dict(ignore_namespace_list=['admin', 'rbac'])
        
        # 数据库中的所有权限的别名
        permissions_name_set = set([i.name for i in permissions])
        
        # 路由系统中的所有权限的别名
        router_name_set = set(router_dict.keys())
        
        if request.method == 'POST' and post_type == 'add':
            add_formset = AddFormSet(request.POST)
            if add_formset.is_valid():
                print(add_formset.cleaned_data)
                permission_obj_list = [models.Permission(**i) for i in add_formset.cleaned_data]
                
                query_list = models.Permission.objects.bulk_create(permission_obj_list)
                
                for i in query_list:
                    permissions_name_set.add(i.name)
        
        add_name_set = router_name_set - permissions_name_set
        add_formset = AddFormSet(initial=[row for name, row in router_dict.items() if name in add_name_set])
        
        del_name_set = permissions_name_set - router_name_set
        del_formset = FormSet(queryset=models.Permission.objects.filter(name__in=del_name_set))
        
        update_name_set = permissions_name_set & router_name_set
        update_formset = FormSet(queryset=models.Permission.objects.filter(name__in=update_name_set))
        
        if request.method == 'POST' and post_type == 'update':
            update_formset = FormSet(request.POST)
            if update_formset.is_valid():
                update_formset.save()
                update_formset = FormSet(queryset=models.Permission.objects.filter(name__in=update_name_set))
        
        return render(
            request,
            'rbac/multi_permissions.html',
            {
                'del_formset': del_formset,
                'update_formset': update_formset,
                'add_formset': add_formset,
            }
        )
    
    
    def distribute_permissions(request):
        """
        分配权限
        :param request:
        :return:
        """
        uid = request.GET.get('uid')
        rid = request.GET.get('rid')
        
        if request.method == 'POST' and request.POST.get('postType') == 'role':
            user = models.User.objects.filter(id=uid).first()
            if not user:
                return HttpResponse('用户不存在')
            user.roles.set(request.POST.getlist('roles'))
        
        if request.method == 'POST' and request.POST.get('postType') == 'permission' and rid:
            role = models.Role.objects.filter(id=rid).first()
            if not role:
                return HttpResponse('角色不存在')
            role.permissions.set(request.POST.getlist('permissions'))
        
        # 所有用户
        user_list = models.User.objects.all()
        
        user_has_roles = models.User.objects.filter(id=uid).values('id', 'roles')
        
        # print(user_has_roles)
        
        user_has_roles_dict = {item['roles']: None for item in user_has_roles}
        
        """
        用户拥有的角色id
        user_has_roles_dict = { 角色id:None }
        """
        
        role_list = models.Role.objects.all()
        
        if rid:
            role_has_permissions = models.Role.objects.filter(id=rid).values('id', 'permissions')
        elif uid and not rid:
            user = models.User.objects.filter(id=uid).first()
            if not user:
                return HttpResponse('用户不存在')
            role_has_permissions = user.roles.values('id', 'permissions')
        else:
            role_has_permissions = []
        
        print(role_has_permissions)
        
        role_has_permissions_dict = {item['permissions']: None for item in role_has_permissions}
        
        """
        角色拥有的权限id
        role_has_permissions_dict = { 权限id:None }
        """
        
        all_menu_list = []
        
        queryset = models.Menu.objects.values('id', 'title')
        menu_dict = {}
        
        """
        
        all_menu_list = [
                {  id:   title :  , children : [
                    { 'id', 'title', 'menu_id', 'children: [
                    'id', 'title', 'parent_id'
                    ]  }
                ] },
                {'id': None, 'title': '其他', 'children': [
                {'id', 'title', 'parent_id'}]}
        ]
        
        menu_dict = {
            菜单的ID: {  id:   title :  , children : [
                { 'id', 'title', 'menu_id', 'children: [
                'id', 'title', 'parent_id'
                ]  }
            ] },
            none:{'id': None, 'title': '其他', 'children': [
            {'id', 'title', 'parent_id'}]}
        }
        """
        
        for item in queryset:
            item['children'] = []  # 放二级菜单,父权限
            menu_dict[item['id']] = item
            all_menu_list.append(item)
        
        other = {'id': None, 'title': '其他', 'children': []}
        all_menu_list.append(other)
        menu_dict[None] = other
        
        root_permission = models.Permission.objects.filter(menu__isnull=False).values('id', 'title', 'menu_id')
        
        root_permission_dict = {}
        
        """
        root_permission_dict = { 父权限的id : { 'id', 'title', 'menu_id', 'children: [
            { 'id', 'title', 'parent_id' }
        ]  }}
        """
        
        for per in root_permission:
            per['children'] = []  # 放子权限
            nid = per['id']
            menu_id = per['menu_id']
            root_permission_dict[nid] = per
            menu_dict[menu_id]['children'].append(per)
        
        node_permission = models.Permission.objects.filter(menu__isnull=True).values('id', 'title', 'parent_id')
        
        for per in node_permission:
            pid = per['parent_id']
            if not pid:
                menu_dict[None]['children'].append(per)
                continue
            root_permission_dict[pid]['children'].append(per)
        
        return render(
            request,
            'rbac/distribute_permissions.html',
            {
                'user_list': user_list,
                'role_list': role_list,
                'user_has_roles_dict': user_has_roles_dict,
                'role_has_permissions_dict': role_has_permissions_dict,
                'all_menu_list': all_menu_list,
                'uid': uid,
                'rid': rid
            }
        )
    views.py

    权限的初始化

    from django.conf import settings
    
    
    def init_permission(request, user):
        # 1. 查当前登录用户拥有的权限
        permission_query = user.roles.filter(permissions__url__isnull=False).values(
            'permissions__url',
            'permissions__title',
            'permissions__id',
            'permissions__name',
            'permissions__parent_id',
            'permissions__parent__name',
            'permissions__menu_id',
            'permissions__menu__title',
            'permissions__menu__icon',
            'permissions__menu__weight',
        ).distinct()
        
        # 存放权限信息
        permission_dict = {}
        
        # 存放菜单信息
        
        menu_dict = {}
        
        for item in permission_query:
            permission_dict[item['permissions__name']] = {'url': item['permissions__url'],
                                                          'id': item['permissions__id'],
                                                          'pid': item['permissions__parent_id'],
                                                          'pname': item['permissions__parent__name'],
                                                          'title': item['permissions__title']}
            
            menu_id = item.get('permissions__menu_id')
            
            if not menu_id:
                continue
            
            if menu_id not in menu_dict:
                menu_dict[menu_id] = {
                    'title': item['permissions__menu__title'],
                    'icon': item['permissions__menu__icon'],
                    'weight': item['permissions__menu__weight'],
                    'children': [
                        {'title': item['permissions__title'], 'url': item['permissions__url'],
                         'id': item['permissions__id'], 'pid': item['permissions__parent_id']}
                    ]
                }
            else:
                menu_dict[menu_id]['children'].append(
                    {'title': item['permissions__title'], 'url': item['permissions__url'], 'id': item['permissions__id'],
                     'pid': item['permissions__parent_id']})
        
        # # 2. 将权限信息写入到session
        request.session[settings.PERMISSION_SESSION_KEY] = permission_dict
        
        # 将菜单信息写入到session
        request.session[settings.MENU_SESSION_KEY] = menu_dict
    init_permission.py
    from django.conf import settings
    from django.utils.module_loading import import_string
    from django.urls import RegexURLResolver, RegexURLPattern
    from collections import OrderedDict
    
    
    def recursion_urls(pre_namespace, pre_url, urlpatterns, url_ordered_dict):
        for item in urlpatterns:
            if isinstance(item, RegexURLResolver):
                if pre_namespace:
                    if item.namespace:
                        namespace = "%s:%s" % (pre_namespace, item.namespace,)
                    else:
                        namespace = pre_namespace
                else:
                    if item.namespace:
                        namespace = item.namespace
                    else:
                        namespace = None
                recursion_urls(namespace, pre_url + item.regex.pattern, item.url_patterns, url_ordered_dict)
            else:
    
                if pre_namespace:
                    name = "%s:%s" % (pre_namespace, item.name,)
                else:
                    name = item.name
                if not item.name:
                    raise Exception('URL路由中必须设置name属性')
    
                url = pre_url + item._regex
                url_ordered_dict[name] = {'name': name, 'url': url.replace('^', '').replace('$', '')}
    
    
    def get_all_url_dict(ignore_namespace_list=None):
        """
        获取路由中
        :return:
        """
        ignore_list = ignore_namespace_list or []
        url_ordered_dict = OrderedDict()
    
        md = import_string(settings.ROOT_URLCONF)
        urlpatterns = []
    
        for item in md.urlpatterns:
            if isinstance(item, RegexURLResolver) and item.namespace in ignore_list:
                continue
            urlpatterns.append(item)
        recursion_urls(None, "/", urlpatterns, url_ordered_dict)
        return url_ordered_dict
    routes.py

    中间件的校验

    from django.utils.deprecation import MiddlewareMixin
    from django.conf import settings
    from django.shortcuts import HttpResponse
    import re
    
    
    class PermissionMiddleware(MiddlewareMixin):
        def process_request(self, request):
            # 对权限进行校验
            # 1. 当前访问的URL
            current_url = request.path_info
            
            # 白名单的判断
            for i in settings.WHITE_URL_LIST:
                if re.match(i, current_url):
                    return
            
            # 2. 获取当前用户的所有权限信息
            
            permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY)
            
            request.breadcrumb_list = [
                {"title": '首页', 'url': '#'},
            ]
            
            # 3. 权限的校验
            print(permission_dict)
            for item in permission_dict.values():
                url = item['url']
                if re.match("^{}$".format(url), current_url):
                    pid = item['pid']
                    id = item['id']
                    pname = item['pname']
                    if pid:
                        # 表示当前权限是子权限,让父权限是展开
                        request.current_menu_id = pid
                        request.breadcrumb_list.extend(
                            [{"title": permission_dict[pname]['title'], 'url': permission_dict[pname]['url']},
                             {"title": item['title'], 'url': item['url']}]
                        )
                    
                    else:
                        # 表示当前权限是父权限,要展开的二级菜单
                        request.current_menu_id = id
                        # 添加面包屑导航
                        request.breadcrumb_list.append({"title": item['title'], 'url': item['url']})
                    
                    return
            
            else:
                return HttpResponse('没有权限')
    middlewares bac.py

    自定义inclusion_tag,simple_tag,filter

    from django import template
    
    register = template.Library()
    
    from django.conf import settings
    import re
    from collections import OrderedDict
    
    
    @register.inclusion_tag('rbac/menu.html')
    def menu(request):
        menu_dict = request.session.get(settings.MENU_SESSION_KEY)
        
        order_dict = OrderedDict()
        
        for key in sorted(menu_dict, key=lambda x: menu_dict[x]['weight'], reverse=True):
            order_dict[key] = menu_dict[key]
            
            item = order_dict[key]
            
            item['class'] = 'hide'
            
            for i in item['children']:
                
                if i['id'] == request.current_menu_id:
                    i['class'] = 'active'
                    item['class'] = ''
        
        return {"menu_list": order_dict}
    
    
    @register.inclusion_tag('rbac/breadcrumb.html')
    def breadcrumb(request):
        return {'breadcrumb_list': request.breadcrumb_list}
    
    
    @register.filter
    def has_permission(request, permission):
        if permission in request.session.get(settings.PERMISSION_SESSION_KEY):
            return True
    
    
    @register.simple_tag
    def gen_role_url(request, rid):
        params = request.GET.copy()
        params._mutable = True
        params['rid'] = rid
        return params.urlencode()
    templatetags/rbac.py

    templates

    <ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">
    
        {% for li in breadcrumb_list %}
            {% if forloop.last %}
                <li>{{ li.title }}</li>
            {% else %}
                <li><a href="{{ li.url }}">{{ li.title }}</a></li>
            {% endif %}
    
        {% endfor %}
    
    </ol>
    breadcrumb.html
    {% extends 'layout.html' %}
    {% load rbac %}
    {% block css %}
        <style>
            .user-area ul {
                padding-left: 20px;
            }
    
            .user-area li {
                cursor: pointer;
                padding: 2px 0;
            }
    
            .user-area li a {
                display: block;
            }
    
            .user-area li.active {
                font-weight: bold;
                color: red;
            }
    
            .user-area li.active a {
                color: red;
            }
    
            .role-area tr td a {
                display: block;
            }
    
            .role-area tr.active {
                background-color: #f1f7fd;
                border-left: 3px solid #fdc00f;
            }
    
            .permission-area tr.root {
                background-color: #f1f7fd;
                cursor: pointer;
            }
    
            .permission-area tr.root td i {
                margin: 3px;
            }
    
            .permission-area .node {
    
            }
    
            .permission-area .node input[type='checkbox'] {
                margin: 0 5px;
            }
    
            .permission-area .node .parent {
                padding: 5px 0;
            }
    
            .permission-area .node label {
                font-weight: normal;
                margin-bottom: 0;
                font-size: 12px;
            }
    
            .permission-area .node .children {
                padding: 0 0 0 20px;
            }
    
            .permission-area .node .children .child {
                display: inline-block;
                margin: 2px 5px;
            }
    
            table {
                font-size: 12px;
            }
    
            .panel-body {
                font-size: 12px;
            }
    
            .panel-body .form-control {
                font-size: 12px;
            }
        </style>
    {% endblock %}
    
    {% block content %}
        <div class="luffy-container">
            <div class="col-md-3 user-area">
                <div class="panel panel-default">
                    <!-- Default panel contents -->
                    <div class="panel-heading">
                        <i class="fa fa-address-book-o" aria-hidden="true"></i> 用户信息
                    </div>
    
                    <div class="panel-body">
                        <ul>
                            {% for user in user_list %}
    
                                <li class= {% if user.id|safe == uid %} "active" {% endif %}>
                                    <a href="?uid={{ user.id }}">{{ user.name }}</a></li>
    
                            {% endfor %}
                        </ul>
                    </div>
    
                </div>
            </div>
    
            <div class="col-md-3 role-area">
                <form method="post">
                    {% csrf_token %}
                    <input type="hidden" name="postType" value="role">
                    <div class="panel panel-default">
                        <!-- Default panel contents -->
                        <div class="panel-heading">
                            <i class="fa fa-book" aria-hidden="true"></i> 角色
                            {% if uid %}
                                <button type="submit" class="right btn btn-success btn-xs"
                                        style="padding: 2px 8px;margin: -3px;">
                                    <i class="fa fa-save" aria-hidden="true"></i>
                                    保存
                                </button>
                            {% endif %}
                        </div>
                        <div class="panel-body" style="color: #d4d4d4;padding:10px  5px;">
                            提示:点击用户后才能为其分配角色
                        </div>
                        <table class="table">
                            <thead>
                            <tr>
                                <th>角色</th>
                                <th>选择</th>
                            </tr>
                            </thead>
                            <tbody>
                            {% for role in role_list %}
                                <tr {% if role.id|safe == rid %} class="active"  {% endif %}>
    
                                    <td><a href="?{% gen_role_url request role.id %}">{{ role.name }}</a></td>
                                    <td>
                                        {% if role.id in user_has_roles_dict %}
                                            <input type="checkbox" name="roles" value="{{ role.id }}" checked/>
                                        {% else %}
                                            <input type="checkbox" name="roles" value="{{ role.id }}"/>
                                        {% endif %}
                                    </td>
                                </tr>
                            {% endfor %}
    
                            </tbody>
                        </table>
    
                    </div>
                </form>
            </div>
    
            <div class="col-md-6 permission-area">
                <form method="post">
                    {% csrf_token %}
                    <input type="hidden" name="postType" value="permission">
                    <div class="panel panel-default">
                        <!-- Default panel contents -->
                        <div class="panel-heading">
                            <i class="fa fa-sitemap" aria-hidden="true"></i> 权限分配
                            {% if rid %}
                                <button class="right btn btn-success btn-xs" style="padding: 2px 8px;margin: -3px;">
                                    <i class="fa fa-save" aria-hidden="true"></i>
                                    保存
                                </button>
                            {% endif %}
                        </div>
                        <div class="panel-body" style="color: #d4d4d4;padding: 10px 5px;">
                            提示:点击角色后,才能为其分配权限。
                        </div>
                        <table class="table">
                            <tbody>
                            {% for item in all_menu_list %}
                                <tr class="root">
                                    <td><i class="fa fa-caret-down" aria-hidden="true"></i>{{ item.title }}</td>
                                </tr>
                                <tr class="node">
                                    <td>
                                        {% for node in item.children %}
                                            <div class="parent">
                                                {% if node.id in role_has_permissions_dict %}
                                                    <input id="permission_{{ node.id }}" name="permissions"
                                                           value="{{ node.id }}" type="checkbox" checked>
                                                {% else %}
                                                    <input id="permission_{{ node.id }}" name="permissions"
                                                           value="{{ node.id }}" type="checkbox">
                                                {% endif %}
    
    
                                                <label for="permission_{{ node.id }}">{{ node.title }}</label>
    
                                            </div>
                                            <div class="children">
                                                {% for child in node.children %}
                                                    <div class="child">
                                                        {% if child.id in role_has_permissions_dict %}
                                                            <input id="permission_{{ child.id }}" name="permissions"
                                                                   type="checkbox" value="{{ child.id }}" checked>
                                                        {% else %}
                                                            <input id="permission_{{ child.id }}" name="permissions"
                                                                   type="checkbox" value="{{ child.id }}">
                                                        {% endif %}
    
                                                        <label for="permission_{{ child.id }}">{{ child.title }}</label>
                                                    </div>
                                                {% endfor %}
                                            </div>
                                        {% endfor %}
                                    </td>
                                </tr>
                            {% endfor %}
                            </tbody>
                        </table>
                    </div>
                </form>
            </div>
    
        </div>
    {% endblock %}
    {% block js %}
        <script>
            $(function () {
                bindRootPermissionClick();
            });
    
            function bindRootPermissionClick() {
                $('.permission-area').on('click', '.root', function () {
                    var caret = $(this).find('i');
                    if (caret.hasClass('fa-caret-right')) {
                        caret.removeClass('fa-caret-right').addClass('fa-caret-down');
                        $(this).next().removeClass('hide');
                    } else {
                        caret.removeClass('fa-caret-down').addClass('fa-caret-right');
                        $(this).next().addClass('hide');
    
                    }
                })
            }
        </script>
    {% endblock %}
    distribute_permissions.html
    {% extends 'layout.html' %}
    
    
    {% block css %}
        <style>
            ul {
                list-style-type: none;
                padding: 0;
            }
    
            ul li {
                float: left;
                padding: 10px;
                padding-left: 0;
                 80px;
            }
    
            ul li i {
                font-size: 18px;
                margin-left: 5px;
                color: #6d6565;
            }
    
        </style>
    {% endblock %}
    
    {% block content %}
    
        <form class="form-horizontal" novalidate method="post" action="" style="margin-top: 50px; 95%">
            {% csrf_token %}
    
            {% for field in form_obj %}
    
                <div class="form-group {% if field.errors %}has-error{% endif %} ">
                    <label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
                    <div class="col-sm-6">
                        {{ field }}
                    </div>
                    <span class="help-block">{{ field.errors.0 }}</span>
                </div>
            {% endfor %}
    
    
            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-6">
                    <button type="submit" class="btn btn-default">提交</button>
                </div>
            </div>
        </form>
    {% endblock %}
    form.html
    {% extends 'layout.html' %}
    
    
    {% block css %}
        <style>
            .permission-area tr.parent {
                background-color: #cae7fd;;
            }
    
            .menu-body tr.active {
                background-color: #f1f7fd;
                border-left: 3px solid #fdc00f;
            }
    
        </style>
    {% endblock %}
    
    {% block content %}
        <div style="margin: 20px">
            <div class="col-sm-3">
                <div class="panel panel-default">
                    <!-- Default panel contents -->
                    <div class="panel-heading"><i class="fa fa-book"></i> 菜单管理
                        <a href="{% url 'rbac:menu_add' %}" class="btn btn-success btn-sm pull-right "
                           style="padding: 2px 8px;margin: -3px;"> <i
                                class="fa fa-plus"></i> 新建</a>
                    </div>
    
                    <!-- Table -->
                    <table class="table">
                        <thead>
                        <tr>
                            <th>名称</th>
                            <th>图标</th>
                            <th>操作</th>
                        </tr>
                        </thead>
                        <tbody class="menu-body">
                        {% for menu in all_menu %}
                            <tr class=" {% if menu.id|safe == mid %} active {% endif %} ">
                                <td><a href="?mid={{ menu.id }}">{{ menu.title }}</a></td>
                                <td><i class="fa {{ menu.icon }} "></i></td>
                                <td>
                                    <a href="{% url 'rbac:menu_edit' menu.id %}"> <i class="fa fa-edit"></i> </a>
                                    <a href=""> <i class="fa fa-trash-o"></i> </a>
                                </td>
                            </tr>
    
                        {% endfor %}
    
                        </tbody>
    
                    </table>
                </div>
            </div>
            <div class="col-sm-9">
                <div class="panel panel-default">
                    <!-- Default panel contents -->
                    <div class="panel-heading"><i class="fa fa-cubes"></i> 权限管理
                        <a href="{% url 'rbac:multi_permissions' %}" class="btn btn-primary btn-sm pull-right "
                           style="padding: 2px 8px;margin: -3px;"> <i class="fa fa-scissors"></i> 批量操作</a>
                        <a href="{% url 'rbac:permission_add' %}" class="btn btn-success btn-sm pull-right "
                           style="padding: 2px 8px;margin: -3px;"> <i class="fa fa-plus"></i> 新建</a>
    
    
                    </div>
    
                    <!-- Table -->
                    <table class="table">
                        <thead>
                        <tr>
                            <th>名称</th>
                            <th>URL</th>
                            <th>URL别名</th>
                            <th>菜单</th>
                            <th>所属菜单</th>
                            <th>操作</th>
                        </tr>
                        </thead>
                        <tbody class="permission-area">
                        {% for p_permission in all_permission_dict.values %}
                            <tr class="parent" id="{{ p_permission.id }}">
                                <td class="title">
                                    <i class="fa fa-caret-down"></i>
                                    {{ p_permission.title }}
                                </td>
                                <td>{{ p_permission.url }}</td>
                                <td>{{ p_permission.name }}</td>
                                <td></td>
                                <td>
                                    {{ p_permission.menu__title }}
                                </td>
    
                                <td>
                                    <a href="{% url 'rbac:permission_edit' p_permission.id %}"> <i class="fa fa-edit"></i>
                                    </a>
                                    <a href="{% url 'rbac:permission_del' p_permission.id %}"> <i class="fa fa-trash-o"></i>
                                    </a>
                                </td>
                            </tr>
                            {% for c_permission in p_permission.children %}
                                <tr pid="{{ c_permission.parent_id }}">
                                    <td>{{ c_permission.title }}</td>
                                    <td>{{ c_permission.url }}</td>
                                    <td>{{ c_permission.name }}</td>
                                    <td></td>
                                    <td></td>
    
                                    <td>
                                        <a href="{% url 'rbac:permission_edit' c_permission.id %}"> <i
                                                class="fa fa-edit"></i> </a>
                                        <a href="{% url 'rbac:permission_del' c_permission.id %}"> <i
                                                class="fa fa-trash-o"></i> </a>
                                    </td>
                                </tr>
                            {% endfor %}
    
    
                        {% endfor %}
    
                        </tbody>
    
                    </table>
                </div>
            </div>
        </div>
    
    
    {% endblock %}
    
    {% block js %}
        <script>
    
    
            $('.permission-area').on('click', '.parent .title', function () {
                var caret = $(this).find('i');
                var id = $(this).parent().attr('id');
                if (caret.hasClass('fa-caret-right')) {
                    caret.removeClass('fa-caret-right').addClass('fa-caret-down');
                    $(this).parent().nextAll('tr[pid="' + id + '"]').removeClass('hide');
                } else {
                    caret.removeClass('fa-caret-down').addClass('fa-caret-right');
                    $(this).parent().nextAll('tr[pid="' + id + '"]').addClass('hide');
    
                }
            })
    
    
        </script>
    {% endblock %}
    menu_list.html
    {% extends 'layout.html' %}
    
    {% block content %}
    
        <div style="margin: 20px">
            <h1>角色管理</h1>
    
            <a href="{% url "rbac:role_add" %}" class="btn  btn-success">添加</a>
    
            <table class="table table-bordered table-hover" style="margin-top: 5px">
                <thead>
                <tr>
                    <th>序号</th>
                    <th>名称</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                {% for role in all_roles %}
                    <tr>
                        <td>{{ forloop.counter }}</td>
                        <td>{{ role.name }}</td>
                        <td>
                            <a href="{% url 'rbac:role_edit' role.id %}"> <i class="fa fa-edit"></i> </a>
                            <a href="{% url 'rbac:role_del' role.id %}"> <i class="fa fa-trash-o"></i> </a>
                        </td>
                    </tr>
    
                {% endfor %}
    
                </tbody>
            </table>
        </div>
    
    
    {% endblock %}
    role_list.html
    {% extends 'layout.html' %}
    
    {% block content %}
        <div class="luffy-container">
            <form method="post" action="?type=add">
                {% csrf_token %}
                {{ add_formset.management_form }}
                <div class="panel panel-default">
                    <!-- Default panel contents -->
                    <div class="panel-heading">
                        <i class="fa fa-binoculars" aria-hidden="true"></i> 待新建权限列表
                        <button class="right btn btn-primary btn-xs" style="padding: 2px 8px;margin: -3px;">
                            <i class="fa fa-save" aria-hidden="true"></i>
                            新建
                        </button>
                    </div>
                    <div class="panel-body" style="color: #9d9d9d;">
                        注意:路由系统中自动发现且数据库中不存在的路由。
                    </div>
    
                    <table class="table table-bordered">
                        <thead>
                        <tr>
                            <th>序号</th>
                            <th>名称</th>
                            <th>URL</th>
                            <th>别名</th>
                            <th>所属菜单</th>
                            <th>根权限</th>
                        </tr>
                        </thead>
                        <tbody>
                        {% for form in add_formset %}
    
                            <tr>
                                <td style="vertical-align: middle;">{{ forloop.counter }}</td>
                                <td>{{ form.title }} <span>{{ form.title.errors.0 }}</span></td>
                                <td>{{ form.url }}</td>
                                <td>{{ form.name }}</td>
                                <td>{{ form.parent }}</td>
                                <td>{{ form.menu }}</td>
    
                            </tr>
                        {% endfor %}
                        </tbody>
                    </table>
                </div>
            </form>
    
            <div class="panel panel-default">
                <!-- Default panel contents -->
                <div class="panel-heading">
                    <i class="fa fa-th-list" aria-hidden="true"></i> 待删除权限列表
                </div>
                <div class="panel-body" style="color: #9d9d9d;">
                    注意:数据库中存在,但路由系统中不存在的路由。
                </div>
    
                <table class="table table-bordered">
                    <thead>
                    <tr>
                        <th>序号</th>
                        <th>名称</th>
                        <th>URL</th>
                        <th>别名</th>
                        <th>父权限</th>
                        <th>所属菜单</th>
                        <th>操作</th>
                    </tr>
                    </thead>
                    <tbody>
                    {% for form in del_formset %}
    
                        <tr>
                            {{ form.id }}
    
                            <td style="vertical-align: middle;">{{ forloop.counter }}</td>
                            <td>{{ form.title }} <span>{{ form.title.errors.0 }}</span></td>
                            <td>{{ form.url }}</td>
                            <td>{{ form.name }}</td>
                            <td>{{ form.parent }}</td>
                            <td>{{ form.menu }}</td>
                            <td>
                                <a href="{% url 'rbac:permission_del' form.id.value %}" style="color:#d9534f;">
                                    <i class="fa fa-trash-o" aria-hidden="true"></i>
                                </a>
                            </td>
                        </tr>
                    {% endfor %}
                    </tbody>
                </table>
            </div>
    
            <form method="post" action="?type=update">
                {% csrf_token %}
                {{ update_formset.management_form }}
                <div class="panel panel-default">
                    <!-- Default panel contents -->
                    <div class="panel-heading">
                        <i class="fa fa-sitemap" aria-hidden="true"></i> 待更新权限列表
                        <button class="right btn btn-primary btn-xs" style="padding: 2px 8px;margin: -3px;">
                            <i class="fa fa-save" aria-hidden="true"></i>
                            更新
                        </button>
                    </div>
                    <div class="panel-body" style="color: #9d9d9d;">
                        注意:数据库和路由系统都存在的路由。
                    </div>
    
                    <table class="table table-bordered">
                        <thead>
                        <tr>
                            <th>序号</th>
                            <th>名称</th>
                            <th>URL</th>
                            <th>别名</th>
                            <th>父权限</th>
                            <th>所属菜单</th>
                            <th>操作</th>
                        </tr>
                        </thead>
                        <tbody>
                        {% for form in update_formset %}
                            <tr>
                                {{ form.id }}
    
                                <td style="vertical-align: middle;">{{ forloop.counter }}</td>
                                <td>{{ form.title }} <span>{{ form.title.errors.0 }}</span></td>
                                <td>{{ form.url }}</td>
                                <td>{{ form.name }}</td>
                                <td>{{ form.parent }}</td>
                                <td>{{ form.menu }}</td>
                                <td>
                                    <a href="{% url 'rbac:permission_del' form.id.value %}" style="color:#d9534f;">
                                        <i class="fa fa-trash-o" aria-hidden="true"></i>
                                    </a>
                                </td>
                            </tr>
                        {% endfor %}
                        </tbody>
                    </table>
                </div>
            </form>
        </div>
    {% endblock %}
    multi_permissions.html
    {% load staticfiles %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>爱软测</title>
        <link rel="shortcut icon" href="{% static 'imgs/luffy-study-logo.png' %} ">
        <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/>
        <link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/commons.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/nav.css' %} "/>
        <link rel="stylesheet" href="{% static 'css/menu.css' %}">
        <style>
            body {
                margin: 0;
            }
    
            .no-radius {
                border-radius: 0;
            }
    
            .no-margin {
                margin: 0;
            }
    
            .pg-body > .left-menu {
                background-color: #EAEDF1;
                position: absolute;
                left: 1px;
                top: 48px;
                bottom: 0;
                 220px;
                border: 1px solid #EAEDF1;
                overflow: auto;
            }
    
            .pg-body > .right-body {
                position: absolute;
                left: 225px;
                right: 0;
                top: 48px;
                bottom: 0;
                overflow: scroll;
                border: 1px solid #ddd;
                border-top: 0;
                font-size: 13px;
                min- 755px;
            }
    
            .navbar-right {
                float: right !important;
                margin-right: -15px;
            }
    
            .luffy-container {
                padding: 15px;
            }
    
            .left-menu .menu-body .static-menu {
    
            }
    
    
        </style>
    
        {% block css %}
    
        {% endblock %}
    </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">
                {% load rbac %}
                {#            {% menu request %}#}
    
            </div>
        </div>
        <div class="right-body">
            <div>
                {#            {% breadcrumb request %}#}
            </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>
    <script src="{% static 'js/menu.js' %} "></script>
    
    {% block js %} {% endblock %}
    </body>
    </html>
    layout.html
    幻想毫无价值,计划渺如尘埃,目标不可能达到。这一切的一切毫无意义——除非我们付诸行动。
  • 相关阅读:
    log4j配置只打印指定jar或包的DEBUG信息
    实现cookie跨域访问
    使用轻量级Spring @Scheduled注解执行定时任务
    Docker容器里时间与宿主机不同步
    Wildfly8 更改response header中的Server参数
    JBoss部署项目log4j配置会造成死锁问题,浏览器访问一直pending状态
    json-lib-2.4.jar Bug,json字符串中value为"[value]"结构时,解析为数组,不会解析成字符串
    【转载】分享下多年积累的对JAVA程序员成长之路的总结
    web项目嵌入Jetty运行的两种方式(Jetty插件和自制Jetty服务器)
    rabbitmq+haproxy+keepalived实现高可用集群搭建
  • 原文地址:https://www.cnblogs.com/TodayWind/p/13867943.html
Copyright © 2011-2022 走看看