django项目--菜单管理

后台菜单管理功能

一、业务功能分析

1. 业务需求分析

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

2.功能分析

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

3.模型设计

  1. 字段分析
    • name
    • url
    • parent
    • order
    • permission
    • icon
    • codename
    • is_visible
  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

二、菜单列表

1.业务流程分析

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

2.接口设计

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

    html

3.后端代码

  1. 视图
#  myadmin/views.py下定义如下视图:
class MenuListView(View):
    """
    菜单列表视图
    url:/admin/menus/
    """

    def get(self, request):
        menus = models.Menu.objects.only('name', 'url', 'icon', 'is_visible', 'order', 'codename').filter(is_delete=False, parent=None)

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

2 . 路由

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

4.前端代码

1.html


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

菜单列表

{% for menu in menus %} {% if menu.children.all %} {% for child in menu.children.all %} {% endfor %} {% endif %} {% endfor %}
菜单 子菜单 url 图标 权限码 顺序 是否可见 逻辑删除 操作
{{ menu.name }} {{ menu.url|default:'' }} {{ menu.icon }} {{ menu.codename }} {{ menu.order }} {% if menu.is_visible %}是{% else %}否{% endif %} {% if menu.children.all %} {% else %} {% endif %}
{{ child.name }} {{ child.url }} {{ child.icon }} {{ child.codename }} {{ child.order }} {% if child.is_visible %}是{% else %}否{% endif %}
{% endblock %}

三、添加菜单页面

1. 接口设计

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

    html

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. 路由

    # 在myadmin/urls.py中添加如下路由
    path('menu/', views.MenuAddView.as_view(), name='add_menu')
    
  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']
    

    为了在渲染表单是能加入自定义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.前端代码

1. html

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

菜单列表

{% for menu in menus %} {% if menu.children.all %} {% for child in menu.children.all %} {% endfor %} {% endif %} {% endfor %}
菜单 子菜单 url 图标 权限码 顺序 是否可见 操作
{{ menu.name }} {{ menu.url|default:'' }} {{ menu.icon }} {{ menu.codename }} {{ menu.order }} {% if menu.is_visible %}是{% else %}否{% endif %} {% if menu.children.all %} {% else %} {% endif %}
{{ child.name }} {{ child.url }} {{ child.icon }} {{ child.codename }} {{ child.order }} {% if child.is_visible %}是{% else %}否{% endif %}
{% endblock %}
   
   {% load admin_customer_tags %}
   {% load static %}
   
   
   

四、添加菜单

1.业务流程分析

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

2.接口设计

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

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

    如果有错误,返回html表单

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.前端代码

  1. html

    
    ...
    
    
  2. 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('服务器超时,请重试');
                });
        });
    
    });
    

五、删除菜单

1.接口设计

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

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

2.后端代码

  1. 视图

    # 在admin/views.py中创建一个MenuUpdateView视图
    class MenuUpdateView(View):
        """
        菜单管理视图
        url:/admin/menu//
        """
    
        def delete(self, request, menu_id):
            menu = models.Menu.objects.filter(is_delete=False, id=menu_id)
            if menu:
                menu = menu[0]
                if menu.children.filter(is_delete=False).exists():
                    return json_response(errno=Code.DATAERR, errmsg='父菜单不能删除!')
                menu.is_delete = True
                menu.save(update_fields=['is_delete'])
                return json_response()
            else:
                return json_response(errno=Code.NODATA, errmsg='菜单不存在!')
    
    
  2. 路由

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

