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\d+)/$', customer.customer_edit, name="customer_edit"),
    url(r'^customer/del/(?P\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\d+)/$', payment.payment_edit, name="payment_edit"),
    url(r'^payment/del/(?P\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\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'),

    path('multi/permissions/', menu.multi_permissions, name='multi_permissions'),
    re_path('^multi_permissions/del/(?P\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\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 %}
    
{% csrf_token %} {{ generate_formset.management_form }}
待新建的权限列表
{% for form in generate_formset %} {% for field in form %} {% endfor %} {% endfor %}
序号 名称 URL 别名 菜单 父权限
{{ forloop.counter }}{{ field }} {{ field.errors.0 }}
待删除的权限列表
{% for row in delete_row_list %} {% endfor %}
序号 名称 URL 别名 操作
{{ forloop.counter }} {{ row.title }} {{ row.url }} {{ row.name }}
{% csrf_token %} {{ update_formset.management_form }}
待更新的权限列表
{% for form in update_formset %} {% for field in form %} {% if forloop.first %} {{ field }} {% else %} {% endif %} {% endfor %} {% endfor %}
序号 名称 URL 别名 菜单 父权限
{{ forloop.counter }}{{ field }} {{ field.errors.0 }}
{% endblock %}

菜单列表前端页面优化
rbac/templates/rbac/menu_list.html

{% extends 'layout.html' %}
{% load rbac %}
{% block css %}
    

{% endblock %}



{% block content %}
    
一级菜单 新建
{% for row in menus %} {% endfor %}
名称 图标 选项
二级菜单 {% if menu_id %} 新建 {% endif %}
{% for row in second_menus %} {% endfor %}
名称 CODE&URL 选项
{{ row.title }} {{ row.name }}
{{ row.url }}
权限
{% if second_menu_id %} 新建 {% endif %} 批量操作
{% for row in permissions %} {% endfor %}
名称 CODE&URL 选项
{{ row.title }} {{ row.name }}
{{ row.url }}
{% endblock %}

相关