我的第一个python web开发框架(30)——定制ORM(

  在开发中,查询操作是使用最多的,而查询列表是其中之一,查询列表可分为分页查询和不分页查询(它们之间多了一次总记录数查询),还可以分为单表查询和多表关联查询,返回的结构体根据前端使用的表单框架不同而有所区别。

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

  我们先看看,对于列表分页查询,在接口中是如何处理的

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

金沙官网线上 1金沙官网线上 2

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

 1 @get('/api/product/')
 2 def callback():
 3     """
 4     获取列表数据
 5     """
 6     # 设置查询条件
 7     wheres = ''
 8     # 产品分类id
 9     product_class_id = convert_helper.to_int0(web_helper.get_query('product_class_id', '产品分类id', is_check_null=False))
10     if product_class_id > 0:
11         wheres = 'where product_class_id=' + str(product_class_id)
12     # 页面索引
13     page_number = convert_helper.to_int1(web_helper.get_query('page', '', is_check_null=False))
14     # 页面显示记录数量
15     page_size = convert_helper.to_int0(web_helper.get_query('rows', '', is_check_null=False))
16     # 排序字段
17     sidx = web_helper.get_query('sidx', '', is_check_null=False)
18     # 顺序还是倒序排序
19     sord = web_helper.get_query('sord', '', is_check_null=False)
20     # 初始化排序字段
21     order_by = 'id desc'
22     ### 设置排序 ###
23     if sidx:
24         order_by = sidx + ' ' + sord
25     # 类型
26     type = web_helper.get_query('type', '类型', is_check_null=False)
27     # 判断是否是前台提交获取数据
28     if type != 'backstage':
29         # 判断是否已经存在查询条件了,是的话在原查询条件后面拼接
30         if wheres:
31             wheres = wheres + ' and is_enable=1'
32         else:
33             wheres = 'where is_enable=1'
34 
35     #############################################################
36     # 初始化输出格式(前端使用jqgrid列表,需要指定输出格式)
37     data = {
38         'records': 0,
39         'total': 0,
40         'page': 1,
41         'rows': [],
42     }
43     #############################################################
44     # 执行sql,获取指定条件的记录总数量
45     sql = 'select count(1) as records from product %(wheres)s' % {'wheres': wheres}
46     result = db_helper.read(sql)
47     # 如果查询失败或不存在指定条件记录,则直接返回初始值
48     if not result or result[0]['records'] == 0:
49         return data
50     # 保存总记录数量
51     data['records'] = result[0].get('records', 0)
52 
53     #############################################################
54     ### 设置分页索引与页面大小 ###
55     # 设置分页大小
56     if page_size is None or page_size <= 0:
57         page_size = 10
58     # 计算总页数
59     if data['records'] % page_size == 0:
60         page_total = data['records'] // page_size
61     else:
62         page_total = data['records'] // page_size + 1
63     # 记录总页面数量
64     data['total'] = page_total
65 
66     # 判断提交的页码是否超出范围
67     if page_number < 1 or page_number > page_total:
68         page_number = page_total
69     # 记录当前页面索引值
70     data['page'] = page_number
71 
72     # 计算当前页面要显示的记录起始位置
73     record_number = (page_number - 1) * page_size
74     # 设置查询分页条件
75     paging = ' limit ' + str(page_size) + ' offset ' + str(record_number)
76 
77     #############################################################
78 
79     # 组合sql查询语句
80     sql = "select * from product %(wheres)s order by %(orderby)s %(paging)s" % 
81            {'wheres': wheres, 'orderby': order_by, 'paging': paging}
82     # 读取记录
83     result = db_helper.read(sql)
84     if result:
85         # 存储记录
86         data['rows'] = result
87 
88     if data:
89         # 直接输出json
90         return web_helper.return_raise(json.dumps(data, cls=json_helper.CJsonEncoder))
91     else:
92         return web_helper.return_msg(-1, "查询失败")
#!/usr/bin/env python# coding=utf-8from logic import _logic_basefrom config import db_configclass MenuInfoLogic(_logic_base.LogicBase):    """菜单管理表逻辑类"""    def __init__:        # 表名称        __table_name = 'menu_info'        # 初始化        _logic_base.LogicBase.__init__(self, db_config.DB, db_config.IS_OUTPUT_SQL, __table_name)

View Code

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

  代码看起来很长,有点复杂,对于这种列表分页查询,如果不封装的话,开发时复制粘贴就很容易出错,所以我们需要重新处理才行。

  金沙官网线上 3

  从上面代码可以看到,具体功能分为几个部分:

  

  第一部分(9到33行)是接收并组合查询条件,接收分页参数和排序参数

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

  第二部分(37到42行)是初始化结果输出参数

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

  第三部分(44到51行)是获取查询总记录数

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

  第四部分(55到75行)是计算总页数,计算当前分页位置要显示的记录位置区间

 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 + ' ' + sord19 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.dumps26     else:27         return web_helper.return_msg(-1, "查询失败")

  第五部分(80到92行)是组合查询语句,查询并输出结果

  7到18行,是接收参数。

  除了产品列表这个接口,大家可以看看产品分类列表接口,会发现两个接口第二部分到第五部分都差不多,所以我们封装ORM时,可以将这些相似部分进行处理,将它们封装到ORM对应的方法里。

  20行初始化菜单逻辑类

 

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

  首先,我们对上面代码的分析,可以提炼出分页查询方法需要有下面参数:查询字段、查询条件、当前分页索引值、每页显示记录数量、排序。如果是多表查询时,我们的ORM是直接绑定当前表单的就不适用了,所以还需要有个设置表名的参数,好灵活处理各种需求,根据这些要求,我们可以创建好列表查询方法:

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

def get_list(self, column_name_list='', wheres='', page_number=None, page_size=None, orderby=None, table_name=None):
    """
    获取指定条件的数据库记录集
    :param column_name_list:      查询字段
    :param wheres:      查询条件
    :param page_number:   分页索引值
    :param page_size:    分页大小, 存在值时才会执行分页
    :param orderby:     排序规则
    :param table_name:     查询数据表,多表查询时需要设置
    :return: 返回记录集总数量与分页记录集
        {'records': 0, 'total': 0, 'page': 0, 'rows': []}
    """

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

  在接收到这些参数以后,我们需要对相关参数进行初始化操作,方便后续代码的执行

金沙官网线上 4

 1     # 初始化输出参数:总记录数量与列表集
 2     data = {
 3         'records': 0,   # 总记录数
 4         'total': 0,     # 总页数
 5         'page': 1,      # 当前页面索引
 6         'rows': [],     # 查询结果(记录列表)
 7     }
 8     # 初始化查询数据表名称
 9     if not table_name:
10         table_name = self.__table_name
11     # 初始化查询字段名
12     if not column_name_list:
13         column_name_list = self.__column_name_list
14     # 初始化查询条件
15     if wheres:
16         # 如果是字符串,表示该查询条件已组装好了,直接可以使用
17         if isinstance(wheres, str):
18             wheres = 'where ' + wheres
19         # 如果是list,则表示查询条件有多个,可以使用join将它们用and方式组合起来使用
20         elif isinstance(wheres, list):
21             wheres = 'where ' + ' and '.join(wheres)
22     # 初始化排序
23     if not orderby:
24         orderby = self.__pk_name + ' desc'
25     # 初始化分页查询的记录区间
26     paging = ''

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

  这里是对传入的参数和后续需要用到的参数进行初始化操作

金沙官网线上 5

  这里需要初始化查询结果输出参数结构,在进行记录数查询时,如果没有记录存在,就可以直接将结果返回出去了;

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

  默认数据表为当前类实体指定的表名称,如果进行多表联合查询时,就需要设置多表联合查询的组合表名称,比如:product left join product_class on product.product_class_id = product_class.id

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

  同时我们还需要设置查询字段内容,如果想查询出所有字段,直接使用*,如果只想要输出指定的几个字段值,则可以填写这几个字段值,比如:id,name,content

金沙官网线上 6

  在查询时,有时不需要查询条件,这时我们可以不填写条件,如果有指定条件时,我们可以将它们组合好,也可以放到list中。它们的区别在于,有多个查询条件时,我们有时很难判断当前条件前需不需要添加and,这时我们就可以使用' and '.join(列表) 来进行合成了,当然用list方式条件之间只能是and的关系。对于复杂的条件,我们可以组合好以后提交进来直接使用;

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

  在查询时,如果没有指定排序方式,我们默认使用主键倒序来进行排序

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

 

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

  在分页列表操作时,我们通常需要获取总记录数返回给前端,所以在执行查询前,我们需要获取当前查询条件的总记录数

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

 1     with db_helper.PgHelper(self.__db, self.__is_output_sql) as db:
 2         #############################################################
 3         # 判断是否需要进行分页
 4         if not page_size is None:
 5             ### 执行sql,获取指定条件的记录总数量
 6             sql = 'select count(1) as records from %(table_name)s %(wheres)s ' % 
 7                   {'table_name': table_name, 'wheres': wheres}
 8             result = db.execute(sql)
 9             # 如果查询失败或不存在指定条件记录,则直接返回初始值
10             if not result or result[0]['records'] == 0:
11                 return data
12 
13             # 设置记录总数量
14             data['records'] = result[0].get('records')
 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 = 023     else:24         level = _menu_info_logic.get_value_for_cache(parent_id, 'level') + 125     # 如果没有设置排序,则自动获取当前级别最大的序号加126     if sort == 0:27         sort = _menu_info_logic.get_max('parent_id', 'parent_id=' + str(parent_id)) + 128 29     # 组合更新字段30     fields = {31         'name': string,32         'icon': string,33         'page_url': string,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_model44     if result:45         return web_helper.return_msg(0, '提交成功')46     else:47         return web_helper.return_msg(-1, "提交失败")

  加上if not page_size is None判断,是因为有时候我们查询时,不需要分页操作,直接将所有记录输出了,这里加上判断可以减少不必要的记录总数量查询

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

 

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

  当我们获取到总记录数量以后,我们需要根据前端页面显示的记录数进行计算,计算出总页面数量,排除页面索引值超出限制可能会带来的异常,还有需要计算当前页面查询时对应的记录起始位置,组合分页查询条件pagin

 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             #########################################################
 2             ### 设置分页索引与页面大小 ###
 3             if page_size <= 0:
 4                 page_size = 10
 5             # 计算总分页数量:通过总记录数除于每页显示数量来计算总分页数量
 6             if data['records'] % page_size == 0:
 7                 page_total = data['records'] // page_size
 8             else:
 9                 page_total = data['records'] // page_size + 1
10             # 判断页码是否超出限制,超出限制查询时会出现异常,所以将页面索引设置为最后一页
11             if page_number < 1 or page_number > page_total:
12                 page_number = page_total
13             # 记录总页面数量
14             data['total'] = page_total
15             # 记录当前页面值
16             data['page'] = page_number
17             # 计算当前页面要显示的记录起始位置(limit指定的位置)
18             record_number = (page_number - 1) * page_size
19             # 设置查询分页条件
20             paging = ' limit ' + str(page_size) + ' offset ' + str(record_number)
21         #############################################################

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

 

  金沙官网线上 7

本文由金沙官网线上发布于编程,转载请注明出处:我的第一个python web开发框架(30)——定制ORM(

您可能还会对下面的文章感兴趣: