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 %}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具