[django项目] 后台菜单管理功能

2023-11-02

后台菜单管理功能

菜单的管理功能其实就是, 对菜单的增删改查

I. 业务功能分析

1>业务需求分析

后台首页菜单根据用户权限动态生成,不同菜单对应不同的功能视图。

菜单的增删改查。

2>功能分析

  • 菜单列表
  • 添加菜单
  • 修改菜单
  • 删除菜单

3>模型设计

3.1>字段分析

  • name, 菜单名
  • url, 菜单的路由
  • parent, 父菜单的id
  • order, 排序
  • permission, 访问该菜单的权限名
  • icon, 菜单显示的icon
  • codename, 菜单的权限码
  • is_visible, 是否可见

3.2>模型定义

# 在myadmin/models.py中定义如下模型
from django.db import models
from django.contrib.auth.models import Permission

from utils.models import BaseModel
# Create your models here.


class Menu(BaseModel):
    name = models.CharField('菜单名', max_length=48, help_text='菜单名')
    url = models.CharField('url', max_length=256, null=True, blank=True, help_text='url')
    parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
    order = models.SmallIntegerField('排序', default=0)
    permission = models.OneToOneField(Permission, on_delete=models.SET_NULL, null=True)
    icon = models.CharField('图标', max_length=48, default='fa-link')
    codename = models.CharField('权限码', max_length=48, help_text='权限码', unique=True)
    is_visible = models.BooleanField('是否可见', default=False)

    class Meta:
        ordering = ['-order']
        db_table = 'tb_menu'
        verbose_name = '菜单'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

模型创建后记得迁移, 项目到目前的阶段, 数据库的内容开始变得比较复杂了, 所以该迁移的时候一定不要落下

II. 菜单列表

1>业务流程分析

  1. 获取未删除,的一级菜单
  2. 根据一级菜单获取未删除的二级菜单
  3. 渲染页面

2>接口设计

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

    html

3>后端代码

3.1>视图

#  myadmin/views.py下定义如下视图:
class MenuListView(View):
    """
    菜单列表视图
    url:/admin/menu_list/
    """
    def get(self, request):
        # 为了便于后续的修改, 需要展示被逻辑删除的菜单,
        # 因此filter的is_delete属性就不许要加了
        menus = models.Menu.objects.only(
            'name', 'url', 'icon', 'is_visible', 'order', 'codename'
        ).filter(parent=None)
        # parent=None表示没有父菜单, 即一级菜单
        return render(request, 'myadmin/menu/menu_list.html', context={'menus': menus})
#  myadmin/views.py中修改菜单管理对应的路由:
class IndexView(View):
    """
    后台首页视图
    """
    def get(self, request):
        menus = [
            {...},
            {...},
            {...},
            {...},
            {...},
            {
                "name": "系统设置",
                "icon": "fa-cogs",
                "children": [
                    {...},
                    {...},
                    {
                        "name": "菜单管理",
                        "url": "myadmin:menu_list"
                    },
                    {...}
                ]
            }

        ]
        return render(request, 'myadmin/index.html', context={'menus': menus})

3.2>路由

# admin/urls.py中添加如下路由
path('menu_list/', views.MenuListView.as_view(), name='menu_list'),

4>前端代码

4.1>html

咱们先简单的写一下前端, 然后去看一下页面的情况

<!-- 创建templates/myadmin/menu/menu_list.html-->
{% extends 'myadmin/base/content_base.html' %}
{% load static %}
{% block page_header %}系统设置{% endblock %}
{% block page_option %}菜单管理{% endblock %}

创建好后记得重启一下django的服务哦

页面效果:

[外链图片转存失败(img-U73ceLqz-1567554944282)(django%E9%A1%B9%E7%9B%AE-%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86.assets/1567311498712.png)]

可以打开这个界面, 就说明路由和视图配置好了

接下来将列表填充到这个页面, 我们可以使用AdminLTE为用户提供的表格模板:

[外链图片转存失败(img-ZlwaLcOP-1567554944283)(django%E9%A1%B9%E7%9B%AE-%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86.assets/1567311671862.png)]

这个页面上有很多类型的表格, 修改前端代码

{% extends 'myadmin/base/content_base.html' %}
{% load static %}
{% block page_header %}系统设置{% endblock %}
{% block page_option %}菜单管理{% endblock %}

