权限组件(9):二级菜单的展示和增删改查
效果图:
二级菜单的展示和增删改查
二级菜单和一级菜单逻辑差不太多。有以下两点需要注意:
- 用户不点一级菜单的时候,二级菜单的信息、新建、编辑、删除不会显示
- 新建二级菜单的时候,默认选中当前用户选中的一级菜单
- 数据库把icon字段的null、blank=True去掉 (需要给已有数据一个默认值)
一、配置URL
rbac/urls.py
... from django.urls import re_path from rbac.views import menu ... urlpatterns = [ ... # 二级菜单 re_path(r'^second/menu/add/(?P<menu_id>\d+)/$', menu.second_menu_add, name='second_menu_add'), re_path(r'^second/menu/edit/(?P<pk>\d+)/$', menu.second_menu_edit, name='second_menu_edit'), re_path(r'^second/menu/del/(?P<pk>\d+)/$', menu.second_menu_del, name='second_menu_del'), ... ]
增加二级菜单需要默认选中一级菜单,所以需要一级菜单的id(在模板层会传入)。
二、forms表单验证
forms/base.py
from django import forms class BaseBootStrapForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name, field in self.fields.items(): field.widget.attrs['class'] = 'form-control'
forms/menu.py
... from rbac import models from rbac.forms.base import BaseBootStrapForm ... class SecondMenuModelForm(BaseBootStrapForm): class Meta: model = models.Permission exclude = ['pid']
三、视图函数
rbac/veiws/menu.py
from django.shortcuts import HttpResponse, render, redirect, reverse from rbac import models from rbac.forms.menu import MenuModelForm, SecondMenuModelForm from rbac.service.urls import memory_reverse def menu_list(request): """ 菜单和权限列表 :param request: :return: """ menu_queryset = models.Menu.objects.all() menu_id = request.GET.get('mid') # 用户选择的一级菜单 second_menu_id = request.GET.get('sid') # 用户选择的二级菜单 + menus_exists = models.Menu.objects.filter(id=menu_id).exists() if not menus_exists: menu_id = None if menu_id: second_menus = models.Permission.objects.filter(menu_id=menu_id) else: second_menus = [] context = { 'menu_list': menu_queryset, 'menu_id': menu_id, 'second_menus': second_menus, 'second_menu_id': second_menu_id } return render(request, 'rbac/menu_list.html', context) ...
# 二级菜单的增删改 def second_menu_add(request, menu_id): """ 增加二级菜单 :param request: :param pk: 已经选择的一级菜单ID(用于设置默认值) :return: """ menu_obj = models.Menu.objects.filter(id=menu_id).first() if request.method == 'GET': forms = SecondMenuModelForm(initial={'menu': menu_obj}) return render(request, 'rbac/change.html', {'forms': forms}) forms = SecondMenuModelForm(data=request.POST) if forms.is_valid(): forms.save() url = memory_reverse(request, 'rbac:menu_list') return redirect(url) return render(request, 'rbac/change.html', {'forms': forms}) def second_menu_edit(request, pk): """ 编辑二级菜单 :param request: :param pk: :return: """ permission_obj = models.Permission.objects.filter(id=pk).first() if request.method == 'GET': forms = SecondMenuModelForm(instance=permission_obj) return render(request, 'rbac/change.html', {'forms': forms}) forms = SecondMenuModelForm(data=request.POST, instance=permission_obj) if forms.is_valid(): forms.save() url = memory_reverse(request, 'rbac:menu_list') return redirect(url) return render(request, 'rbac/change.html', {'forms': forms}) def second_menu_del(request, pk): """ 删除二级菜单 :param request: :param pk: :return: """ menu_list_url = memory_reverse(request, 'rbac:menu_list') if request.method == 'GET': return render(request, 'rbac/delete.html', {'cancel': menu_list_url}) models.Permission.objects.filter(id=pk).delete() return redirect(menu_list_url)
四、模板渲染
rbac/templates/menu_list.html
{% extends 'layout.html' %} {% load rbac %} <style> tr.active { border-left: 3px solid #fdc00f; } </style> {% block content %} <div class="luffy-container"> <!-- 一级菜单 --> <div class="col-md-3"> <div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"> <i class="fa fa-book" aria-hidden="true">一级菜单</i> <a href="{% memory_url request 'rbac:menu_add' %}" class="right btn btn-success btn-xs" style="padding: 2px 8px;margin:-3px"> <i class="fa fa-plus-circle" aria-hidden="true">新建</i> </a> </div> <!-- Table --> <table class="table"> <thead> <tr> <th>名称</th> <th>图标</th> <th>选项</th> </tr> </thead> <tbody> {% for menu in menu_list %} <!-- 管道符可以将后端传来的整型,转换成字符串 --> <tr class="{% if menu.id|safe == menu_id %}active{% endif %}"> <td><a href="?mid={{ menu.id }}">{{ menu.title }}</a></td> <td><i class="fa {{ menu.icon }}" aria-hidden="true"></i></td> <td> <a style="color: #333333; font-size:18px" href="{% memory_url request 'rbac:menu_edit' pk=menu.id %}"> <i class="fa fa-edit" aria-hidden="true"></i> </a> <a style="color: red; font-size:18px" href="{% memory_url request 'rbac:menu_del' pk=menu.id %}"> <i class="fa fa-trash-o" aria-hidden="true"></i> </a> </td> </tr> {% endfor %} </tbody> </table> </div> </div> <!-- 二级菜单 --> <div class="col-md-4"> <div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"> <i class="fa fa-flag" aria-hidden="true">二级菜单</i> {% if menu_id %} <a href="{% memory_url request 'rbac:second_menu_add' menu_id=menu_id %}" class="right btn btn-success btn-xs" style="padding: 2px 8px;margin:-3px"> <i class="fa fa-plus-circle" aria-hidden="true">新建</i> </a> {% endif %} </div> <!-- Table --> <table class="table"> <thead> <tr> <td>名称</td> <td>CODE&URL</td> <td>选项</td> </tr> </thead> <tbody> {% for second_menu in second_menus %} <!-- 管道符可以将后端传来的整型,转换成字符串 --> <tr class="{% if second_menu.id|safe == second_menu_id %}active{% endif %}"> <td rowspan="2"> <a href="?mid={{ menu_id }}&sid={{ second_menu.id }}">{{ second_menu.title }}</a> </td> <td>{{ second_menu.name }}</td> <td> <a href="{% memory_url request 'rbac:second_menu_edit' pk=second_menu.id %}" style="color:#333333;font-size:18px;"> <i class="fa fa-edit" aria-hidden="true"></i> </a> <a href="{% memory_url request 'rbac:second_menu_del' pk=second_menu.id %}" style="color:red;font-size:18px;"> <i class="fa fa-trash-o" aria-hidden="true"></i> </a> </td> </tr> <tr class="{% if second_menu.id|safe == second_menu_id %}active{% endif %}"> <td colspan="2" style="border-top:0;">{{ second_menu.url }}</td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> {% endblock content %}
在总结一下django处理url的流程:在模板层通过反向解析找到url,并把模板层传递的参数传过去,然后到路由层,把request和模板层传过来的参数交给视图函数。