对于后台管理系统来说,要做好权限管理离不开菜单项和页面按钮控件功能的管理。由于程序没法智能的知道有什么菜单和控件,哪些人拥有哪些操作权限,所以首先要做的是菜单管理功能,将需要管理的菜单项和各个功能项添加(注册)到菜单管理表中,方便后续权限控制管理。

  要开发一个菜单管理功能,离不开这些功能:菜单列表展示(需要菜单列表获取接口)、新增菜单(新增接口)、编辑菜单(获取菜单记录以及提交修改接口)、删除菜单(删除接口),由于菜单是多层级的关系,所以还需要增加菜单树列表获取接口来绑定菜单层级,在主页面还需要增加菜单列表项输出接口,用来展示菜单项。

  在正式编写菜单管理功能之前,我们需要先在逻辑层(logic文件夹)中添加菜单逻辑类:menu_info_logic.py,继承前面我们开发的ORM基类,让当前的菜单管理逻辑类拥有ORM的所有方法。

  1. #!/usr/bin/env python
  2. # coding=utf-8
  3.  
  4. from logic import _logic_base
  5. from config import db_config
  6.  
  7. class MenuInfoLogic(_logic_base.LogicBase):
  8. """菜单管理表逻辑类"""
  9.  
  10. def __init__(self):
  11. # 表名称
  12. __table_name = 'menu_info'
  13. # 初始化
  14. _logic_base.LogicBase.__init__(self, db_config.DB, db_config.IS_OUTPUT_SQL, __table_name)

  为了方便管理,我们在api文件中创建system文件夹,用来存放所有后台权限管理功能的代码,并创建menu_info.py文件,来存放菜单管理接口

  

  

  接下来我们先实现菜单列表获取接口,由第一部分的后端管理功能可以知道,我们前端使用的是jqGrid插件,这一块我们在前面已经实现过了,而ORM中也封装好对应的方法,所以直接调用就可以了。(这个接口在实现时,我们要了解清楚的是,前端插件jqGrid它会传递什么参数和需要返回什么格式的数据回去)

  jqGrid会通过接口,将当前页面索引值page、页面显示记录行数rows、排序字段sidx和排序方式sord(顺序或倒序)提交到服务器端接口,如果我们使用树列表,它还会提交当前节点id参数nodeid

  所以我们在服务器端接口需要做好这几个参数的接收与使用操作,然后我们通过调用前面实现的ORM的get_list方法,就可以获取对应的数据返回给客户端了,具休代码如下:

  1. @get('/system/menu_info/')
  2. def callback():
  3. """
  4. 获取列表数据
  5. """
  6. # 菜单列表中,当前节点id,即父节点id
  7. parent_id = convert_helper.to_int0(web_helper.get_query('nodeid', '', is_check_null=False))
  8. # 页面索引
  9. page_number = convert_helper.to_int1(web_helper.get_query('page', '', is_check_null=False))
  10. # 页面页码与显示记录数量
  11. page_size = convert_helper.to_int0(web_helper.get_query('rows', '', is_check_null=False))
  12. # 接收排序参数
  13. sidx = web_helper.get_query('sidx', '', is_check_null=False)
  14. sord = web_helper.get_query('sord', '', is_check_null=False)
  15. # 初始化排序字段
  16. order_by = 'sort asc'
  17. if sidx:
  18. order_by = sidx + ' ' + sord
  19.  
  20. _menu_info_logic = menu_info_logic.MenuInfoLogic()
  21. # 读取记录
  22. wheres = 'parent_id=' + str(parent_id)
  23. result = _menu_info_logic.get_list('*', wheres, page_number, page_size, order_by)
  24. if result:
  25. return json.dumps(result)
  26. else:
  27. return web_helper.return_msg(-1, "查询失败")

  7到18行,是接收参数。

  20行初始化菜单逻辑类

  22行是设置查询条件,默认菜单列表我们只显示第一级菜单,也就是父id为0的菜单。在列表第一次加载时,列表提交上来的nodeid为空(即父节点为默认为0),所以设置查询条件时父节点会赋值为parent_id=0。当我们点击树菜单展开时,才加载下一级菜单出来,这时jqGrid控件会再次访问接口,提交当前要展开发节点id给接口,接口接收到参数以后返回对应的子节点列表给客户端。

  get_list是前端ORM中封装好的参数,它会返回jqGrid所需要的数据格式,所以第25行直接将符合jqGrid要求的数据返回给列表展示出来。

  我们在后台main.html中添加菜单,方便登录后台查看效果

  前端菜单管理的hmtl页面大家自行下载源码包查看,下面是完成后展示效果

  由于当前还没有数据,所以暂时列表是空的,下面我们创建添加和修改功能

  先看看新增页面效果(页面内容项一般我们是根据数据字典和原型来设计的,大家可以参照一下上一章菜单管理的数据结构)

  我们需要接收页面提交上来的这些参数,然后向数据库中添加一条记录

  上级菜单选项,这里我们点击选择时,需要显示菜单树列表,让我们选择当前新增菜单项所属菜单层级,方便菜单层级的管理,如果为顶级菜单,则不需要进行选择

  为了让后台菜单好看一些,我们可以增加菜单小图标,H-ui框架中,提供了字体图标,这里的查看增加链接到官网中,可以直接查询字体图标编码复制过来使用

  排序可以输入任意的数字,通过从 小到大顺序排列菜单项,为了方便排序项可以自行累加,代码中可以获取当前菜单层级最大值加1的方式来进行赋值

  1. @post('/api/system/menu_info/')
  2. def callback():
  3. """
  4. 新增记录
  5. """
  6. name = web_helper.get_form('name', '菜单名称')
  7. icon = web_helper.get_form('icon', '菜单小图标', True, 10, False, is_check_special_char=False)
  8. icon = icon.replace('\'', '').replace('|', '').replace('%', '')
  9. page_url = web_helper.get_form('page_url', '页面URL', is_check_null=False)
  10. interface_url = web_helper.get_form('interface_url', '接口url', is_check_null=False, is_check_special_char=False)
  11. # 替换编码
  12. interface_url = interface_url.replace('@', '').replace('\'', '').replace('|', '').replace('%', '')
  13. parent_id = convert_helper.to_int0(web_helper.get_form('parent_id', '父id', is_check_null=False))
  14. sort = convert_helper.to_int0(web_helper.get_form('sort', '排序', is_check_null=False))
  15. is_leaf = web_helper.get_form('is_leaf', '是否最终节点', is_check_null=False)
  16. is_show = web_helper.get_form('is_show', '是否显示', is_check_null=False)
  17. is_enabled = web_helper.get_form('is_enabled', '是否启用', is_check_null=False)
  18.  
  19. _menu_info_logic = menu_info_logic.MenuInfoLogic()
  20. # 计算深度级别,即当前菜单在哪一级
  21. if parent_id == 0:
  22. level = 0
  23. else:
  24. level = _menu_info_logic.get_value_for_cache(parent_id, 'level') + 1
  25. # 如果没有设置排序,则自动获取当前级别最大的序号加1
  26. if sort == 0:
  27. sort = _menu_info_logic.get_max('parent_id', 'parent_id=' + str(parent_id)) + 1
  28.  
  29. # 组合更新字段
  30. fields = {
  31. 'name': string(name),
  32. 'icon': string(icon),
  33. 'page_url': string(page_url),
  34. 'interface_url': string(interface_url),
  35. 'parent_id': parent_id,
  36. 'sort': sort,
  37. 'level': level,
  38. 'is_leaf': is_leaf,
  39. 'is_show': is_show,
  40. 'is_enabled': is_enabled,
  41. }
  42. # 新增记录
  43. result = _menu_info_logic.add_model(fields)
  44. if result:
  45. return web_helper.return_msg(0, '提交成功')
  46. else:
  47. return web_helper.return_msg(-1, "提交失败")

  前端菜单新增页面(menu_info_edit.html)大家下载源码包查看

  新增菜单页面树列表我们使用的是zTree插件,它需要我们输出指定的数据格式才能正常显示,所以调用接口返回:id、parent_id、name、open这几个字段,在菜单项中,我们有是否最终节点的字段,所以查询条件中我们指定查询出所有非最终节点的项就可以了

  1. @get('/api/system/menu_info/tree/')
  2. def callback():
  3. """
  4. 获取列表数据(树列表)
  5. """
  6. _menu_info_logic = menu_info_logic.MenuInfoLogic()
  7. # 读取记录
  8. result = _menu_info_logic.get_list('id, parent_id, name, not is_leaf as open', 'is_leaf=false', orderby='sort asc')
  9. if result:
  10. return web_helper.return_msg(0, "成功", {'tree_list': result.get('rows')})
  11. else:
  12. return web_helper.return_msg(-1, "查询失败")

  完成后直接填写参数就可以提交新增菜单记录了。

  

  对于编辑接口,它基本上和新增接口代码相差不大,区别地方有下面几点:

  1.为了减少菜单层级变更所造成的错误,在编辑记录接口我们需要屏蔽对父节点id的修改

  2.不需要再计算当前菜单所在层级的深度

  3.将新增方法add_model()更改为edit_model()方法

  1. @put('/api/system/menu_info/<id:int>/')
  2. def callback(id):
  3. """
  4. 修改记录
  5. """
  6. name = web_helper.get_form('name', '菜单名称')
  7. icon = web_helper.get_form('icon', '菜单小图标', True, 10, False, is_check_special_char=False)
  8. icon = icon.replace('\'', '').replace('|', '').replace('%', '')
  9. page_url = web_helper.get_form('page_url', '页面URL', is_check_null=False)
  10. interface_url = web_helper.get_form('interface_url', '接口url', is_check_null=False, is_check_special_char=False)
  11. # 替换编码
  12. interface_url = interface_url.replace('\'', '').replace('|', '').replace('%', '')
  13. parent_id = convert_helper.to_int0(web_helper.get_form('parent_id', '父id', is_check_null=False))
  14. sort = convert_helper.to_int0(web_helper.get_form('sort', '排序', is_check_null=False))
  15. is_leaf = web_helper.get_form('is_leaf', '是否最终节点', is_check_null=False)
  16. is_show = web_helper.get_form('is_show', '是否显示', is_check_null=False)
  17. is_enabled = web_helper.get_form('is_enabled', '是否启用', is_check_null=False)
  18.  
  19. _menu_info_logic = menu_info_logic.MenuInfoLogic()
  20. # 如果没有设置排序,则自动获取当前级别最大的序号加1
  21. if sort == 0:
  22. sort = _menu_info_logic.get_max('parent_id', 'parent_id=' + str(parent_id)) + 1
  23.  
  24. # 组合更新字段
  25. fields = {
  26. 'name': string(name),
  27. 'icon': string(icon),
  28. 'page_url': string(page_url),
  29. 'interface_url': string(interface_url),
  30. 'sort': sort,
  31. 'is_leaf': is_leaf,
  32. 'is_show': is_show,
  33. 'is_enabled': is_enabled,
  34. }
  35. # 修改记录
  36. result = _menu_info_logic.edit_model(id, fields)
  37. if result:
  38. return web_helper.return_msg(0, '提交成功')
  39. else:
  40. return web_helper.return_msg(-1, "提交失败")

  大家可以比较一下新增与编辑接口代码,可以发现代码几乎都是一样的。

  

  最后增加删除接口

  1. @delete('/api/system/menu_info/<id:int>/')
  2. def callback(id):
  3. """
  4. 删除指定记录
  5. """
  6. _menu_info_logic = menu_info_logic.MenuInfoLogic()
  7. # 判断要删除的节点是否有子节点,是的话不能删除
  8. if _menu_info_logic.exists('parent_id=' + str(id)):
  9. return web_helper.return_msg(-1, "当前菜单存在子菜单,不能直接删除")
  10.  
  11. # 删除记录
  12. result = _menu_info_logic.delete_model(id)
  13. if result:
  14. return web_helper.return_msg(0, '删除成功')
  15. else:
  16. return web_helper.return_msg(-1, "删除失败")

  删除接口跟前端产品分类删除接口一样,在删除前需要判断当前菜单是否已被引用(即当前菜单下是否存在子菜单)

  菜单管理项添加完成后,列表效果图

  完成这些之后,我们还需要改造一下管理主界面左栏的菜单列表,改为从菜单管理数据表中读取方式

  为了方便后续权限的管理改造,我们在接口中组合菜单代码来实现菜单的展示效果。

  首先我们通过查看左栏菜单列表的html代码,提取出菜单html展示代码

  然后在接口中,获取设置为显示并启用状态的菜单列表

  通过循环判断,拼接一级菜单和二级菜单项的html输出代码

  最后将结果输出到前端展示出来

  1. @get('/api/main/menu_info/')
  2. def callback():
  3. """
  4. 主页面获取菜单列表数据
  5. """
  6. _menu_info_logic = menu_info_logic.MenuInfoLogic()
  7. # 读取记录
  8. result = _menu_info_logic.get_list('*', 'is_show and is_enabled', orderby='sort')
  9. if result:
  10. # 定义最终输出的html存储变量
  11. html = ''
  12. for model in result.get('rows'):
  13. # 提取出第一级菜单
  14. if model.get('parent_id') == 0:
  15. # 添加一级菜单
  16. temp = """
  17. <dl id="menu-%(id)s">
  18. <dt><i class="Hui-iconfont">%(icon)s</i> %(name)s<i class="Hui-iconfont menu_dropdown-arrow"></i></dt>
  19. <dd>
  20. <ul>
  21. """ % {'id': model.get('id'), 'icon': model.get('icon'), 'name': model.get('name')}
  22. html = html + temp
  23.  
  24. # 从所有菜单记录中提取当前一级菜单下的子菜单
  25. for sub_model in result.get('rows'):
  26. # 如果父id等于当前一级菜单id,则为当前菜单的子菜单
  27. if sub_model.get('parent_id') == model.get('id'):
  28. temp = """
  29. <li><a data-href="%(page_url)s" data-title="%(name)s" href="javascript:void(0)">%(name)s</a></li>
  30. """ % {'page_url': sub_model.get('page_url'), 'name': sub_model.get('name')}
  31. html = html + temp
  32.  
  33. # 闭合菜单html
  34. temp = """
  35. </ul>
  36. </dd>
  37. </dl>
  38. """
  39. html = html + temp
  40.  
  41. return web_helper.return_msg(0, '成功', {'menu_html': html})
  42. else:
  43. return web_helper.return_msg(-1, "查询失败")

  执行后会输出下面结果:

  1. {
  2. "data": {
  3. "menu_html": "\n <dl id=\"menu-1\">\n <dt><i class=\"Hui-iconfont\"></i> 系统管理<i class=\"Hui-iconfont menu_dropdown-arrow\"></i></dt>\n <dd>\n <ul>\n \n <li><a data-href=\"menu_info.html\" data-title=\"菜单管理\" href=\"javascript:void(0)\">菜单管理</a></li>\n \n </ul>\n </dd>\n </dl>\n "
  4. },
  5. "msg": "成功",
  6. "state": 0
  7. }

  前端通过AJAX获取菜单列表hmtl代码,然后添加到后台左栏菜单列表中就实现我们想要的效果了

  1. <aside class="Hui-aside">
  2. <div class="menu_dropdown bk_2" id="menu">
  3.  
  4. </div>
  5. </aside>
  6.  
  7. <script type="text/javascript">
  8. $(function () {
  9. $.ajax({
  10. url: "/api/main/menu_info/?" + 100 * Math.random(),
  11. type: "GET",
  12. dataType:'json',
  13. success: function (data) {
  14. if (checkLogin(data, true))
  15. {
  16. $("#menu").html(data.data.menu_html);
  17. $.Huifold(".menu_dropdown dl dt",".menu_dropdown dl dd","fast",1,"click");
  18. }
  19. }
  20. });
  21. });
  22. </script>

  页面展示效果

  对于菜单管理的改造,完成上面这些项就算完厉了。对于菜单权限的控制,后续完成整个改造后会专门讲解。

  权限系统中的部门管理(角色权限组管理),它的基本功能和菜单功能相似,所以就不开新章节进行讲解,大家可以根据数据结构尝试编写,也可以参考本节提供的源码进行研究。

  PS:部门管理中,部门编码生成是一个比较特殊的方法,需要多debug理解。

  本文对应的源码下载 (内附本章源码对应数据库表单和记录创建sql代码,上传的图片如果显示不了,可以nginx.conf配置的location项中添加upload,第一部分章节的nginx那里忘记添加了)

