django项目--权限分组管理

权限分组管理

一、权限分组列表

1. 接口设计

  1. 接口说明
类目 说明
请求方法 GET
url定义 /admin/groups/
参数格式 无参数
  1. 返回结果

    html

2. 后端代码

  1. 视图

    # 在myadmin/views.py中定义如下视图
    class GroupListView(View):
        """
        分组列表视图
        url:/admin/groups/
        """
        def get(self, request):
            groups = Group.objects.only('name').all()
    
            return render(request, 'myadmin/group/group_list.html', context={'groups': groups})
    
    
  2. 路由

    # 在myadmin/urls.py中添加如下路由
    path('groups/', views.GroupsView.as_view(), name='group_list')
    

3. 前端代码

  1. html

    <!-- 新建myadmin/group/group_list.html模板 -->
    {% extends 'admin/content_base.html' %}
    {% load static %}
    {% load news_template_filters %}
    {% block page_header %}
        系统设置
    {% endblock %}
    {% block page_option %}
        权限分组
    {% endblock %}
    {% block content %}
        <div class="box">
            <div class="box-header with-border">
                <h3 class="box-title">分组列表</h3>
                <div class="box-tools">
                </div>
            </div>
            <!-- /.box-header -->
    
            <div class="box-body">
                <div style="margin-bottom: 10px">
    
                </div>
    
                <table class="table table-bordered">
                    <tbody>
                    <tr>
                        <th>#</th>
                        <th>组名</th>
                        <th>菜单</th>
                    </tr>
                    {% for group in groups %}
                        <tr>
                            <td style="width: 40px" data-url="{% url 'admin:update_group' group.id %}"><a
                                    href="#">{{ forloop.counter }}</a></td>
                            <td>{{ group.name }}</td>
                            <td>
                                {% for permis in group.permissions.all %}
                                    {{ permis.name }}/
                                {% empty %}
                                    暂未分配权限
                                {% endfor %}
                            </td>
                        </tr>
                    {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
    {% endblock %}
    

二、权限分组详情页

1.接口设计

  1. 接口说明:
类目 说明
请求方法 GET
url定义 /admin/group/<int:group_id>/
参数格式 路径参数
  1. 参数说明:
参数名 类型 是否必须 描述
group_id 整数 分组id
  1. 返回数据

    返回html表单

2.后端代码

  1. 视图

    # 在myadmin/views.py中添加如下视图
    class GroupUdateView(View):
        """
        分组更新视图
        url: /admin/group/<int:group_id>/
        """
        def get(self, request, group_id):
            group = Group.objects.filter(id=group_id).first()
            if group:
                form = GroupModeForm(instance=group)
                permissions = group.permissions.only('id').all()
            else:
                form = GroupModeForm()
                permissions = []
            menus = models.Menu.objects.only('name', 'permission_id').select_related('permission').filter(is_delete=False, parent=None)
            return render(request, 'myadmin/group/group_detail.html', context={
                'form': form,
                'menus': menus,
                'permissions': permissions
            })
    
  2. 路由

    # 在admin/urls.py中添加如下路由
    path('group/<int:group_id>/', views.GroupUdateView.as_view(), name='update_group')
    
  3. 表单

    # 在 admin/forms.py中添加如下表单
    class GroupModeForm(forms.ModelForm):
        permissions = forms.ModelMultipleChoiceField(queryset=None, required=False,  help_text='权限', label='权限')
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.fields['permissions'].queryset = Permission.objects.filter(menu__is_delete=False)
    
        class Meta:
            model = Group
            fields = ['name', 'permissions']
    

3.前端代码

  1. html

    <!-- 创建模板myadmin/group/group_detail.html -->
    {% extends 'myadmin/base/content_base.html' %}
    {% load static %}
    {% load admin_customer_tags %}
    {% block page_header %}
        系统设置
    {% endblock %}
    {% block page_option %}
        权限分组
    {% endblock %}
    
    {% block content %}
        <div class="box box-primary">
            <div class="box-header with-border">
                <h3 class="box-title">分组详情</h3>
            </div>
            <!-- /.box-header -->
            <!-- form start -->
            <div class="box-body">
                <div class="row">
                    <div class="col-md-3"></div>
                    <div class="col-md-6">
                        <form class="form-horizontal">
                            {% csrf_token %}
    
                            {% for field in form %}
                                {% if field.name == 'permissions' %}
                                    <div class="form-group {% if field.errors %}has-error{% endif %}">
    
                                        <label for="{{ field.id_for_label }}"
                                               class="col-sm-2 control-label">{{ field.label }}</label>
    
                                        <div class="col-sm-10">
                                            {% for error in field.errors %}
                                                <label class="control-label"
                                                       for="{{ field.id_for_label }}">{{ error }}</label>
                                            {% endfor %}
                                            {% for menu in menus %}
                                                <div class="row" style="margin: 0">
                                                    <div class="checkbox">
                                                        <label for="menu_{{ menu.permission.id }}">
                                                            <input {% if menu.permission in permissions %}checked{% endif %} type="checkbox" name="permissions" id="menu_{{ menu.permission.id }}"
                                                                   value="{{ menu.permission.id }}">{{ menu.name }}
                                                        </label>
                                                    </div>
                                                    {% for child in menu.children.all %}
                                                        <div class="checkbox col-sm-offset-1">
                                                            <label for="menu_{{ child.permission.id }}">
                                                                <input type="checkbox" {% if child.permission in permissions %}checked{% endif %} name="permissions" id="menu_{{ child.permission.id }}"
                                                                       value="{{ child.permission.id }}">{{ child.name }}
                                                            </label>
                                                        </div>
                                                    {% endfor %}
    
                                                </div>
                                            {% endfor %}
    
                                        </div>
                                    </div>
                                {% else %}
                                    <div class="form-group {% if field.errors %}has-error{% endif %}">
    
                                        <label for="{{ field.id_for_label }}"
                                               class="col-sm-2 control-label">{{ field.label }}</label>
    
                                        <div class="col-sm-10">
                                            {% for error in field.errors %}
                                                <label class="control-label"
                                                       for="{{ field.id_for_label }}">{{ error }}</label>
                                            {% endfor %}
                                            {% add_class field 'form-control' %}
                                        </div>
                                    </div>
                                {% endif %}
                            {% endfor %}
    
    
                        </form>
                    </div>
                    <div class="col-md-3"></div>
                </div>
            </div>
            <div class="box-footer">
    
                <button type="button" class="btn btn-default back">返回</button>
                <button type="button" data-url="{% url 'myadmin:group_update' form.instance.id %}"
                        class="btn btn-primary pull-right save">保存
                </button>
    
    
            </div>
        </div>
    {% endblock %}
    
    
    
  2. js

    // 创建 js/myadmin/group/group_list.js
    $(()=>{
            // 分组详情
        $('tr').each(function () {
            $(this).children('td:first').click(function () {
                $('#content').load(
                    $(this).data('url'),
                    (response, status, xhr) => {
                        if (status !== 'success') {
                            message.showError('服务器超时,请重试!')
                        }
                    }
                );
            })
        });
    });
    

    记得再group_list.html中引用

三、权限分组修改

1.接口设计

  1. 接口说明:
类目 说明
请求方法 PUT
url定义 /admin/group/<int:group_id>/
参数格式 路径参数+表单参数
  1. 参数说明:
参数名 类型 是否必须 描述
group_id 整数 分组id
name 字符串 分组名称
permissions 整数 权限id
  1. 返回数据

    # 修改正常返回json数据
    {
    "errno": "0",
    "errmsg": "用户修改成功!"
    }
    

    如果有错误,返回html表单

2.后端代码

  1. 视图

    # 在myadmin/views.py的GroupUpdateView视图中添加put方法
    class GroupUpdateView(View):
        """
        分组更新视图
        url:/admin/group/<int:group_id>/
        """
        def get(self, request, group_id):
            # 1. 拿到要修改的分组
            group = Group.objects.filter(id=group_id).first()
            # 1.1 判断是否存不存在
            if not group:
                return json_response(errno=Code.NODATA, errmsg='没有此分组!')
            # 2.创建表单
            form = GroupModeForm(instance=group)
            # 3.拿到所有的可用一集菜单
            menus = Menu.objects.only('name', 'permission_id').select_related('permission').filter(is_delete=False,parent=None)
            # 4.拿到当前组的可用权限
            permissions = group.permissions.only('id').all()
            # 3.返回渲染html
            return render(request, 'myadmin/group/group_detail.html', context={
                'form': form,
                'menus': menus,
                'permissions': permissions
            })
    
        def put(self, request, group_id):
                # 1. 拿到要修改的分组
                group = Group.objects.filter(id=group_id).first()
                # 1.1 判断是否存不存在
                # 1.1 判断是否存不存在
                if not group:
                    return json_response(errno=Code.NODATA, errmsg='没有此分组!')
                # 2. 拿到前端传递的参数
                put_data = QueryDict(request.body)
                # 3. 校验参数
                # 3.1 创建表单对象
                form = GroupModeForm(put_data, instance=group)
                if form.is_valid():
                    # 4. 如果成功,保存数据
                    form.save()
                    return json_response(errmsg='修改分组成功!')
                else:
                    # 5. 如果失败
                    menus = Menu.objects.only('name', 'permission_id').select_related('permission').filter(is_delete=False,
                                                                                                           parent=None)
                    # 4.拿到当前组的可用权限
                    permissions = group.permissions.only('id').all()
                    return render(request, 'myadmin/group/group_detail.html', context={
                        'form': form,
                        'menus': menus,
                        'permissions': permissions
                    })
    

3.前端代码

  1. js

    # 创建 js/admin/group/group_detail.js
    $(() => {
        // 返回按钮
        $('.box-footer button.back').click(() => {
            $('#content').load(
                $('.sidebar-menu li.active a').data('url'),
                (response, status, xhr) => {
                    if (status !== 'success') {
                        message.showError('服务器超时,请重试!')
                    }
                }
            );
        });
        // 保存按钮
        $('.box-footer button.save').click(function () {
            // 将表单中的数据进行格式化
            $
                .ajax({
                    url: $(this).data('url'),
                    data: $('form').serialize(),
                    type: 'PUT'
                })
                .done((res) => {
                    if (res.errno === '0') {
                        message.showSuccess('修改分组成功!');
                        $('#content').load(
                            $('.sidebar-menu li.active a').data('url'),
                            (response, status, xhr) => {
                                if (status !== 'success') {
                                    message.showError('服务器超时,请重试!')
                                }
                            }
                        );
                    } else {
                        $('#content').html(res)
                    }
                })
                .fail((res) => {
                    message.showError('服务器超时,请重试!')
                })
        });
    
        // 复选框逻辑
        // 点击一级菜单,二级菜单联动
        // 注意要在一级菜单中class属性中加上one,二级菜单中加上two
        $('div.checkbox.one').each(function () {
            let $this = $(this);
            $this.find(':checkbox').click(function () {
    
                if($(this).is(':checked')){
                    $this.siblings('div.checkbox.two').find(':checkbox').prop('checked', true)
                }else{
                    $this.siblings('div.checkbox.two').find(':checkbox').prop('checked', false)
    
                }
            })
        });
    
        // 点击二级菜单,一级菜单联动
        $('div.checkbox.two').each(function () {
            let $this = $(this);
            $this.find(':checkbox').click(function () {
                if($(this).is(':checked')){
                    $this.siblings('div.checkbox.one').find(':checkbox').prop('checked', true)
                }else {
                    if(!$this.siblings('div.checkbox.two').find(':checkbox').is(':checked')){
                        $this.siblings('div.checkbox.one').find(':checkbox').prop('checked', false)
                    }
                }
            })
        });
    });
    

四、添加分组页面

1.接口设计

1.1接口说明:

类目 说明
请求方法 GET
url定义 /admin/group/
参数格式 无参数

1.2返回数据

返回html表单

2.后端代码

2.1 视图

# 在admin/views.py中添加如下视图
class GroupAddView(View):
    """
    添加分组视图
    """

    def get(self, request):
        form = GroupModeForm()
        menus = models.Menu.objects.only('name', 'permission_id').select_related('permission').filter(is_delete=False,
                                                                                                      parent=None)
        return render(request, 'admin/group/group_detail.html', context={'form': form, 'menus': menus})

2.2路由

# 在admin/urls.py中添加如下路由
path('group/', views.GroupAddView.as_view(), name='add_group'),

3.前端代码

3.1html

<!-- 在admin/group/group_list.html 中添加 添加group的按钮-->
            <div class="box-tools">
                <button type="button" class="btn btn-primary btn-sm"
                        data-url="{% url 'admin:add_group' %}">添加分组
                </button>
            </div>

3.2js

<!-- 在admin/group/group.js 中添加 添加group的按钮的js代码如下 -->
    // 添加分组
    $('.box-tools button').click(function () {
        $('#content').load(
                $(this).data('url'),
                (response, status, xhr) => {
                    if (status !== 'success') {
                        message.showError('服务器超时,请重试!')
                    }
                }
            );

    });

五、添加分组

1.接口设计

1.1接口说明:

类目 说明
请求方法 POST
url定义 /admin/group/
参数格式 表单参数

1.2参数说明:

参数名 类型 是否必须 描述
name 字符串 分组名称
permissions 整数 权限id

1.3返回数据

# 修改正常返回json数据
{
"errno": "0",
"errmsg": "添加分组成功!"
}

如果有错误,返回html表单

2.后端代码

2.1 视图

# 在admin/views.py中的GroupAddView视图中添加post方法如下
class GroupAddView(View):
    """
    添加分组视图
    """

    def get(self, request):
        form = GroupModeForm()
        menus = models.Menu.objects.only('name', 'permission_id').select_related('permission').filter(is_delete=False,
                                                                                                      parent=None)
        return render(request, 'admin/group/group_detail.html', context={'form': form, 'menus': menus})

    def post(self, request):
        form = GroupModeForm(request.POST)
        if form.is_valid():
            form.save()
            return json_response(errmsg='添加分组成功!')
        else:
            menus = models.Menu.objects.only('name', 'permission_id').select_related('permission').filter(
                is_delete=False,
                parent=None)
            return render(request, 'admin/group/group_detail.html', context={'form': form, 'menus': menus})

3.前端代码

3.1 html

<!-- 修改group_detail.html中的保存按钮代码 -->
                        <button type="button" {% if form.instance.id %}
                    data-url="{% url 'admin:update_group' form.instance.id %}"
                    data-type="PUT"
            {% else %}
                    data-url="{% url 'admin:add_group' %}"
                    data-type="POST"
            {% endif %}
                    class="btn btn-primary pull-right save">保存
            </button>

3.2 js

// 修改 group_detail.js中保存按钮的js代码如下
// 保存按钮
    $('.box-footer button.save').click(function () {
        // 将表单中的数据进行格式化
        $
            .ajax({
                url: $(this).data('url'),
                data: $('form').serialize(),
                type: $(this).data('type')
            })
            .done((res) => {
                if (res.errno === '0') {
                    message.showSuccess('修改分组成功!');
                    $('#content').load(
                        $('.sidebar-menu li.active a').data('url'),
                        (response, status, xhr) => {
                            if (status !== 'success') {
                                message.showError('服务器超时,请重试!')
                            }
                        }
                    );
                } else {
                    $('#content').html(res)
                }
            })
            .fail((res) => {
                message.showError('服务器超时,请重试!')
            })
    });

六、权限认证整合

1.业务需求

根据django内置的权限模块功能,可以很好的进行权限认证。但是本项目大量使用ajax,在进行权限认证时会遇到麻烦。且本项目的url设计符合RESTFUL api,所以在使用内置权限认证时也会出现问题。因此本项目对权限认证做了二次开发。

2.权限认证Mixin

  1. Mixin类
class MyPermissionRequiredMinxin(PermissionRequiredMixin):

    def has_permission(self):
        """
        覆盖父类方法,解决不同请求,权限不同的问题。
        """
        perms = self.get_permission_required()
        if isinstance(perms, dict):
            if self.request.method.lower() in perms:
                return self.request.user.has_perms(perms[self.request.method.lower()])
        else:
            return self.request.user.has_perms(perms)

    def handle_no_permission(self):
        """
        覆盖父类方法,解决ajax返回json数据的问题
        :return: 
        """
        if self.request.is_ajax():
            if self.request.user.is_authenticated:
                return json_response(errno=Code.ROLEERR, errmsg='您没有权限!' )
            else:
                return json_response(errno=Code.SESSIONERR, errmsg=
                                     '您未登录,请登录!', data={'url': reverse(self.get_login_url())})

        else:
            return super().handle_no_permission()

3.视图权限认证

使用方法和django提供的权限认证方法一致,新增同一个视图通过请求方式进行权限验证的功能。

# 
class MenuUpdateView(MyPermissionRequiredMinxin, View):
    """
    菜单管理视图
    url:/admin/menu/<int:menu_id>/
    """
    # 不同请求,对应不同的权限
    permission_required = {
        'get': ('admin.menu_update',),
        'put': ('admin.menu_update',),
        'delete': ('admin.menu_delete',),
    }
...

4.ajax接收处理

    // 编辑菜单
    $editBtns.click(function () {
        let $this = $(this);
        $currentMenu = $this.parent().parent();
        menuId = $this.parent().data('id');
        $
            .ajax({
                url: '/admin/menu/' + menuId + '/',
                type: 'get'
            })
            .done((res) => {
                if (res.errno === '4101') {
                    message.showError(res.errmsg);
                    setTimeout(() => {
                        window.location.href = res.data.url
                    }, 1500)
                } else if (res.errno === '4105') {
                    message.showError(res.errmsg)
                } else {
                    $('#modal-update .modal-content').html(res);
                    $('#modal-update').modal('show')
                }

            })
            .fail(() => {

                message.showError('服务器超时,请重试!')

            })
    });
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容