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.

leobject.py 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. #
  2. # This file is part of Lodel 2 (https://github.com/OpenEdition)
  3. #
  4. # Copyright (C) 2015-2017 Cléo UMS-3287
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU Affero General Public License as published
  8. # by the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU Affero General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Affero General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. ## @package lodel.leapi.leobject
  20. # This module is centered around the basic LeObject class, which is the main class for all the objects managed by lodel.
  21. import importlib
  22. import warnings
  23. import copy
  24. from lodel.context import LodelContext
  25. LodelContext.expose_modules(globals(), {
  26. 'lodel.logger': 'logger',
  27. 'lodel.settings': 'Settings',
  28. 'lodel.settings.utils': 'SettingsError',
  29. 'lodel.leapi.query': ['LeInsertQuery', 'LeUpdateQuery', 'LeDeleteQuery',
  30. 'LeGetQuery'],
  31. 'lodel.leapi.exceptions': ['LeApiError', 'LeApiErrors',
  32. 'LeApiDataCheckError', 'LeApiDataCheckErrors', 'LeApiQueryError',
  33. 'LeApiQueryErrors'],
  34. 'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
  35. 'LodelScriptError', 'DatasourcePluginError'],
  36. 'lodel.exceptions': ['LodelFatalError'],
  37. 'lodel.plugin.hooks': ['LodelHook'],
  38. 'lodel.plugin': ['Plugin', 'DatasourcePlugin'],
  39. 'lodel.leapi.datahandlers.base_classes': ['DatasConstructor', 'Reference']})
  40. ## @brief Stores the name of the field present in each LeObject that indicates the name of LeObject subclass represented by this object
  41. CLASS_ID_FIELDNAME = "classname"
  42. ## @brief Wrapper class for LeObject getter & setter
  43. #
  44. # This class intend to provide easy & friendly access to LeObject fields values without name collision problems
  45. # @note Wrapped methods are : LeObject.data() & LeObject.set_data()
  46. class LeObjectValues(object):
  47. # @param fieldnames_callback method
  48. # @param set_callback method : The LeObject.set_datas() method of corresponding LeObject class
  49. # @param get_callback method : The LeObject.get_datas() method of corresponding LeObject class
  50. def __init__(self, fieldnames_callback, set_callback, get_callback):
  51. self._setter = set_callback
  52. self._getter = get_callback
  53. ## @brief Provides read access to datas values
  54. # @note Read access should be provided for all fields
  55. # @param fname str : Field name
  56. # @return method
  57. def __getattribute__(self, fname):
  58. getter = super().__getattribute__('_getter')
  59. return getter(fname)
  60. ## @brief Provides write access to datas values
  61. # @note Write acces shouldn't be provided for internal or immutable fields
  62. # @param fname str : Field name
  63. # @param fval * : the field value
  64. # @return method
  65. def __setattribute__(self, fname, fval):
  66. setter = super().__getattribute__('_setter')
  67. return setter(fname, fval)
  68. ## @brief Represents a handled object in Lodel.
  69. class LeObject(object):
  70. ## @brief boolean that tells if an object is abtract or not
  71. _abstract = None
  72. ## @brief A dict that stores DataHandler instances indexed by field name
  73. _fields = None
  74. ## @brief A tuple of fieldname (or a uniq fieldname) representing uid
  75. _uid = None
  76. ## @brief Read only datasource ( see @ref lodel2_datasources )
  77. _ro_datasource = None
  78. ## @brief Read & write datasource ( see @ref lodel2_datasources )
  79. _rw_datasource = None
  80. ## @brief Store the list of child classes
  81. _child_classes = None
  82. ## @brief Name of the datasource plugin
  83. _datasource_name = None
  84. def __new__(cls, **kwargs):
  85. self = object.__new__(cls)
  86. ## @brief A dict that stores fieldvalues indexed by fieldname
  87. self.__datas = {fname: None for fname in self._fields}
  88. ## @brief Store a list of initianilized fields when instanciation not complete else store True
  89. self.__initialized = list()
  90. ## @brief Datas accessor. Instance of @ref LeObjectValues
  91. self.d = LeObjectValues(self.fieldnames, self.set_data, self.data)
  92. for fieldname, fieldval in kwargs.items():
  93. self.__datas[fieldname] = fieldval
  94. self.__initialized.append(fieldname)
  95. self.__is_initialized = False
  96. self.__set_initialized()
  97. return self
  98. # @note Can be considered as EmClass instance
  99. # @param **kwargs
  100. # @throw NotImplementedError when the class being instanciated is noted as abstract and then should not be instanciated.
  101. # @throw LeApiError in case of missing or invalid data.
  102. def __init__(self, **kwargs):
  103. if self._abstract:
  104. raise NotImplementedError(
  105. "%s is abstract, you cannot instanciate it." % self.__class__.__name__)
  106. # Checks that uid is given
  107. for uid_name in self._uid:
  108. if uid_name not in kwargs:
  109. raise LeApiError("Cannot instanciate a LeObject without it's identifier")
  110. self.__datas[uid_name] = kwargs[uid_name]
  111. del(kwargs[uid_name])
  112. self.__initialized.append(uid_name)
  113. # Processing given fields
  114. allowed_fieldnames = self.fieldnames(include_ro=False)
  115. err_list = dict()
  116. for fieldname, fieldval in kwargs.items():
  117. if fieldname not in allowed_fieldnames:
  118. if fieldname in self._fields:
  119. err_list[fieldname] = LeApiError(
  120. "Value given but the field is internal")
  121. else:
  122. err_list[fieldname] = LeApiError(
  123. "Unknown fieldname : '%s'" % fieldname)
  124. else:
  125. self.__datas[fieldname] = fieldval
  126. self.__initialized.append(fieldname)
  127. if len(err_list) > 0:
  128. raise LeApiErrors(msg="Unable to __init__ %s" % self.__class__,
  129. exceptions=err_list)
  130. self.__set_initialized()
  131. #-----------------------------------#
  132. # Fields datas handling methods #
  133. #-----------------------------------#
  134. ## @brief Property method True if LeObject is initialized else False
  135. # @return bool
  136. @property
  137. def initialized(self):
  138. return self.__is_initialized
  139. ## @brief Returns the uid field name
  140. # @return str
  141. @classmethod
  142. def uid_fieldname(cls):
  143. return cls._uid
  144. ## @brief Returns a list of fieldnames
  145. # @param include_ro bool : if True includes the read only field names
  146. # @return list of string
  147. @classmethod
  148. def fieldnames(cls, include_ro=False):
  149. if not include_ro:
  150. return [fname for fname in cls._fields if not cls._fields[fname].is_internal()]
  151. else:
  152. return list(cls._fields.keys())
  153. ## @brief Returns a name, capitalizing the first character of each word
  154. # @param name str
  155. # @return str
  156. @classmethod
  157. def name2objname(cls, name):
  158. return name.title()
  159. ## @brief Returns the datahandler asssociated with a LeObject field
  160. # @param fieldname str : The field's name
  161. # @return A data handler instance
  162. # @throw NameError when the given field name doesn't exist
  163. #@todo update class of exception raised
  164. @classmethod
  165. def data_handler(cls, fieldname):
  166. if not fieldname in cls._fields:
  167. raise NameError("No field named '%s' in %s" % (fieldname, cls.__name__))
  168. return cls._fields[fieldname]
  169. ## @brief Returns a dictionary containing the reference datahandlers
  170. # @param with_backref bool : if true return only references with back_references
  171. # @return dict : <code>{'fieldname': datahandler, ...}</code>
  172. @classmethod
  173. def reference_handlers(cls, with_backref=True):
  174. return {fname: fdh
  175. for fname, fdh in cls.fields(True).items()
  176. if fdh.is_reference() and
  177. (not with_backref or fdh.back_reference is not None)}
  178. ## @brief Returns a LeObject child class from a name
  179. # @warning This method has to be called from dynamically generated LeObjects
  180. # @param leobject_name str : LeObject name
  181. # @return A LeObject child class
  182. # @throw NotImplementedError if the method is abstract (if we use the LeObject class)
  183. # @throw LeApiError if an unexisting name is given
  184. @classmethod
  185. def name2class(cls, leobject_name):
  186. if cls.__module__ == 'lodel.leapi.leobject':
  187. raise NotImplementedError("Abstract method")
  188. mod = importlib.import_module(cls.__module__)
  189. try:
  190. return getattr(mod, leobject_name)
  191. except (AttributeError, TypeError):
  192. raise LeApiError("No LeObject named '%s'" % leobject_name)
  193. ## @brief Checks if the class is abstract or not
  194. # @return bool
  195. @classmethod
  196. def is_abstract(cls):
  197. return cls._abstract
  198. ## @brief Field data handler getter
  199. # @param fieldname str : The field name
  200. # @return A datahandler instance
  201. # @throw NameError if the field doesn't exist
  202. @classmethod
  203. def field(cls, fieldname):
  204. try:
  205. return cls._fields[fieldname]
  206. except KeyError:
  207. raise NameError("No field named '%s' in %s" % (fieldname,
  208. cls.__name__))
  209. ## @brief Returns the fields' datahandlers as a dictionary
  210. # @param include_ro bool : if True, includes the read-only fields (default value : False)
  211. # @return dict
  212. @classmethod
  213. def fields(cls, include_ro=False):
  214. if include_ro:
  215. return copy.copy(cls._fields)
  216. else:
  217. return {fname: cls._fields[fname] for fname in cls._fields\
  218. if not cls._fields[fname].is_internal()}
  219. ## @brief Return the list of parents classes
  220. #
  221. # @note the first item of the list is the current class, the second is its parent etc...
  222. # @warning multiple inheritance broken by this method
  223. # @return a list of LeObject child classes
  224. # @todo multiple parent capabilities implementation
  225. @classmethod
  226. def hierarch(cls):
  227. res = [cls]
  228. cur = cls
  229. while True:
  230. cur = cur.__bases__[0] # Multiple inheritance broken HERE
  231. if cur in (LeObject, object):
  232. break
  233. else:
  234. res.append(cur)
  235. return res
  236. ## @brief Returns a tuple of child classes
  237. # @return tuple
  238. @classmethod
  239. def child_classes(cls):
  240. return copy.copy(cls._child_classes)
  241. ## @brief Returns the parent class that defines the unique id
  242. #
  243. # @return a LeObject child class or false if no UID defined
  244. @classmethod
  245. def uid_source(cls):
  246. if cls._uid is None or len(cls._uid) == 0:
  247. return False
  248. hierarch = cls.hierarch()
  249. prev = hierarch[0]
  250. uid_handlers = set(cls._fields[name] for name in cls._uid)
  251. for pcls in cls.hierarch()[1:]:
  252. puid_handlers = set(cls._fields[name] for name in pcls._uid)
  253. if set(pcls._uid) != set(prev._uid) \
  254. or puid_handlers != uid_handlers:
  255. break
  256. prev = pcls
  257. return prev
  258. ## @brief Initialise both datasources (ro and rw)
  259. #
  260. # This method is used once at dyncode load to replace the datasource string
  261. # by a datasource instance to avoid doing this operation for each query
  262. # @see LeObject::_init_datasource()
  263. @classmethod
  264. def _init_datasources(cls):
  265. if isinstance(cls._datasource_name, str):
  266. rw_ds = ro_ds = cls._datasource_name
  267. else:
  268. ro_ds, rw_ds = cls._datasource_name
  269. # Read only datasource initialisation
  270. cls._ro_datasource = DatasourcePlugin.init_datasource(ro_ds, True)
  271. if cls._ro_datasource is None:
  272. log_msg = "No read only datasource set for LeObject %s"
  273. log_msg %= cls.__name__
  274. logger.debug(log_msg)
  275. else:
  276. log_msg = "Read only datasource '%s' initialized for LeObject %s"
  277. log_msg %= (ro_ds, cls.__name__)
  278. logger.debug(log_msg)
  279. # Read write datasource initialisation
  280. cls._rw_datasource = DatasourcePlugin.init_datasource(rw_ds, False)
  281. if cls._ro_datasource is None:
  282. log_msg = "No read/write datasource set for LeObject %s"
  283. log_msg %= cls.__name__
  284. logger.debug(log_msg)
  285. else:
  286. log_msg = "Read/write datasource '%s' initialized for LeObject %s"
  287. log_msg %= (ro_ds, cls.__name__)
  288. logger.debug(log_msg)
  289. ## @brief Returns the uid of the current LeObject instance
  290. # @return str
  291. # @warning Broke multiple uid capabilities
  292. def uid(self):
  293. return self.data(self._uid[0])
  294. ## @brief Returns the value of a field
  295. # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
  296. # @param field_name str : field's name
  297. # @return the value
  298. # @throw RuntimeError if the field is not initialized yet
  299. # @throw NameError if name is not an existing field name
  300. def data(self, field_name):
  301. if field_name not in self._fields.keys():
  302. raise NameError("No such field in %s : %s" % (self.__class__.__name__, field_name))
  303. if not self.initialized and field_name not in self.__initialized:
  304. raise RuntimeError(
  305. "The field %s is not initialized yet (and have no value)" % field_name)
  306. return self.__datas[field_name]
  307. ## @brief Returns a dictionary containing all the fields' values
  308. # @return dict
  309. def datas(self, internal=False):
  310. return {fname: self.data(fname) for fname in self.fieldnames(internal)}
  311. ## @brief Datas setter
  312. # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
  313. # @param fname str : field's name
  314. # @param fval * : field value
  315. # @return the value that is really set
  316. # @throw NameError if fname is not valid
  317. # @throw AttributeError if the field is not writtable
  318. # @throw LeApiErrors if the data check generates an error
  319. def set_data(self, fname, fval):
  320. if fname not in self.fieldnames(include_ro=False):
  321. if fname not in self._fields.keys():
  322. raise NameError("No such field in %s : %s" % (self.__class__.__name__, fname))
  323. else:
  324. raise AttributeError("The field %s is read only" % fname)
  325. self.__datas[fname] = fval
  326. if not self.initialized and fname not in self.__initialized:
  327. # Add field to initialized fields list
  328. self.__initialized.append(fname)
  329. self.__set_initialized()
  330. if self.initialized:
  331. # Running full value check
  332. ret = self.__check_modified_values()
  333. if ret is None:
  334. return self.__datas[fname]
  335. else:
  336. raise LeApiErrors("Data check error", ret)
  337. else:
  338. # Doing value check on modified field
  339. # We skip full validation here because the LeObject is not fully initialized yet
  340. val, err = self._fields[fname].check_data_value(fval)
  341. if isinstance(err, Exception):
  342. # Revert change to be in valid state
  343. del(self.__datas[fname])
  344. del(self.__initialized[-1])
  345. raise LeApiErrors("Data check error", {fname: err})
  346. else:
  347. self.__datas[fname] = val
  348. ## @brief Updates the __initialized attribute according to LeObject internal state
  349. #
  350. # Checks the list of initialized fields and sets __initialized at True if all fields initialized
  351. def __set_initialized(self):
  352. if isinstance(self.__initialized, list):
  353. expected_fields = self.fieldnames(include_ro=False) + self._uid
  354. if set(expected_fields) == set(self.__initialized):
  355. self.__is_initialized = True
  356. ## @brief Designed to be called when datas are modified
  357. #
  358. # Makes different checks on the LeObject given it's state (fully initialized or not)
  359. # @return None if checks succeded else return an exception list
  360. def __check_modified_values(self):
  361. err_list = dict()
  362. if self.__initialized is True:
  363. # Data value check
  364. for fname in self.fieldnames(include_ro=False):
  365. val, err = self._fields[fname].check_data_value(self.__datas[fname])
  366. if err is not None:
  367. err_list[fname] = err
  368. else:
  369. self.__datas[fname] = val
  370. # Data construction
  371. if len(err_list) == 0:
  372. for fname in self.fieldnames(include_ro=True):
  373. try:
  374. field = self._fields[fname]
  375. self.__datas[fname] = field.construct_data(self,
  376. fname,
  377. self.__datas,
  378. self.__datas[fname]
  379. )
  380. except Exception as exp:
  381. err_list[fname] = exp
  382. # Datas consistency check
  383. if len(err_list) == 0:
  384. for fname in self.fieldnames(include_ro=True):
  385. field = self._fields[fname]
  386. ret = field.check_data_consistency(self, fname, self.__datas)
  387. if isinstance(ret, Exception):
  388. err_list[fname] = ret
  389. else:
  390. # Data value check for initialized datas
  391. for fname in self.__initialized:
  392. val, err = self._fields[fname].check_data_value(self.__datas[fname])
  393. if err is not None:
  394. err_list[fname] = err
  395. else:
  396. self.__datas[fname] = val
  397. return err_list if len(err_list) > 0 else None
  398. #--------------------#
  399. # Other methods #
  400. #--------------------#
  401. ## @brief Temporary method to set private fields attribute at dynamic code generation
  402. #
  403. # This method is used in the generated dynamic code to set the _fields attribute
  404. # at the end of the dyncode parse
  405. # @warning This method is deleted once the dynamic code loaded
  406. # @param cls
  407. # @param field_list list : list of EmField instance
  408. @classmethod
  409. def _set__fields(cls, field_list):
  410. cls._fields = field_list
  411. ## @brief Checks if the data is valid for this type
  412. # @param datas dict : key == field name value are field values
  413. # @param complete bool : if True expects that values are provided for all non internal fields
  414. # @param allow_internal bool : if True does not raise an error if a field is internal
  415. # @param cls
  416. # @return Checked datas
  417. # @throw LeApiDataCheckErrors if errors are reported during check
  418. @classmethod
  419. def check_datas_value(cls, datas, complete=False, allow_internal=True):
  420. err_l = dict() # Error storing
  421. correct = set() # valid fields name
  422. mandatory = set() # mandatory fields name
  423. for fname, datahandler in cls._fields.items():
  424. if allow_internal or not datahandler.is_internal():
  425. correct.add(fname)
  426. if complete and not hasattr(datahandler, 'default'):
  427. mandatory.add(fname)
  428. provided = set(datas.keys())
  429. # searching for unknow fields
  430. for u_f in provided - correct:
  431. # Here we can check if the field is invalid or rejected because
  432. # it is internel
  433. err_l[u_f] = AttributeError("Unknown or unauthorized field '%s'" % u_f)
  434. # searching for missing mandatory fieldsa
  435. for missing in mandatory - provided:
  436. err_l[missing] = AttributeError("The data for field '%s' is missing" % missing)
  437. # Checks datas
  438. checked_datas = dict()
  439. for name, value in [(name, value) for name, value in datas.items() if name in correct]:
  440. dh = cls._fields[name]
  441. res = dh.check_data_value(value)
  442. checked_datas[name], err = res
  443. if err:
  444. err_l[name] = err
  445. if len(err_l) > 0:
  446. raise LeApiDataCheckErrors("Error while checking datas", err_l)
  447. return checked_datas
  448. ## @brief Checks and prepares all the data
  449. #
  450. # @warning when complete = False we are not able to make construct_datas() and _check_data_consistency()
  451. #
  452. # @param datas dict : {fieldname : fieldvalue, ...}
  453. # @param complete bool : If True you MUST give all the datas (default value : False)
  454. # @param allow_internal : Wether or not interal fields are expected in datas (default value : True)
  455. # @return Datas ready for use
  456. # @todo: complete is very unsafe, find a way to get rid of it
  457. @classmethod
  458. def prepare_datas(cls, datas, complete=False, allow_internal=True):
  459. if not complete:
  460. warnings.warn("\nActual implementation can make broken datas \
  461. construction and consitency when datas are not complete\n")
  462. ret_datas = cls.check_datas_value(datas, complete, allow_internal)
  463. if isinstance(ret_datas, Exception):
  464. raise ret_datas
  465. if complete:
  466. ret_datas = cls._construct_datas(ret_datas)
  467. cls._check_datas_consistency(ret_datas)
  468. return ret_datas
  469. ## @brief Constructs datas values
  470. #
  471. # @param datas dict : Datas that have been returned by LeCrud.check_datas_value() methods
  472. # @return A new dict of datas
  473. # @todo IMPLEMENTATION
  474. @classmethod
  475. def _construct_datas(cls, datas):
  476. constructor = DatasConstructor(cls, datas, cls._fields)
  477. ret = {
  478. fname: constructor[fname]
  479. for fname, ftype in cls._fields.items()
  480. if not ftype.is_internal() or ftype.internal != 'autosql'
  481. }
  482. return ret
  483. ## @brief Checks datas consistency
  484. # @warning assert that datas is complete
  485. # @param cls
  486. # @param datas dict : Datas that have been returned by LeCrud._construct_datas() method
  487. # @throw LeApiDataCheckError in case of failure
  488. @classmethod
  489. def _check_datas_consistency(cls, datas):
  490. err_l = []
  491. err_l = dict()
  492. for fname, dh in cls._fields.items():
  493. ret = dh.check_data_consistency(cls, fname, datas)
  494. if isinstance(ret, Exception):
  495. err_l[fname] = ret
  496. if len(err_l) > 0:
  497. raise LeApiDataCheckError("Datas consistency checks fails", err_l)
  498. ## @brief Checks data consistency
  499. # @warning assert that datas is complete
  500. # @param datas dict : Data that have been returned by prepare_datas() method
  501. # @param type_query str : Type of query to be performed , default value : insert
  502. @classmethod
  503. def make_consistency(cls, datas, type_query='insert'):
  504. for fname, dh in cls._fields.items():
  505. ret = dh.make_consistency(fname, datas, type_query)
  506. ## @brief Adds a new instance of LeObject
  507. # @param datas dict : LeObject's data
  508. # @return a new uid in case of success, False otherwise
  509. @classmethod
  510. def insert(cls, datas):
  511. query = LeInsertQuery(cls)
  512. return query.execute(datas)
  513. ## @brief Update an instance of LeObject
  514. #
  515. # @param datas : list of new datas
  516. # @return LeObject
  517. def update(self, datas=None):
  518. datas = self.datas(internal=False) if datas is None else datas
  519. uids = self._uid
  520. query_filter = list()
  521. for uid in uids:
  522. query_filter.append((uid, '=', self.data(uid)))
  523. try:
  524. query = LeUpdateQuery(self.__class__, query_filter)
  525. except Exception as err:
  526. raise err
  527. try:
  528. result = query.execute(datas)
  529. except Exception as err:
  530. raise err
  531. return result
  532. ## @brief Delete an instance of LeObject
  533. #
  534. # @return 1 if the objet has been deleted
  535. def delete(self):
  536. uids = self._uid
  537. query_filter = list()
  538. for uid in uids:
  539. query_filter.append((uid, '=', self.data(uid)))
  540. query = LeDeleteQuery(self.__class__, query_filter)
  541. result = query.execute()
  542. return result
  543. ## @brief Deletes instances of LeObject
  544. # @param query_filters list
  545. # @return the number of deleted items
  546. @classmethod
  547. def delete_bundle(cls, query_filters):
  548. deleted = 0
  549. try:
  550. query = LeDeleteQuery(cls, query_filters)
  551. except Exception as err:
  552. raise err
  553. try:
  554. result = query.execute()
  555. except Exception as err:
  556. raise err
  557. if not result is None:
  558. deleted += result
  559. return deleted
  560. ## @brief Gets instances of LeObject
  561. #
  562. # @param query_filters dict : (filters, relational filters), with filters is a list of tuples : (FIELD, OPERATOR, VALUE) )
  563. # @param field_list list|None : list of string representing fields see
  564. # @ref leobject_filters
  565. # @param order list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
  566. # @param group list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
  567. # @param limit int : The maximum number of returned results
  568. # @param offset int : offset (default value : 0)
  569. # @return a list of items (lists of (fieldname, fieldvalue))
  570. @classmethod
  571. def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0):
  572. if field_list is not None:
  573. for uid in [uidname
  574. for uidname in cls.uid_fieldname()
  575. if uidname not in field_list]:
  576. field_list.append(uid)
  577. if CLASS_ID_FIELDNAME not in field_list:
  578. field_list.append(CLASS_ID_FIELDNAME)
  579. try:
  580. query = LeGetQuery(
  581. cls, query_filters=query_filters, field_list=field_list,
  582. order=order, group=group, limit=limit, offset=offset)
  583. except ValueError as err:
  584. raise err
  585. try:
  586. result = query.execute()
  587. except Exception as err:
  588. raise err
  589. objects = list()
  590. for res in result:
  591. res_cls = cls.name2class(res[CLASS_ID_FIELDNAME])
  592. inst = res_cls.__new__(res_cls, **res)
  593. objects.append(inst)
  594. return objects
  595. ## @brief Retrieves an object given an UID
  596. # @param uid str : Unique ID of the searched LeObject
  597. # @return LeObject
  598. # @throw LodelFatalError if the class does not have such a UID defined or if duplicates are found
  599. #@todo broken multiple UID
  600. @classmethod
  601. def get_from_uid(cls, uid):
  602. if cls.uid_fieldname() is None:
  603. raise LodelFatalError(
  604. "No uid defined for class %s" % cls.__name__)
  605. uidname = cls.uid_fieldname()[0] # Brokes composed UID
  606. res = cls.get([(uidname, '=', uid)])
  607. # dedoublonnage vu que query ou la datasource est bugué
  608. if len(res) > 1:
  609. res_cp = res
  610. res = []
  611. while len(res_cp) > 0:
  612. cur_res = res_cp.pop()
  613. if cur_res.uid() in [r.uid() for r in res_cp]:
  614. logger.error("Duplicates detected in query results !!!")
  615. else:
  616. res.append(cur_res)
  617. if len(res) > 1:
  618. raise LodelFatalError("Get from uid returned more than one \
  619. object ! For class %s with uid value = %s" % (cls, uid))
  620. elif len(res) == 0:
  621. return None
  622. return res[0]