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 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. #-*- coding: utf-8 -*-
  2. ## @package leobject API to access lodel datas
  3. #
  4. # This package contains abstract classes leapi.leclass.LeClass , leapi.letype.LeType, leapi.leapi._LeObject.
  5. # Those abstract classes are designed to be mother classes of dynamically generated classes ( see leapi.lefactory.LeFactory )
  6. ## @package leapi.leobject
  7. # @brief Abstract class designed to be implemented by LeObject
  8. #
  9. # @note LeObject will be generated by leapi.lefactory.LeFactory
  10. import re
  11. import copy
  12. import warnings
  13. import leapi
  14. from leapi.lecrud import _LeCrud, REL_SUP, REL_SUB
  15. from leapi.lefactory import LeFactory
  16. import EditorialModel
  17. from EditorialModel.types import EmType
  18. ## @brief Main class to handle objects defined by the types of an Editorial Model
  19. class _LeObject(_LeCrud):
  20. ## @brief maps em uid with LeType or LeClass keys are uid values are LeObject childs classes
  21. # @todo check if this attribute shouldn't be in _LeCrud
  22. _me_uid = dict()
  23. ## @brief Stores the fields name associated with fieldtype of the fields that are common to every LeObject
  24. _leo_fieldtypes = dict()
  25. ## @brief Instanciate a partial LeObject with a lodel_id
  26. # @note use the get_instance method to fetch datas and instanciate a concret LeObject
  27. def __init__(self, lodel_id):
  28. #Warning ! Handles only single pk
  29. uid_fname, uid_ft = list(self._uid_fieldtype.items())[0]
  30. new_id, err = uid_ft.check_data_value(lodel_id)
  31. if not (err is None):
  32. raise err
  33. setattr(self, uid_fname, lodel_id)
  34. ## @return Corresponding populated LeObject
  35. def get_instance(self):
  36. uid_fname = self.uidname()
  37. qfilter = '{uid_fname} = {uid}'.format(uid_fname = uid_fname, uid = getattr(self, uid_fname))
  38. return leobject.get([qfilter])[0]
  39. ## @return True if the LeObject is partially instanciated
  40. def is_partial(self):
  41. return not hasattr(self, '_classtype')
  42. ## @brief Check if a LeObject is the relation tree Root
  43. # @todo implementation
  44. def is_root(self):
  45. return False
  46. ## @brief Dirty & quick comparison implementation
  47. def __cmp__(self, other):
  48. return 0 if self == other else 1
  49. ## @brief Dirty & quick equality implementation
  50. # @todo check class
  51. def __eq__(self, other):
  52. uid_fname = self.uidname()
  53. if not hasattr(other, uid_fname):
  54. return False
  55. return getattr(self, uid_fname) == getattr(other, uid_fname)
  56. ## @brief Quick str cast method implementation
  57. def __str__(self):
  58. return "<%s lodel_id=%d>"%(self.__class__, getattr(self, self.uidname()))
  59. def __repr__(self):
  60. return self.__str__()
  61. ## @brief Given a ME uid return the corresponding LeClass or LeType class
  62. # @return a LeType or LeClass child class
  63. # @throw KeyError if no corresponding child classes
  64. # @todo check if this method shouldn't be in _LeCrud
  65. @classmethod
  66. def uid2leobj(cls, uid):
  67. uid = int(uid)
  68. if uid not in cls._me_uid:
  69. raise KeyError("No LeType or LeClass child classes with uid '%d'"%uid)
  70. return cls._me_uid[uid]
  71. @classmethod
  72. def fieldtypes(cls):
  73. if cls._fieldtypes_all is None:
  74. cls._fieldtypes_all = dict()
  75. cls._fieldtypes_all.update(cls._uid_fieldtype)
  76. cls._fieldtypes_all.update(cls._leo_fieldtypes)
  77. return cls._fieldtypes_all
  78. @classmethod
  79. def typefilter(cls):
  80. if hasattr(cls, '_type_id'):
  81. return ('type_id','=', cls._type_id)
  82. elif hasattr(cls, '_class_id'):
  83. return ('class_id', '=', cls._class_id)
  84. else:
  85. raise ValueError("Cannot generate a typefilter with %s class"%cls.__name__)
  86. ## @brief Delete LeObjects from db given filters and a classname
  87. # @note if no classname given, take the caller class
  88. # @param filters list :
  89. # @param classname None|str : the classname or None
  90. # @return number of deleted LeObjects
  91. # @see leapi.lecrud._LeCrud.delete()
  92. @classmethod
  93. def delete(cls, filters, classname = None):
  94. ccls = cls if classname is None else cls.name2class(classname)
  95. new_filters = copy.copy(filters)
  96. new_filters.append(ccls.typefilter())
  97. return _LeCrud.delete(ccls, new_filters)
  98. ## @brief Check that a relational field is valid
  99. # @param field str : a relational field
  100. # @return a nature
  101. @staticmethod
  102. def _prepare_relational_field(field):
  103. spl = field.split('.')
  104. if len(spl) != 2:
  105. return ValueError("The relationalfield '%s' is not valid"%field)
  106. nature = spl[-1]
  107. if nature not in EditorialModel.classtypes.EmNature.getall():
  108. return ValueError("'%s' is not a valid nature in the field %s"%(nature, field))
  109. if spl[0] == 'superior':
  110. return (REL_SUP, nature)
  111. elif spl[0] == 'subordinate':
  112. return (REL_SUB, nature)
  113. else:
  114. return ValueError("Invalid preffix for relationnal field : '%s'"%spl[0])
  115. ## @brief Check if a LeType is a hierarchy root
  116. @staticmethod
  117. def ___is_root(leo):
  118. if isinstance(leo, leapi.letype.LeType):
  119. return False
  120. elif isinstance(leo, LeRoot):
  121. return True
  122. raise ValueError("Invalid value for a LeType : %s"%leo)
  123. ## @brief Return a LeRoot instance
  124. @staticmethod
  125. def ___get_root():
  126. return LeRoot()
  127. ## @brief Link two leobject together using a rel2type field
  128. # @param lesup LeType : LeType child class instance linked as superior
  129. # @param lesub LeType : LeType child class instance linked as subordinate
  130. # @param **rel_attr : Relation attributes
  131. # @return True if linked without problems
  132. # @throw LeObjectError if the link is not valid
  133. # @throw LeObkectError if the link already exists
  134. # @throw AttributeError if an non existing relation attribute is given as argument
  135. # @throw ValueError if the relation attrivute value check fails
  136. #
  137. # @todo Code factorisation on relation check
  138. # @todo unit tests
  139. @classmethod
  140. def ___link_together(cls, lesup, lesub, rank = 'last', **rel_attr):
  141. if lesub.__class__ not in lesup._linked_types.keys():
  142. raise LeObjectError("Relation error : %s cannot be linked with %s"%(lesup.__class__.__name__, lesub.__class__.__name__))
  143. for attr_name in rel_attr.keys():
  144. if attr_name not in [ f for f,g in lesup._linked_types[lesub.__class__] ]:
  145. raise AttributeError("A rel2type between a %s and a %s doesn't have an attribute %s"%(lesup.__class__.__name__, lesub.__class__.__name__))
  146. if not sup._linked_types[lesub.__class__][1].check(rel_attr[attr_name]):
  147. raise ValueError("Wrong value '%s' for attribute %s"%(rel_attr[attr_name], attr_name))
  148. #Checks that attributes are uniq for this relation
  149. rels_attr = [ attrs for lesup, lesub, attrs in cls.links_get(lesup) if lesup == lesup ]
  150. for e_attrs in rels_attrs:
  151. if rel_attr == e_attrs:
  152. raise LeObjectError("Relation error : a relation with the same attributes already exists")
  153. return cls._datasource.add_related(lesup, lesub, rank, **rel_attr)
  154. ## @brief Get related objects
  155. # @param leo LeType(instance) : LeType child class instance
  156. # @param letype LeType(class) : the wanted LeType child class (not instance)
  157. # @param leo_is_superior bool : if True leo is the superior in the relation
  158. # @return A dict with LeType child class instance as key and dict {rel_attr_name:rel_attr_value, ...}
  159. # @throw LeObjectError if the relation is not possible
  160. #
  161. # @todo Code factorisation on relation check
  162. # @todo unit tests
  163. @classmethod
  164. def ___linked_together(cls, leo, letype, leo_is_superior = True):
  165. valid_link = letype in leo._linked_types.keys() if leo_is_superior else leo.__class__ in letype._linked_types.keys()
  166. if not valid_link:
  167. raise LeObjectError("Relation error : %s have no links with %s"%(
  168. leo.__class__ if leo_is_superior else letype,
  169. letype if leo_is_superior else leo.__class__
  170. ))
  171. return cls._datasource.get_related(leo, letype, leo_is_superior)
  172. ## @brief Fetch a relation and its attributes
  173. # @param id_relation int : the relation identifier
  174. # @return a tuple(lesup, lesub, dict_attr) or False if no relation exists with this id
  175. # @throw Exception if the relation is not a rel2type relation
  176. @classmethod
  177. def ___link_get(cls, id_relation):
  178. return cls._datasource.get_relation(id_relation)
  179. ## @brief Fetch all relations for an objects
  180. # @param leo LeType : LeType child class instance
  181. # @return a list of tuple (lesup, lesub, dict_attr)
  182. def ___links_get(cls, leo):
  183. return cls._datasource.get_relations(leo)
  184. ## @brief Remove a link (and attributes) between two LeObject
  185. # @param id_relation int : Relation identifier
  186. # @return True if a link has been deleted
  187. # @throw LeObjectError if the relation is not a rel2type
  188. #
  189. # @todo Code factorisation on relation check
  190. # @todo unit tests
  191. @classmethod
  192. def ___link_remove(cls, id_relation):
  193. if lesub.__class__ not in lesup._linked_types.keys():
  194. raise LeObjectError("Relation errorr : %s cannot be linked with %s"%(lesup.__class__.__name__, lesub.__class__.__name__))
  195. return cls._datasource.del_related(lesup, lesub)
  196. ## @brief Add a hierarchy relation between two LeObject
  197. # @param lesup LeType|LeRoot : LeType child class instance
  198. # @param lesub LeType : LeType child class instance
  199. # @param nature str : The nature of the relation @ref EditorialModel.classtypes
  200. # @param rank str|int : The relation rank. Can be 'last', 'first' or an integer
  201. # @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
  202. # @return The relation ID or False if fails
  203. # @throw LeObjectQueryError replace_if_exists == False and there is a superior
  204. @classmethod
  205. def ___hierarchy_add(cls, lesup, lesub, nature, rank = 'last', replace_if_exists = False):
  206. #Arguments check
  207. if nature not in EditorialModel.classtypes.EmClassType.natures(lesub._classtype):
  208. raise ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
  209. if not cls.leo_is_root(lesup):
  210. if nature not in EditorialModel.classtypes.EmClassType.natures(lesup._classtype):
  211. raise ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
  212. if lesup.__class__ not in lesub._superiors[nature]:
  213. raise ValueError("%s is not a valid superior for %s"%(lesup.__class__, lesub.__class__))
  214. #else:
  215. # lesup is not a LeType but a hierarchy root
  216. if rank not in ['first', 'last'] and not isinstance(rank, int):
  217. raise ValueError("Allowed values for rank are integers and 'first' or 'last' but '%s' found"%rank)
  218. superiors = cls.hierarchy_get(lesub, nature, leo_is_sup = False)
  219. if lesup in len(superiors) > 0:
  220. if not replace_if_exists:
  221. raise LeObjectQueryError("The subordinate allready has a superior")
  222. #remove existig superior
  223. if not cls.hierarchy_del(superiors[0], lesub, nature):
  224. raise RuntimeError("Unable to delete the previous superior")
  225. return self._datasource.add_superior(lesup, lesub, nature, rank)
  226. ## @brief Delete a hierarchy link between two LeObject
  227. # @param lesup LeType | LeRoot : LeType child class or hierarchy root
  228. # @param lesub LeType : LeType child class
  229. # @param nature str : The nature of the relation @ref EditorialModel.classtypes
  230. # @return True if deletion done successfully
  231. # @throw ValueError when bad arguments given
  232. @classmethod
  233. def ___hierarchy_del(cls, lesup, lesub, nature):
  234. if nature not in EditorialModel.classtypes.EmClassType.natures(lesub._classtype):
  235. raise ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
  236. if not cls.leo_is_root(lesup):
  237. if nature not in EditorialModel.classtypes.EmClassType.natures(lesup._classtype):
  238. raise ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
  239. if lesup.__class__ not in lesub._superiors[nature]:
  240. raise ValueError("%s is not a valid superior for %s"%(lesup.__class__, lesub.__class__))
  241. superiors = cls.hierarchy_get(lesub, nature, leo_is_sup = False)
  242. res = True
  243. for _lesup in superiors:
  244. if not cls._datasource.del_superior(_lesup, lesub, nature):
  245. #How to handler this ?
  246. res = False
  247. return res
  248. ## @brief Fetch neighbour in hierarchy relation
  249. # @param leo LeType | LeRoot : We want the neighbour of this LeObject (can be the root)
  250. # @param nature str : @ref EditorialModel.classtypes
  251. # @param leo_is_sup bool : if True leo is the superior and we want to fetch the subordinates else its the oposite
  252. # @return A list of LeObject ordered by depth if leo_is_sup, else a list of subordinates
  253. @classmethod
  254. def ___hierarchy_get(cls, leo, nature, leo_is_sup = True):
  255. #Checking arguments
  256. if not (nature is None) and not cls.is_root(leo):
  257. if nature not in EditorialModel.classtypes.EmClassType.natures(leo._classtype):
  258. raise ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
  259. if leo_is_sup:
  260. return cls._datasource.get_subordinates(leo, nature)
  261. else:
  262. return cls._datasource.get_superiors(leo, nature)
  263. ## @brief Preparing letype and leclass arguments
  264. #
  265. # This function will do multiple things :
  266. # - Convert string to LeType or LeClass child instances
  267. # - If both letype and leclass given, check that letype inherit from leclass
  268. #  - If only a letype is given, fetch the parent leclass
  269. # @note If we give only a leclass as argument returned letype will be None
  270. # @note Its possible to give letype=None and leclass=None. In this case the method will return tuple(None,None)
  271. # @param letype LeType|str|None : LeType child instant or its name
  272. # @param leclass LeClass|str|None : LeClass child instant or its name
  273. # @return a tuple with 2 python classes (LeTypeChild, LeClassChild)
  274. @classmethod
  275. def ___prepare_targets(cls, letype = None , leclass = None):
  276. warnings.warn("_LeObject._prepare_targets is deprecated", DeprecationWarning)
  277. raise ValueError()
  278. if not(leclass is None):
  279. if isinstance(leclass, str):
  280. leclass = LeFactory.leobj_from_name(leclass)
  281. if not isinstance(leclass, type) or not (leapi.leclass.LeClass in leclass.__bases__) or leclass.__class__ == leapi.leclass.LeClass:
  282. raise ValueError("None | str | LeType child class excpected, but got : '%s' %s"%(leclass,type(leclass)))
  283. if not(letype is None):
  284. if isinstance(letype, str):
  285. letype = LeFactory.leobj_from_name(letype)
  286. if not isinstance(letype, type) or not leapi.letype.LeType in letype.__bases__ or letype.__class__ == leapi.letype.LeType:
  287. raise ValueError("None | str | LeType child class excpected, but got : %s"%type(letype))
  288. if leclass is None:
  289. leclass = letype._leclass
  290. elif leclass != letype._leclass:
  291. raise ValueError("LeType child class %s does'nt inherite from LeClass %s"%(letype.__name__, leclass.__name__))
  292. return (letype, leclass)
  293. ## @brief Class designed to represent the hierarchy roots
  294. # @see _LeObject.get_root() _LeObject.is_root()
  295. class LeRoot(object):
  296. pass
  297. class LeObjectError(Exception):
  298. pass
  299. class LeObjectQueryError(LeObjectError):
  300. pass