day14 权限分配之权限批量操作
思路
web/urls中路由地址新增别名
web/urls.py
# -*- encoding: utf-8 -*-
"""
@File : urls.py
@Time : 2021-12-15 22:02
@Author : tangsai
@Email : 294168604@qq.com
@Software: PyCharm
"""
from django.conf.urls import url
from web.views import account
from web.views import customer
from web.views import payment
urlpatterns = [
url(r'^customer/list/$', customer.customer_list, name="customer_list"),
url(r'^customer/add/$', customer.customer_add, name="customer_add"),
url(r'^customer/edit/(?P<cid>\d+)/$', customer.customer_edit, name="customer_edit"),
url(r'^customer/del/(?P<cid>\d+)/$', customer.customer_del, name="customer_del"),
url(r'^customer/import/$', customer.customer_import, name="customer_import"),
url(r'^customer/tpl/$', customer.customer_tpl, name="customer_tpl"),
url(r'^payment/list/$', payment.payment_list, name="payment_list"),
url(r'^payment/add/$', payment.payment_add, name="payment_add"),
url(r'^payment/edit/(?P<pid>\d+)/$', payment.payment_edit, name="payment_edit"),
url(r'^payment/del/(?P<pid>\d+)/$', payment.payment_del, name="payment_del"),
url(r'^login/$', account.login)
]
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'),
path('multi/permissions/', menu.multi_permissions, name='multi_permissions'),
re_path('^multi_permissions/del/(?P<pk>\d+)/$', menu.multi_permissions_del, name='multi_permissions_del'),
]
菜单后端代码新增批量操作菜单权限的逻辑
rbac/views/menu.py
def multi_permissions(request):
"""
权限的批量操作
:param request:
:return:
"""
post_type = request.GET.get("type")
generate_formset_class = formset_factory(MultiAddPermissionForm, extra=0)
update_formset_class = formset_factory(MultiUpdatePermissionModelForm, extra=0)
generate_formset = None
if request.method == "POST" and post_type == "generate":
# 批量添加
formset = generate_formset_class(data=request.POST)
if formset.is_valid():
object_list = []
post_row_list = formset.cleaned_data
has_error = False
for i in range(0, formset.total_form_count()):
row_dict = post_row_list[i]
try:
new_object = models.Permission(**row_dict)
new_object.validate_unique()
object_list.append(new_object)
except Exception as e:
formset.errors[i].update(e)
generate_formset = formset
has_error = True
if not has_error:
models.Permission.objects.bulk_create(object_list, batch_size=10)
else:
generate_formset = formset
update_formset = None
if request.method == "POST" and post_type == "update":
# 批量更新
formset = update_formset_class(data=request.POST)
if formset.is_valid():
post_row_list = formset.cleaned_data
flag = True
for i in range(formset.total_form_count()):
row_dict = post_row_list[i]
permission_id = row_dict.get('id')
try:
obj = models.Permission.objects.filter(pk=permission_id).first()
for k, v in row_dict.items():
setattr(obj, k, v)
obj.validate_unique()
obj.save()
except Exception as e:
formset.errors[i].update(e)
flag = False
# if flag:
# return redirect(reverse('rbac:mutil_permissions'))
else:
update_formset = formset
# 获取项目中的url
all_url_dict = get_all_url_dict()
for k, v in all_url_dict.items():
print(k, v)
# 1 项目中的所有的url的集合 project_url_set
project_url_set = set(all_url_dict.keys())
# 2 数据中所有的url的集合 permission_db_url_set
permissions = models.Permission.objects.all().values("id", "title", "name", "url", "menu_id", "pid_id")
permission_dict = OrderedDict()
permission_db_url_set = set()
for row in permissions:
permission_dict[row['name']] = row
permission_db_url_set.add(row["name"])
# 数据库中的路由和自动发现的url是不是一致,提醒用户要保留哪一个
for name, value in permission_dict.items():
router_row_dict = all_url_dict.get(name)
if not router_row_dict: continue
if value["url"] != router_row_dict["url"]:
value["url"] = "路由和数据库中的url不一致"
# 3.1 计算出应该增加的name
if not generate_formset:
generate_name_list = project_url_set - permission_db_url_set
# generate_formset_class = formset_factory(MultiAddPermissionModelForm, extra=0)
generate_formset = generate_formset_class(
initial=[row_dict for name, row_dict in all_url_dict.items() if name in generate_name_list])
# 3.2 计算出应该删除的name
delete_name_list = permission_db_url_set - project_url_set
delete_row_list = [row_dict for name, row_dict in permission_dict.items() if name in delete_name_list]
# 3.3 计算出应该更新的name
if not update_formset:
update_name_list = permission_db_url_set & project_url_set
# update_formset_class = formset_factory(MultiUpdatePermissionModelForm, extra=0)
update_formset = update_formset_class(
initial=[row_dict for name, row_dict in permission_dict.items() if name in update_name_list])
return render(request, "rbac/multi_permissions.html", {
"generate_formset": generate_formset,
"delete_row_list": delete_row_list,
"update_formset": update_formset
})
def multi_permissions_del(request, pk):
"""
批量页面的权限删除
:param request:
:param pk:
:return:
"""
url = memory_reverse(request, "rbac:multi_permissions")
if request.method == 'GET':
return render(request, 'rbac/delete.html', {'cancel': url})
models.Permission.objects.filter(id=pk).delete()
return redirect(url)
自动发现路由
rbac/service/routes.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import re
from collections import OrderedDict
from django.conf import settings
from django.utils.module_loading import import_string
from django.urls import URLPattern,URLResolver
def check_url_exclude(url):
"""
排除一些特定的URL
:param url:
:return:
"""
for regex in settings.AUTO_DISCOVER_EXCLUDE:
if re.match(regex, url):
return True
def recursion_urls(pre_namespace, pre_url, urlpatterns, url_ordered_dict):
"""
递归的去获取URL
:param pre_namespace: namespace前缀,以后用户拼接name
:param pre_url: url前缀,以后用于拼接url
:param urlpatterns: 路由关系列表
:param url_ordered_dict: 用于保存递归中获取的所有路由
:return:
"""
for item in urlpatterns:
if isinstance(item, URLPattern): # 非路由分发,讲路由添加到url_ordered_dict
if not item.name:
continue
if pre_namespace:
name = "%s:%s" % (pre_namespace, item.name)
else:
name = item.name
url = pre_url + item.pattern.regex.pattern # /rbac/user/edit/(?P<pk>\d+)/
url = url.replace('^', '').replace('$', '')
if check_url_exclude(url):
continue
url_ordered_dict[name] = {'name': name, 'url': url}
elif isinstance(item, URLResolver): # 路由分发,递归操作
if pre_namespace:
if item.namespace:
namespace = "%s:%s" % (pre_namespace, item.namespace,)
else:
namespace = item.namespace
else:
if item.namespace:
namespace = item.namespace
else:
namespace = None
recursion_urls(namespace, pre_url + item.pattern.regex.pattern, item.url_patterns, url_ordered_dict)
def get_all_url_dict():
"""
获取项目中所有的URL(必须有name别名)
:return:
"""
url_ordered_dict = OrderedDict()
md = import_string(settings.ROOT_URLCONF) # from luff.. import urls
recursion_urls(None, '/', md.urlpatterns, url_ordered_dict) # 递归去获取所有的路由
return url_ordered_dict
forms组件menu代码新增隐藏标签逻辑
rbac/forms/menu.py
class MultiUpdatePermissionModelForm(forms.Form):
# HiddenInput隐藏input标签
id = forms.IntegerField(label='id', widget=forms.HiddenInput())
title = forms.CharField(widget=forms.widgets.TextInput(attrs={'class': 'form-control'}))
url = forms.CharField(widget=forms.widgets.TextInput(attrs={'class': 'form-control'}))
name = forms.CharField(widget=forms.widgets.TextInput(attrs={'class': 'form-control'}))
menu_id = forms.ChoiceField(choices=[(None, '-----')],
widget=forms.widgets.Select(attrs={'class': 'form-control'}),
required=False,
)
pid_id = forms.ChoiceField(
choices=[(None, '-----')],
widget=forms.widgets.Select(attrs={'class': 'form-control'}),
required=False,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['menu_id'].choices += Menu.objects.values_list('id', 'title')
self.fields['pid_id'].choices += Permission.objects.filter(pid__isnull=True, menu__isnull=False).values_list(
'id', 'title')
class MultiAddPermissionForm(forms.Form):
title = forms.CharField(widget=forms.widgets.TextInput(attrs={'class': 'form-control'}))
url = forms.CharField(widget=forms.widgets.TextInput(attrs={'class': 'form-control'}))
name = forms.CharField(widget=forms.widgets.TextInput(attrs={'class': 'form-control'}))
menu_id = forms.ChoiceField(choices=[(None, '-----')],
widget=forms.widgets.Select(attrs={'class': 'form-control'}),
required=False,
)
pid_id = forms.ChoiceField(
choices=[(None, '-----')],
widget=forms.widgets.Select(attrs={'class': 'form-control'}),
required=False,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['menu_id'].choices += Menu.objects.values_list('id', 'title')
self.fields['pid_id'].choices += Permission.objects.filter(pid__isnull=True, menu__isnull=False).values_list(
'id', 'title')
class MultiEditPermissionForm(forms.Form):
id = forms.IntegerField(
widget=forms.HiddenInput()
)
title = forms.CharField(
widget=forms.TextInput(attrs={'class': "form-control"})
)
url = forms.CharField(
widget=forms.TextInput(attrs={'class': "form-control"})
)
name = forms.CharField(
widget=forms.TextInput(attrs={'class': "form-control"})
)
menu_id = forms.ChoiceField(
choices=[(None, '-----')],
widget=forms.Select(attrs={'class': "form-control"}),
required=False,
)
pid_id = forms.ChoiceField(
choices=[(None, '-----')],
widget=forms.Select(attrs={'class': "form-control"}),
required=False,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['menu_id'].choices += models.Menu.objects.values_list('id', 'title')
self.fields['pid_id'].choices += models.Permission.objects.filter(pid__isnull=True).exclude(
menu__isnull=True).values_list('id', 'title')
批量操作菜单前端页面
rbac/templates/rbac/multi_permissions.html
{% extends 'layout.html' %}
{% block content %}
{% load rbac %}
<div class="luffy-container">
<div class="col-md-12">
<form action="?type=generate" method="post">
{% csrf_token %}
{{ generate_formset.management_form }}
<div class="card">
<div class="card-header">
<strong class="text-success"><i class="fa fa-book" aria-hidden="true"></i> 待新建的权限列表</strong>
<button type="submit" class="btn btn-success right btn-xs"
style="margin: -3px;padding: 2px 8px">
<i class="fa fa-plus-circle" aria-hidden="true"></i> 新建
</button>
</div>
<div class="card-body">
<table class="table table-hover ">
<thead class="bold">
<tr>
<th>序号</th>
<th>名称</th>
<th>URL</th>
<th>别名</th>
<th>菜单</th>
<th>父权限</th>
</tr>
</thead>
<tbody>
{% for form in generate_formset %}
<tr>
<td>{{ forloop.counter }}</td>
{% for field in form %}
<td>{{ field }} <span style="color: red;">{{ field.errors.0 }}</span></td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</form>
<div class="card mt-2">
<div class="card-header">
<strong class="text-danger"><i class="fa fa-book" aria-hidden="true"></i> 待删除的权限列表</strong>
</div>
<div class="card-body">
<table class="table table-hover ">
<thead class="bold">
<tr>
<th>序号</th>
<th>名称</th>
<th>URL</th>
<th>别名</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for row in delete_row_list %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ row.title }}</td>
<td>{{ row.url }}</td>
<td>{{ row.name }}</td>
<td><a href="{% url 'rbac:multi_permissions_del' pk=row.id %}" class="fa fa-trash-o red"
style="color: red"></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<form action="?type=update" method="post">
{% csrf_token %}
{{ update_formset.management_form }}
<div class="card mt-2">
<div class="card-header">
<strong class="text-info"><i class="fa fa-book" aria-hidden="true"></i> 待更新的权限列表</strong>
<button type="submit" class="btn btn-primary right btn-xs"
style="margin: -3px;padding: 2px 8px">
<i class="fa fa-save" aria-hidden="true"></i> 保存
</button>
</div>
<div class="card-body">
<table class="table table-hover ">
<thead class="bold">
<tr>
<th>序号</th>
<th>名称</th>
<th>URL</th>
<th>别名</th>
<th>菜单</th>
<th>父权限</th>
</tr>
</thead>
<tbody>
{% for form in update_formset %}
<tr>
<td>{{ forloop.counter }}</td>
{% for field in form %}
{% if forloop.first %}
{{ field }}
{% else %}
<td>{{ field }} <span style="color: red;">{{ field.errors.0 }}</span></td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</form>
</div>
</div>
{% endblock %}
菜单列表前端页面优化
rbac/templates/rbac/menu_list.html
{% extends 'layout.html' %}
{% load rbac %}
{% block css %}
<style>
tr.active {
border-left: 3px solid #fdc00f;
}
</style>
{% endblock %}
{% block content %}
<div class="luffy-container">
<div class="col-md-3">
<div class="panel panel-default">
<!-- Default panel contents -->
<div class="panel-heading">
<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>
<!-- Table -->
<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="panel panel-default">
<!-- Default panel contents -->
<div class="panel-heading">
<i class="fa fa-gavel" 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>
<!-- Table -->
<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-5">
<div class="panel panel-default">
<!-- Default panel contents -->
<div class="panel-heading">
<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 %}
<a href="{% memory_url request 'rbac:multi_permissions' %}" class="btn btn-xs btn-primary"
style="padding: 2px 8px;margin: -3px 0;">
<i class="fa fa-mail-forward" aria-hidden="true"></i>
批量操作
</a>
</div>
</div>
<!-- Table -->
<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>
{% 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工具