{% block content %}
    <div class="box">
        <div class="box-header">
            <h3 class="box-title">菜单列表</h3>
            <div class="box-tools">
                <button type="button" class="btn btn-primary btn-sm">添加菜单
                </button>
            </div>
        </div>
        <!-- /.box-header -->
        <div class="box-body">
            <table class="table table-bordered ">
                <tbody>
                <tr role="row">
                    <!-- 列表字段 -->
                    <th>菜单</th>
                    <th>子菜单</th>
                    <th>路由地址</th>
                    <th>图标</th>
                    <th>权限码</th>
                    <th>顺序</th>
                    <th>是否可见</th>
                    <th>逻辑删除</th>
                    <th>操作</th>
                </tr>
                {% for menu in menus %}
                    <!-- 循环遍历列表内容 -->
                    <tr>
                    <!-- 遍历父菜单属性 -->
                        <td>{{ menu.name }}</td>
                        <td><!-- 子菜单为空 --></td>
                        <td>{{ menu.url|default:'' }}</td>
                        <td>{{ menu.icon }}</td>
                        <td>{{ menu.codename }}</td>
                        <td>{{ menu.order }}</td>
                        <td>
                            {% if menu.is_visible %}
                                是
                            {% else %}
                                否
                            {% endif %}
                        </td>
                        <td style="width: 100px" data-id="{{ menu.id }}" data-name="{{ menu.name }}">
                            {% if menu.children.all %}
                                <button type="button" class="btn btn-info btn-xs edit">编辑</button>
                            {% else %}
                                <button type="button" class="btn btn-info btn-xs edit">编辑</button>
                                <button type="button" class="btn btn-danger btn-xs delete">删除</button>
                            {% endif %}
                        </td>
                    </tr>
                    {% if menu.children.all %}
                        {% for child in menu.children.all %}
                            <!-- 遍历子菜单属性 -->
                            <tr>
                                <td><!-- 父菜单为空 --></td>
                                <td>{{ child.name }}</td>
                                <td>{{ child.url }}</td>
                                <td>{{ child.icon }}</td>
                                <td>{{ child.codename }}</td>
                                <td>{{ child.order }}</td>
                                <td style="width: 80px">
                                    {% if child.is_visible %}
                                        是
                                    {% else %}
                                        否
                                    {% endif %}
                                </td>
                                <td style="width: 100px" data-id="{{ child.id }}" data-name="{{ child.name }}">
                                    <button type="button" class="btn btn-info btn-xs edit">编辑</button>
                                    <button type="button" class="btn btn-danger btn-xs delete">删除</button>
                                </td>
                            </tr>
                        {% endfor %}
                    {% endif %}
                {% endfor %}
                </tbody>
            </table>
        </div>
        <!-- /.box-body -->
    </div>
{% endblock %}

注意父菜单和子菜单循环体中的那两个<td></td>标签, 这样做得目的是为了更好的分辨父菜单和子菜单

III. 添加菜单页面

功能概述: 点击添加菜单按钮, 弹出新增菜单窗口, 输入信息提交, 即可添加到菜单列表中

1>接口设计

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

    html

2>后端代码

2.1>视图

# 在myadmin/views.py中添加如下视图
class MenuAddView(View):
    """
    添加菜单视图
    url:/admin/menu/
    """

    def get(self, request):

        form = MenuModelForm()
        return render(request, 'myadmin/menu/add_menu.html', context={'form': form})

2.2>路由

# 在myadmin/urls.py中添加如下路由
path('menu/', views.MenuAddView.as_view(), name='add_menu')

2.3>表单

# 在myadmin/forms.py中定义如下表单
from django import forms

from .models import Menu


class MenuModelForm(forms.ModelForm):
    parent = forms.ModelChoiceField(queryset=None, required=False, help_text='父菜单')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['parent'].queryset = Menu.objects.filter(is_delete=False, is_visible=True, parent=None)
        # https://docs.djangoproject.com/en/2.2/ref/forms/fields/#fields-which-handle-relationships

    class Meta:
        model = Menu
        fields = ['name', 'url', 'order', 'parent', 'icon', 'codename', 'is_visible']

2.4>自定义标签

为了在渲染表单是能加入自定义css样式,在应用admin中定义自定义标签,在admin下创建templatetags包,在其中创建admin_customer_tags.py模块

# myadmin/tamplatetags/admin_customer_tags.py
from django.template import Library

register = Library()


@register.simple_tag()
def add_class(field, class_str):
    return field.as_widget(attrs={'class': class_str})

3>前端代码

3.1>html