3.前端代码

  1. html

    
    {% block content %}
    ...
    ...
        
        
    {% endblock %}
    {% block script %}
        
    {% endblock %}
    
  2. js

    // 创建 static/js/admin/menu/menu.js
    $(() => {
        let $editBtns = $('button.edit');
        let $deleteBtns = $('button.delete');       // 删除按钮
        menuId = 0;                                 // 被点击菜单id
        let $currentMenu = null;                    // 当前被点击菜单对象
    
        $deleteBtns.click(function () {
            let $this = $(this);
            $currentMenu = $this.parent().parent();
            menuId = $this.parent().data('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') {
                        $('#modal-delete').modal('hide');
                        $('#modal-delete button.delete-confirm').unbind();
                        $currentMenu.remove();
                        message.showSuccess('删除成功!');
    
                    } else {
                        message.showError(res.errmsg)
                    }
                })
                .fail(() => {
                    message.showError('服务器超时,请重试!')
                });
    
        }
    
    });
    

六、编辑菜单页面

1. 接口设计

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

    html

2.后端代码

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

    def get(self, request, menu_id):
        menu = models.Menu.objects.filter(is_delete=False, id=menu_id).first()
        form = MenuModelForm(instance=menu)
        return render(request, 'admin/menu/update_menu.html', context={'form': form})

3.前端代码

  1. html

    
    {% block content %}
     ...
        
        
        
    {% endblock %}
    
    
    {% load admin_customer_tags %}
    {% load static %}
    
    
    
    
  2. js

    // 修改 static/js/admin/menu/menu.js 
    $(() => {
        let $editBtns = $('button.edit');
        let $deleteBtns = $('button.delete');       // 删除按钮
        menuId = 0;                                 // 被点击菜单id
        let $currentMenu = null;                    // 当前被点击菜单对象
    
        // 编辑菜单
        $editBtns.click(function () {
            let $this = $(this);
            $currentMenu = $this.parent().parent();
            menuId = $this.parent().data('id');
            $
                .ajax({
                    url: '/admin/menu/'+ menuId +'/',
                    type: 'get'
                })
                .done((res)=>{
                    $('#modal-update .modal-content').html(res);
                    $('#modal-update').modal('show')
                })
                .fail(()=>{
                    message.showError('服务器超时,请重试!')
                })
        });
    
        $deleteBtns.click(function () {
            let $this = $(this);
            $currentMenu = $this.parent().parent();
            menuId = $this.parent().data('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') {
                        $('#modal-delete').modal('hide');
                        $('#modal-delete button.delete-confirm').unbind();
                        $currentMenu.remove();
                        message.showSuccess('删除成功!');
    
                    } else {
                        message.showError(res.errmsg)
                    }
                })
                .fail(() => {
                    message.showError('服务器超时,请重试!')
                });
        }
    });
    

七、编辑菜单

1.业务流程分析

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

2.接口设计

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

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

    如果有错误,返回html表单

3.后端代码

  1. 视图

    # 在admin/views.py中的MenuUpdateView视图中添加一个put方法
    class MenuUpdateView(View):
        """
        菜单管理视图
        url:/admin/menu//
        """
    ...
    
        def put(self, request, menu_id):
            menu = models.Menu.objects.filter(is_delete=False, id=menu_id).first()
            put = QueryDict(request.body)
            form = MenuModelForm(put, instance=menu)
            if form.is_valid():
                obj = form.save()
                if 'name' in form.changed_data:
                    obj.permission.name = obj.name
                if 'codename' in form.changed_data:
                    obj.permission.codename = obj.codename
                obj.permission.save()
                return json_response(errmsg='菜单修改成功!')
            else:
                return render(request, 'admin/menu/update_menu.html', context={'form': form})
    ...
    
    

4.前端代码

  1. html

...

  1. js

    $(()=>{
        let $updateBtn = $('#modal-update button.update');
        let $form = $('#update-menu');
        let data = {};
        $updateBtn.click(function () {
            $.each($form.serializeArray(), function () {
                data[this.name] = this.value
            });
            $
                .ajax({
                    url: '/admin/menu/' + menuId + '/',
                    type: 'PUT',
                    data: data,
                    // 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('服务器超时,请重试');
                });
        });
    });
    

八、整合后台首页面菜单加载

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})
    

效果如图:

你可能感兴趣的:(django项目--菜单管理)