day13 权限分配之菜单和权限管理

知识点总结

项目目录

路由设置

rbac/urls.py

#! -*- coding:utf-8 -*-
from django.urls import path, re_path
from rbac.views import role, user, menu

app_name = 'rbac'
urlpatterns = [
    path(r"role/list/", role.role_list, name="role_list"),
    path(r"role/add/", role.role_add, name="role_add"),
    re_path(r"^role/edit/(?P<pk>\d+)/$", role.role_edit, name="role_edit"),
    re_path(r"^role/del/(?P<pk>\d+)/$", role.role_del, name="role_del"),

    path(r"user/list/", user.user_list, name="user_list"),
    path(r"user/add/", user.user_add, name="user_add"),
    re_path(r"^user/edit/(?P<pk>\d+)/$", user.user_edit, name="user_edit"),
    re_path(r"^user/del/(?P<pk>\d+)/$", user.user_del, name="user_del"),
    re_path(r"^user/reset/password/(?P<pk>\d+)/$", user.user_reset_pwd, name="user_reset_pwd"),

    path("menu/list/", menu.menu_list, name="menu_list"),
    path('menu/add/', menu.menu_add, name='menu_add'),
    re_path('^menu/eidt/(?P<pk>\d+)/$', menu.menu_edit, name='menu_edit'),
    re_path('^menu/del/(?P<pk>\d+)/$', menu.menu_del, name='menu_del'),

    re_path('^second/menu/add/(?P<menu_id>\d+)/$', menu.second_menu_add, name='second_menu_add'),
    re_path('^second/menu/eidt/(?P<pk>\d+)/$', menu.second_menu_edit, name='second_menu_edit'),
    re_path('^second/menu/del/(?P<pk>\d+)/$', menu.second_menu_del, name='second_menu_del'),

    re_path('^permission/add/(?P<second_menu_id>\d+)/$', menu.permission_add, name='permission_add'),
    re_path('^permission/edit/(?P<pk>\d+)/$', menu.permission_edit, name='permission_edit'),
    re_path('^permission/del/(?P<pk>\d+)/$', menu.permission_del, name='permission_del'),

]

forms组件代码
rbac/forms/base.py

# -*- encoding: utf-8 -*-
"""
@File    : base.py
@Time    : 2021-12-26 21:32
@Author  : tangsai
@Email   : 294168604@qq.com
@Software: PyCharm
"""
from django import forms