<!-- 修改 templates/myadmin/menu/menu_list.html -->
{% extends 'myadmin/base/content_base.html' %}
{% load static %}
{% block page_header %}系统设置{% endblock %}
{% block page_option %}菜单管理{% endblock %}
{% block content %}
    <div class="box">
        <div class="box-header with-border">...</div>
        <!-- /.box-header -->
        <div class="box-body">...</div>
    </div>

    <!-- add modle -->
    <div class="modal fade" id="modal-add" role="dialog" >
        <div class="modal-dialog">
            <div class="modal-content">

            </div>
            <!-- /.modal-content -->
        </div>
        <!-- /.modal-dialog -->
    </div>
    <!-- /.modal -->

{% endblock %}

弹出窗口我们选用表单的形式来完成, 因此这里单独创建一个模型, 方便渲染

这里的模型来自Bootstarp

   <!-- 新建 templates/myadmin/menu/add_menu.html -->
   {% load admin_customer_tags %}
   {% load static %}
   <div class="modal-header">
       <button type="button" class="close" data-dismiss="modal" aria-label="Close">
           <span aria-hidden="true">&times;</span></button>
       <h4 class="modal-title">添加菜单</h4>
   </div>
   <div class="modal-body">
       <form class="form-horizontal" id="add-menu">
           {% csrf_token %}
           <div class="box-body">
               {% for field in form %}
                   {% if field.name == 'is_visible' %}
                       <div class="form-group">
   
                           <div class="col-sm-offset-2 col-sm-10">
   
                               <div class="checkbox">
                                   <label for="{{ field.id_for_label }}">{{ field }}{{ field.label }}</label>
                               </div>
                           </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 %}
   
           </div>
       </form>
   </div>
   <div class="modal-footer">
       <button type="button" class="btn btn-default pull-left" data-dismiss="modal">取消</button>
       <button type="button" class="btn btn-primary add">添加</button>
   </div>


根据官方文档我们还需要加一个href=#foo(路由地址)属性到添加菜单按钮标签中

<div class="box-tools">
	<button type="button" class="btn btn-primary btn-sm" 
            data-toggle="modal" data-target="#modal-add" 
            href="/admin/add_menu/">添加菜单</button>
</div>


页面效果:

[外链图片转存失败(img-QtGhDXrh-1567554944294)(django%E9%A1%B9%E7%9B%AE-%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86.assets/)]

IIII. 添加菜单

1>业务流程分析

  • 接收表单参数
  • 校验表单参数
  • 校验成功保存菜单数据,创建菜单一对一关联权限对象,返回创建成功的json数据
  • 校验失败,返回渲染了错误信息的表单

2>接口设计

2.1>接口说明:

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

2.2>参数说明:

参数名 类型 是否必须 描述
name 字符串 菜单名
url 字符串 当前文章页数
order 整数 排序
parent 整数 父菜单id
icon 字符串 渲染图标类名
codename 字符串 权限码
is_visible 整数 是否可见

2.3>返回数据

# 添加正常返回json数据
{
"errno": "0",
"errmsg": "菜单添加成功!"
}

如果有错误,返回html表单

3>后端代码

3.1>视图

# 在myadmin/views.py中的MenuAddView视图中添加post方法
class MenuAddView(View):
    """
    添加菜单视图
    url:/admin/menu/
    """

    def get(self, request):
        form = MenuModelForm()
        return render(request, 'myadmin/menu/add_menu.html', context={'form': form})

    def post(self, request):
        form = MenuModelForm(request.POST)
        
        if form.is_valid():
            new_menu = form.save()
            content_type = ContentType.objects.filter(app_label='myadmin', model='menu').first()
            permission = Permission.objects.create(name=new_menu.name, content_type=content_type, codename=new_menu.codename)
            new_menu.permission = permission
            new_menu.save(update_fields=['permission'])
            return json_response(errmsg='菜单添加成功!')
        else:
            return render(request, 'myadmin/menu/add_menu.html', context={'form': form})

4>前端代码

4.1>js

