• RABC权限控制(页面操作角色,权限和进行分配)


     上一节主要说的是如何通过url进行权限控制,这一节就说一下如何开发一个权限控制的界面,这样我们就能很方便的创建角色,并分配给用户不同角色和不同权限.

    1.编写角色管理页面

    这个编写较为简单,主要是通过modelform快速实现的,下面代码比较简单,我就不多说了

    效果图如下:

    代码如下:

    def role_list(request):
        """角色列表"""
        roles_list = Role.objects.all()
        # 分页
        current_page_num = request.GET.get('page')
        pagination = MyPagination(current_page_num, roles_list.count(), request)
        roles_list = roles_list[pagination.start:pagination.end]
        return render(request, 'rbac/role_list.html', {'roles_list': roles_list, 'pagination':pagination})
    
    
    def role_operate(request, edit_id=None):
        """角色操作"""
        role_obj = Role.objects.filter(pk=edit_id).first()
        if request.method == "POST":
            role_form = RoleModelForm(request.POST, instance=role_obj)
            if role_form.is_valid():
                role_form.save()
                return redirect(reverse('rbac:role_list'))
            return render(request, 'rbac/role_operate.html', {'role_form': role_form})
        role_form = RoleModelForm(instance=role_obj)
        return render(request, 'rbac/role_operate.html', {'role_form': role_form, 'role_obj': role_obj})
    
    
    def role_del(request, del_id):
        """删除角色"""
        Role.objects.filter(pk=del_id).delete()
        return redirect(reverse('rbac:role_list'))
    RoleModelForm,这里就只有一个name字段,不给角色字段是创建完用户后到时再分配
    class RoleModelForm(forms.ModelForm):
        """角色的modelform"""
        class Meta:
            model = Role
            fields = ['name']
    
            error_messages = {
                'name': {'required': '角色名称不能为空'}
            }
            widgets = {
                'name': wid.TextInput(attrs={'class': 'form-control'})
            }

     2.编写菜单权限管理页面

    这个也不是特别难,因为主要就是两张表在页面渲染的事情,效果图如下:

    代码如下:

    def menu_list(request):
        """菜单权限列表"""
        # 获取所有的菜单
        menu_list = Menu.objects.all()
        # 菜单管理目前选择的菜单名称id
        mid = request.GET.get('mid')
        # 如果mid有值则通过二级菜单中菜单id是一级菜单的和二级菜单下子权限id属于一级菜单的全部找出来显示,没有则显示全部菜单
        if mid:
            permission_list = Permission.objects.filter(Q(parent__menu__id=mid) | Q(menu_id=mid))
        else:
            permission_list = Permission.objects.all()
        # 查询出权限表中的所有字段
        all_permission_list = permission_list.values('id', 'url', 'title', 'url_name',
                                                     'menu_id', 'parent_id', 'menu__title')
        # 把所有菜单以字典形式保存在字典中
        all_permission_dict = {}
        # 第一次for循环将二级菜单加入字典中
        for permission in all_permission_list:
            menu_id = permission.get('menu_id')
            # 有menu_id就证明是二级菜单,加入字典
            if menu_id:
                permission['children'] = []
                all_permission_dict[permission['id']] = permission
        # 第二次for循环将三级菜单(子权限)加入到二级菜单的children中
        for permission in all_permission_list:
            parent_id = permission.get('parent_id')
            if parent_id:
                all_permission_dict[parent_id]['children'].append(permission)
        return render(request, 'rbac/menu_list.html', {'menu_list': menu_list,
                                                       'all_permission_dict': all_permission_dict, 'mid': mid})
    
    
    def menu_operate(request, edit_id=None):
        """菜单管理操作"""
        menu_obj = Menu.objects.filter(pk=edit_id).first()
        if request.method == "POST":
            form_obj = MenuModelForm(request.POST, instance=menu_obj)
            if form_obj.is_valid():
                form_obj.save()
                return redirect(reverse('rbac:menu_list'))
            return render(request, 'rbac/menu_operate.html', {'form_obj': form_obj})
        form_obj = MenuModelForm(instance=menu_obj)
        return render(request, 'rbac/menu_operate.html', {'form_obj': form_obj, 'menu_obj': menu_obj})
    
    
    def menu_del(request, del_id):
        """菜单管理删除"""
        Menu.objects.filter(pk=del_id).delete()
        return redirect(reverse('rbac:menu_list'))
    
    
    def permission_operate(request, edit_id=None):
        """权限管理操作"""
        permission_obj = Permission.objects.filter(pk=edit_id).first()
        if request.method == "POST":
            form_obj = PermissionModelForm(request.POST, instance=permission_obj)
            if form_obj.is_valid():
                form_obj.save()
                return redirect(reverse('rbac:menu_list'))
            return render(request, 'rbac/permission_operate.html', {'form_obj': form_obj})
        form_obj = PermissionModelForm(instance=permission_obj)
        return render(request, 'rbac/permission_operate.html', {'form_obj': form_obj, 'permission_obj': permission_obj})
    
    
    def permission_del(request, del_id):
        """权限管理删除"""
        Permission.objects.filter(pk=del_id).delete()
        return redirect(reverse('rbac:menu_list'))

    页面对上面两个数据的for循环展示(这也是最主要的数据展示部分)

    {% for p_permission in all_permission_dict.values %}
                            <tr class="parent" id="{{ p_permission.id }}">
                                <td class="title"><i class="fa fa-caret-down"></i>{{ p_permission.title }}</td>
                                <td>{{ p_permission.url }}</td>
                                <td>{{ p_permission.url_name }}</td>
                                <td></td>
                                <td>{{ p_permission.menu__title }}</td>
                                <td>
                                    <a href="{% url 'rbac:permission_edit' p_permission.id %}"><i class="fa fa-edit"></i></a>
                                    <a style="margin-left: 10px" href="{% url 'rbac:permission_del' p_permission.id %}"><i
                                            class="fa fa-trash-o text-danger"></i></a>
                                </td>
                            </tr>
                            {% for c_permission in p_permission.children %}
                                <tr pid="{{ c_permission.parent_id }}">
                                    <td style="padding-left: 20px">{{ c_permission.title }}</td>
                                    <td>{{ c_permission.url }}</td>
                                    <td>{{ c_permission.url_name }}</td>
                                    <td></td>
                                    <td></td>
                                    <td>
                                        <a href="{% url 'rbac:permission_edit' c_permission.id %}"><i class="fa fa-edit"></i></a>
                                        <a style="margin-left: 10px" href="{% url 'rbac:permission_del' c_permission.id %}"><i
                                                class="fa fa-trash-o text-danger"></i></a>
                                    </td>
                                </tr>
                            {% endfor %}
    
                        {% endfor %}

    点击二级菜单显示和隐藏,这里巧妙的用到了二级菜单的id和子权限的parent_id相等去显示和隐藏,下面是显示隐藏的jquery代码

    <script>
            $('.permisson-area').on('click', 'tr .title', function () {
                var caret = $(this).find('i');
                var id = $(this).parent().attr('id');
                if (caret.hasClass('fa-caret-right')){
                    caret.removeClass('fa-caret-right').addClass('fa-caret-down');
                   $(this).parent().nextAll('tr[pid="' + id + '"]').removeClass('hide');
                }else{
                    caret.removeClass('fa-caret-down').addClass('fa-caret-right');
                    $(this).parent().nextAll('tr[pid=' + id + ']').addClass('hide');
                }
            })
        </script>

     3.分配权限管理页面编写(这个比较麻烦,主要是数据结构比较复杂,嵌套太多层,注意这还是二级菜单)

    实现的效果图如下:

    代码如下,备注在代码中写了,详细请看代码:

    def distribute_permissions(request):
        """分配权限"""
        # uid是前端提交的用户id,rid是前端提交的角色id
        uid = request.GET.get('uid')
        rid = request.GET.get('rid')
    
        # 用户添加角色,由于有多个from表单所以给每个from表单一个postType
        if request.method == 'POST' and request.POST.get('postType') == 'role' and uid:
            user = User.objects.filter(id=uid).first()
            if not user:
                return HttpResponse('用户不存在')
            # 因为是多对多的关系,所以用set就可以直接更新数据了,记得set里面必须是可迭代对象,所以getlist
            user.roles.set(request.POST.getlist('roles'))
        # 角色添加权限
        if request.method == 'POST' and request.POST.get('postType') == 'permission' and rid:
            role = Role.objects.filter(id=rid).first()
            if not role:
                return HttpResponse('角色不存在')
            role.permissions.set(request.POST.getlist('permissions'))
    
        # 所有用户,界面用户展示
        user_list = User.objects.all()
        # 取得当前用户的所有角色
        user_has_roles = User.objects.filter(id=uid).values('id', 'roles')
        # 获取用户拥有的角色id,数据结构是{角色id: None},这种数据结构推荐,到时直接in就能判断了,效率高
        user_has_roles_dict = {item['roles']: None for item in user_has_roles}
    
        # 角色列表(所有角色),界面用户展示
        role_list = Role.objects.all()
    
        # 如过选中了角色,那么就根据角色id拿到所有的权限
        if rid:
            role_has_permissions = Role.objects.filter(id=rid).values('id', 'permissions')
        # 如果只选中了用户没有选择角色,那么就通过用户的角色去拿对应的所有权限
        elif uid and not rid:
            user = User.objects.filter(id=uid).first()
            if not user:
                return HttpResponse('用户不存在')
            role_has_permissions = user.roles.values('id', 'permissions')
        else:
            # 都没选中,就是初始化状态,界面不勾选任何权限菜单
            role_has_permissions = []
    
        # 获取角色拥有的权限id,数据结构是{权限id: None}
        role_has_permissions_dict = {item['permissions']: None for item in role_has_permissions}
    
        # 以列表形式存放所有的菜单信息
        all_menu_list = []
    
        # 查询出所有菜单
        menu_queryset = Menu.objects.values('id', 'title')
        # 以字典形式存放所有的菜单信息
        menu_dict = {}
    
        # 这个for循环的作用是将一级菜单信息分别放入了menu_dict字典和all_menu_list列表中
        for item in menu_queryset:
            item['children'] = []   # 存放二级菜单(父权限)
            menu_dict[item['id']] = item    # 注意这里是将item对象赋值给了item['id'],所以menu_dict和all_menu_list是一起变化的
            all_menu_list.append(item)
    
        """
        下面是这两个的数据结构,字典套字典,然后children字段子菜单就是列表,然后反复这样嵌套
        menu_dict = {'menu_id': {'id':1, 'title': 'xxx', 'children': [
                    {'id', 'title', 'menu_id', 'children': [
                        {'id', 'title', 'parent_id'}
                    ]},
                    ]},
                    None: {'id': None, 'title': '其他', 'children': [{'id', 'title', 'parent_id'}]}
                    }
        all_menu_list = [
            {'id':1, 'title': 'xxx', 'children': [
            {'id', 'title', 'menu_id', 'children': [
                {'id', 'title', 'parent_id'}
            ]},
            ]},
            {'id': None, 'title': '其他', 'children': [{'id', 'title', 'parent_id'}]}
        ]
        """
        # 像首页这些不属于任何一级菜单,所以可以归属于other下面
        other = {'id': None, 'title': '其他', 'children': []}
        # 两个数据结构分别加入other
        all_menu_list.append(other)
        menu_dict[None] = other
    
        # 查询二级菜单的权限信息
        parent_permission = Permission.objects.filter(menu__isnull=False).values('id', 'title', 'menu_id')
        # 二级菜单信息字典
        parent_permission_dict = {}
        """
        parent_permission_dict = {父权限id: {'id', 'title', 'menu_id', 'children': [
            {'id', 'title', 'parent_id'}
        ]} }
        """
    
        for per in parent_permission:
            per['children'] = []    # 存放子权限
            nid = per['id']
            menu_id = per['menu_id']
            # 以二级菜单id为键,二级菜单信息为值加入到二级菜单字典中
            parent_permission_dict[nid] = per
            # 一级菜单字典将二级菜单加入到children下,注意一级菜单列表数据结构也会跟着增加(py内存使用导致)
            menu_dict[menu_id]['children'].append(per)
    
        # 类似上面的操作,将不是二级菜单的权限全部找出来,包括子权限和other
        node_permission = Permission.objects.filter(menu__isnull=True).values('id', 'title', 'parent_id')
    
        for per in node_permission:
            pid = per['parent_id']
            # 如果不是子权限,就将信息加入到other的children下
            if not pid:
                menu_dict[None]['children'].append(per)
                continue
            # 是子权限就加入到二级菜单的children下,因为menu_dict存放的是二级菜单的对象,所以此时menu_dict就有了各个层级的数据
            parent_permission_dict[pid]['children'].append(per)
    
        return render(request, 'rbac/distribute_permissions.html',
                      {
                          'user_list': user_list,
                          'role_list': role_list,
                          'user_has_roles_dict': user_has_roles_dict,
                          'role_has_permissions_dict': role_has_permissions_dict,
                          'all_menu_list': all_menu_list,
                          'uid': uid,
                          'rid': rid,
                      })

    前端代码:

    {% extends 'layout.html' %}
    {% block css %}
        <style>
            .user-area ul {
                padding-left: 20px;
            }
    
            .user-area li {
                cursor: pointer;
                padding: 2px 0;
            }
    
            .user-area li a {
                display: block;
            }
    
            .user-area li.active {
                font-weight: bold;
                color: red;
            }
    
            .user-area li.active a {
                color: red;
            }
    
            .role-area tr td a {
                display: block;
            }
    
            .role-area tr.active {
                background-color: #f1f7fd;
                border-left: 3px solid #fdc00f;
            }
    
            .permission-area tr.root {
                background-color: #f1f7fd;
                cursor: pointer;
            }
    
            .permission-area tr.root td i {
                margin: 3px;
            }
    
            .permission-area .node {
    
            }
    
            .permission-area .node input[type='checkbox'] {
                margin: 0 5px;
            }
    
            .permission-area .node .parent {
                padding: 5px 0;
            }
    
            .permission-area .node label {
                font-weight: normal;
                margin-bottom: 0;
                font-size: 12px;
            }
    
            .permission-area .node .children {
                padding: 0 0 0 20px;
            }
    
            .permission-area .node .children .child {
                display: inline-block;
                margin: 2px 5px;
            }
    
            table {
                font-size: 12px;
            }
    
            .panel-body {
                font-size: 12px;
            }
    
            .panel-body .form-control {
                font-size: 12px;
            }
        </style>
    {% endblock %}
    {% block content %}
        <div class="container-fluid" style="margin-top: 20px">
            <div class="col-sm-3 user-area">
                <div class="panel panel-default">
                    <div class="panel-heading"><i class="fa fa-user"></i> 用户信息</div>
                    <div class="panel-body">
                        <ul>
                            {% for user in user_list %}
                                <li class={% if user.id|safe == uid %}"active"{% endif %}>
                                    <a href="?uid={{ user.id }}">{{ user.name }}</a>
                                </li>
                            {% endfor %}
    
                        </ul>
                    </div>
                </div>
            </div>
            <div class="col-sm-3 role-area">
                <form action="" method="post">
                    {% csrf_token %}
                    <input type="hidden" name="postType" value="role">
                    <div class="panel panel-default">
                        <div class="panel-heading"><i class="fa fa-book"></i> 角色
                            {% if uid %}
                                <button type="submit" style="padding: 2px 6px;position: relative;top: -3px;"
                                        class="btn btn-success pull-right"><i style="margin-right: 2px"
                                                                              class="fa fa-save"></i> 保存
                                </button>
                            {% endif %}
                        </div>
                        <div class="panel-body">
                            <span style="color: darkgray">提示:点击用户后才能为其分配角色</span>
                        </div>
                        <table class="table table-hover">
                            <thead>
                            <tr>
                                <th>角色</th>
                                <th>选择</th>
                            </tr>
                            </thead>
                            <tbody>
                            {% load my_tag %}
                            {% for role in role_list %}
                                <tr {% if role.id|safe == rid %}class="active"{% endif %}>
                                    <td><a href="?{% get_role_url request role.id %}">{{ role.name }}</a></td>
                                    <td>
                                        {% if role.id in user_has_roles_dict %}
                                            <input type="checkbox" name="roles" value="{{ role.id }}" checked>
                                        {% else %}
                                            <input type="checkbox" name="roles" value="{{ role.id }}">
                                        {% endif %}
                                    </td>
                                </tr>
                            {% endfor %}
    
                            </tbody>
                        </table>
    
                    </div>
                </form>
            </div>
            <div class="col-sm-6 permission-area">
                <form action="" method="post">
                    {% csrf_token %}
                    <input type="hidden" name="postType" value="permission">
                    <div class="panel panel-default">
                        <div class="panel-heading"><i class="fa fa-reddit"></i> 权限分配</div>
                        {% if rid %}
                            <button type="submit"
                                    style="padding: 2px 6px;position: relative;top: -32px; margin-right: 10px;"
                                    class="btn btn-success pull-right"><i style="margin-right: 2px" class="fa fa-save"></i>
                                保存
                            </button>
                        {% endif %}
                        <div class="panel-body">
                            <span style="color: darkgray">提示:点击角色后,才能为其分配权限</span>
                        </div>
                        <table class="table">
                            <tbody class="permission-tbody">
                            {% for item in all_menu_list %}
                                <tr class="root">
                                    <td><i class="fa fa-caret-down"></i>{{ item.title }}</td>
                                </tr>
                                <tr class="node">
                                    <td>
                                        {% for node in item.children %}
                                            <div class="parent">
                                                {% if node.id in role_has_permissions_dict %}
                                                    <input id="permission_{{ node.id }}" name="permissions"
                                                           value="{{ node.id }}" type="checkbox" checked>
                                                {% else %}
                                                    <input id="permission_{{ node.id }}" name="permissions"
                                                           value="{{ node.id }}" type="checkbox">
                                                {% endif %}
                                                <label for="permission_{{ node.id }}">{{ node.title }}</label>
                                            </div>
                                            <div class="children">
                                                {% for child in node.children %}
                                                    <div class="child">
                                                        {% if child.id in role_has_permissions_dict %}
                                                            <input id="permission_{{ child.id }}" name="permissions"
                                                                   value="{{ child.id }}" type="checkbox" checked>
                                                        {% else %}
                                                            <input id="permission_{{ child.id }}" name="permissions"
                                                                   value="{{ child.id }}" type="checkbox">
                                                        {% endif %}
                                                        <label for="permission_{{ child.id }}">{{ child.title }}</label>
                                                    </div>
                                                {% endfor %}
                                            </div>
                                        {% endfor %}
                                    </td>
                                </tr>
                            {% endfor %}
                            </tbody>
                        </table>
    
                    </div>
                </form>
            </div>
        </div>
    {% endblock %}
    {% block js %}
        <script>
            $('.permission-tbody').on('click', '.root', function () {
                var caret = $(this).find('i');
                if (caret.hasClass('fa-caret-right')) {
                    caret.removeClass('fa-caret-right').addClass('fa-caret-down');
                    $(this).next('.node').removeClass('hide');
                } else {
                    caret.removeClass('fa-caret-down').addClass('fa-caret-right');
                    $(this).next('.node').addClass('hide');
                }
            })
        </script>
    
    {% endblock %}

    至此,页面大致开发完成,单独开发完成rbac之后,还得嵌入项目中去,大致也说一下迁移的过程:

    rbac应用于其他项目流程
    1.拷贝rbac到新项目
    2.在settings中注册rabc app
    3.数据库迁移
        首先先删除原有migrations下的文件,再执行数据库迁移命令
        python manage.py makemigrations
        python manage.py migrate
    4.在根目录下的urls.py中添加rbac相关的url
        re_path(r'^rbac/', include('rbac.urls', namespace='rbac'))
    5.layout.html的创建和编写,因为rbac中的模板都继承了这个
    6.录入权限信息
        角色管理
        权限管理
    7.分配权限
        先用户关联,原系统用户表一对一关联rbac用户表
        from rbac.models import User
        user = models.OneToOneField(User, null=True, blank=True, on_delete=models.CASCADE)
        给用户分角色和权限
    8.登录应用权限
        登录成功后获取rbac的user_obj,然后初始化用户信息
    
        应用权限校验中间件
    9.注意模板layout内容和名称
    
    10.应用面包屑导航栏,中间会遇到很多样式js等不同,慢慢调试吧
    
    11.权限控制到按钮级别
  • 相关阅读:
    SQLyog连接MySQL8.0报2058错误的完美解决方法
    WPF之Binding深入探讨未参考
    C# SQLite 创建数据库的方法增删查改语法和命令
    winform实现INotifyPropertyChanged
    排序算法
    GitHub代码上传
    SQLyog
    Jenkins 部署 .NET MVC 项目
    Visual Studio 2019 代码规范
    C# SqlHelper类
  • 原文地址:https://www.cnblogs.com/leixiaobai/p/11161102.html
Copyright © 2020-2023  润新知