[django项目] 后台菜单管理功能
后台菜单管理功能菜单的管理功能其实就是, 对菜单的增删改查I. 业务功能分析1>业务需求分析后台首页菜单根据用户权限动态生成,不同菜单对应不同的功能视图。菜单的增删改查。2>功能分析菜单列表添加菜单修改菜单删除菜单3>模型设计3.1>字段分析name, 菜单名url, 菜单的路由parent, 父菜单的idorder, 排序permi...
后台菜单管理功能
菜单的管理功能其实就是, 对菜单的增删改查
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>业务流程分析
- 获取未删除,的一级菜单
- 根据一级菜单获取未删除的二级菜单
- 渲染页面
2>接口设计
- 接口说明
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /admin/menus/ |
参数格式 | 无参数 |
-
返回结果
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的服务哦
页面效果:
可以打开这个界面, 就说明路由和视图配置好了
接下来将列表填充到这个页面, 我们可以使用AdminLTE
为用户提供的表格模板:
这个页面上有很多类型的表格, 修改前端代码
{% 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>接口设计
- 接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /admin/menu/ |
参数格式 | 无参数 |
-
返回数据
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">×</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>
页面效果:
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">×</span></button>
<h4 class="modal-title">警告</h4>
</div>
<div class="modal-body">
<p>One fine body…</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>接口设计
- 接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /admin/menu/<int:menu_id>/ |
参数格式 | 路径参数 |
- 参数说明
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
menu_id | 整数 | 是 | 菜单id |
-
返回数据
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">×</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>接口设计
- 接口说明:
类目 | 说明 |
---|---|
请求方法 | PUT |
url定义 | /admin/menu/<int:menu_id> |
参数格式 | 路径参数+表单参数 |
- 参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
menu_id | 整数 | 是 | 菜单id |
name | 字符串 | 是 | 菜单名 |
url | 字符串 | 否 | 当前文章页数 |
order | 整数 | 是 | 排序 |
parent | 整数 | 否 | 父菜单id |
icon | 字符串 | 是 | 渲染图标类名 |
codename | 字符串 | 是 | 权限码 |
is_visible | 整数 | 是 | 是否可见 |
-
返回数据
# 添加正常返回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>
更多推荐
所有评论(0)