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
幻想毫无价值,计划渺如尘埃,目标不可能达到。这一切的一切毫无意义——除非我们付诸行动。
原文地址:https://www.cnblogs.com/TodayWind/p/13867943.html