权限组件(13):批量操作权限页面的展示和增删改查
效果图:
一、路由配置
rbac/urls.py
配置一个批量操作页面的路由和删除权限的路由
... from django.urls import re_path from rbac.views import menu ... urlpatterns = [ ... # 批量操作权限 re_path(r'^multi/permissions/$', menu.multi_permissions, name='multi_permissions'), # 自动发现项目中的所有URL re_path(r'^multi/permissions/delete/(?P<pk>\d+)', menu.multi_permissions_delete, name='multi_permissions_delete') ... ]
二、forms表单验证
rbac/forms/menu.py
... from django import forms ... ... 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) 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() ) # 获取用户id,进行修改操作 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/views/menu.py
...
from django.forms.models import formset_factory
...
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(MultiEditPermissionForm, extra=0) generate_formset = None # 出错了赋值,为了返回给页面错误信息 update_formset = None # 出错了赋值,为了返回给页面错误信息 # 批量添加 if request.method == 'POST' and post_type == 'generate': formset = generate_formset_class(data=request.POST) # 储存的所有信息,包括html标签 if formset.is_valid(): has_repeat_error = False permission_obj_list = [] url_form_list = formset.cleaned_data for num in range(0, formset.total_form_count()): url_form = url_form_list[num] # 下面的方式和model.Permission.object.create(**row)效果一样,这里用这种方式是为了捕获唯一性错误 try: permission_obj = models.Permission(**url_form) permission_obj.validate_unique() # 检查当前对象在数据库是否存在唯一的 permission_obj_list.append(permission_obj) except Exception as e: formset.errors[num].update(e) # 把错误信息放到对应的form里面 generate_formset = formset # 要把用户批量增加时出错的错误信息传给模板 has_repeat_error = True if not has_repeat_error: models.Permission.objects.bulk_create(permission_obj_list, batch_size=formset.total_form_count()) else: generate_formset = formset # 出错信息传给模板 # 批量更新 if request.method == 'POST' and post_type == 'update': formset = update_formset_class(data=request.POST) if formset.is_valid(): url_form_list = formset.cleaned_data for num in range(0, formset.total_form_count()): url_form = url_form_list[num] permission_id = url_form.pop('id') try: permission_obj = models.Permission.objects.filter(id=permission_id).first() for key, value in url_form.items(): setattr(permission_obj, key, value) permission_obj.validate_unique() permission_obj.save() except Exception as e: formset.errors[num].update(e) update_formset = formset # 要把用户批量更新时出错的错误信息传给模板 else: update_formset = formset # 出错信息传给模板 # 1. 获取项目中所有的url all_url_dict = get_all_url_dict() router_name_set = set(all_url_dict.keys()) # 所有路由中的url集合 """ set里不能有重复的值,转换成set后只会剩下key { 'rbac:menu_list': {'name': 'rbac:menu_list', 'url': 'xxxxx/yyyy/menu/list'} } 会变成 {'rbac:menu_list'} """ # # 2. 获取数据库中所有的url all_db_permissions = models.Permission.objects.all().values('id', 'title', 'name', 'url', 'menu_id', 'pid_id') db_permission_name_set = set() # 数据库中的set集合 db_permission_dict = OrderedDict() for db_permission in all_db_permissions: db_permission_dict[db_permission[ 'name']] = db_permission # {'rbac:menu_list':{'id':1,'title':'角色列表',name:'rbac:role_list',url:'/rbac/role/list'},} db_permission_name_set.add(db_permission['name']) # {'rbac:menu_list','rbac:menu_add'......} for name, value in db_permission_dict.items(): router_row_dict = all_url_dict.get(name) # {'name':'rbac:role_list','url':'/rbac/role/list'}, if not router_row_dict: # 没有别名和url的直接跳过 continue if value['url'] != router_row_dict['url']: # 数据库里的url和自动发现的url进行对比 value['url'] = '路由和数据库中的不一致' # 3. 应该添加、删除和修改的权限 # 3.1 计算出应该添加的name if not generate_formset: """ 如果目标没有通过验证,generate_formset的值就是上面出错了的formset,就不会执行下面的代码,页面就会显示错误信息 如果通过验证,就会返回给页面自动发现的数据库中有、路由中没有的url。 下面的 if not update_formset同理 """ generate_name_list = router_name_set - db_permission_name_set generate_formset = generate_formset_class( initial=[add_url for name, add_url in all_url_dict.items() if name in generate_name_list] ) # 3.2 计算出应该删除的name : 数据库有,路由中没有 delete_url_name_list = db_permission_name_set - router_name_set # 数据库里的url - 路由中的url delete_url_list = [delete_url_obj for name, delete_url_obj in db_permission_dict.items() if name in delete_url_name_list] # 3.3 计算出应该更新的name :数据库和路由中都有 if not update_formset: update_name_list = db_permission_name_set & router_name_set # 都包含的元素 update_formset = update_formset_class( initial=[update_url for name, update_url in db_permission_dict.items() if name in update_name_list] ) context = { 'generate_formset': generate_formset, 'delete_url_list': delete_url_list, 'update_formset': update_formset, } return render(request, 'rbac/multi_permissions.html', context) def multi_permissions_delete(request, pk): """ 批量页面的权限删除 :param request: :param pk: :return: """ multi_pemrission_url = memory_reverse(request, 'rbac:multi_permissions') if request.method == 'GET': return render(request, 'rbac/delete.html', {'cancel': multi_pemrission_url}) models.Permission.objects.filter(id=pk).delete() return redirect(multi_pemrission_url)
四、模板
rbac/templates/multi_permissions.html
{% extends 'layout.html' %} {% block content %} <div class="luffy-container"> <!-- 待新建的权限列表 --> <form action="?type=generate" method="post"> {% csrf_token %} {{ generate_formset.management_form }} <div class="panel panel-success"> <div class="panel-heading"> <i class="fa fa-th-list" aria-hidden="true">待新建的权限列表</i> <button href="" class="right btn btn-primary btn-xs" style="padding: 2px 8px;margin:-3px"> <i class="fa fa-save" aria-hidden="true">新建</i> </button> </div> <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 class="panel panel-danger"> <!-- Default panel contents --> <div class="panel-heading"> <i class="fa fa-th-list" aria-hidden="true">数据库中有,路由中没有的权限列表</i> </div> <table class="table"> <thead> <tr> <th>序号</th> <th>名称</th> <th>URL</th> <th>别名</th> <th>删除</th> </tr> </thead> <tbody> {% for delete_url_obj in delete_url_list %} <tr> <td>{{ forloop.counter }}</td> <td>{{ delete_url_obj.title }}</td> <td>{{ delete_url_obj.url }}</td> <td>{{ delete_url_obj.name }}</td> <td> <a style="color: red; font-size:18px" href="{% url 'rbac:multi_permissions_delete' delete_url_obj.id %}"> <i class="fa fa-trash-o" aria-hidden="true"></i> </a> </td> </tr> {% endfor %} </tbody> </table> </div> <!-- 待更新的权限列表 --> <form action="?type=update" method="post"> {% csrf_token %} {{ update_formset.management_form }} <div class="panel panel-primary"> <div class="panel-heading"> <i class="fa fa-th-list" aria-hidden="true">待更新的权限列表</i> <button href="" class="right btn btn-primary btn-xs" style="padding: 2px 8px;margin:-3px"> <i class="fa fa-plus-circle" aria-hidden="true">更新</i> </button> </div> <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> {% endblock content %}
在rbac/templates/menu_list.html的权限表里加上批量操作权限的按钮
{% extends 'layout.html' %} {% load rbac %} {% block css %} <style> tr.active { border-left: 3px solid #fdc00f; } </style> {% endblock css %} {% 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 menu in menus %} <!-- 管道符可以将后端传来的整型,转换成字符串 --> <tr class="{% if menu.id|safe == menu_id %}active{% endif %}"> <td><a href="?mid={{ menu.id }}">{{ menu.title }}</a></td> <td><i class="fa {{ menu.icon }}" aria-hidden="true"></i></td> <td> <a style="color: #333333; font-size:18px" href="{% memory_url request 'rbac:menu_edit' pk=menu.id %}"> <i class="fa fa-edit" aria-hidden="true"></i> </a> <a style="color: red; font-size:18px" href="{% memory_url request 'rbac:menu_delete' pk=menu.id %}"> <i class="fa fa-trash-o" aria-hidden="true"></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-flag" 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 second_menu in second_menus %} <!-- 管道符可以将后端传来的整型,转换成字符串 --> <tr class="{% if second_menu.id|safe == second_menu_id %}active{% endif %}"> <td rowspan="2"><a href="?mid={{ menu_id }}&sid={{ second_menu.id }}">{{ second_menu.title }}</a> </td> <td>{{ second_menu.name }}</td> <td> <a style="color: #333333; font-size:18px" href="{% memory_url request 'rbac:second_menu_edit' pk=second_menu.id %}"> <i class="fa fa-edit" aria-hidden="true"></i> </a> <a style="color: red; font-size:18px" href="{% memory_url request 'rbac:second_menu_delete' pk=second_menu.id %}"> <i class="fa fa-trash-o" aria-hidden="true"></i> </a> </td> </tr> <tr class="{% if second_menu.id|safe == second_menu_id %}active{% endif %}"> <td colspan="2" style="border-top:0;">{{ second_menu.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-flag" 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 right btn-primary 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>CODE&URL</th> <th>选项</th> </tr> </thead> <tbody> {% for permission in permissions %} <!-- 管道符可以将后端传来的整型,转换成字符串 --> <tr class=""> <td rowspan="2">{{ permission.title }}</td> <td>{{ permission.name }}</td> <td> <a style="color: #333333; font-size:18px" href="{% memory_url request 'rbac:permission_edit' pk=permission.id %}"> <i class="fa fa-edit" aria-hidden="true"></i> </a> <a style="color: red; font-size:18px" href="{% memory_url request 'rbac:permission_delete' pk=permission.id %}"> <i class="fa fa-trash-o" aria-hidden="true"></i> </a> </td> </tr> <tr class=""> <td colspan="2" style="border-top:0;">{{ permission.url }}</td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> </div> {% endblock content %}