需先掌握的知识点
formset
Form组件或者ModelForm用于做一个表单的验证,一个表单即是一个表中的一行数据的操作。
formset组件是用于做多个表单验证的组件。
参考:formset批量操作多个表单
自动发现项目中的所有url
参考:自动发现项目中url
批量操作思路
- 自动发现的项目中所有带别名的权限,称之为a
- 数据库中已经存储了的权限,称之为b
以权限信息中的name作为对比,会有不同的三种情况
情况一:a的数量 > b的数量
出现场景是,项目开始阶段,数据库中还没有录入数据,自动发现的权限多,此时做数据库的权限批量增加。
情况二:a的数量 < b的数量
出现的场景是,项目中,我们在urls.py中注释掉了一些url(某些功能废弃的场景),数据库中还没有删除掉,此时需要做的是权限从数据库的批量删除。
情况三:a的数量 == b的数量
出现的场景是,项目中,一些url在urls.py中修改了内容,但是没有同步更新到数据库中,两处的信息不一致,此时做权限的批量更新到数据库。
批量操作页面展示
在此页面需展示三个部分:待新增内容、待删除内容、待更新内容
路由
url(r'^multi/permissions/$', menu.multi_permissions, name='multi_permissions'),
Form
class MultiAddPermissionForm(forms.Form):
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)
# 给Choice中动态添加数据库中的数据
self.fields['menu_id'].choices += models.Menu.objects.all().values_list('id', 'title')
self.fields['pid_id'].choices += models.Permission.objects.filter(
pid__isnull=True).exclude(menu__isnull=True).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)
# 给Choice中动态添加数据库中的数据
self.fields['menu_id'].choices += models.Menu.objects.all().values_list('id', 'title')
self.fields['pid_id'].choices += models.Permission.objects.filter(
pid__isnull=True).exclude(menu__isnull=True).values_list('id', 'title')
视图函数
def multi_permissions(request):
"""
批量操作权限
:param request:
:return:
"""
# 获取项目中所有的url
url_ordered_dict = get_all_url_dict()
router_name_set = set(url_ordered_dict.keys())
# 获取数据库中所有的url
permissions = models.Permission.objects.all().values('id', 'title', 'name', 'url', 'menu_id', 'pid_id')
permissions_dict = OrderedDict()
for row in permissions:
permissions_dict[row.get('name')] = row
print(permissions_dict)
permission_name_set = set(permissions_dict.keys())
for name, value in permissions_dict.items():
router_row_dict = url_ordered_dict.get(name)
if not router_row_dict:
# 数据库中有,自动发现的没有
continue
# 数据库和自动发现的都有,判断两者的url是否一致
if value['url'] != router_row_dict['url']:
print('数据库:{}({}) <==> 自动发现:{}({})'.format(value['url'], value['name'], router_row_dict['url'], router_row_dict['name']))
value['url'] = "路由和数据库中不一致"
# 应该添加、删除、修改的权限有哪些?
# 添加 差集
generate_name_list = router_name_set - permission_name_set
generate_formset_class = formset_factory(MultiAddPermissionForm, extra=0)
generate_formset = generate_formset_class(
initial=[row_dict for name, row_dict in url_ordered_dict.items() if name in generate_name_list]
)
# 删除 差集
delete_name_list = permission_name_set - router_name_set
delete_row_list = [row_dict for name, row_dict in permissions_dict.items() if name in delete_name_list]
print(delete_row_list)
# 更新 交集
update_name_list = permission_name_set & router_name_set
update_formset_class = formset_factory(MultiEditPermissionForm, extra=0)
update_formset = update_formset_class(
initial=[row_dict for name, row_dict in permissions_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
}
)
模板multi_permissions.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="row">
<div class="panel panel-info">
<!-- Default panel contents -->
<div class="panel-heading">
<i class="fa fa-th-list" aria-hidden="true"></i>待新建权限列表
<a href="#" class="right btn btn-primary btx-xs"
style="margin: -3px; padding: 2px 8px;">
<i class="fa fa-plus-circle" aria-hidden="true"></i>
新建
</a>
</div>
<!-- Table -->
<table class="table">
<thead>
<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.o }}</span>
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="panel panel-info">
<!-- Default panel contents -->
<div class="panel-heading">
<i class="fa fa-th-list" aria-hidden="true"></i>待删除权限列表
</div>
<!-- Table -->
<table class="table">
<thead>
<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 style="color: #d9534f;" href="#">
<i class="fa fa-trash-o"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="panel panel-info">
<!-- Default panel contents -->
<div class="panel-heading">
<i class="fa fa-th-list" aria-hidden="true"></i>待更新权限列表
<a href="#" class="right btn btn-primary btx-xs"
style="margin: -3px; padding: 2px 8px;">
<i class="fa fa-plus-circle" aria-hidden="true"></i>
更新
</a>
</div>
<!-- Table -->
<table class="table">
<thead>
<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.o }}</span>
</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
效果:
批量增删改
就是界面上按钮的功能实现
对于增,点击待新建权限列表的新建按钮,就要将面板下所有的内容更新到数据库中,在面板外部用form包起来,一点击新建将所有内容提交。
对于删,就是待删除面板下的删除图标点击所触发的功能,将要删除的id传递到后端进行删除。
对于改,也就是更新功能,点击待更新面板上的更新按钮触发的功能,将面板中的所有表单更新到数据库中。
代码实现
路由
# 批量操作权限
url(r'^multi/permissions/$', menu.multi_permissions, name='multi_permissions'),
url(r'^multi/permissions/del/(?P<pk>\d+)/$', menu.multi_permissions_del, name='multi_permissions_del'),
视图函数
def multi_permissions(request):
"""
批量操作权限
:param request:
:return:
"""
post_type = request.GET.get('type') # 页面上新增和更新都是提交的post请求,增加参数来判断是新增还是更新
generate_formset_class = formset_factory(MultiAddPermissionForm, extra=0)
update_formset_class = formset_factory(MultiEditPermissionForm, extra=0)
generate_formset = None
update_formset = None
print(post_type, request.method)
if request.method == 'POST' and post_type == 'generate':
# 新增
formset = generate_formset_class(data=request.POST)
if formset.is_valid():
has_error = False # 用于判断是否所以数据都正确可存储到数据库
post_row_list = formset.cleaned_data
add_object_list = [] # 存储没有问题的数据对象
for i in range(formset.total_form_count()):
row_dict = post_row_list[i]
try:
obj = models.Permission(**row_dict)
obj.validate_unique()
add_object_list.append(obj)
except Exception as e:
formset.errors[i].update(e)
# 有错误信息的formset 传递到前端显示
generate_formset = formset
has_error = True
if not has_error:
# 所有的新增内容检测没有问题,才进行更新到数据库
models.Permission.objects.bulk_create(add_object_list, batch_size=100)
else:
# 有错误信息的formset 传递到前端显示
generate_formset = formset
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
for i in range(formset.total_form_count()):
row_dict = post_row_list[i]
permission_id = row_dict.pop('id')
try:
row_obj = models.Permission.objects.filter(pk=permission_id).first()
for key, value in row_dict.items():
setattr(row_obj, key, value)
row_obj.validate_unique()
row_obj.save()
except Exception as e:
formset.errors[i].update(e)
update_formset = formset
else:
# 带有错误信息的formset,没有通过验证,显示错误信息
update_formset = formset
# 获取项目中所有的url
url_ordered_dict = get_all_url_dict()
router_name_set = set(url_ordered_dict.keys())
# 获取数据库中所有的url
permissions = models.Permission.objects.all().values('id', 'title', 'name', 'url', 'menu_id', 'pid_id')
permissions_dict = OrderedDict()
for row in permissions:
permissions_dict[row.get('name')] = row
# print(permissions_dict)
permission_name_set = set(permissions_dict.keys())
for name, value in permissions_dict.items():
router_row_dict = url_ordered_dict.get(name)
if not router_row_dict:
# 数据库中有,自动发现的没有
continue
# 数据库和自动发现的都有,判断两者的url是否一致
if value['url'] != router_row_dict['url']:
print('数据库:{}({}) <==> 自动发现:{}({})'.format(value['url'], value['name'], router_row_dict['url'], router_row_dict['name']))
value['url'] = "路由和数据库中不一致"
# 应该添加、删除、修改的权限有哪些?
# 添加 差集
if not generate_formset:
generate_name_list = router_name_set - permission_name_set
generate_formset = generate_formset_class(
initial=[row_dict for name, row_dict in url_ordered_dict.items() if name in generate_name_list]
)
# 删除 差集
delete_name_list = permission_name_set - router_name_set
delete_row_list = [row_dict for name, row_dict in permissions_dict.items() if name in delete_name_list]
# print(delete_row_list)
# 更新 交集
if not update_formset:
update_name_list = permission_name_set & router_name_set
update_formset = update_formset_class(
initial=[row_dict for name, row_dict in permissions_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:
"""
basic_url = memory_reverse(request, 'rbac:multi_permissions')
if request.method == "GET":
return render(request, 'rbac/delete.html', {'cancel_url': basic_url})
models.Permission.objects.filter(id=pk).delete()
return redirect(basic_url)
模板
{% extends 'layout.html' %}
{% load rbac %}
{% block content %}
<div class="luffy-container">
<div class="row">
<form action="?type=generate" method="post">
{% csrf_token %}
{{ generate_formset.management_form }}
<div class="panel panel-info">
<!-- Default panel contents -->
<div class="panel-heading">
<i class="fa fa-th-list" aria-hidden="true"></i>待新建权限列表
<button href="#" class="right btn btn-primary btx-xs"
style="margin: -3px; padding: 2px 8px;">
<i class="fa fa-plus-circle" aria-hidden="true"></i>
新建
</button>
</div>
<!-- Table -->
<table class="table">
<thead>
<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>
</form>
</div>
<div class="row">
<div class="panel panel-info">
<!-- Default panel contents -->
<div class="panel-heading">
<i class="fa fa-th-list" aria-hidden="true"></i>待删除权限列表
</div>
<!-- Table -->
<table class="table">
<thead>
<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 style="color: #d9534f;" href="{% url 'rbac:multi_permissions_del' pk=row.id %}">
<i class="fa fa-trash-o"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="row">
<form action="?type=update" method="post">
{% csrf_token %}
{{ update_formset.management_form }}
<div class="panel panel-info">
<!-- Default panel contents -->
<div class="panel-heading">
<i class="fa fa-th-list" aria-hidden="true"></i>待更新权限列表
<button href="#" class="right btn btn-primary btx-xs"
style="margin: -3px; padding: 2px 8px;">
<i class="fa fa-plus-circle" aria-hidden="true"></i>
更新
</button>
</div>
<!-- Table -->
<table class="table">
<thead>
<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>
</form>
</div>
</div>
{% endblock %}