条目较少的选项集合,确实可以在程序中直接定义(其实最合适的还是存储在一个分类别的数据库表里),但条目较多的选项集合,或者是复杂的树型结构选项集合,一般都是存储在数据库中的,这样维护起来比较方便,也容易迁移和共享。所以,在完成基础选项集合框架后,考虑应用的方便性,面向数据库存储的选项集合扩展类就成了下一步开发的任务。
有了基础类做底,这些实现扩展起来也是十分方便。基于数据库存储的类包括两大类型,一种是列表类,这个就是基础选项类的扩展,数据结构没有区别,只是多了选项条目从数据库中装载到内存中的过程,另一种是树型类,它的数据结构比列表类复杂了一些,数据库里也是以父子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 = dbclass
self.fdinf = fdinf
self.opt_name = n_opt
self.opt_title = t_opt
if 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 None
return 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] = valname
self.opt_dict = opt_info
except SQLAlchemyError as e:
logging.debug('装入选项集合[%s]数据库读取失败!!%s' % (self.opt_name,str(e.orig)))
return 0
return 1
#数据重载函数
def reload(self) :
rtncode = self.load(self.dbclass,self.fdinf)
if rtncode == 0 :
return 0
if 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 = dbclass
self.fdinf = fdinf
self.opt_name = n_opt
self.opt_title = t_opt
if 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 + '_' + idname
else :
return str(id) + '_' + idname
# 获取选项映射
def get_map(self):
itemlist = {}
d_opt = self.get_dict()
if (d_opt == None) :
return None
for (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 None
for (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 itemlist
return 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'] = valvalue
self.opt_dict = opt_info
except SQLAlchemyError as e:
logging.debug('装入树型选项集合[%s]数据库读取失败!!%s' % (self.opt_name,str(e.orig)))
return 0
return 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 None
if 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:
continue
row = dict(id = item[0], title= item[1], parent_id=item[2],level= level)
if level==0:
row['spread'] = True
child = self.build_tree(data, row['id'], level+1)
row['children'] = []
if child:
row['children'] += child
tree.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的功能再次提升。