Bez popisu
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.

leapidatasource.py 38KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. #-*- coding: utf-8 -*-
  2. import pymysql
  3. import copy
  4. import leapi
  5. from leapi.leobject import REL_SUB, REL_SUP
  6. from leapi.lecrud import _LeCrud
  7. from mosql.db import Database, all_to_dicts, one_to_dict
  8. from mosql.query import select, insert, update, delete, join, left_join
  9. from mosql.util import raw, or_
  10. import mosql.mysql
  11. from DataSource.dummy.leapidatasource import DummyDatasource
  12. from DataSource.MySQL import utils
  13. from EditorialModel.classtypes import EmNature
  14. from Lodel.settings import Settings
  15. ## MySQL DataSource for LeObject
  16. class LeDataSourceSQL(DummyDatasource):
  17. RELATIONS_POSITIONS_FIELDS = {REL_SUP: 'superior_id', REL_SUB: 'subordinate_id'}
  18. def __init__(self, module=pymysql, conn_args=None):
  19. super(LeDataSourceSQL, self).__init__()
  20. self.module = module
  21. if conn_args is None:
  22. conn_args = copy.copy(Settings.get('datasource')['default'])
  23. self.module = conn_args['module']
  24. del conn_args['module']
  25. self.connection = Database(self.module, **conn_args)
  26. ## @brief select lodel editorial components using given filters
  27. # @param target_cls LeCrud(class): The component class concerned by the select (a LeCrud child class (not instance !) )
  28. # @param field_list list: List of field to fetch
  29. # @param filters list: List of filters (see @ref lecrud_filters)
  30. # @param rel_filters list: List of relational filters (see @ref lecrud_filters)
  31. # @return a list of LeCrud child classes
  32. # @todo this only works with LeObject.get(), LeClass.get() and LeType.get()
  33. # @todo for speed get rid of all_to_dicts
  34. # @todo filters: all use cases are not implemented
  35. # @todo group: mosql does not permit direction in group_by clause, it should, so for now we don't use direction in group clause
  36. def select(self, target_cls, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=None):
  37. joins = []
  38. # it is a LeObject, query only on main table
  39. if target_cls.__name__ == 'LeObject':
  40. main_table = utils.common_tables['object']
  41. fields = [(main_table, target_cls.fieldlist())]
  42. # it is a LeType or a LeClass, query on main table left join class table on lodel_id
  43. elif target_cls.is_letype() or target_cls.is_leclass():
  44. # find main table and main table datas
  45. main_table = utils.common_tables['object']
  46. main_class = target_cls.leo_class()
  47. class_table = utils.object_table_name(main_class.__name__)
  48. main_lodel_id = utils.column_prefix(main_table, main_class.uidname())
  49. class_lodel_id = utils.column_prefix(class_table, main_class.uidname())
  50. joins = [left_join(class_table, {main_lodel_id:class_lodel_id})]
  51. fields = [(main_table, target_cls.name2class('LeObject').fieldlist()), (class_table, main_class.fieldlist())]
  52. elif target_cls.is_lehierarch():
  53. main_table = utils.common_tables['relation']
  54. fields = [(main_table, target_cls.name2class('LeRelation').fieldlist())]
  55. elif target_cls.is_lerel2type():
  56. main_table = utils.common_tables['relation']
  57. fields = [(main_table, target_cls.name2class('LeRelation').fieldlist())]
  58. else:
  59. raise AttributeError("Target class '%s' in get() is not a Lodel Editorial Object !" % target_cls)
  60. # prefix column name in fields list
  61. prefixed_field_list = [utils.find_prefix(name, fields) for name in field_list]
  62. kwargs = {}
  63. if group:
  64. kwargs['group_by'] = (utils.find_prefix(column, fields) for column, direction in group)
  65. if order:
  66. kwargs['order_by'] = (utils.find_prefix(column, fields) + ' ' + direction for column, direction in order)
  67. if limit:
  68. kwargs['limit'] = limit
  69. if offset:
  70. kwargs['offset'] = offset
  71. # @todo implement relational filters
  72. # prefix filters'' column names, and prepare dict for mosql where {(fieldname, op): value}
  73. wheres = {(utils.find_prefix(name, fields), op):value for name,op,value in filters}
  74. query = select(main_table, select=prefixed_field_list, where=wheres, joins=joins, **kwargs)
  75. #print ('SQL', query)
  76. # Executing the query
  77. cur = utils.query(self.connection, query)
  78. results = all_to_dicts(cur)
  79. # instanciate each row to editorial components
  80. results = [target_cls.object_from_data(datas) for datas in results]
  81. #print('results', results)
  82. return results
  83. ## @brief delete lodel editorial components given filters
  84. # @param target_cls LeCrud(class): The component class concerned by the delete (a LeCrud child class (not instance !) )
  85. # @param filters list : List of filters (see @ref leobject_filters)
  86. # @param rel_filters list : List of relational filters (see @ref leobject_filters)
  87. # @return the number of deleted components
  88. def delete(self, target_cls, filters, rel_filters):
  89. query_table_name = self.datasource_utils.get_table_name_from_class(target_cls.__name__)
  90. prep_filters = self._prepare_filters(filters, query_table_name)
  91. prep_rel_filters = self._prepare_rel_filters(rel_filters)
  92. if len(prep_rel_filters) > 0:
  93. query = "DELETE %s FROM" % query_table_name
  94. for prep_rel_filter in prep_rel_filters:
  95. query += "%s INNER JOIN %s ON (%s.%s = %s.%s)" % (
  96. self.datasource_utils.relations_table_name,
  97. query_table_name,
  98. self.datasource_utils.relations_table_name,
  99. prep_rel_filter['position'],
  100. query_table_name,
  101. self.datasource_utils.field_lodel_id
  102. )
  103. if prep_rel_filter['condition_key'][0] is not None:
  104. 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']
  105. if prep_filters is not None and len(prep_filters) > 0:
  106. query += " WHERE "
  107. filter_counter = 0
  108. for filter_item in prep_filters:
  109. if filter_counter > 1:
  110. query += " AND "
  111. query += "%s %s %s" % (filter_item[0][0], filter_item[0][1], filter_item[1])
  112. else:
  113. query = delete(query_table_name, prep_filters)
  114. query_delete_from_object = delete(self.datasource_utils.objects_table_name, {'lodel_id': filters['lodel_id']})
  115. with self.connection as cur:
  116. result = cur.execute(query)
  117. cur.execute(query_delete_from_object)
  118. return result
  119. ## @brief update an existing lodel editorial component
  120. # @param target_cls LeCrud(class) : The component class concerned by the update (a LeCrud child class (not instance !) )
  121. # @param filters list : List of filters (see @ref leobject_filters)
  122. # @param rel_filters list : List of relationnal filters (see @ref leobject_filters)
  123. # @param **datas : Datas in kwargs
  124. # @return the number of updated components
  125. # @todo implement other filters than lodel_id
  126. def update(self, target_cls, filters, rel_filters, **datas):
  127. #print(target_cls, filters, rel_filters, datas)
  128. # it is a LeType
  129. if not target_cls.is_letype():
  130. raise AttributeError("'%s' is not a LeType, it is not possible to update it" % target_cls)
  131. # find main table and main table datas
  132. main_table = self.datasource_utils.objects_table_name
  133. main_datas = {self.datasource_utils.field_lodel_id: raw(self.datasource_utils.field_lodel_id)} # be sure to have one SET clause
  134. for main_column_name in common_fields:
  135. if main_column_name in datas:
  136. main_datas[main_column_name] = datas[main_column_name]
  137. del(datas[main_column_name])
  138. wheres = {(name, op):value for name,op,value in filters}
  139. query = update(main_table, wheres, main_datas)
  140. #print(query)
  141. self.datasource_utils.query(self.connection, query)
  142. # update on class table
  143. if datas:
  144. class_table = self.datasource_utils.get_table_name_from_class(target_cls._leclass.__name__)
  145. query = update(class_table, wheres, datas)
  146. #print(query)
  147. self.datasource_utils.query(self.connection, query)
  148. return True
  149. ## @brief inserts a new lodel editorial component
  150. # @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
  151. # @param **datas : The datas to insert
  152. # @return The inserted component's id
  153. # @todo should work with LeType, LeClass, and Relations
  154. def insert(self, target_cls, **datas):
  155. # it is a LeType
  156. if target_cls.is_letype():
  157. main_table = utils.common_tables['object']
  158. main_datas = {'class_id':target_cls._leclass._class_id, 'type_id':target_cls._type_id}
  159. main_fields = target_cls.name2class('LeObject').fieldlist()
  160. class_table = utils.object_table_name(target_cls.leo_class().__name__)
  161. fk_name = target_cls.uidname()
  162. # it is a hierarchy
  163. elif target_cls.is_lehierarch():
  164. main_table = utils.common_tables['relation']
  165. main_datas = {
  166. utils.column_name(target_cls._lesup_name): datas[target_cls._lesup_name].lodel_id,
  167. utils.column_name(target_cls._lesub_name): datas[target_cls._lesub_name].lodel_id
  168. }
  169. main_fields = target_cls.name2class('LeRelation').fieldlist()
  170. class_table = False
  171. # it is a relation
  172. elif target_cls.is_lerel2type():
  173. main_table = utils.common_tables['relation']
  174. main_datas = {
  175. utils.column_name(target_cls._lesup_name): datas[target_cls._lesup_name].lodel_id,
  176. utils.column_name(target_cls._lesub_name): datas[target_cls._lesub_name].lodel_id
  177. }
  178. main_fields = target_cls.name2class('LeRelation').fieldlist()
  179. superior_class = datas['superior'].leo_class()
  180. class_table = utils.r2t_table_name(superior_class.__name__, datas['subordinate'].__class__.__name__)
  181. fk_name = superior_class.name2class('LeRelation').uidname()
  182. else:
  183. raise AttributeError("'%s' is not a LeType or a LeRelation, it is not possible to insert it" % target_cls)
  184. # extract main table datas from datas
  185. for main_column_name in main_fields:
  186. if main_column_name in datas:
  187. if main_column_name not in main_datas:
  188. main_datas[main_column_name] = datas[main_column_name]
  189. del(datas[main_column_name])
  190. sql = insert(main_table, main_datas)
  191. #print (sql)
  192. cur = utils.query(self.connection, sql)
  193. lodel_id = cur.lastrowid
  194. if class_table:
  195. # insert in class_table
  196. datas[fk_name] = lodel_id
  197. sql = insert(class_table, datas)
  198. #print (sql)
  199. utils.query(self.connection, sql)
  200. return lodel_id
  201. ## @brief insert multiple editorial component
  202. # @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
  203. # @param datas_list list : A list of dict representing the datas to insert
  204. # @return int the number of inserted component
  205. def insert_multi(self, target_cls, datas_list):
  206. res = list()
  207. for data in datas_list:
  208. res.append(self.insert(target_cls, data))
  209. return len(res)
  210. ## @brief prepares the relational filters
  211. # @params rel_filters : (("superior"|"subordinate"), operator, value)
  212. # @return list
  213. def _prepare_rel_filters(self, rel_filters):
  214. prepared_rel_filters = []
  215. if rel_filters is not None and len(rel_filters) > 0:
  216. for rel_filter in rel_filters:
  217. rel_filter_dict = {
  218. 'position': REL_SUB if rel_filter[0][0] == REL_SUP else REL_SUB,
  219. 'nature': rel_filter[0][1],
  220. 'condition_key': (self.RELATIONS_POSITIONS_FIELDS[rel_filter[0][0]], rel_filter[1]),
  221. 'condition_value': rel_filter[2]
  222. }
  223. prepared_rel_filters.append(rel_filter_dict)
  224. return prepared_rel_filters
  225. ## @brief prepares the filters to be used by the mosql library's functions
  226. # @params filters : (FIELD, OPERATOR, VALUE) tuples
  227. # @return dict : Dictionnary with (FIELD, OPERATOR):VALUE style elements
  228. def _prepare_filters(self, filters, tablename=None):
  229. prepared_filters = {}
  230. if filters is not None and len(filters) > 0:
  231. for filter_item in filters:
  232. if '.' in filter_item[0]:
  233. prepared_filter_key = (filter_item[0], filter_item[1])
  234. else:
  235. prepared_filter_key = ("%s.%s" % (tablename, filter_item[0]), filter_item[1])
  236. prepared_filter_value = filter_item[2]
  237. prepared_filters[prepared_filter_key] = prepared_filter_value
  238. return prepared_filters
  239. # ================================================================================================================ #
  240. # FONCTIONS A DEPLACER #
  241. # ================================================================================================================ #
  242. ## @brief Make a relation between 2 LeType
  243. # @note rel2type relations. Superior is the LeType from the EmClass and subordinate the LeType for the EmType
  244. # @param lesup LeType : LeType child class instance that is from the EmClass containing the rel2type field
  245. # @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 )
  246. # @return The relation_id if success else return False
  247. def add_related(self, lesup, lesub, rank, **rel_attr):
  248. with self.connection as cur:
  249. #First step : relation table insert
  250. sql = insert(MySQL.relations_table_name, {
  251. 'id_sup': lesup.lodel_id,
  252. 'id_sub': lesub.lodel_id,
  253. 'rank': 0, # default value that will be set latter
  254. })
  255. cur.execute(sql)
  256. relation_id = cur.lastrowid
  257. if len(rel_attr) > 0:
  258. #There is some relation attribute to add in another table
  259. attr_table = get_r2t2table_name(lesup._leclass.__name__, lesub.__class__.__name__)
  260. rel_attr['id_relation'] = relation_id
  261. sql = insert(attr_table, rel_attr)
  262. cur.execute(sql)
  263. self._set_relation_rank(id_relation, rank)
  264. return relation_id
  265. ## @brief Deletes the relation between 2 LeType
  266. # @param lesup LeType
  267. # @param lesub LeType
  268. # @param fields dict
  269. # @return True if success else False
  270. # @todo Add fields parameter to identify relation
  271. # @todo Delete relationnal fields if some exists
  272. def del_related(self, lesup, lesub, fields=None):
  273. with self.connection as cur:
  274. del_params = {
  275. 'id_sup': lesup.lodel_id,
  276. 'id_sub': lesub.lodel_id
  277. }
  278. delete_params = {}
  279. if fields is not None:
  280. delete_params = del_params.copy()
  281. delete_params.update(fields)
  282. else:
  283. delete_params = del_params
  284. sql = delete(
  285. self.datasource_utils.relations_table_name,
  286. delete_params
  287. )
  288. if cur.execute(sql) != 1:
  289. return False
  290. return True
  291. ## @brief Fetch related (rel2type) by LeType
  292. # @param leo LeType : We want related LeObject of this LeType child class instance
  293. # @param letype LeType(class) : We want related LeObject of this LeType child class (not instance)
  294. # @param get_sub bool : If True leo is the superior and we want subordinates, else its the opposite
  295. # @return a list of dict { 'id_relation':.., 'rank':.., 'lesup':.., 'lesub'.., 'rel_attrs': dict() }
  296. # TODO A conserver , utilisé par la nouvelle méthode update_rank
  297. def get_related(self, leo, letype, get_sub=True):
  298. if LeCrud.name2class('LeType') not in letype.__bases__:
  299. raise ValueError("letype argument should be a LeType child class, but got %s" % type(letype))
  300. if not isinstance(leo, LeType):
  301. raise ValueError("leo argument should be a LeType child class instance but got %s" % type(leo))
  302. with self.connection as cur:
  303. id_leo, id_type = 'id_sup', 'id_sub' if get_sub else 'id_sub', 'id_sup'
  304. joins = [
  305. join(
  306. (MySQL.objects_table_name, 'o'),
  307. on={'r.' + id_type: 'o.' + MySQL.field_lodel_id}
  308. ),
  309. join(
  310. (MySQL.objects_table_name, 'p'),
  311. on={'r.' + id_leo: 'p.' + MySQL.field_lodel_id}
  312. ),
  313. ]
  314. lesup, lesub = leo.__class__, letype if get_sub else letype, leo.__class__
  315. common_infos = ('r.id_relation', 'r.id_sup', 'r.id_sub', 'r.rank', 'r.depth')
  316. if len(lesup._linked_types[lesub]) > 0:
  317. #relationnal attributes, need to join with r2t table
  318. cls_name = leo.__class__.__name__ if get_sub else letype.__name__
  319. type_name = letype.__name__ if get_sub else leo.__class__.__name__
  320. joins.append(
  321. join(
  322. (MySQL.get_r2t2table_name(cls_name, type_name), 'r2t'),
  323. on={'r.' + MySQL.relations_pkname: 'r2t' + MySQL.relations_pkname}
  324. )
  325. )
  326. select = ('r.id_relation', 'r.id_sup', 'r.id_sub', 'r.rank', 'r.depth', 'r2t.*')
  327. else:
  328. select = common_infos
  329. sql = select(
  330. (MySQL.relations_table_name, 'r'),
  331. select=select,
  332. where={
  333. id_leo: leo.lodel_id,
  334. 'type_id': letype._type_id,
  335. },
  336. joins=joins
  337. )
  338. cur.execute(sql)
  339. res = all_to_dicts(cur)
  340. #Building result
  341. ret = list()
  342. for datas in res:
  343. r_letype = letype(res['r.' + id_type])
  344. ret_item = {
  345. 'id_relation': +datas[MySQL.relations_pkname],
  346. 'lesup': r_leo if get_sub else r_letype,
  347. 'lesub': r_letype if get_sub else r_leo,
  348. 'rank': res['rank']
  349. }
  350. rel_attr = copy.copy(datas)
  351. for todel in common_infos:
  352. del rel_attr[todel]
  353. ret_item['rel_attrs'] = rel_attr
  354. ret.append(ret_item)
  355. return ret
  356. ## @brief Set the rank of a relation identified by its ID
  357. # @param id_relation int : relation ID
  358. # @param rank int|str : 'first', 'last', or an integer value
  359. # @throw ValueError if rank is not valid
  360. # @throw leapi.leapi.LeObjectQueryError if id_relation don't exists
  361. def set_relation_rank(self, id_relation, rank):
  362. self._check_rank(rank)
  363. self._set_relation_rank(id_relation, rank)
  364. ## @brief Sets a new rank on a relation
  365. # @param le_relation LeRelation
  366. # @param new_rank int: integer representing the absolute new rank
  367. # @return True if success, False if failure
  368. # TODO Conserver cette méthode dans le datasource du fait des requêtes SQL. Elle est appelée par le set_rank de LeRelation
  369. def update_rank(self, le_relation, rank):
  370. lesup = le_relation.id_sup
  371. lesub = le_relation.id_sub
  372. current_rank = le_relation.rank
  373. relations = le_relation.__class__.get(query_filters=[('id_sup', '=', lesup)], order=[('rank', 'ASC')])
  374. # relations = self.get_related(lesup, lesub.__class__, get_sub=True)
  375. # insert the relation at the right position considering its new rank
  376. our_relation = relations.pop(current_rank)
  377. relations.insert(our_relation, rank)
  378. # rebuild now the list of relations from the resorted list and recalculating the ranks
  379. rdatas = [(attrs['relation_id'], new_rank+1) for new_rank, (sup, sub, attrs) in enumerate(relations)]
  380. sql = insert(MySQL.relations_table_name, columns=(MySQL.relations_pkname, 'rank'), values=rdatas, on_duplicate_key_update={'rank', mosql.util.raw('VALUES(`rank`)')})
  381. with self.connection as cur:
  382. if cur.execute(sql) != 1:
  383. return False
  384. else:
  385. return True
  386. ## @brief Set the rank of a relation identified by its ID
  387. #
  388. # @note this solution is not the more efficient solution but it
  389. # garantee that ranks are continuous and starts at 1
  390. # @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
  391. # @param id_relation int : relation ID
  392. # @param rank int|str : 'first', 'last', or an integer value
  393. # @throw leapi.leapi.LeObjectQueryError if id_relation don't exists
  394. def _set_relation_rank(self, id_relation, rank):
  395. ret = self.get_relation(id_relation, no_attr=True)
  396. if not ret:
  397. raise leapi.leapi.LeObjectQueryError("No relation with id_relation = %d" % id_relation)
  398. lesup = ret['lesup']
  399. lesub = ret['lesup']
  400. cur_rank = ret['rank']
  401. rank = 1 if rank == 'first' or rank < 1 else rank
  402. if cur_rank == rank:
  403. return True
  404. relations = self.get_related(lesup, lesub.__class__, get_sub=True)
  405. if not isinstance(rank, int) or rank > len(relations):
  406. rank = len(relations)
  407. if cur_rank == rank:
  408. return True
  409. #insert the relation at the good position
  410. our_relation = relations.pop(cur_rank)
  411. relations.insert(our_relation, rank)
  412. #gathering (relation_id, new_rank)
  413. rdatas = [(attrs['relation_id'], new_rank + 1) for new_rank, (sup, sub, attrs) in enumerate(relations)]
  414. sql = insert(MySQL.relations_table_name, columns=(MySQL.relations_pkname, 'rank'), values=rdatas, on_duplicate_key_update={'rank', mosql.util.raw('VALUES(`rank`)')})
  415. ## @brief Check a rank value
  416. # @param rank int | str : Can be an integer >= 1 , 'first' or 'last'
  417. # @throw ValueError if the rank is not valid
  418. def _check_rank(self, rank):
  419. if isinstance(rank, str) and rank != 'first' and rank != 'last':
  420. raise ValueError("Invalid rank value : %s" % rank)
  421. elif isinstance(rank, int) and rank < 1:
  422. raise ValueError("Invalid rank value : %d" % rank)
  423. else:
  424. raise ValueError("Invalid rank type : %s" % type(rank))
  425. ## @brief Link two object given a relation nature, depth and rank
  426. # @param lesup LeObject : a LeObject
  427. # @param lesub LeObject : a LeObject
  428. # @param nature str|None : The relation nature or None if rel2type
  429. # @param rank int : a rank
  430. def add_relation(self, lesup, lesub, nature=None, depth=None, rank=None, **rel_attr):
  431. if len(rel_attr) > 0 and nature is not None:
  432. #not a rel2type but have some relation attribute
  433. raise AttributeError("No relation attributes allowed for non rel2type relations")
  434. with self.connection as cur:
  435. sql = insert(self.datasource_utils.relations_table_name, {'id_sup': lesup.lodel_id, 'id_sub': lesub.lodel_id, 'nature': nature, 'rank': rank, 'depth': depth})
  436. if cur.execute(sql) != 1:
  437. raise RuntimeError("Unknow SQL error")
  438. if len(rel_attr) > 0:
  439. #a relation table exists
  440. cur.execute('SELECT last_insert_id()')
  441. relation_id, = cur.fetchone()
  442. raise NotImplementedError()
  443. return True
  444. ## @brief Delete a relation
  445. # @warning this method may not be efficient
  446. # @param id_relation int : The relation identifier
  447. # @return bool
  448. def del_relation(self, id_relation):
  449. with self.connection as cur:
  450. pk_where = {MySQL.relations_pkname: id_relation}
  451. if not MySQL.fk_on_delete_cascade and len(lesup._linked_types[lesub.__class__]) > 0:
  452. #Delete the row in the relation attribute table
  453. ret = self.get_relation(id_relation, no_attr=False)
  454. lesup = ret['lesup']
  455. lesub = ret['lesub']
  456. sql = delete(MySQL.relations_table_name, pk_where)
  457. if cur.execute(sql) != 1:
  458. raise RuntimeError("Unknown SQL Error")
  459. sql = delete(MySQL.relations_table_name, pk_where)
  460. if cur.execute(sql) != 1:
  461. raise RuntimeError("Unknown SQL Error")
  462. return True
  463. ## @brief Fetch a relation
  464. # @param id_relation int : The relation identifier
  465. # @param no_attr bool : If true dont fetch rel_attr
  466. # @return a dict{'id_relation':.., 'lesup':.., 'lesub':..,'rank':.., 'depth':.., #if not none#'nature':.., #if exists#'dict_attr':..>}
  467. #
  468. # @todo TESTS
  469. # TODO conserver, appelé par la méthode update_rank
  470. def get_relation(self, id_relation, no_attr=False):
  471. relation = dict()
  472. with self.connection as cur:
  473. sql = select(MySQL.relation_table_name, {MySQL.relations_pkname: id_relation})
  474. if cur.execute(sql) != 1:
  475. raise RuntimeError("Unknow SQL error")
  476. res = all_to_dicts(cur)
  477. if len(res) == 0:
  478. return False
  479. if len(res) > 1:
  480. raise RuntimeError("When selecting on primary key, get more than one result. Bailout")
  481. if res['nature'] is not None:
  482. raise ValueError("The relation with id %d is not a rel2type relation" % id_relation)
  483. leobj = leapi.lefactory.LeFactory.leobj_from_name('LeObject')
  484. lesup = leobj.uid2leobj(res['id_sup'])
  485. lesub = leobj.uid2leobj(res['id_sub'])
  486. relation['id_relation'] = res['id_relation']
  487. relation['lesup'] = lesup
  488. relation['lesub'] = lesub
  489. relation['rank'] = rank
  490. relation['depth'] = depth
  491. if res['nature'] is not None:
  492. relation['nature'] = res['nature']
  493. if not no_attr and res['nature'] is None and len(lesup._linked_types[lesub.__class__]) != 0:
  494. #Fetch relation attributes
  495. rel_attr_table = MySQL.get_r2t2table_name(lesup.__class__.__name__, lesub.__class__.__name__)
  496. sql = select(MySQL.rel_attr_table, {MySQL.relations_pkname: id_relation})
  497. if cur.execute(sql) != 1:
  498. raise RuntimeError("Unknow SQL error")
  499. res = all_to_dicts(cur)
  500. if len(res) == 0:
  501. #Here raising a warning and adding empty (or default) attributes will be better
  502. raise RuntimeError("This relation should have attributes but none found !!!")
  503. if len(res) > 1:
  504. raise RuntimeError("When selecting on primary key, get more than one result. Bailout")
  505. attrs = res[0]
  506. relation['rel_attr'] = attrs
  507. return relation
  508. ## @brief Fetch all relations concerning an object (rel2type relations)
  509. # @param leo LeType : LeType child instance
  510. # @return a list of tuple (lesup, lesub, dict_attr)
  511. def get_relations(self, leo):
  512. sql = select(self.datasource_utils.relations_table_name, where=or_(({'id_sub': leo.lodel_id}, {'id_sup': leo.lodel_id})))
  513. with self.connection as cur:
  514. results = all_to_dicts(cur.execute(sql))
  515. relations = []
  516. for result in results:
  517. id_sup = result['id_sup']
  518. id_sub = result['id_sub']
  519. del result['id_sup']
  520. del result['id_sub']
  521. rel_attr = result
  522. relations.append((id_sup, id_sub, rel_attr))
  523. return relations
  524. ## @brief Add a superior to a LeObject
  525. # @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
  526. # @param lesup LeType : superior LeType child class instance
  527. # @param lesub LeType : subordinate LeType child class instance
  528. # @param nature str : A relation nature @ref EditorialModel.classtypesa
  529. # @param rank int : The rank of this relation
  530. # @param depth None|int : The depth of the relation (used to make reccursive calls in order to link with all superiors)
  531. # @return The relation ID or False if fails
  532. def add_superior(self, lesup, lesub, nature, rank, depth=None):
  533. params = {'id_sup': lesup.lodel_id, 'id_sub': lesub.lodel_id, 'nature': nature, 'rank': rank}
  534. if depth is not None:
  535. params['depth'] = depth
  536. sql_insert = insert(self.datasource_utils.relations_table_name, params)
  537. with self.connection as cur:
  538. if cur.execute(sql_insert) != 1:
  539. return False
  540. cur.execute('SELECT last_insert_id()')
  541. relation_id, = cur.fetchone()
  542. if nature in EmNature.getall():
  543. parent_superiors = lesup.superiors()
  544. for superior in parent_superiors:
  545. depth = depth - 1 if depth is not None else 1
  546. self.add_relation(lesup=superior.lodel_id, lesub=lesub.lodel_id, nature=nature, depth=depth, rank=rank)
  547. return relation_id
  548. ## @brief Fetch a superiors list ordered by depth for a LeType
  549. # @param lesub LeType : subordinate LeType child class instance
  550. # @param nature str : A relation nature @ref EditorialModel.classtypes
  551. # @return A list of LeType ordered by depth (the first is the direct superior)
  552. def get_superiors(self, lesub, nature):
  553. sql = select(
  554. self.datasource_utils.relations_table_name,
  555. columns=('id_sup',),
  556. where={'id_sub': lesub.lodel_id, 'nature': nature},
  557. order_by=('depth desc',)
  558. )
  559. result = []
  560. with self.connection as cur:
  561. results = all_to_dicts(cur.execute(sql))
  562. superiors = [LeType(result['id_sup']) for result in results]
  563. return superiors
  564. ## @brief Fetch the list of the subordinates given a nature
  565. # @param lesup LeType : superior LeType child class instance
  566. # @param nature str : A relation nature @ref EditorialModel.classtypes
  567. # @return A list of LeType ordered by rank that are subordinates of lesup in a "nature" relation
  568. def get_subordinates(self, lesup, nature):
  569. with self.connection as cur:
  570. id_sup = lesup.lodel_id if isinstance(lesup, leapi.letype.LeType) else MySQL.leroot_lodel_id
  571. sql = select(
  572. MySQL.relations_table_name,
  573. columns=('id_sup',),
  574. where={'id_sup': id_sup, 'nature': nature},
  575. order_by=('rank',)
  576. )
  577. cur.execut(sql)
  578. res = all_to_dicts(cur)
  579. return [LeType(r['id_sup']) for r in res]
  580. # ================================================================================================================ #
  581. # FONCTIONS A SUPPRIMER #
  582. # ================================================================================================================ #
  583. ## @brief inserts a new object
  584. # @param letype LeType
  585. # @param leclass LeClass
  586. # @param datas dict : dictionnary of field:value pairs to save
  587. # @return int : lodel_id of the created object
  588. # @todo add the returning clause and the insertion in "object"
  589. # def insert(self, letype, leclass, datas):
  590. # if isinstance(datas, list):
  591. # res = list()
  592. # for data in datas:
  593. # res.append(self.insert(letype, leclass, data))
  594. # return res if len(res)>1 else res[0]
  595. # elif isinstance(datas, dict):
  596. #
  597. # object_datas = {'class_id': leclass._class_id, 'type_id': letype._type_id}
  598. #
  599. # cur = self.datasource_utils.query(self.connection, insert(self.datasource_utils.objects_table_name, object_datas))
  600. # lodel_id = cur.lastrowid
  601. #
  602. # datas[self.datasource_utils.field_lodel_id] = lodel_id
  603. # query_table_name = self.datasource_utils.get_table_name_from_class(leclass.__name__)
  604. # self.datasource_utils.query(self.connection, insert(query_table_name, datas))
  605. #
  606. # return lodel_id
  607. ## @brief search for a collection of objects
  608. # @param leclass LeClass
  609. # @param letype LeType
  610. # @field_list list
  611. # @param filters list : list of tuples formatted as (FIELD, OPERATOR, VALUE)
  612. # @param relation_filters list : list of tuples formatted as (('superior'|'subordinate', FIELD), OPERATOR, VALUE)
  613. # @return list
  614. # def get(self, leclass, letype, field_list, filters, relational_filters=None):
  615. #
  616. # if leclass is None:
  617. # query_table_name = self.datasource_utils.objects_table_name
  618. # else:
  619. # query_table_name = self.datasource_utils.get_table_name_from_class(leclass.__name__)
  620. # where_filters = self._prepare_filters(filters, query_table_name)
  621. # join_fields = {}
  622. #
  623. # if relational_filters is not None and len(relational_filters) > 0:
  624. # rel_filters = self._prepare_rel_filters(relational_filters)
  625. # for rel_filter in rel_filters:
  626. # # join condition
  627. # relation_table_join_field = "%s.%s" % (self.datasource_utils.relations_table_name, self.RELATIONS_POSITIONS_FIELDS[rel_filter['position']])
  628. # query_table_join_field = "%s.%s" % (query_table_name, self.datasource_utils.field_lodel_id)
  629. # join_fields[query_table_join_field] = relation_table_join_field
  630. # # Adding "where" filters
  631. # where_filters['%s.%s' % (self.datasource_utils.relations_table_name, self.datasource_utils.relations_field_nature)] = rel_filter['nature']
  632. # where_filters[rel_filter['condition_key']] = rel_filter['condition_value']
  633. #
  634. # # building the query
  635. # query = select(query_table_name, where=where_filters, select=field_list, joins=join(self.datasource_utils.relations_table_name, join_fields))
  636. # else:
  637. # query = select(query_table_name, where=where_filters, select=field_list)
  638. #
  639. # Executing the query
  640. # cur = self.datasource_utils.query(self.connection, query)
  641. # results = all_to_dicts(cur)
  642. #
  643. # return results
  644. ## @brief delete an existing object
  645. # @param letype LeType
  646. # @param leclass LeClass
  647. # @param filters list : list of tuples formatted as (FIELD, OPERATOR, VALUE)
  648. # @param relational_filters list : list of tuples formatted as (('superior'|'subordinate', FIELD), OPERATOR, VALUE)
  649. # @return bool : True on success
  650. # def delete(self, letype, leclass, filters, relational_filters):
  651. # query_table_name = self.datasource_utils.get_table_name_from_class(leclass.__name__)
  652. # prep_filters = self._prepare_filters(filters, query_table_name)
  653. # prep_rel_filters = self._prepare_rel_filters(relational_filters)
  654. #
  655. # if len(prep_rel_filters) > 0:
  656. # query = "DELETE %s FROM " % query_table_name
  657. #
  658. # for prep_rel_filter in prep_rel_filters:
  659. # query += "%s INNER JOIN %s ON (%s.%s = %s.%s)" % (
  660. # self.datasource_utils.relations_table_name,
  661. # query_table_name,
  662. # self.datasource_utils.relations_table_name,
  663. # prep_rel_filter['position'],
  664. # query_table_name,
  665. # self.datasource_utils.field_lodel_id
  666. # )
  667. #
  668. # if prep_rel_filter['condition_key'][0] is not None:
  669. # 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']
  670. #
  671. # if prep_filters is not None and len(prep_filters) > 0:
  672. # query += " WHERE "
  673. # filter_counter = 0
  674. # for filter_item in prep_filters:
  675. # if filter_counter > 1:
  676. # query += " AND "
  677. # query += "%s %s %s" % (filter_item[0][0], filter_item[0][1], filter_item[1])
  678. # else:
  679. # query = delete(query_table_name, filters)
  680. #
  681. # query_delete_from_object = delete(self.datasource_utils.objects_table_name, {'lodel_id': filters['lodel_id']})
  682. # with self.connection as cur:
  683. # cur.execute(query)
  684. # cur.execute(query_delete_from_object)
  685. #
  686. # return True
  687. ## @brief update an existing object's data
  688. # @param letype LeType
  689. # @param leclass LeClass
  690. # @param filters list : list of tuples formatted as (FIELD, OPERATOR, VALUE)
  691. # @param rel_filters list : list of tuples formatted as (('superior'|'subordinate', FIELD), OPERATOR, VALUE)
  692. # @param data dict
  693. # @return bool
  694. # @todo prendre en compte les rel_filters
  695. # def update(self, letype, leclass, filters, rel_filters, data):
  696. #
  697. # query_table_name = self.datasource_utils.get_table_name_from_class(leclass.__name__)
  698. # where_filters = filters
  699. # set_data = data
  700. #
  701. # prepared_rel_filters = self._prepare_rel_filters(rel_filters)
  702. #
  703. # Building the query
  704. # query = update(table=query_table_name, where=where_filters, set=set_data)
  705. # Executing the query
  706. # with self.connection as cur:
  707. # cur.execute(query)
  708. # return True