class BootStrapModelForm(forms.ModelForm):
    """
    任意的modelform继承该类,可自动为每个字段设置样式
    """

    def __init__(self, *args, **kwargs):
        super(BootStrapModelForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'
            if 'ModelChoiceField' in field.__repr__():
                field.widget.attrs = {
                    "class": 'form-control selectpicker',
                    'title': '请选择',
                    'data-live-search': 'true',
                    'data-style': 'btn-success',
                }

            if name == 'id':
                self.fields.move_to_end('id', last=False)

rbac/forms/menu.py

# -*- encoding: utf-8 -*-
"""
@File    : menu.py
@Time    : 2021-12-26 21:31
@Author  : tangsai
@Email   : 294168604@qq.com
@Software: PyCharm
"""

from django import forms
from django.utils.safestring import mark_safe

from rbac.forms.base import BootStrapModelForm
from rbac.models import Menu, Permission


class MenuModelForm(forms.ModelForm):
    class Meta:
        model = Menu
        fields = ['title', 'icon']
        widgets = {
            'title': forms.widgets.TextInput(attrs={'class': 'form-control'}),
            'icon': forms.RadioSelect(
                choices=[
                    # 第一个元素为checkbox的值,第二个元素为value
                    ('fa-address-book', mark_safe('<i class="fa fa-address-book" aria-hidden="true"></i>')),
                    ('fa-meetup', mark_safe('<i class="fa fa-meetup" aria-hidden="true"></i>')),
                    ('fa-microchip', mark_safe('<i class="fa fa-microchip" aria-hidden="true"></i>')),
                    ('fa-shower', mark_safe('<i class="fa fa-shower" aria-hidden="true"></i>')),
                    ('fa-thermometer-three-quarters',
                     mark_safe('<i class="fa fa-thermometer-three-quarters" aria-hidden="true"></i>')),
                    ('fa-address-card', mark_safe('<i class="fa fa-address-card" aria-hidden="true"></i>')),
                    ('fa-handshake-o', mark_safe('<i class="fa fa-handshake-o" aria-hidden="true"></i>')),
                    ('fa-eercast', mark_safe('<i class="fa fa-eercast" aria-hidden="true"></i>')),
                    ('fa-free-code-camp', mark_safe('<i class="fa fa-free-code-camp" aria-hidden="true"></i>')),
                    ('fa-grav', mark_safe('<i class="fa fa-grav" aria-hidden="true"></i>')),
                    ('fa-podcast', mark_safe('<i class="fa fa-podcast" aria-hidden="true"></i>')),
                    ('fa-snowflake-o', mark_safe('<i class="fa fa-snowflake-o" aria-hidden="true"></i>')),
                    ('fa-superpowers', mark_safe('<i class="fa fa-superpowers" aria-hidden="true"></i>')),
                    ('fa-wpexplorer', mark_safe('<i class="fa fa-wpexplorer" aria-hidden="true"></i>')),
                    ('fa-gitlab', mark_safe('<i class="fa fa-gitlab" aria-hidden="true"></i>')),
                    ('fa-question-circle-o', mark_safe('<i class="fa fa-question-circle-o" aria-hidden="true"></i>')),
                    ('fa-window-close', mark_safe('<i class="fa fa-window-close" aria-hidden="true"></i>')),
                ],
            )
        }


class SecondMenuModelForm(BootStrapModelForm):
    menu = forms.models.ModelChoiceField(
        queryset=Menu.objects.all(),
        label='所属菜单',
        initial=0,
        required=True,
        error_messages={'required': '请选择一个一级菜单'}
    )

    class Meta:
        model = Permission
        exclude = ['pid']


class PermissionModelForm(BootStrapModelForm):
    class Meta:
        model = Permission
        fields = ['title', 'name', 'url']


class MultiPermissionModelForm(BootStrapModelForm):
    class Meta:
        model = Permission
        fields = '__all__'


转义url
rbac/service/url_filter.py

# -*- encoding: utf-8 -*-
"""
@File    : url_filter.py
@Time    : 2021-12-26 21:33
@Author  : tangsai
@Email   : 294168604@qq.com
@Software: PyCharm
"""

from django.http import QueryDict
from django.urls import reverse


def memory_url(request, name, *args, **kwargs):
    """
    打包转义带有请求参数的URL
    :param request:
    :param name: url别名
    :return:
    """
    # 原生url
    url = reverse(name, args=args, kwargs=kwargs)
    # 有get参数才返回参数
    if request.GET:
        query_dict = QueryDict(mutable=True)
        query_dict['_filter'] = request.GET.urlencode()
        # 带get请求参数的转义url
        url = '{0}?{1}'.format(url, query_dict.urlencode())
    return url


def memory_reverse(request, name, *args, **kwargs):
    """
    先反向生成之前页面的原生url,再与当前页面的get请求参数拼接组成新的url,
    即可跳转回之前的页面
    :param request:
    :param name:
    :param args:
    :param kwargs:
    :return:
    """
    url = reverse(name, args=args, kwargs=kwargs)
    if request.GET:
        menu_id = request.GET.get('_filter')
        url += '?{}'.format(menu_id)
    return url

rbac/service/urls.py

# -*- encoding: utf-8 -*-
"""
@File    : url_filter.py
@Time    : 2021-12-26 21:33
@Author  : tangsai
@Email   : 294168604@qq.com
@Software: PyCharm
"""

from django.http import QueryDict
from django.urls import reverse


def memory_url(request, name, *args, **kwargs):
    """
    打包转义带有请求参数的URL
    :param request:
    :param name: url别名
    :return:
    """
    # 原生url
    url = reverse(name, args=args, kwargs=kwargs)
    # 有get参数才返回参数
    if request.GET:
        query_dict = QueryDict(mutable=True)
        query_dict['_filter'] = request.GET.urlencode()
        # 带get请求参数的转义url
        url = '{0}?{1}'.format(url, query_dict.urlencode())
    return url


def memory_reverse(request, name, *args, **kwargs):
    """
    先反向生成之前页面的原生url,再与当前页面的get请求参数拼接组成新的url,
    即可跳转回之前的页面
    :param request:
    :param name:
    :param args:
    :param kwargs:
    :return:
    """
    url = reverse(name, args=args, kwargs=kwargs)
    if request.GET:
        menu_id = request.GET.get('_filter')
        url += '?{}'.format(menu_id)
    return url

rbac/templatetags/rbac.py代码增加转义逻辑

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from django.template import Library
from django.conf import settings
from collections import OrderedDict
from rbac.service import urls
register = Library()


@register.inclusion_tag('rbac/static_menu.html')
def static_menu(request):
    """
    创建一级菜单
    :return:
    """
    menu_list = request.session[settings.MENU_SESSION_KEY]
    return {'menu_list': menu_list}


@register.inclusion_tag('rbac/multi_menu.html')
def multi_menu(request):
    """
    创建二级菜单
    :return:
    """
    menu_dict = request.session[settings.MENU_SESSION_KEY]

    # 对字典的key进行排序
    key_list = sorted(menu_dict)

    # 空的有序字典
    ordered_dict = OrderedDict()

    for key in key_list:
        val = menu_dict[key]
        val['class'] = 'hide'

        for per in val['children']:
            if per['id'] == request.current_selected_permission:
                per['class'] = 'active'
                val['class'] = ''
        ordered_dict[key] = val

    return {'menu_dict': ordered_dict}


@register.inclusion_tag('rbac/breadcrumb.html')
def breadcrumb(request):
    return {'record_list': request.breadcrumb}


@register.filter
def has_permission(request, name):
    """
    判断是否有权限
    :param request:
    :param name:
    :return:
    """
    if name in request.session[settings.PERMISSION_SESSION_KEY]:
        return True

@register.simple_tag
def memory_url(request, name, *args, **kwargs):
    """
    打包转义带有请求参数的URL  返回到原来的页面 之前的参数也要携带着
    :param request:
    :param name: url别名
    :return:
    """

    return urls.memory_url(request, name, *args, **kwargs)

菜单和权限管理后端代码
rbac/views/menu.py

# -*- encoding: utf-8 -*-
"""
@File    : menu.py
@Time    : 2021-12-26 21:27
@Author  : tangsai
@Email   : 294168604@qq.com
@Software: PyCharm
"""


from django.shortcuts import render, redirect, HttpResponse
from rbac import models
from rbac.forms.menu import MenuModelForm, SecondMenuModelForm, PermissionModelForm

from rbac.service.url_filter import memory_reverse
from rbac.service.urls import memory_reverse_url

def menu_list(request):
    """
    菜单和权限列表
    :param request:
    :return:
    """

    menus = models.Menu.objects.all()
    menu_id = request.GET.get('mid')  # 用户选择的一级菜单
    second_menu_id = request.GET.get('sid')  # 用户选择的二级菜单
    # 避免用户自己随意填写menu_id
    menu_exists = models.Menu.objects.filter(id=menu_id).exists()
    if not menu_exists:
        menu_id = None

    # 如果 有menu_id 才会展示 二级菜单,否则不展示
    if menu_id:
        second_menus = models.Permission.objects.filter(menu_id=menu_id)
    else:
        second_menus = []

    # 避免用户自己填写 second menu 的id
    second_menu_exists = models.Permission.objects.filter(id=second_menu_id).exists()
    if not second_menu_exists:
        second_menu_id = None

    if second_menu_id:
        permissions = models.Permission.objects.filter(pid_id=second_menu_id)
    else:
        permissions = []

    return render(
        request,
        'rbac/menu_list.html',
        {
            'menus': menus,
            'second_menus': second_menus,
            'permissions': permissions,
            'menu_id': menu_id,
            'second_menu_id': second_menu_id,
        }
    )


def menu_add(request):
    """
    添加一级菜单
    :param request:
    :return:
    """
    if request.method == 'GET':
        form = MenuModelForm()
        return render(request, 'rbac/change.html', {'form': form})

    form = MenuModelForm(data=request.POST)
    if form.is_valid():
        form.save()
        return redirect(memory_reverse_url(request, "rbac:menu_list"))

    return render(request, 'rbac/change.html', {'form': form})


def menu_edit(request, pk):
    """

    :param request:
    :param pk:
    :return:
    """
    obj = models.Menu.objects.filter(id=pk).first()
    if not obj:
        return HttpResponse('菜单不存在')
    if request.method == 'GET':
        form = MenuModelForm(instance=obj)
        return render(request, 'rbac/change.html', {'form': form})

    form = MenuModelForm(instance=obj, data=request.POST)
    if form.is_valid():
        form.save()
        return redirect(memory_reverse_url(request, "rbac:menu_list"))

    return render(request, 'rbac/change.html', {'form': form})


def menu_del(request, pk):
    """
    :param request:
    :param pk:
    :return:
    """
    url = memory_reverse_url(request, "rbac:menu_list")
    if request.method == 'GET':
        return render(request, 'rbac/delete.html', {'cancel': url})

    models.Menu.objects.filter(id=pk).delete()
    return redirect(url)


def second_menu_add(request, menu_id):
    """
    添加二级菜单
    :param request:
    :param menu_id: 已选择的一级菜单ID(用于设置默认值)
    :return:
    """

    menu_object = models.Menu.objects.filter(id=menu_id).first()

    if request.method == 'GET':
        form = SecondMenuModelForm(initial={'menu': menu_object})
        return render(request, 'rbac/change.html', {'form': form})

    form = SecondMenuModelForm(data=request.POST)
    if form.is_valid():
        form.save()
        return redirect(memory_reverse(request, 'rbac:menu_list'))

    return render(request, 'rbac/change.html', {'form': form})


def second_menu_edit(request, pk):
    """
    编辑二级菜单
    :param request:
    :param pk: 当前要编辑的二级菜单
    :return:
    """

    permission_object = models.Permission.objects.filter(id=pk).first()

    if request.method == 'GET':
        form = SecondMenuModelForm(instance=permission_object)
        return render(request, 'rbac/change.html', {'form': form})

    form = SecondMenuModelForm(data=request.POST, instance=permission_object)
    if form.is_valid():
        form.save()
        return redirect(memory_reverse(request, 'rbac:menu_list'))

    return render(request, 'rbac/change.html', {'form': form})


def second_menu_del(request, pk):
    """
    :param request:
    :param pk:
    :return:
    """
    url = memory_reverse(request, 'rbac:menu_list')
    if request.method == 'GET':
        return render(request, 'rbac/delete.html', {'cancel': url})

    models.Permission.objects.filter(id=pk).delete()
    return redirect(url)


def permission_add(request, second_menu_id):
    """
    添加权限
    :param request:
    :param second_menu_id:
    :return:
    """
    if request.method == 'GET':
        form = PermissionModelForm()
        return render(request, 'rbac/change.html', {'form': form})

    form = PermissionModelForm(data=request.POST)
    if form.is_valid():
        second_menu_object = models.Permission.objects.filter(id=second_menu_id).first()
        if not second_menu_object:
            return HttpResponse('二级菜单不存在,请重新选择!')
        form.instance.pid = second_menu_object
        form.save()
        return redirect(memory_reverse(request, 'rbac:menu_list'))

    return render(request, 'rbac/change.html', {'form': form})


def permission_edit(request, pk):
    """
    编辑权限
    :param request:
    :param pk: 当前要编辑的权限ID
    :return:
    """

    permission_object = models.Permission.objects.filter(id=pk).first()

    if request.method == 'GET':
        form = PermissionModelForm(instance=permission_object)
        return render(request, 'rbac/change.html', {'form': form})

    form = PermissionModelForm(data=request.POST, instance=permission_object)
    if form.is_valid():
        form.save()
        return redirect(memory_reverse(request, 'rbac:menu_list'))

    return render(request, 'rbac/change.html', {'form': form})


def permission_del(request, pk):
    """
    :param request:
    :param pk:
    :return:
    """
    url = memory_reverse(request, 'rbac:menu_list')
    if request.method == 'GET':
        return render(request, 'rbac/delete.html', {'cancel': url})

    models.Permission.objects.filter(id=pk).delete()
    return redirect(url)

前端模板代码
rbac/templates/rbac/menu_list.html

{% extends 'layout.html' %}
{% load static %}
{% load rbac %}
{% block css %}
    <link rel="stylesheet" href="{% static 'css/menu_list.css' %}">
{% endblock %}

{% block content %}
    <div class="luffy-container">
        <div class="card-deck">
            <div class="col-md-4">
                <div class="card-header">
                    <i class="fa fa-book" aria-hidden="true"></i> 一级菜单
                    <a href="{% memory_url request 'rbac:menu_add' %}" class="right btn btn-success btn-xs"
                       style="padding: 2px 8px;margin: -3px;">
                        <i class="fa fa-plus-circle" aria-hidden="true"></i>新建
                    </a>
                </div>
                <div class="card-body">
                    <table class="table">
                        <thead>
                        <tr>
                            <th>名称</th>
                            <th>图标</th>
                            <th>选项</th>
                        </tr>
                        </thead>
                        <tbody>
                        {% for row in menus %}
                            <tr class="{% if row.id|safe == menu_id %}active{% endif %}">
                                <td>
                                    <a href="?mid={{ row.id }}">{{ row.title }}</a>
                                </td>
                                <td>
                                    <i class="fa {{ row.icon }}" aria-hidden="true"></i>
                                </td>
                                <td>
                                    <a style="color: #333333;"
                                       href="{% memory_url request 'rbac:menu_edit' pk=row.id %}">
                                        <i class="fa fa-edit" aria-hidden="true"></i></a>
                                    <a style="color: #d9534f;"
                                       href="{% memory_url request 'rbac:menu_del' pk=row.id %}"><i
                                            class="fa fa-trash-o"></i></a>
                                </td>
                            </tr>
                        {% endfor %}
                        </tbody>
                    </table>
                </div>
            </div>
            <div class="col-md-4">
                <div class="card-header">
                    <i class="fa fa-window-restore" aria-hidden="true"></i> 二级菜单
                    {% if menu_id %}
                        <a href="{% memory_url request 'rbac:second_menu_add' menu_id=menu_id %}"
                           class="right btn btn-success btn-xs"
                           style="padding: 2px 8px;margin: -3px;">
                            <i class="fa fa-plus-circle" aria-hidden="true"></i>
                            新建
                        </a>
                    {% endif %}
                </div>
                <div class="card-body">
                    <table class="table">
                        <thead>
                        <tr>
                            <th>名称</th>
                            <th>CODE&URL</th>
                            <th>选项</th>
                        </tr>
                        </thead>
                        <tbody>
                        {% for row in second_menus %}
                            <tr class="{% if row.id|safe == second_menu_id %}active {% endif %}">
                                <td rowspan="2">
                                    <a href="?mid={{ menu_id }}&sid={{ row.id }}">{{ row.title }}</a>
                                </td>
                                <td>{{ row.name }}</td>
                                <td>
                                    <a style="color: #333333;"
                                       href="{% memory_url request 'rbac:second_menu_edit' pk=row.id %}">
                                        <i class="fa fa-edit" aria-hidden="true"></i></a>
                                    <a style="color: #d9534f;"
                                       href="{% memory_url request 'rbac:second_menu_del' pk=row.id %}"><i
                                            class="fa fa-trash-o"></i></a>
                                </td>
                            </tr>
                            <tr class="{% if row.id|safe == second_menu_id %}active {% endif %}">
                                <td colspan="2" style="border-top: 0">{{ row.url }}</td>
                            </tr>
                        {% endfor %}
                        </tbody>
                    </table>
                </div>
            </div>
            <div class="col-md-4">
                <div class="card-header">
                    <i class="fa fa-gavel" aria-hidden="true"></i> 权限
                    <div class="btn-group  right">
                        {% if second_menu_id %}
                            <a href="{% memory_url request 'rbac:permission_add' second_menu_id=second_menu_id %}"
                               class="right btn btn-success btn-xs" style="padding: 2px 8px;margin: -3px;">
                                <i class="fa fa-plus-circle" aria-hidden="true"></i>
                                新建
                            </a>
                        {% endif %}
                    </div>
                </div>
                <div class="card-body">
                    <table class="table">
                        <thead>
                        <tr>
                            <th>名称</th>
                            <th>CODE&URL</th>
                            <th>选项</th>
                        </tr>
                        </thead>
                        <tbody>
                        {% for row in permissions %}
                            <tr>
                                <td rowspan="2">{{ row.title }}</td>
                                <td>{{ row.name }}</td>
                                <td>
                                    <a style="color: #333333;"
                                       href="{% memory_url request 'rbac:permission_edit' pk=row.id %}">
                                        <i class="fa fa-edit" aria-hidden="true"></i></a>
                                    <a style="color: #d9534f;"
                                       href="{% memory_url request 'rbac:permission_del' pk=row.id %}"><i
                                            class="fa fa-trash-o"></i></a>
                                </td>
                            </tr>
                            <tr>
                                <td colspan="2" style="border-top: 0">{{ row.url }}</td>
                            </tr>
                        {% endfor %}

                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
{% endblock %}
posted @ 2021-12-26 21:23  simon_T  阅读(157)  评论(0编辑  收藏  举报