版权声明:本文原创发表于 博客园,作者为 AllEmpty 本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。

python开发QQ群:669058475(本群已满)、733466321(可以加2群)    作者博客:http://www.cnblogs.com/EmptyFS/

我的第一个python web开发框架(36)——后台菜单管理功能的更多相关文章

  1. 我的第一个python web开发框架(41)——总结

    我的第一个python web开发框架系列博文从17年6.7月份开始写(存了近十章稿留到9月份才开始发布),到今天结束,一年多时间,想想真不容易啊. 整个过程断断续续,中间有段时间由于工作繁忙停了好长 ...

  2. 我的第一个python web开发框架(14)——后台管理系统登录功能

    接下来正式进入网站的功能开发.要完成后台管理系统登录功能,通过查看登录页面,我们可以了解到,我们需要编写验证码图片获取接口和登录处理接口,然后在登录页面的HTML上编写AJAX. 在进行接口开发之前, ...

  3. 我的第一个python web开发框架(21)——小结

    这个小网站终于成功上线,小白除了收获一笔不多的费用外,还得到女神小美的赞赏,心中满满的成就感.这一天下班后,他请老菜一起下馆子,兑现请吃饭的承诺,顺便让老菜点评一下. 小白:老大,在你的指导下终于完成 ...

  4. 我的第一个python web开发框架(3)——怎么开始?

    小白与小美公司经过几次接触商谈,好不容易将外包签订了下来,准备开始大干一场.不过小白由于没有太多的项目经验,学过python懂得python的基本语法,在公司跟着大家做过简单功能,另外还会一些HTML ...

  5. 我的第一个python web开发框架(39)——后台接口权限访问控制处理

    前面的菜单.部门.职位与管理员管理功能完成后,接下来要处理的是将它们关联起来,根据职位管理中选定的权限控制菜单显示以及页面数据的访问和操作. 那么要怎么改造呢?我们可以通过用户的操作步骤来一步步进行处 ...

  6. 我的第一个python web开发框架(34)——后台管理系统权限设计

    框架底层和接口终于改造完成了,小白再次找到老菜. 小白:老大,上次你对后台权限系统简单的讲了一下,我一点头绪都没有,现在有空完整的说一说吗? 老菜:说到权限系统,要讲明白真不容易,权限系统并不是越复杂 ...

  7. 我的第一个python web开发框架(1)——前言

    由于之前经验不是很丰富,写的C#系统太过复杂,所以一直想重写,但学的越多越觉得自己懂的越少,越觉的底气不足.所以一直不敢动手,在内心深处对自己讲,要静下心来认真学习,继续沉淀沉淀.这两年多以来找各种机 ...

  8. 我的第一个python web开发框架(22)——一个安全小事故

    在周末的一个早上,小白还在做着美梦,就收到了小美的连环追魂call,电话一直响个不停. 小白打着哈欠拿起电话:早上好美女. 小美:出事了出事了,我们公司网站一早访问是一片空白,什么内容都没有了,你赶急 ...

  9. 我的第一个python web开发框架(40)——后台日志与异常处理

    后台权限和底层框架的改造终于完成了,小白也终于可以放下紧悬着的心,可以轻松一下了.这不他为了感谢老菜,又找老菜聊了起来. 小白:多谢老大的帮忙,系统终于改造完成了,可以好好放松一下了. 老菜:呵呵,对 ...

