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 %}
posted @ 2021-12-28 22:35  simon_T  阅读(48)  评论(0编辑  收藏  举报