// 创建static/js/myadmin/menu/add_menu.js
$(() => {
    let $addBtn = $('button.add');          //  模态框中的添加按钮
    let $form = $('#add-menu');             //  模态矿中的表单
    let data = {};
    $addBtn.click(function () {

        $
            .ajax({
                url: '/admin/menu/',
                type: 'POST',
                data: $form.serialize(),
                // dataType: "json"
            })
            .done((res) => {
                if (res.errno === '0') {
                    // 添加成功,关闭模态框,并刷新菜单列表
                    $('#modal-add').modal('hide').on('hidden.bs.modal', function (e) {
                        $('#content').load(
                            $('.sidebar-menu li.active a').data('url'),
                            (response, status, xhr) => {
                                if (status !== 'success') {
                                    message.showError('服务器超时,请重试!')
                                }
                            }
                        );
                    });
                    message.showSuccess(res.errmsg);


                } else {
                    message.showError('添加菜单失败!');
                    // 更新模特框中的表单信息
                    $('#modal-add .modal-content').html(res)
                }
            })
            .fail(() => {
                message.showError('服务器超时,请重试');
            });
    });

});

4.2>html

<!-- 在 templates/myadmin/menu/add_menu.html 中引入js -->
...
<script src="{% static 'js/myadmin/menu/add_menu.js' %}"></script>

V. 删除菜单

1>接口设计

1.1>接口说明:

类目 说明
请求方法 DELETE
url定义 /admin/menu/<int:menu_id>/
参数格式 路径参数

1.2>参数说明

参数名 类型 是否必须 描述
menu_id 整数 菜单id

1.3>返回值

{
"errno": "0",
"errmsg": "删除菜单成功!"
}


后台展示的菜单不重要,所以我们不用逻辑删除菜单,而是采用真删除

2>后端代码

2.1>视图

# 在admin/views.py中创建一个MenuUpdateView视图
class MenuUpdateView(View):
    """
    菜单管理视图,delete删除菜单
    url:/admin/menu/<int:menu_id>/
    """
    def delete(self, request, menu_id):
        # 获取到需要删除的菜单
        menu = models.Menu.objects.only('name').filter(id=menu_id)

        if menu:
            menu = menu[0]
            # 判断是是否为父菜单
            if menu.children.filter(is_delete=False).exists():
                return json_response(errno=Code.DATAERR, errmsg='父菜单不能删除!')
            # 将menu模型中的permission设置为CASCADE级联删除, 就可以使其在被删除的时候同时删除当条菜单
            menu.permission.delete()
            # menu.delete()
            return json_response(errmsg='删除菜单:%s成功' % menu.name)
        else:
            return json_response(errno=Code.NODATA, errmsg='菜单不存在!')



2.2>路由

# 在admin/urls.py中添加如下路由
path('menu/<int:menu_id>/', views.MenuUpdateView.as_view(), name='menu_manage'),


3>前端代码

3.1>html

<!-- 
修改 templates/admin/menu/menu_list.html 
在content中,添加删除模态框
然后引入menu.js
-->
{% block content %}
...
...
    <div class="modal modal-danger fade" id="modal-delete">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">&times;</span></button>
                    <h4 class="modal-title">警告</h4>
                </div>
                <div class="modal-body">
                    <p>One fine body&hellip;</p>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-outline pull-left" data-dismiss="modal">取消</button>
                    <button type="button" class="btn btn-outline delete-confirm">删除</button>
                </div>
            </div>
            <!-- /.modal-content -->
        </div>
        <!-- /.modal-dialog -->
    </div>
    <!-- /.modal -->
{% endblock %}
{% block script %}
    <script src="{% static 'js/admin/menu/menu_list.js' %}"></script>
{% endblock %}

记得再去content_base里挖一个script的block

3.2>js

// 创建 static/js/admin/menu/menu_list.js
$(() => {
    let $deleteBtns = $('button.delete');	// 删除按钮
    menuId = 0;		// 被点击菜单id
    let $currentMenu = null;	// 当前被点击菜单对象, 也就是对应的tr标签(表格中的一行)

    $deleteBtns.click(function () {
        let $this = $(this);
        $currentMenu = $this.parent().parent();
        menuId = $this.parent().data('id');	// 菜单id
        let menuName = $this.parent().data('name');	// 菜单名
        
        // 改变模态框的显示内容
        $('#modal-delete .modal-body p').html('确定删除菜单: " + menuName + " ?');
        
        // 显示 模态框
        $('#modal-delete').modal('show');

    });

    $('#modal-delete button.delete-confirm').click(() => {
        deleteMenu()
    });

    // 删除菜单的函数
    function deleteMenu() {
        $
            .ajax({
                url: '/admin/menu/' + menuId + '/',
                type: 'DELETE',
                dataType: 'json'
            })
            .done((res) => {
                if (res.errno === '0') {	// errno为0代表成功
                    // 关闭模态框
                    $('#modal-delete').modal('hide');
                    // 删除菜单元素
                    $currentMenu.remove();
                    message.showSuccess('删除成功!');
                } else {	// 否则就表示出现了问题
                    message.showError(res.errmsg)
                }
            })
            .fail(() => {
                message.showError('服务器超时请重试!')
            })
    }
});

