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\d+)/$", role.role_edit, name="role_edit"),
re_path(r"^role/del/(?P\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\d+)/$", user.user_edit, name="user_edit"),
re_path(r"^user/del/(?P\d+)/$", user.user_del, name="user_del"),
re_path(r"^user/reset/password/(?P\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\d+)/$', menu.menu_edit, name='menu_edit'),
re_path('^menu/del/(?P\d+)/$', menu.menu_del, name='menu_del'),
re_path('^second/menu/add/(?P\d+)/$', menu.second_menu_add, name='second_menu_add'),
re_path('^second/menu/eidt/(?P\d+)/$', menu.second_menu_edit, name='second_menu_edit'),
re_path('^second/menu/del/(?P\d+)/$', menu.second_menu_del, name='second_menu_del'),
re_path('^permission/add/(?P\d+)/$', menu.permission_add, name='permission_add'),
re_path('^permission/edit/(?P\d+)/$', menu.permission_edit, name='permission_edit'),
re_path('^permission/del/(?P\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('')),
('fa-meetup', mark_safe('')),
('fa-microchip', mark_safe('')),
('fa-shower', mark_safe('')),
('fa-thermometer-three-quarters',
mark_safe('')),
('fa-address-card', mark_safe('')),
('fa-handshake-o', mark_safe('')),
('fa-eercast', mark_safe('')),
('fa-free-code-camp', mark_safe('')),
('fa-grav', mark_safe('')),
('fa-podcast', mark_safe('')),
('fa-snowflake-o', mark_safe('')),
('fa-superpowers', mark_safe('')),
('fa-wpexplorer', mark_safe('')),
('fa-gitlab', mark_safe('')),
('fa-question-circle-o', mark_safe('')),
('fa-window-close', mark_safe('')),
],
)
}
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 %}
{% endblock %}
{% block content %}
一级菜单
新建
名称
图标
选项
{% for row in menus %}
{{ row.title }}
{% endfor %}
二级菜单
{% if menu_id %}
新建
{% endif %}
名称
CODE&URL
选项
{% for row in second_menus %}
{{ row.title }}
{{ row.name }}
{{ row.url }}
{% endfor %}
权限
{% if second_menu_id %}
新建
{% endif %}
{% endblock %}