No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ledatasourcesql.py 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. #-*- coding: utf-8 -*-
  2. import pymysql
  3. import copy
  4. import leobject
  5. from leobject.datasources.dummy import DummyDatasource
  6. from leobject.leobject import REL_SUB, REL_SUP
  7. from leobject.letype import LeType
  8. from mosql.db import Database, all_to_dicts, one_to_dict
  9. from mosql.query import select, insert, update, delete, join
  10. from mosql.util import raw, or_
  11. import mosql.mysql
  12. from DataSource.MySQL.MySQL import MySQL
  13. from EditorialModel.classtypes import EmNature
  14. ## MySQL DataSource for LeObject
  15. class LeDataSourceSQL(DummyDatasource):
  16. RELATIONS_POSITIONS_FIELDS = {REL_SUP: 'superior_id', REL_SUB: 'subordinate_id'}
  17. def __init__(self, module=pymysql, conn_args=None):
  18. super(LeDataSourceSQL, self).__init__()
  19. self.module = module
  20. self.datasource_utils = MySQL
  21. if conn_args is None:
  22. conn_args = self.datasource_utils.connections['default']
  23. self.connection = Database(self.module, host=conn_args['host'], user=conn_args['user'], passwd=conn_args['passwd'], db=conn_args['db'])
  24. ## @brief inserts a new object
  25. # @param letype LeType
  26. # @param leclass LeClass
  27. # @param datas dict : dictionnary of field:value pairs to save
  28. # @return int : lodel_id of the created object
  29. # @todo add the returning clause and the insertion in "object"
  30. def insert(self, letype, leclass, datas):
  31. if isinstance(datas, list):
  32. res = list()
  33. for data in datas:
  34. res.append(self.insert(letype, leclass, data))
  35. return res
  36. elif isinstance(datas, dict):
  37. with self.connection as cur:
  38. object_datas = {'class_id': leclass._class_id, 'type_id': letype._type_id}
  39. if cur.execute(insert(self.datasource_utils.objects_table_name, object_datas)) != 1:
  40. raise RuntimeError('SQL error')
  41. if cur.execute('SELECT last_insert_id() as lodel_id') != 1:
  42. raise RuntimeError('SQL error')
  43. lodel_id, = cur.fetchone()
  44. datas[self.datasource_utils.field_lodel_id] = lodel_id
  45. query_table_name = self.datasource_utils.get_table_name_from_class(leclass.__name__)
  46. query = insert(query_table_name, datas)
  47. if cur.execute(query) != 1:
  48. raise RuntimeError('SQL error')
  49. return lodel_id
  50. ## @brief search for a collection of objects
  51. # @param leclass LeClass
  52. # @param letype LeType
  53. # @field_list list
  54. # @param filters list : list of tuples formatted as (FIELD, OPERATOR, VALUE)
  55. # @param relation_filters list : list of tuples formatted as (('superior'|'subordinate', FIELD), OPERATOR, VALUE)
  56. # @return list
  57. def get(self, leclass, letype, field_list, filters, relational_filters=None):
  58. if leclass is None:
  59. query_table_name = self.datasource_utils.objects_table_name
  60. else:
  61. query_table_name = self.datasource_utils.get_table_name_from_class(leclass.__name__)
  62. where_filters = self._prepare_filters(filters, query_table_name)
  63. join_fields = {}
  64. if relational_filters is not None and len(relational_filters) > 0:
  65. rel_filters = self._prepare_rel_filters(relational_filters)
  66. for rel_filter in rel_filters:
  67. # join condition
  68. relation_table_join_field = "%s.%s" % (self.datasource_utils.relations_table_name, self.RELATIONS_POSITIONS_FIELDS[rel_filter['position']])
  69. query_table_join_field = "%s.%s" % (query_table_name, self.datasource_utils.field_lodel_id)
  70. join_fields[query_table_join_field] = relation_table_join_field
  71. # Adding "where" filters
  72. where_filters['%s.%s' % (self.datasource_utils.relations_table_name, self.datasource_utils.relations_field_nature)] = rel_filter['nature']
  73. where_filters[rel_filter['condition_key']] = rel_filter['condition_value']
  74. # building the query
  75. query = select(query_table_name, where=where_filters, select=field_list, joins=join(self.datasource_utils.relations_table_name, join_fields))
  76. else:
  77. query = select(query_table_name, where=where_filters, select=field_list)
  78. # Executing the query
  79. with self.connection as cur:
  80. cur.execute(query)
  81. results = all_to_dicts(cur)
  82. return results
  83. ## @brief delete an existing object
  84. # @param letype LeType
  85. # @param leclass LeClass
  86. # @param filters list : list of tuples formatted as (FIELD, OPERATOR, VALUE)
  87. # @param relational_filters list : list of tuples formatted as (('superior'|'subordinate', FIELD), OPERATOR, VALUE)
  88. # @return bool : True on success
  89. def delete(self, letype, leclass, filters, relational_filters):
  90. query_table_name = self.datasource_utils.get_table_name_from_class(leclass.__name__)
  91. prep_filters = self._prepare_filters(filters, query_table_name)
  92. prep_rel_filters = self._prepare_rel_filters(relational_filters)
  93. if len(prep_rel_filters) > 0:
  94. query = "DELETE %s FROM " % query_table_name
  95. for prep_rel_filter in prep_rel_filters:
  96. query += "%s INNER JOIN %s ON (%s.%s = %s.%s)" % (
  97. self.datasource_utils.relations_table_name,
  98. query_table_name,
  99. self.datasource_utils.relations_table_name,
  100. prep_rel_filter['position'],
  101. query_table_name,
  102. self.datasource_utils.field_lodel_id
  103. )
  104. if prep_rel_filter['condition_key'][0] is not None:
  105. prep_filters[("%s.%s" % (self.datasource_utils.relations_table_name, prep_rel_filter['condition_key'][0]), prep_rel_filter['condition_key'][1])] = prep_rel_filter['condition_value']
  106. if prep_filters is not None and len(prep_filters) > 0:
  107. query += " WHERE "
  108. filter_counter = 0
  109. for filter_item in prep_filters:
  110. if filter_counter > 1:
  111. query += " AND "
  112. query += "%s %s %s" % (filter_item[0][0], filter_item[0][1], filter_item[1])
  113. else:
  114. query = delete(query_table_name, filters)
  115. query_delete_from_object = delete(self.datasource_utils.objects_table_name, {'lodel_id': filters['lodel_id']})
  116. with self.connection as cur:
  117. cur.execute(query)
  118. cur.execute(query_delete_from_object)
  119. return True
  120. ## @brief update an existing object's data
  121. # @param letype LeType
  122. # @param leclass LeClass
  123. # @param filters list : list of tuples formatted as (FIELD, OPERATOR, VALUE)
  124. # @param rel_filters list : list of tuples formatted as (('superior'|'subordinate', FIELD), OPERATOR, VALUE)
  125. # @param data dict
  126. # @return bool
  127. # @todo prendre en compte les rel_filters
  128. def update(self, letype, leclass, filters, rel_filters, data):
  129. query_table_name = self.datasource_utils.get_table_name_from_class(leclass.__name__)
  130. where_filters = filters
  131. set_data = data
  132. prepared_rel_filters = self._prepare_rel_filters(rel_filters)
  133. # Building the query
  134. query = update(table=query_table_name, where=where_filters, set=set_data)
  135. # Executing the query
  136. with self.connection as cur:
  137. cur.execute(query)
  138. return True
  139. ## @brief prepares the relational filters
  140. # @params rel_filters : (("superior"|"subordinate"), operator, value)
  141. # @return list
  142. def _prepare_rel_filters(self, rel_filters):
  143. prepared_rel_filters = []
  144. if rel_filters is not None and len(rel_filters) > 0:
  145. for rel_filter in rel_filters:
  146. rel_filter_dict = {
  147. 'position': REL_SUB if rel_filter[0][0] == REL_SUP else REL_SUB,
  148. 'nature': rel_filter[0][1],
  149. 'condition_key': (self.RELATIONS_POSITIONS_FIELDS[rel_filter[0][0]], rel_filter[1]),
  150. 'condition_value': rel_filter[2]
  151. }
  152. prepared_rel_filters.append(rel_filter_dict)
  153. return prepared_rel_filters
  154. ## @brief prepares the filters to be used by the mosql library's functions
  155. # @params filters : (FIELD, OPERATOR, VALUE) tuples
  156. # @return dict : Dictionnary with (FIELD, OPERATOR):VALUE style elements
  157. def _prepare_filters(self, filters, tablename=None):
  158. prepared_filters = {}
  159. if filters is not None and len(filters) > 0:
  160. for filter_item in filters:
  161. if '.' in filter_item[0]:
  162. prepared_filter_key = (filter_item[0], filter_item[1])
  163. else:
  164. prepared_filter_key = ("%s.%s" % (tablename, filter_item[0]), filter_item[1])
  165. prepared_filter_value = filter_item[2]
  166. prepared_filters[prepared_filter_key] = prepared_filter_value
  167. return prepared_filters
  168. ## @brief Make a relation between 2 LeType
  169. # @note rel2type relations. Superior is the LeType from the EmClass and subordinate the LeType for the EmType
  170. # @param lesup LeType : LeType child class instance that is from the EmClass containing the rel2type field
  171. # @param lesub LeType : LeType child class instance that is from the EmType linked by the rel2type field ( @ref EditorialModel.fieldtypes.rel2type.EmFieldType.rel_to_type_id )
  172. # @return The relation_id if success else return False
  173. def add_related(self, lesup, lesub, rank, **rel_attr):
  174. with self.connection as cur:
  175. #First step : relation table insert
  176. sql = insert(MySQL.relations_table_name,{
  177. 'id_sup': lesup.lodel_id,
  178. 'id_sub': lesub.lodel_id,
  179. 'rank': 0, #default value that will be set latter
  180. })
  181. cur.execute(sql)
  182. relation_id = cur.lastrowid
  183. if len(rel_attr) > 0:
  184. #There is some relation attribute to add in another table
  185. attr_table = get_r2t2table_name(lesup._leclass.__name__, lesub.__class__.__name__)
  186. rel_attr['id_relation'] = relation_id
  187. sql = insert(attr_table, rel_attr)
  188. cur.execute(sql)
  189. self._set_relation_rank(id_relation, rank)
  190. return relation_id
  191. ## @brief Deletes the relation between 2 LeType
  192. # @param lesup LeType
  193. # @param lesub LeType
  194. # @param fields dict
  195. # @return True if success else False
  196. # @todo Add fields parameter to identify relation
  197. # @todo Delete relationnal fields if some exists
  198. def del_related(self, lesup, lesub, fields=None):
  199. with self.connection as cur:
  200. del_params = {
  201. 'id_sup': lesup.lodel_id,
  202. 'id_sub': lesub.lodel_id
  203. }
  204. delete_params = {}
  205. if fields is not None:
  206. delete_params = del_params.copy()
  207. delete_params.update(fields)
  208. else:
  209. delete_params = del_params
  210. sql = delete(
  211. self.datasource_utils.relations_table_name,
  212. delete_params
  213. )
  214. if cur.execute(sql) != 1:
  215. return False
  216. return True
  217. ## @brief Fetch related (rel2type) by LeType
  218. # @param leo LeType : We want related LeObject of this LeType child class instance
  219. # @param letype LeType(class) : We want related LeObject of this LeType child class (not instance)
  220. # @param get_sub bool : If True leo is the superior and we want subordinates, else its the opposite
  221. # @return a list of dict { 'id_relation':.., 'rank':.., 'lesup':.., 'lesub'.., 'rel_attrs': dict() }
  222. def get_related(self, leo, letype, get_sub=True):
  223. if leobject.letype.LeType not in letype.__bases__:
  224. raise ValueError("letype argument should be a LeType child class, but got %s"%type(letype))
  225. if not isinstance(leo, LeType):
  226. raise ValueError("leo argument should be a LeType child class instance but got %s"%type(leo))
  227. with self.connection as cur:
  228. id_leo, id_type = 'id_sup', 'id_sub' if get_sub else 'id_sub', 'id_sup'
  229. joins = [
  230. join(
  231. (MySQL.objects_table_name, 'o'),
  232. on={'r.'+id_type: 'o.'+MySQL.field_lodel_id}
  233. ),
  234. join(
  235. (MySQL.objects_table_name, 'p'),
  236. on={'r.'+id_leo: 'p.'+MySQL.field_lodel_id}
  237. ),
  238. ]
  239. lesup, lesub = leo.__class__, letype if get_sub else letype, leo.__class__
  240. common_infos = ('r.id_relation', 'r.id_sup', 'r.id_sub', 'r.rank', 'r.depth')
  241. if len(lesup._linked_types[lesub]) > 0:
  242. #relationnal attributes, need to join with r2t table
  243. cls_name = leo.__class__.__name__ if get_sub else letype.__name__
  244. type_name = letype.__name__ if get_sub else leo.__class__.__name__
  245. joins.append(
  246. join(
  247. (MySQL.get_r2t2table_name(cls_name, type_name), 'r2t'),
  248. on={'r.'+MySQL.relations_pkname: 'r2t'+MySQL.relations_pkname}
  249. )
  250. )
  251. select=('r.id_relation', 'r.id_sup', 'r.id_sub', 'r.rank', 'r.depth', 'r2t.*')
  252. else:
  253. select = common_infos
  254. sql = select(
  255. (MySQL.relations_table_name, 'r'),
  256. select=select,
  257. where={
  258. id_leo: leo.lodel_id,
  259. 'type_id': letype._type_id,
  260. },
  261. joins=joins
  262. )
  263. cur.execute(sql)
  264. res = all_to_dicts(cur)
  265. #Building result
  266. ret = list()
  267. for datas in res:
  268. r_letype = letype(res['r.'+id_type])
  269. ret_item = {
  270. 'id_relation':+datas[MySQL.relations_pkname],
  271. 'lesup': r_leo if get_sub else r_letype,
  272. 'lesub': r_letype if get_sub else r_leo,
  273. 'rank': res['rank']
  274. }
  275. rel_attr = copy.copy(datas)
  276. for todel in common_infos:
  277. del(rel_attr[todel])
  278. ret_item['rel_attrs'] = rel_attr
  279. ret.append(ret_item)
  280. return ret
  281. ## @brief Set the rank of a relation identified by its ID
  282. # @param id_relation int : relation ID
  283. # @param rank int|str : 'first', 'last', or an integer value
  284. # @throw ValueError if rank is not valid
  285. # @throw leobject.leobject.LeObjectQueryError if id_relation don't exists
  286. def set_relation_rank(self, id_relation, rank):
  287. self._check_rank(rank)
  288. self._set_relation_rank(id_relation, rank)
  289. ## @brief Set the rank of a relation identified by its ID
  290. #
  291. # @note this solution is not the more efficient solution but it
  292. # garantee that ranks are continuous and starts at 1
  293. # @warning there is no way to fail on rank parameters even giving very bad parameters, if you want a method that may fail on rank use set_relation_rank() instead
  294. # @param id_relation int : relation ID
  295. # @param rank int|str : 'first', 'last', or an integer value
  296. # @throw leobject.leobject.LeObjectQueryError if id_relation don't exists
  297. def _set_relation_rank(self, id_relation, rank):
  298. ret = self.get_relation(id_relation, no_attr = True)
  299. if not ret:
  300. raise leobject.leobject.LeObjectQueryError("No relation with id_relation = %d"%id_relation)
  301. lesup = ret['lesup']
  302. lesub = ret['lesup']
  303. cur_rank = ret['rank']
  304. rank = 1 if rank == 'first' or rank < 1 else rank
  305. if cur_rank == rank:
  306. return True
  307. relations = self.get_related(lesup, lesub.__class__, get_sub=True)
  308. if not isinstance(rank, int) or rank > len(relations):
  309. rank = len(relations)
  310. if cur_rank == rank:
  311. return True
  312. #insert the relation at the good position
  313. our_relation = relations.pop(cur_rank)
  314. relations.insert(our_relation, rank)
  315. #gathering (relation_id, new_rank)
  316. rdatas = [ (attrs['relation_id'], new_rank+1) for new_rank,(sup, sub, attrs) in enumerate(relations) ]
  317. sql = insert(MySQL.relations_table_name, columns=(MySQL.relations_pkname, 'rank'), values = rdatas, on_duplicate_key_update={'rank',mosql.util.raw('VALUES(`rank`)')})
  318. ## @brief Check a rank value
  319. # @param rank int | str : Can be an integer >= 1 , 'first' or 'last'
  320. # @throw ValueError if the rank is not valid
  321. def _check_rank(self, rank):
  322. if isinstance(rank, str) and rank != 'first' and rank != 'last':
  323. raise ValueError("Invalid rank value : %s"%rank)
  324. elif isinstance(rank, int) and rank < 1:
  325. raise ValueError("Invalid rank value : %d"%rank)
  326. else:
  327. raise ValueError("Invalid rank type : %s"%type(rank))
  328. ## @brief Link two object given a relation nature, depth and rank
  329. # @param lesup LeObject : a LeObject
  330. # @param lesub LeObject : a LeObject
  331. # @param nature str|None : The relation nature or None if rel2type
  332. # @param rank int : a rank
  333. def add_relation(self, lesup, lesub, nature=None, depth=None, rank=None, **rel_attr):
  334. if len(rel_attr) > 0 and nature is not None:
  335. #not a rel2type but have some relation attribute
  336. raise AttributeError("No relation attributes allowed for non rel2type relations")
  337. with self.connection as cur:
  338. sql = insert(self.datasource_utils.relations_table_name, {'id_sup': lesup.lodel_id, 'id_sub': lesub.lodel_id, 'nature': nature, 'rank': rank, 'depth': depth})
  339. if cur.execute(sql) != 1:
  340. raise RuntimeError("Unknow SQL error")
  341. if len(rel_attr) > 0:
  342. #a relation table exists
  343. cur.execute('SELECT last_insert_id()')
  344. relation_id, = cur.fetchone()
  345. raise NotImplementedError()
  346. return True
  347. ## @brief Delete a relation
  348. # @warning this method may not be efficient
  349. # @param id_relation int : The relation identifier
  350. # @return bool
  351. def del_relation(self, id_relation):
  352. with self.connection as cur:
  353. pk_where = {MySQL.relations_pkname:id_relation}
  354. if not MySQL.fk_on_delete_cascade and len(lesup._linked_types[lesub.__class__]) > 0:
  355. #Delete the row in the relation attribute table
  356. ret = self.get_relation(id_relation, no_attr = False)
  357. lesup = ret['lesup']
  358. lesub = ret['lesub']
  359. sql = delete(MySQL.relations_table_name, pk_where)
  360. if cur.execute(sql) != 1:
  361. raise RuntimeError("Unknown SQL Error")
  362. sql = delete(MySQL.relations_table_name, pk_where)
  363. if cur.execute(sql) != 1:
  364. raise RuntimeError("Unknown SQL Error")
  365. return True
  366. ## @brief Fetch a relation
  367. # @param id_relation int : The relation identifier
  368. # @param no_attr bool : If true dont fetch rel_attr
  369. # @return a dict{'id_relation':.., 'lesup':.., 'lesub':..,'rank':.., 'depth':.., #if not none#'nature':.., #if exists#'dict_attr':..>}
  370. #
  371. # @todo TESTS
  372. def get_relation(self, id_relation, no_attr = False):
  373. relation = dict()
  374. with self.connection as cur:
  375. sql = select(MySQL.relation_table_name, {MySQL.relations_pkname: id_relation})
  376. if cur.execute(sql) != 1:
  377. raise RuntimeError("Unknow SQL error")
  378. res = all_to_dicts(cur)
  379. if len(res) == 0:
  380. return False
  381. if len(res) > 1:
  382. raise RuntimeError("When selecting on primary key, get more than one result. Bailout")
  383. if res['nature'] != None:
  384. raise ValueError("The relation with id %d is not a rel2type relation"%id_relation)
  385. leobj = leobject.lefactory.LeFactory.leobj_from_name('LeObject')
  386. lesup = leobj.uid2leobj(res['id_sup'])
  387. lesub = leobj.uid2leobj(res['id_sub'])
  388. relation['id_relation'] = res['id_relation']
  389. relation['lesup'] = lesup
  390. relation['lesub'] = lesub
  391. relation['rank'] = rank
  392. relation['depth'] = depth
  393. if not (res['nature'] is None):
  394. relation['nature'] = res['nature']
  395. if not no_attr and res['nature'] is None and len(lesup._linked_types[lesub.__class__]) != 0:
  396. #Fetch relation attributes
  397. rel_attr_table = MySQL.get_r2t2table_name(lesup.__class__.__name__, lesub.__class__.__name__)
  398. sql = select(MySQL.rel_attr_table, {MySQL.relations_pkname: id_relation})
  399. if cur.execute(sql) != 1:
  400. raise RuntimeError("Unknow SQL error")
  401. res = all_to_dicts(cur)
  402. if len(res) == 0:
  403. #Here raising a warning and adding empty (or default) attributes will be better
  404. raise RuntimeError("This relation should have attributes but none found !!!")
  405. if len(res) > 1:
  406. raise RuntimeError("When selecting on primary key, get more than one result. Bailout")
  407. attrs = res[0]
  408. relation['rel_attr'] = attrs
  409. return relation
  410. ## @brief Fetch all relations concerning an object (rel2type relations)
  411. # @param leo LeType : LeType child instance
  412. # @return a list of tuple (lesup, lesub, dict_attr)
  413. def get_relations(self, leo):
  414. sql = select(self.datasource_utils.relations_table_name, where=or_(({'id_sub':leo.lodel_id},{'id_sup':leo.lodel_id})))
  415. with self.connection as cur:
  416. results = all_to_dicts(cur.execute(sql))
  417. relations = []
  418. for result in results:
  419. id_sup = result['id_sup']
  420. id_sub = result['id_sub']
  421. del result['id_sup']
  422. del result['id_sub']
  423. rel_attr = result
  424. relations.append((id_sup, id_sub, rel_attr))
  425. return relations
  426. ## @brief Add a superior to a LeObject
  427. # @note in the MySQL version the method will have a depth=None argument to allow reccursive calls to add all the path to the root with corresponding depth
  428. # @param lesup LeType : superior LeType child class instance
  429. # @param lesub LeType : subordinate LeType child class instance
  430. # @param nature str : A relation nature @ref EditorialModel.classtypesa
  431. # @param rank int : The rank of this relation
  432. # @param depth None|int : The depth of the relation (used to make reccursive calls in order to link with all superiors)
  433. # @return The relation ID or False if fails
  434. def add_superior(self, lesup, lesub, nature, rank, depth=None):
  435. params = {'id_sup': lesup.lodel_id,'id_sub': lesub.lodel_id,'nature': nature,'rank': rank}
  436. if depth is not None:
  437. params['depth'] = depth
  438. sql_insert = insert(self.datasource_utils.relations_table_name, params)
  439. with self.connection as cur:
  440. if cur.execute(sql_insert) != 1:
  441. return False
  442. cur.execute('SELECT last_insert_id()')
  443. relation_id, = cur.fetchone()
  444. if nature in EmNature.getall():
  445. parent_superiors = lesup.superiors()
  446. for superior in parent_superiors:
  447. depth = depth - 1 if depth is not None else 1
  448. self.add_relation(lesup=superior.lodel_id, lesub=lesub.lodel_id, nature=nature, depth=depth, rank=rank)
  449. return relation_id
  450. ## @brief Fetch a superiors list ordered by depth for a LeType
  451. # @param lesub LeType : subordinate LeType child class instance
  452. # @param nature str : A relation nature @ref EditorialModel.classtypes
  453. # @return A list of LeType ordered by depth (the first is the direct superior)
  454. def get_superiors(self, lesub, nature):
  455. sql = select(
  456. self.datasource_utils.relations_table_name,
  457. columns=('id_sup',),
  458. where={'id_sub': lesub.lodel_id, 'nature': nature},
  459. order_by=('depth desc',)
  460. )
  461. result = []
  462. with self.connection as cur:
  463. results = all_to_dicts(cur.execute(sql))
  464. superiors = [LeType(result['id_sup']) for result in results]
  465. return superiors
  466. ## @brief Fetch the list of the subordinates given a nature
  467. # @param lesup LeType : superior LeType child class instance
  468. # @param nature str : A relation nature @ref EditorialModel.classtypes
  469. # @return A list of LeType ordered by rank that are subordinates of lesup in a "nature" relation
  470. def get_subordinates(self, lesup, nature):
  471. with self.connection as cur:
  472. id_sup = lesup.lodel_id if isinstance(lesup, leobject.letype.LeType) else MySQL.leroot_lodel_id
  473. sql = select(
  474. MySQL.relations_table_name,
  475. columns=('id_sup',),
  476. where={'id_sup': id_sup, 'nature': nature},
  477. order_by=('rank',)
  478. )
  479. cur.execut(sql)
  480. res = all_to_dicts(cur)
  481. return [LeType(r['id_sup']) for r in res]