因为delete方法会改变数据库,所以需要csrftoken,在js/myadmin/menu.js中添加如下代码

   function getCookie(name) {
           var cookieValue = null;
           if (document.cookie && document.cookie !== '') {
               var cookies = document.cookie.split(';');
               for (var i = 0; i < cookies.length; i++) {
                   var cookie = cookies[i].trim();
                   // Does this cookie string begin with the name we want?
                   if (cookie.substring(0, name.length + 1) === (name + '=')) {
                       cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                       break;
                   }
               }
           }
           return cookieValue;
       }
   
       function csrfSafeMethod(method) {
           // these HTTP methods do not require CSRF protection
           return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
       }
   
       $.ajaxSetup({
           beforeSend: function (xhr, settings) {
               if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                   xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
               }
           }
       });

记得前端要加上{% csrf_token %}, 关于cookie还有一点就是

如果你先执行添加菜单再执行删除的话, 即使不加{% csrf_token %}也会执行删除

这是因为执行添加的话就会生成cookie, 已经有了当前页面的cookie, 其他需要cookie的请求(post,put,delete)都可以执行

VI. 编辑菜单页面

1>接口设计

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

    html

2>后端代码

# 在admin/views.py中的MenuUpdateView视图中添加一个get方法
class MenuUpdateView(View):
    """
    菜单管理视图
    url:/admin/menu/<int:menu_id>/
    """
    
    def get(self, request, menu_id):
        # 找到需要编辑的菜单
        menu = models.Menu.objects.filter(id=menu_id).first()
        # 使用之前定义的表单
        form = MenuModelForm(instance=menu)
        return render(request, 'myadmin/menu/update_menu.html', context={'form': form})

3>前端代码

3.1>html

<!-- 
修改 templates/admin/menu/menu_list.html 
在content中,添加修改模态框
-->
{% block content %}
	...
    <!-- update modle -->
    <div class="modal fade" id="modal-update" role="dialog" aria-labelledby="myLargeModalLabel">
        <div class="modal-dialog">
            <div class="modal-content">

            </div>
            <!-- /.modal-content -->
        </div>
        <!-- /.modal-dialog -->
    </div>
    <!-- /.modal -->
{% endblock %}

编辑的模态框其实和添加模态框很相似, 是可以进行代码复用的, 这个我们后面再说, 先完成功能

   <!-- 新建 templates/admin/menu/update_menu.html -->
   {% load admin_customer_tags %}
   {% load static %}
   <div class="modal-header">
       <button type="button" class="close" data-dismiss="modal" aria-label="Close">
           <span aria-hidden="true">&times;</span></button>
       <h4 class="modal-title">修改菜单</h4>
   </div>
   <div class="modal-body">
       <form class="form-horizontal" id="update-menu">
           {% csrf_token %}
           <div class="box-body">
               {% for field in form %}
                   {% if field.name == 'is_visible' %}
                       <div class="form-group">
   
                           <div class="col-sm-offset-2 col-sm-10">
   
                               <div class="checkbox">
                                   <label for="{{ field.id_for_label }}">{{ field }}{{ field.label }}</label>
                               </div>
                           </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 %}
   
           </div>
       </form>
   </div>
   <div class="modal-footer">
       <button type="button" class="btn btn-default pull-left" data-dismiss="modal">取消</button>
       <button type="button" class="btn btn-primary update">修改</button>
   </div>

3.2>js

// 修改 static/js/admin/menu/menu_list.js 
$(() => {
    let $editBtns = $('button.edit');           // 编辑按钮
    let $deleteBtns = $('button.delete');       // 删除按钮
    menuId = 0;      // 被点击菜单id,要设置为全局变量, 不加let就行
    let $currentMenu = null;                    // 当前被点击菜单对象

    $deleteBtns.click(function () {...});

    $('#modal-delete button.delete-confirm').click(() => {...});

    // 删除菜单的函数
    function deleteMenu() {...}

    // 编辑菜单
    $editBtns.click(function () {
        let $this = $(this);
        $currentMenu = $this.parent().parent();
        menuId = $this.parent().data('id');

        $
            .ajax({
                url: '/admin/menu/' + menuId + '/',
                type: 'GET'
            })
            .done((res)=>{	// res是ajax请求回来的数据
                // 改变模态框的html
                $('#modal-update .modal-content').html(res);
                // 显示模态框
                $('#modal-update').modal('show')
            })
            .fail(()=>{
                message.showError('服务器超时,请重试!')
            })
    })
});