随机推荐

  1. Python之路【第七篇】:Python装饰器

    阅读目录 一.装饰器 1.装饰器的概念 #装饰器定义:本质就是函数,功能是为其他函数添加附加功能 二.装饰器需要遵循的原则 #原则: 1.不修改被修饰函数的源代码 2.不修改被修饰函数的调用方式 装饰 ...

  2. LeetCode算法题-Robot Return to Origin(Java实现)

    这是悦乐书的第281次更新,第298篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第149题(顺位题号是657).在2D平面上有一个从位置(0,0)开始的机器人.给定其移 ...

  3. redis 初识

    架构 sharding redis 集群是主从式架构,数据分片是根据hash slot(哈希槽来分布) 总共有16384个哈希槽,所以理论上来说,集群的最大节点(master) 数量是16384个.一 ...

  4. 《白帽子讲Web安全》- 学习笔记

    一.为何要了解Web安全 最近加入新公司后,公司的官网突然被Google标记为了不安全的诈骗网站,一时间我们信息技术部门成为了众矢之的,虽然老官网并不是我们开发的(因为开发老官网的前辈们全都跑路了). ...

  5. OA发展史:由点到生态

    在当今无边界组织的商业背景下,企业与员工关系已经转化为联盟关系,以往通过工作场所.劳动合同等约束的形式已经逐步弱化,管理行为空前复杂,OA正是将一个个散点整合起来的看不见的手.那么,推动OA发展的核心 ...

  6. Eureka服务下线后快速感知配置

    现在由于eureka服务越来越多,发现服务提供者在停掉很久之后,服务调用者很长时间并没有感知到变化,依旧还在持续调用下线的服务,导致长时间后才能返回错误,因此需要调整eureka服务和客户端的配置,以 ...

  7. [深度概念]·K-Fold 交叉验证 (Cross-Validation)的理解与应用

    K-Fold 交叉验证 (Cross-Validation)的理解与应用 我的网站 1.K-Fold 交叉验证概念 在机器学习建模过程中,通行的做法通常是将数据分为训练集和测试集.测试集是与训练独立的 ...

  8. 补习系列(15)-springboot 分布式会话原理

    目录 一.背景 二.SpringBoot 分布式会话 三.样例程序 四.原理进阶 A. 序列化 B. 会话代理 C. 数据老化 小结 一.背景 在 补习系列(3)-springboot 几种scope ...

  9. 搞懂MySQL分区

    一.InnoDB逻辑存储结构 首先要先介绍一下InnoDB逻辑存储结构和区的概念,它的所有数据都被逻辑地存放在表空间,表空间又由段,区,页组成. 段 段就是上图的segment区域,常见的段有数据段. ...

  10. python 题库1

    1. 生成一个1到50的大字符串,每个数字之间有个空格,例如1 2 3 4 ……50 解题思路: (1)声明一个空字符串变量用于保存生成的字符串 (2)使用for循环+range()函数生成一个1到5 ...