条目较少的选项集合,确实可以在程序中直接定义(其实最合适的还是存储在一个分类别的数据库表里),但条目较多的选项集合,或者是复杂的树型结构选项集合,一般都是存储在数据库中的,这样维护起来比较方便,也容易迁移和共享。所以,在完成基础选项集合框架后,考虑应用的方便性,面向数据库存储的选项集合扩展类就成了下一步开发的任务。
有了基础类做底,这些实现扩展起来也是十分方便。基于数据库存储的类包括两大类型,一种是列表类,这个就是基础选项类的扩展,数据结构没有区别,只是多了选项条目从数据库中装载到内存中的过程,另一种是树型类,它的数据结构比列表类复杂了一些,数据库里也是以父子ID方式存放数据,放在内存里则是一个包括父子ID和名称的字典,依据父子ID关系可以生成与layui-tree组件接口一致的树型结构传送到前端。
下面这段是列表类的python服务类List_OptionSet,它是基于OptionSet基础类扩展的,所以,相同功能的类函数直接复用基础类。重写的子类函数包括构造函数__init__()和get_dict(),新写的函数包括load()和reload()。可以看出,列表类与基础类的区别是比较小的。
(注:写到这儿时,发现原来的基础类中有个错误,就是reload()函数也必须在基础类里编写出来,否则如果对基础类选项集合进行重载时,就会出现系统级错误)
#取自数据库表的列表选项集合
class List_OptionSet(OptionSet):def __init__(self,dbclass=None,fdinf=None,n_opt=None,t_opt=None) :super().__init__()self.dbclass = dbclassself.fdinf = fdinfself.opt_name = n_optself.opt_title = t_optif n_opt :sysOptionPool.add_optset(n_opt,self)# 取选项字典def get_dict(self):if (self.opt_dict == None):if self.load(self.dbclass,self.fdinf) == 0:return Nonereturn self.opt_dict# 数据装入函数def load(self,dbclass,fdinf):logging.info('Load Table OptionSet %s....' % self.opt_name)fdkey = fdinf.get('key')fdname = fdinf.get('name')filtstr = fdinf.get('filter')try :rows = db.session.query(dbclass).filter(text(filtstr)).all()opt_info = {}for irow in rows:valkey = getattr(irow,fdkey)valname = getattr(irow,fdname)opt_info[valkey] = valnameself.opt_dict = opt_infoexcept SQLAlchemyError as e:logging.debug('装入选项集合[%s]数据库读取失败!!%s' % (self.opt_name,str(e.orig)))return 0return 1#数据重载函数def reload(self) :rtncode = self.load(self.dbclass,self.fdinf)if rtncode == 0 :return 0if self.opt_name :sysOptionPool.add_optset(self.opt_name,self)return 1
列表类实现时,重点要强调一下,类实例初始化时并不会从数据库中取数据,而是在第一次使用时,如果判断条目字典为空,才会从数据库中加载条目数据,这是系统中经常用到的方法,因为在系统启动时,所有的选项实例就已经生成了,但大部分的实例在运行时并不会用到,如果这时加载,那会占用较多内存,并且启动时间也会加长。
上面这个实现在get_dict()中完成,当self.opt_dict为空时调用load函数完成加载。reload()函数的主要用途是将条目数据从数据库重新装载到类实例中。当选项条目被维护后,就会调用此函数完成选项集合的同步。重载是选项集合服务一个路由请求实现,在上一节中有体现。
下面这段代码是树型选项集合类Tree_OptionSet的实现,它是在列表类的基础上扩展的。可以看出,树型类进行了大量的扩充,几乎所有的函数都进行了重写,并且增加了多个函数。树型选项的存储结构不再是简单的键值对字典,而是一个以ID为键字的复合字典,并为前端提供了列表、映射以及树型等多种格式的数据服务。
##树型的选项集合类
class Tree_OptionSet(List_OptionSet) :def __init__(self,dbclass=None,fdinf=None,n_opt=None,t_opt=None) :super().__init__()self.dbclass = dbclassself.fdinf = fdinfself.opt_name = n_optself.opt_title = t_optif n_opt :sysOptionPool.add_optset(n_opt,self)# 获取选项名称def get_name(self,id) :dt_id = self.get_dict().get(id)if dt_id == None :return '-'return dt_id.get('name')# 获取选项格式串(废弃保留)def id_format(self,id):dt_id = self.get_dict().get(id)if dt_id == None :return '-'idname = dt_id.get('name')if isinstance(id, str) :return id + '_' + idnameelse :return str(id) + '_' + idname# 获取选项映射def get_map(self):itemlist = {}d_opt = self.get_dict()if (d_opt == None) :return Nonefor (k,v) in d_opt.items():itemlist[k] = v['name']return itemlist# 获取选项列表def get_list(self,**kwargs) :itemlist = []d_opt = self.get_dict()if (d_opt == None) :return Nonefor (k,v) in d_opt.items():item = [k,v['name'],v['pid'],0]itemlist.append(item)f_sort = kwargs.get('sort')if f_sort == None or f_sort == False:return itemlistreturn sorted(itemlist)# 数据装载def load(self,dbclass,fdinf):logging.debug('Load Table Tree Options %s....' % self.opt_name)fdkey = fdinf.get('key')fdname = fdinf.get('name')fdvalue = fdinf.get('value')fdparent = fdinf.get('parent')filtstr = fdinf.get('filter')try :rows = db.session.query(dbclass).filter(text(filtstr)).all()opt_info = {}for irow in rows:valkey = getattr(irow,fdkey)valname = getattr(irow,fdname)valparent = getattr(irow,fdparent)opt_info[valkey] = dict(name=valname,pid=valparent)if (fdvalue != None) :valvalue = getattr(irow,fdvalue)opt_info[valkey]['value'] = valvalueself.opt_dict = opt_infoexcept SQLAlchemyError as e:logging.debug('装入树型选项集合[%s]数据库读取失败!!%s' % (self.opt_name,str(e.orig)))return 0return 1# 获取选项集合def get_option(self,optcat=None) :return self.get_tree()# 获取选项树型def get_tree(self) :item_opt = self.get_list(sort=True)if item_opt == None:return Noneif hasattr(self,'fdinf') :root_id = self.fdinf.get('rootid')else :root_id = 0#logging.debug('item_opt: %s' % item_opt)item_tree = self.build_tree(item_opt,root_id,0)return item_tree# 生成选项树型# 基于Layui-Tree接口格式生成数据def build_tree(self,data,p_id,level=0):tree = []for item in data:if item[2] !=p_id:continuerow = dict(id = item[0], title= item[1], parent_id=item[2],level= level)if level==0:row['spread'] = Truechild = self.build_tree(data, row['id'], level+1)row['children'] = []if child:row['children'] += childtree.append(row)return tree
通过加载函数load()完成条目字典的原始数据结构加载,其数据结构为{id111:{name:xxx,pid:xxx,value:xxx},id222:{...},...}。之后由原始数据结构为基础,然后通过一系列函数完成数据服务,主要包括:
get_name():根据键值取名称
id_format():生成格式化的选项展示(后台生成展示项时用)
get_map():生成键值map映射表
get_list():生成键值列表
get_tree():生成树型选项集合
get_option():对外提供的统一的选项集合接口
上述功能实现后,统一选项集合数据服务类就基本完成了。下面是几个具体的选项集合实例。
# 系统用户角色选项处理
optOprRole = List_OptionSet(Role,{'key': 'role_cd','name' : 'rolename','filter' : 'status=0 and roletype=0'},'OprRole')#机构树型选项处理
optBranchTree = Tree_OptionSet(Branchs,{'key': 'id','name' : 'short_name','parent' : 'parent_id','rootid' : 0,'filter' : 'status=0'},'BranchTree')#机构状态
optBranchStatus = OptionSet({0:'正常',1:'停用',9:'废弃'},'BranchStatus')
前端通过服务请求可以下载选项集合数据,并按个性化的需求对前端组件进行渲染。比如对layui-form中已经有的select、checkbox进行功能增强,也可以自行实现单选树型、多选树型等复合选择域,从而将layui-form的功能再次提升。