VII. 编辑菜单

完成了页面, 接下来就是点击修改, 再发送一个ajax回去, 我们需要再写一个后台接收编辑数据

1>业务流程分析

  • 接收表单参数
  • 校验表单参数
  • 校验成功, 保存菜单,判断改动字段是否影响了权限,如果有影响,修改权限,返回json信息
  • 校验失败,返回包含错误信息的html

2>接口设计

  1. 接口说明:
类目 说明
请求方法 PUT
url定义 /admin/menu/<int:menu_id>
参数格式 路径参数+表单参数
  1. 参数说明:
参数名 类型 是否必须 描述
menu_id 整数 菜单id
name 字符串 菜单名
url 字符串 当前文章页数
order 整数 排序
parent 整数 父菜单id
icon 字符串 渲染图标类名
codename 字符串 权限码
is_visible 整数 是否可见
  1. 返回数据

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

    如果有错误,返回html表单

3>后端代码

3.1>视图

# 在admin/views.py中的MenuUpdateView视图中添加一个put方法
class MenuUpdateView(View):
    """
    菜单管理视图
    url:/admin/menu/<int:menu_id>/
    """
...

    def put(self, request, menu_id):
        # 获取到需要修改的菜单
        menu = models.Menu.objects.filter(id=menu_id).first()
        if not menu:
            return json_response(errno=Code.NODATA, errmsg='菜单不存在!')
        # 获取put请求的数据, request.body就是前端表单中所有的输入
        put_data = QueryDict(request.body)
        # QueryDict这个方法会将body转换成一个类字典对象
        form = MenuModelForm(put_data, instance=menu)
        if form.is_valid():
            obj = form.save()	# 拿到一个新的menu对象,命名为obj
            # 检查修改了的字段是否和权限有关
            flag = False
            if 'name' in form.changed_data:
                # changed_data会返回一个列表,包含当前修改了的字段
                # 将已有的name更新为最新的
                obj.permission.name = obj.name
                flag = True
            if 'codename' in form.changed_data:
                obj.permission.codename = obj.name
                flag = True
            if flag:
                obj.permission.save()
            return json_response(errmsg='菜单修改成功!')
        else:
            return render(request, 'admin/menu/update_menu.html', context={'form': form})
...

4>前端代码

4.1>html

<!-- 在 templates/myadmin/menu/update_menu.html 
中引入update_menu.js
-->
...
<script src="{% static 'js/myadmin/menu/update_menu.js' %}"></script>

4.2>js

// 这里的代码跟添加的js写法很相似,又是一个代码复用点
$(()=>{
    let $updateBtn = $('#modal-update button.update');
    let $form = $('#update-menu');
    
    $updateBtn.click(function () {
        $
            .ajax({
                url: '/admin/menu/' + menuId + '/',
                type: 'PUT',
                data: $form.serialize(),
                // dataType: "json"
            })
            .done((res) => {
                if (res.errno === '0') {
                    // 关闭模态框
                    $('#modal-update').modal('hide').on('hidden.bs.modal', function (e) {
                        $('#content').load(
                            $('.sidebar-menu li.active a').data('url'),
                            (response, status, xhr) => {
                                if (status !== 'success') {
                                    message.showError('服务器超时,请重试!')
                                }
                            }
                        );
                    });
                    message.showSuccess(res.errmsg);
                } else {
                    message.showError('修改菜单失败!');
                    // 修改模态框的内容为, 后端返回的带有错误信息的表单
                    $('#modal-update .modal-content').html(res)
                }
            })
            .fail(() => {
                message.showError('服务器超时,请重试');
            });
    });
});

VIII. 整合后台首页面菜单加载

我们来填之前菜单列表的坑, 之前我们使用list写的硬编码, 我们来到数据库中拿可用的菜单

1>后端代码

1.1>视图

