Нема описа
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

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