Aucune description
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

leobject.py 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. #-*- coding: utf-8 -*-
  2. ## @package leobject API to access lodel datas
  3. #
  4. # This package contains abstract classes leobject.leclass.LeClass , leobject.letype.LeType, leobject.leobject._LeObject.
  5. # Those abstract classes are designed to be mother classes of dynamically generated classes ( see leobject.lefactory.LeFactory )
  6. ## @package leobject.leobject
  7. # @brief Abstract class designed to be implemented by LeObject
  8. #
  9. # @note LeObject will be generated by leobject.lefactory.LeFactory
  10. import re
  11. import leobject
  12. from .lefactory import LeFactory
  13. import EditorialModel
  14. from EditorialModel.types import EmType
  15. REL_SUP = 0
  16. REL_SUB = 1
  17. ## @brief Main class to handle objects defined by the types of an Editorial Model
  18. class _LeObject(object):
  19. ## @brief The editorial model
  20. _model = None
  21. ## @brief The datasource
  22. _datasource = None
  23. ## @brief maps em uid with LeType or LeClass keys are uid values are LeObject childs classes
  24. _me_uid = dict()
  25. _query_re = None
  26. _query_operators = ['=', '<=', '>=', '!=', '<', '>', ' in ', ' not in ']
  27. ## @brief Instantiate with a Model and a DataSource
  28. # @param **kwargs dict : datas usefull to instanciate a _LeObject
  29. def __init__(self, **kwargs):
  30. raise NotImplementedError("Abstract constructor")
  31. ## @brief Given a ME uid return the corresponding LeClass or LeType class
  32. # @return a LeType or LeClass child class
  33. # @throw KeyError if no corresponding child classes
  34. @classmethod
  35. def uid2leobj(cls, uid):
  36. uid = int(uid)
  37. if uid not in cls._me_uid:
  38. raise KeyError("No LeType or LeClass child classes with uid '%d'"%uid)
  39. return cls._me_uid[uid]
  40. ## @brief Creates new entries in the datasource
  41. # @param datas list : A list a dict with fieldname as key
  42. # @param cls
  43. # @return a list of inserted lodel_id
  44. # @see leobject.datasources.dummy.DummyDatasource.insert(), leobject.letype.LeType.insert()
  45. @classmethod
  46. def insert(cls, letype, datas):
  47. if isinstance(datas, dict):
  48. datas = [datas]
  49. if cls == _LeObject:
  50. raise NotImplementedError("Abstract method")
  51. letype,leclass = cls._prepare_targets(letype)
  52. if letype is None:
  53. raise ValueError("letype argument cannot be None")
  54. for data in datas:
  55. letype.check_datas_or_raise(data, complete = True)
  56. return cls._datasource.insert(letype, leclass, datas)
  57. ## @brief Check if a LeType is a hierarchy root
  58. @staticmethod
  59. def is_root(leo):
  60. if isinstance(leo, leobject.letype.LeType):
  61. return False
  62. elif isinstance(leo, LeRoot):
  63. return True
  64. raise ValueError("Invalid value for a LeType : %s"%leo)
  65. ## @brief Return a LeRoot instance
  66. @staticmethod
  67. def get_root():
  68. return LeRoot()
  69. ## @brief Delete LeObjects given filters
  70. # @param cls
  71. # @param letype LeType|str : LeType child class or name
  72. # @param leclass LeClass|str : LeClass child class or name
  73. # @param filters list : list of filters (see @ref leobject_filters)
  74. # @return bool
  75. @classmethod
  76. def delete(cls, letype, filters):
  77. letype, leclass = cls._prepare_targets(letype)
  78. filters,relationnal_filters = leobject.leobject._LeObject._prepare_filters(filters, letype, leclass)
  79. return cls._datasource.delete(letype, leclass, filters, relationnal_filters)
  80. ## @brief Update LeObjects given filters and datas
  81. # @param cls
  82. # @param letype LeType|str : LeType child class or name
  83. # @param filters list : list of filters (see @ref leobject_filters)
  84. @classmethod
  85. def update(cls, letype, filters, datas):
  86. letype, leclass = cls._prepare_targets(letype)
  87. filters,relationnal_filters = leobject.leobject._LeObject._prepare_filters(filters, letype, leclass)
  88. if letype is None:
  89. raise ValueError("Argument letype cannot be None")
  90. letype.check_datas_or_raise(datas, False)
  91. return cls._datasource.update(letype, leclass, filters, relationnal_filters, datas)
  92. ## @brief make a search to retrieve a collection of LeObject
  93. # @param query_filters list : list of string of query filters (or tuple (FIELD, OPERATOR, VALUE) ) see @ref leobject_filters
  94. # @param field_list list|None : list of string representing fields see @ref leobject_filters
  95. # @param typename str : The name of the LeType we want
  96. # @param classname str : The name of the LeClass we want
  97. # @param cls
  98. # @return responses ({string:*}): a list of dict with field:value
  99. @classmethod
  100. def get(cls, query_filters, field_list = None, typename = None, classname = None):
  101. letype,leclass = cls._prepare_targets(typename, classname)
  102. #Checking field_list
  103. if field_list is None or len(field_list) == 0:
  104. #default field_list
  105. if not (letype is None):
  106. field_list = letype._fields
  107. elif not (leclass is None):
  108. field_list = leclass._fieldtypes.keys()
  109. else:
  110. field_list = list(EditorialModel.classtypes.common_fields.keys())
  111. #Fetching LeType
  112. if letype is None:
  113. if 'type_id' not in field_list:
  114. field_list.append('type_id')
  115. field_list = cls._prepare_field_list(field_list, letype, leclass)
  116. #preparing filters
  117. filters, relationnal_filters = cls._prepare_filters(query_filters, letype, leclass)
  118. #Fetching datas from datasource
  119. datas = cls._datasource.get(leclass, letype, field_list, filters, relationnal_filters)
  120. #Instanciating corresponding LeType child classes with datas
  121. result = list()
  122. for leobj_datas in datas:
  123. letype = cls.uid2leobj(leobj_datas['type_id']) if letype is None else letype
  124. result.append(letype(**leobj_datas))
  125. return result
  126. ## @brief Link two leobject together using a rel2type field
  127. # @param lesup LeType : LeType child class instance linked as superior
  128. # @param lesub LeType : LeType child class instance linked as subordinate
  129. # @param **rel_attr : Relation attributes
  130. # @return True if linked without problems
  131. # @throw LeObjectError if the link is not valid
  132. # @throw LeObkectError if the link already exists
  133. # @throw AttributeError if an non existing relation attribute is given as argument
  134. # @throw ValueError if the relation attrivute value check fails
  135. #
  136. # @todo Code factorisation on relation check
  137. # @todo unit tests
  138. @classmethod
  139. def link_together(cls, lesup, lesub, rank = 'last', **rel_attr):
  140. if lesub.__class__ not in lesup._linked_types.keys():
  141. raise LeObjectError("Relation error : %s cannot be linked with %s"%(lesup.__class__.__name__, lesub.__class__.__name__))
  142. for attr_name in rel_attr.keys():
  143. if attr_name not in [ f for f,g in lesup._linked_types[lesub.__class__] ]:
  144. raise AttributeError("A rel2type between a %s and a %s doesn't have an attribute %s"%(lesup.__class__.__name__, lesub.__class__.__name__))
  145. if not sup._linked_types[lesub.__class__][1].check(rel_attr[attr_name]):
  146. raise ValueError("Wrong value '%s' for attribute %s"%(rel_attr[attr_name], attr_name))
  147. #Checks that attributes are uniq for this relation
  148. rels_attr = [ attrs for lesup, lesub, attrs in cls.links_get(lesup) if lesup == lesup ]
  149. for e_attrs in rels_attrs:
  150. if rel_attr == e_attrs:
  151. raise LeObjectError("Relation error : a relation with the same attributes already exists")
  152. return cls._datasource.add_related(lesup, lesub, rank, **rel_attr)
  153. ## @brief Get related objects
  154. # @param leo LeType(instance) : LeType child class instance
  155. # @param letype LeType(class) : the wanted LeType child class (not instance)
  156. # @param leo_is_superior bool : if True leo is the superior in the relation
  157. # @return A dict with LeType child class instance as key and dict {rel_attr_name:rel_attr_value, ...}
  158. # @throw LeObjectError if the relation is not possible
  159. #
  160. # @todo Code factorisation on relation check
  161. # @todo unit tests
  162. @classmethod
  163. def linked_together(cls, leo, letype, leo_is_superior = True):
  164. valid_link = letype in leo._linked_types.keys() if leo_is_superior else leo.__class__ in letype._linked_types.keys()
  165. if not valid_link:
  166. raise LeObjectError("Relation error : %s have no links with %s"%(
  167. leo.__class__ if leo_is_superior else letype,
  168. letype if leo_is_superior else leo.__class__
  169. ))
  170. return cls._datasource.get_related(leo, letype, leo_is_superior)
  171. ## @brief Fetch a relation and its attributes
  172. # @param id_relation int : the relation identifier
  173. # @return a tuple(lesup, lesub, dict_attr) or False if no relation exists with this id
  174. # @throw Exception if the relation is not a rel2type relation
  175. @classmethod
  176. def link_get(cls, id_relation):
  177. return cls._datasource.get_relation(id_relation)
  178. ## @brief Fetch all relations for an objects
  179. # @param leo LeType : LeType child class instance
  180. # @return a list of tuple (lesup, lesub, dict_attr)
  181. def links_get(cls, leo):
  182. return cls._datasource.get_relations(leo)
  183. ## @brief Remove a link (and attributes) between two LeObject
  184. # @param id_relation int : Relation identifier
  185. # @return True if a link has been deleted
  186. # @throw LeObjectError if the relation is not a rel2type
  187. #
  188. # @todo Code factorisation on relation check
  189. # @todo unit tests
  190. @classmethod
  191. def link_remove(cls, id_relation):
  192. if lesub.__class__ not in lesup._linked_types.keys():
  193. raise LeObjectError("Relation errorr : %s cannot be linked with %s"%(lesup.__class__.__name__, lesub.__class__.__name__))
  194. return cls._datasource.del_related(lesup, lesub)
  195. ## @brief Add a hierarchy relation between two LeObject
  196. # @param lesup LeType|LeRoot : LeType child class instance
  197. # @param lesub LeType : LeType child class instance
  198. # @param nature str : The nature of the relation @ref EditorialModel.classtypes
  199. # @param rank str|int : The relation rank. Can be 'last', 'first' or an integer
  200. # @param replace_if_exists bool : if True delete the old superior and set the new one. If False and there is a superior raise an LeObjectQueryError
  201. # @return The relation ID or False if fails
  202. # @throw LeObjectQueryError replace_if_exists == False and there is a superior
  203. @classmethod
  204. def hierarchy_add(cls, lesup, lesub, nature, rank = 'last', replace_if_exists = False):
  205. #Arguments check
  206. if nature not in EditorialModel.classtypes.EmClassType.natures(lesub._classtype):
  207. raise ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
  208. if not cls.leo_is_root(lesup):
  209. if nature not in EditorialModel.classtypes.EmClassType.natures(lesup._classtype):
  210. raise ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
  211. if lesup.__class__ not in lesub._superiors[nature]:
  212. raise ValueError("%s is not a valid superior for %s"%(lesup.__class__, lesub.__class__))
  213. #else:
  214. # lesup is not a LeType but a hierarchy root
  215. if rank not in ['first', 'last'] and not isinstance(rank, int):
  216. raise ValueError("Allowed values for rank are integers and 'first' or 'last' but '%s' found"%rank)
  217. superiors = cls.hierarchy_get(lesub, nature, leo_is_sup = False)
  218. if lesup in len(superiors) > 0:
  219. if not replace_if_exists:
  220. raise LeObjectQueryError("The subordinate allready has a superior")
  221. #remove existig superior
  222. if not cls.hierarchy_del(superiors[0], lesub, nature):
  223. raise RuntimeError("Unable to delete the previous superior")
  224. return self._datasource.add_superior(lesup, lesub, nature, rank)
  225. ## @brief Delete a hierarchy link between two LeObject
  226. # @param lesup LeType | LeRoot : LeType child class or hierarchy root
  227. # @param lesub LeType : LeType child class
  228. # @param nature str : The nature of the relation @ref EditorialModel.classtypes
  229. # @return True if deletion done successfully
  230. # @throw ValueError when bad arguments given
  231. @classmethod
  232. def hierarchy_del(cls, lesup, lesub, nature):
  233. if nature not in EditorialModel.classtypes.EmClassType.natures(lesub._classtype):
  234. raise ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
  235. if not cls.leo_is_root(lesup):
  236. if nature not in EditorialModel.classtypes.EmClassType.natures(lesup._classtype):
  237. raise ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
  238. if lesup.__class__ not in lesub._superiors[nature]:
  239. raise ValueError("%s is not a valid superior for %s"%(lesup.__class__, lesub.__class__))
  240. superiors = cls.hierarchy_get(lesub, nature, leo_is_sup = False)
  241. res = True
  242. for _lesup in superiors:
  243. if not cls._datasource.del_superior(_lesup, lesub, nature):
  244. #How to handler this ?
  245. res = False
  246. return res
  247. ## @brief Fetch neighbour in hierarchy relation
  248. # @param leo LeType | LeRoot : We want the neighbour of this LeObject (can be the root)
  249. # @param nature str : @ref EditorialModel.classtypes
  250. # @param leo_is_sup bool : if True leo is the superior and we want to fetch the subordinates else its the oposite
  251. # @return A list of LeObject ordered by depth if leo_is_sup, else a list of subordinates
  252. @classmethod
  253. def hierarchy_get(cls, leo, nature, leo_is_sup = True):
  254. #Checking arguments
  255. if not (nature is None) and not cls.is_root(leo):
  256. if nature not in EditorialModel.classtypes.EmClassType.natures(leo._classtype):
  257. raise ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
  258. if leo_is_sup:
  259. return cls._datasource.get_subordinates(leo, nature)
  260. else:
  261. return cls._datasource.get_superiors(leo, nature)
  262. ## @brief Prepare a field_list
  263. # @param field_list list : List of string representing fields
  264. # @param letype LeType : LeType child class
  265. # @param leclass LeClass : LeClass child class
  266. # @return A well formated field list
  267. @classmethod
  268. def _prepare_field_list(cls, field_list, letype, leclass):
  269. cls._check_fields(letype, leclass, [f for f in field_list if not cls._field_is_relational(f)])
  270. for i, field in enumerate(field_list):
  271. if cls._field_is_relational(field):
  272. field_list[i] = cls._prepare_relational_field(field)
  273. return field_list
  274. ## @brief Preparing letype and leclass arguments
  275. #
  276. # This function will do multiple things :
  277. # - Convert string to LeType or LeClass child instances
  278. # - If both letype and leclass given, check that letype inherit from leclass
  279. #  - If only a letype is given, fetch the parent leclass
  280. # @note If we give only a leclass as argument returned letype will be None
  281. # @note Its possible to give letype=None and leclass=None. In this case the method will return tuple(None,None)
  282. # @param letype LeType|str|None : LeType child instant or its name
  283. # @param leclass LeClass|str|None : LeClass child instant or its name
  284. # @return a tuple with 2 python classes (LeTypeChild, LeClassChild)
  285. @staticmethod
  286. def _prepare_targets(letype = None , leclass = None):
  287. if not(leclass is None):
  288. if isinstance(leclass, str):
  289. leclass = LeFactory.leobj_from_name(leclass)
  290. if not isinstance(leclass, type) or not (leobject.leclass.LeClass in leclass.__bases__) or leclass.__class__ == leobject.leclass.LeClass:
  291. raise ValueError("None | str | LeType child class excpected, but got : '%s' %s"%(leclass,type(leclass)))
  292. if not(letype is None):
  293. if isinstance(letype, str):
  294. letype = LeFactory.leobj_from_name(letype)
  295. if not isinstance(letype, type) or not leobject.letype.LeType in letype.__bases__ or letype.__class__ == leobject.letype.LeType:
  296. raise ValueError("None | str | LeType child class excpected, but got : %s"%type(letype))
  297. if leclass is None:
  298. leclass = letype._leclass
  299. elif leclass != letype._leclass:
  300. raise ValueError("LeType child class %s does'nt inherite from LeClass %s"%(letype.__name__, leclass.__name__))
  301. return (letype, leclass)
  302. ## @brief Check if a fieldname is valid
  303. # @param letype LeType|None : The concerned type (or None)
  304. # @param leclass LeClass|None : The concerned class (or None)
  305. # @param fields list : List of string representing fields
  306. # @throw LeObjectQueryError if their is some problems
  307. # @throw AttributeError if letype is not from the leclass class
  308. # @todo Delete the checks of letype and leclass and ensure that this method is called with letype and leclass arguments from _prepare_targets()
  309. #
  310. # @see @ref leobject_filters
  311. @staticmethod
  312. def _check_fields(letype, leclass, fields):
  313. #Checking that fields in the query_filters are correct
  314. if letype is None and leclass is None:
  315. #Only fields from the object table are allowed
  316. for field in fields:
  317. if field not in EditorialModel.classtypes.common_fields.keys():
  318. raise LeObjectQueryError("Not typename and no classname given, but the field %s is not in the common_fields list"%field)
  319. else:
  320. if letype is None:
  321. field_l = leclass._fieldtypes.keys()
  322. else:
  323. if not (leclass is None):
  324. if letype._leclass != leclass:
  325. raise AttributeError("The EmType %s is not a specialisation of the EmClass %s"%(typename, classname))
  326. field_l = letype._fields
  327. #Checks that fields are in this type
  328. for field in fields:
  329. if field not in field_l:
  330. raise LeObjectQueryError("No field named '%s' in '%s'"%(field, letype.__name__))
  331. pass
  332. ## @brief Prepare filters for datasource
  333. #
  334. # This method divide filters in two categories :
  335. # - filters : standart FIELDNAME OP VALUE filter
  336. # - relationnal_filters : filter on object relation RELATION_NATURE OP VALUE
  337. #
  338. # Both categories of filters are represented in the same way, a tuple with 3 elements (NAME|NAT , OP, VALUE )
  339. #
  340. # @warning This method assume that letype and leclass are returned from _LeObject._prepare_targets() method
  341. # @param filters_l list : This list can contain str "FIELDNAME OP VALUE" and tuples (FIELDNAME, OP, VALUE)
  342. # @param letype LeType|None : needed to check filters
  343. # @param leclass LeClass|None : needed to check filters
  344. # @return a tuple(FILTERS, RELATIONNAL_FILTERS
  345. #
  346. # @see @ref datasource_side
  347. @staticmethod
  348. def _prepare_filters(filters_l, letype = None, leclass = None):
  349. filters = list()
  350. for fil in filters_l:
  351. if len(fil) == 3 and not isinstance(fil, str):
  352. filters.append(tuple(fil))
  353. else:
  354. filters.append(_LeObject._split_filter(fil))
  355. #Checking relational filters (for the moment fields like superior.NATURE)
  356. relational_filters = [ (_LeObject._prepare_relational_field(field), operator, value) for field, operator, value in filters if _LeObject._field_is_relational(field)]
  357. filters = [f for f in filters if not _LeObject._field_is_relational(f[0])]
  358. #Checking the rest of the fields
  359. _LeObject._check_fields(letype, leclass, [ f[0] for f in filters ])
  360. return (filters, relational_filters)
  361. ## @brief Check if a field is relational or not
  362. # @param field str : the field to test
  363. # @return True if the field is relational else False
  364. @staticmethod
  365. def _field_is_relational(field):
  366. return field.startswith('superior.') or field.startswith('subordinate')
  367. ## @brief Check that a relational field is valid
  368. # @param field str : a relational field
  369. # @return a nature
  370. @staticmethod
  371. def _prepare_relational_field(field):
  372. spl = field.split('.')
  373. if len(spl) != 2:
  374. raise LeObjectQueryError("The relationalfield '%s' is not valid"%field)
  375. nature = spl[-1]
  376. if nature not in EditorialModel.classtypes.EmNature.getall():
  377. raise LeObjectQueryError("'%s' is not a valid nature in the field %s"%(nature, field))
  378. if spl[0] == 'superior':
  379. return (REL_SUP, nature)
  380. elif spl[0] == 'subordinate':
  381. return (REL_SUB, nature)
  382. else:
  383. raise LeObjectQueryError("Invalid preffix for relationnal field : '%s'"%spl[0])
  384. ## @brief Check and split a query filter
  385. # @note The query_filter format is "FIELD OPERATOR VALUE"
  386. # @param query_filter str : A query_filter string
  387. # @param cls
  388. # @return a tuple (FIELD, OPERATOR, VALUE)
  389. @classmethod
  390. def _split_filter(cls, query_filter):
  391. if cls._query_re is None:
  392. cls._compile_query_re()
  393. matches = cls._query_re.match(query_filter)
  394. if not matches:
  395. raise ValueError("The query_filter '%s' seems to be invalid"%query_filter)
  396. result = (matches.group('field'), re.sub(r'\s', ' ', matches.group('operator'), count=0), matches.group('value').strip())
  397. for r in result:
  398. if len(r) == 0:
  399. raise ValueError("The query_filter '%s' seems to be invalid"%query_filter)
  400. return result
  401. ## @brief Compile the regex for query_filter processing
  402. # @note Set _LeObject._query_re
  403. @classmethod
  404. def _compile_query_re(cls):
  405. op_re_piece = '(?P<operator>(%s)'%cls._query_operators[0].replace(' ', '\s')
  406. for operator in cls._query_operators[1:]:
  407. op_re_piece += '|(%s)'%operator.replace(' ', '\s')
  408. op_re_piece += ')'
  409. cls._query_re = re.compile('^\s*(?P<field>(((superior)|(subordinate))\.)?[a-z_][a-z0-9\-_]*)\s*'+op_re_piece+'\s*(?P<value>[^<>=!].*)\s*$', flags=re.IGNORECASE)
  410. pass
  411. ## @brief Class designed to represent the hierarchy roots
  412. # @see _LeObject.get_root() _LeObject.is_root()
  413. class LeRoot(object):
  414. pass
  415. class LeObjectError(Exception):
  416. pass
  417. class LeObjectQueryError(LeObjectError):
  418. pass
  419. ## @page leobject_filters LeObject query filters
  420. # The LeObject API provide methods that accept filters allowing the user
  421. # to query the database and fetch LodelEditorialObjects.
  422. #
  423. # The LeObject API translate those filters for the datasource.
  424. #
  425. # @section api_user_side API user side filters
  426. # Filters are string expressing a condition. The string composition
  427. # is as follow : "<FIELD> <OPERATOR> <VALUE>"
  428. # @subsection fpart FIELD
  429. # @subsubsection standart fields
  430. # Standart fields, represents a value of the LeObject for example "title", "lodel_id" etc.
  431. # @subsubsection rfields relationnal fields
  432. # relationnal fields, represents a relation with the object hierarchy. Those fields are composed as follow :
  433. # "<RELATION>.<NATURE>".
  434. #
  435. # - Relation can takes two values : superiors or subordinates
  436. # - Nature is a relation nature ( see EditorialModel.classtypes )
  437. # Examples : "superiors.parent", "subordinates.translation" etc.
  438. # @note The field_list arguement of leobject.leobject._LeObject.get() use the same syntax than the FIELD filter part
  439. # @subsection oppart OPERATOR
  440. # The OPERATOR part of a filter is a comparison operator. There is
  441. # - standart comparison operators : = , <, > , <=, >=, !=
  442. # - list operators : 'in' and 'not in'
  443. # The list of allowed operators is sotred at leobject.leobject._LeObject._query_operators .
  444. # @subsection valpart VALUE
  445. # The VALUE part of a filter is... just a value...
  446. #
  447. # @section datasource_side Datasource side filters
  448. # As said above the API "translate" filters before forwarding them to the datasource.
  449. #
  450. # The translation process transform filters in tuple composed of 3 elements
  451. # ( @ref fpart , @ref oppart , @ref valpart ). Each element is a string.
  452. #
  453. # There is a special case for @ref rfields : the field element is a tuple composed with two elements
  454. # ( RELATION, NATURE ) where NATURE is a string ( see EditorialModel.classtypes ) and RELATION is one of
  455. # the defined constant :
  456. #
  457. # - leobject.leobject.REL_SUB for "subordinates"
  458. # - leobject.leobject.REL_SUP for "superiors"
  459. #
  460. # @note The filters translation process also check if given field are valids compared to the concerned letype and/or the leclass