class IndexView(LoginRequiredMixin, View):
    """
    后台首页视图
    """

    def get(self, request):
		
        objs = models.Menu.objects.only('name', 'url', 'icon', 'permission__codename',
                                        'permission__content_type__app_label').select_related(
            'permission__content_type').filter(is_delete=False, is_visible=True, parent=None)
        has_permissions = request.user.get_all_permissions()
        menus = []
        for menu in objs:
            if '%s.%s' % (menu.permission.content_type.app_label, menu.permission.codename) in has_permissions:
                temp = {
                    'name': menu.name,
                    'icon': menu.icon
                }

                children = menu.children.filter(is_delete=False, is_visible=True)
                if children:
                    temp['children'] = []
                    for child in children:
                        if '%s.%s' % (child.permission.content_type.app_label, child.permission.codename) in has_permissions:
                            temp['children'].append({
                                'name': child.name,
                                'url': child.url
                            })
                else:
                    if not menu.url:
                        continue
                    temp['url'] = menu.url
                menus.append(temp)
        print(menus)
     return render(request, 'admin/index.html', context={'menus': menus})

再运行之前, 一定要先添加一个菜单管理, 这样才可以看到我们的菜单列表效果

但是有一个bug, 当我们添加了一个新菜单之后, 虽然可以展示出来, 但是点击访问时就会提示我们没有定义路由,

这个可以设计一个自定义标签, 使用异常处理的方式, 若使用的使错误的路由, 就展示wait界面, 代码如下:

# 在myadmin/templatetags/admin_customer_tags.py中添加如下过滤器
@register.simple_tag()
def my_url(pattern, *args):
    try:
        url = reverse(pattern, *args)
    except Exception as e:
        url = reverse('myadmin:wait')
    return url

然后在前端代码中引用:

<!-- 在templates/myadmin/index.html中修改如下代码 -->
            <!-- Sidebar Menu -->
            <ul class="sidebar-menu" data-widget="tree">
                {% for menu in menus %}
                    {% if 'children' in menu %}
                        <li class="treeview">
                            <a href="#"><i class="fa {{ menu.icon }}"></i> <span>{{ menu.name }}</span>
                                <span class="pull-right-container">
                                    <i class="fa fa-angle-left pull-right"></i>
                                </span>
                            </a>
                            <ul class="treeview-menu">
                                {% for child in menu.children %}
                                    <li>
                                        <a href="#" data-url="{% my_url child.url  %}"><!-- 使用my_url过滤器 -->
                                            {{ child.name }}
                                        </a>
                                    </li>
                                {% endfor %}
                            </ul>
                        </li>
                    {% else %}
                        <li><a href="#" data-url="{% my_url menu.url %}"><!-- 使用my_url过滤器 -->
                            <i class="fa {{ menu.icon }}"></i>
                            <span>{{ menu.name }}</span></a></li>
                    {% endif %}
                {% endfor %}
            </ul>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[django项目] 后台菜单管理功能 的相关文章

随机推荐

  • 双系统grub引导时,出现error: file '/boot/vmlinuz-xxx-xxx-xxx' not found的解决方法

    晚上本想修改一下grub cfg文件 去掉多余的引导项 没成想出现如题之尴尬局面 一番度娘之后终解决 下面是解决方法 Note 在grub下 注意提示按c进入grub命令行模式 首先需要确定你的ubutun安装到哪个硬盘哪个区 1 1 可以
  • 从零开始搭建物联网平台(三)数模设计

    首先 我们先从数模设计开始 这是一个系统的核心和精髓 决定了系统的架构和扩展控件 ETCloud的数模共有22张表 三个模块 一 用户模块 用户模块主要由企业表 用户表 角色表 权限表构成 1 数据权限 用户共分为三大类 model use
  • 力扣算法题解析

    一 278题 第一个错误的版本 难度 简单 1 题目描述 你是产品经理 目前正在带领一个团队开发新的产品 不幸的是 你的产品的最新版本没有通过质量检测 由于每个版本都是基于之前的版本开发的 所以错误的版本之后的所有版本都是错的 假设你有 n
  • stm32Cubemx:串口的空闲中断和接收中断------stm32f407zgt6

    介绍串口的接收中断与空闲中断 这两种中断都是在串口进行接收时可能会发生的中断 串口接收中断即每当串口完成一次接收之后触发一次中断 在 STM32 中相应的中断处理函数为 USARTx IRQHandler 中断回调函数为 HAL UART
  • 2023数据要素专题研究报告

    数据要素是实际参与社会生产活动的 完成确权的数据资源 数字化的文本 图形等数据经过权属确定后即为数据资产 再经过数据处理 分析后 形成数据产品并参与社会生产经营 对其他生产要素赋能并发挥数据自身价值 完成数据的价值闭环 根据国务院印发的 关
  • Neo4j CQl语句(持续更新)

    1 清空所有数据 MATCH n OPTIONAL MATCH n r DELETE n r 2 删除一个节点及其所有的关系 MATCH r WHERE id r 11 DETACH DELETE r 3 删除一个节点 DELETE 通过属
  • Mac Safari 此连接非私人连接

    1 问题 连接公司vpn的时候 Mac弹出此连接非私人连接 点击访问此网站后输入密码将证书手动设为可信后 又弹出了此连接非私人连接 之后进入了无限循环无论怎样都无法访问该网页 2 解决方案 2 1 点击页面上查看此证书 记住证书名字 可以看
  • 自制GUI

    包含了 sqlmap GUI Xray GUI dirmap GUI
  • selenium测试框架快速搭建(UI自动化测试)

    一 介绍 selenium目前主流的web自动化测试框架 支持多种编程语言Java pythan go js等 selenium 提供一系列的api 供我们使用 因此在web测试时我们要点页面中的某一个按钮 那么我们只需要获取页面 然后根据
  • js控制获得焦点与失去焦点样式

    function focusInput focusClass normalClass var elements document getElementsByTagName input for var i 0 i lt elements le
  • Vue项目保存代码之后页面自动更新

    Vue项目保存代码之后页面自动更新 想要在代码中保存之后 页面自动刷新 命令行敲如下代码 npm install webpack dev server 下载了这个东西就不用每次都手动刷新了 我也不知道这个是干嘛的 留在以后研究研究
  • Chromium OS autotest

    autotest三种主要测试手段 直接调用系统命令 相当于直接运行shell命令 通过dbus进行method call 通过加载插件到browser的方式 运行js代码 以js代码来调用C 方法 通过extension来运行js代码 目的
  • XSS闯关——第五关:level5

    第五关 level5 输入语句测试 gt 观察源代码发现字符被替换 把部分字符换成大写尝试 gt 一样的结果 采用html事件方法 失败 同样是字符被替换 使用伪链接方式假造一个超链接尝试 gt a href link a 点击后执行脚本
  • Laravel Collection 常用方法(1)

    我的个人博客 逐步前行STEP 1 first 返回集合第一个通过指定测试的元素 collect 1 2 3 4 gt first 1 collect 1 2 3 4 gt first function value key return v
  • 深度学习deep learning

    一 简介 深度学习是包含多个隐层的机器学习模型 核心是基于训练的方式 从海量数据中挖掘有用信息 实现分类与预测 早期的深度学习模型 编码器 循环神经网络 深度置信网络 卷积神经网络 衍生模型 堆叠降噪自编码器 稀疏自编码器 降噪自编码器 深
  • mysql 集成测试_使用Go进行集成测试的MySQL Docker容器

    使用Go进行集成测试的MySQL Docker容器 原文链接 https itnext io mysql docker container for integration testing using go f784b70a03b 作者 Mi
  • 【Linux】在Xilinx平台上实现UVC Gadget(2)- 解决dwc3驱动bug

    Linux 在Xilinx平台上实现UVC Gadget 2 解决dwc3驱动bug 一 bug描述 二 具体修改方法 1 找到内核源码位置并复制到其他目录 2 Petalinux里面设置使用自定义内核源码 1 选第2个Linux Comp
  • 数列分段

    描述 对于给定的一个长度为N的正整数数列A i 现要将其分成M M N 段 并要求每段连续 且每段和的最大值最小 关于最大值最小 例如一数列4 2 4 5 1要分成3段 将其如下分段 4 2 4 5 1 第一段和为6 第2段和为9 第3段和
  • MySQL查询语句的执行顺序

    SQL语句执行顺序 FROM ON JOIN WHERE GROUP BY AGG FUNC WITH HAVING SELECT UNION DISTINCT ORDER BY LIMIT 在实际执行过程中 每个步骤都会为下一个步骤生成一
  • [django项目] 后台菜单管理功能

    后台菜单管理功能 菜单的管理功能其实就是 对菜单的增删改查 I 业务功能分析 1 gt 业务需求分析 后台首页菜单根据用户权限动态生成 不同菜单对应不同的功能视图 菜单的增删改查 2 gt 功能分析 菜单列表 添加菜单 修改菜单 删除菜单