Ei kuvausta
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.

letype.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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 leapi
  11. from leapi.lecrud import _LeCrud, LeApiDataCheckError, LeApiQueryError
  12. from leapi.leclass import _LeClass
  13. from leapi.leobject import LeObjectError
  14. ## @brief Represent an EmType data instance
  15. # @note Is not a derivated class of LeClass because the concrete class will be a derivated class from LeClass
  16. class _LeType(_LeClass):
  17. ## @brief Stores selected fields with key = name
  18. _fields = list()
  19. ## @brief Allowed LeType superiors
  20. _superiors = list()
  21. ## @brief Stores the class of LeClass
  22. _leclass = None
  23. ## @brief Stores the EM uid
  24. _type_id = None
  25. ## @brief Instanciate a new LeType
  26. # @param lodel_id : The lodel id
  27. # @param **kwargs : Datas used to populate a LeType
  28. def __init__(self, lodel_id, **kwargs):
  29. if self._leclass is None:
  30. raise NotImplementedError("Abstract class")
  31. self.lodel_id, err = self._uid_fieldtype['lodel_id'].check_data_value(lodel_id)
  32. if isinstance(err, Exception):
  33. raise err
  34. if 'type_id' in kwargs:
  35. if self.__class__._type_id != int(kwargs['type_id']):
  36. raise RuntimeError("Trying to instanciate a %s with an type_id that is not correct"%self.__class__.__name__)
  37. if 'class_id' in kwargs:
  38. if self.__class__._class_id != int(kwargs['class_id']):
  39. raise RuntimeError("Trying to instanciate a %s with a clas_id that is not correct"%self.__class__.__name__)
  40. ## Populate the object from the datas received in kwargs
  41. err_l = dict()
  42. for name, value in kwargs.items():
  43. if name not in self._fields:
  44. err_l[name] = AttributeError("No such field '%s' for %s"%(name, self.__class__.__name__))
  45. else:
  46. cvalue, err = self.fieldtypes()[name].check_data_value(value)
  47. if isinstance(err, Exception):
  48. err_l[name] = err
  49. else:
  50. setattr(self, name, value)
  51. if len(err_l) > 0:
  52. raise LeApiDataCheckError("Invalid arguments given to constructor", err_l)
  53. @classmethod
  54. def leo_class(cls):
  55. return cls._leclass
  56. @classmethod
  57. def fieldlist(cls):
  58. return cls._fields
  59. @classmethod
  60. def get(cls, query_filters, field_list = None, order = None, group = None, limit = None, offset = 0):
  61. query_filters.append(('type_id', '=', cls._type_id))
  62. return super().get(query_filters, field_list, order, group, limit, offset)
  63. @classmethod
  64. def fieldtypes(cls):
  65. return { fname: cls._fieldtypes[fname] for fname in cls._fieldtypes if fname in cls._fields }
  66. ## @brief Populate the LeType wih datas from DB
  67. # @param field_list None|list : List of fieldname to fetch. If None fetch all the missing datas
  68. def populate(self, field_list=None):
  69. if field_list == None:
  70. field_list = [ fname for fname in self._fields if not hasattr(self, fname) ]
  71. filters = [self._id_filter()]
  72. rel_filters = []
  73. fdatas = self._datasource.select(self.__class__, field_list, filters, rel_filters)
  74. if fdatas is None or len(fdatas) == 0:
  75. raise LeApiQueryError("Error when trying to populate an object. For type %s id : %d"% (self.__class__.__name__, self.lodel_id))
  76. for fname, fval in fdatas[0].items():
  77. setattr(self, fname, fval)
  78. ## @brief Get a fieldname:value dict
  79. # @return A dict with field name as key and the field value as value
  80. def datas(self):
  81. return { fname: getattr(self, fname) for fname in self._fields if hasattr(self,fname) }
  82. ## @brief Get all the datas for this LeType
  83. # @return a dict with fieldname as key and field value as value
  84. # @warning Can represent a performance issue
  85. def all_datas(self):
  86. self.populate()
  87. return self.datas()
  88. ## @brief Delete current instance from DB
  89. def delete(self):
  90. _LeCrud._delete(self)
  91. ## @brief Add a superior
  92. # @param lesup LeObject : LeObject child class instance
  93. # @param nature str : Relation nature
  94. # @param del_if_exists bool : If true delete the superior if any before setting the new one
  95. # @return relation id if successfully created else returns false
  96. def add_superior(self, lesup, nature, del_if_exists = False):
  97. lehierarch = self.name2class('LeHierarch')
  98. if del_if_exists:
  99. prev_sup = lehierarch.get(
  100. [('lesub', '=', self), ('nature', '=', nature)],
  101. [ lehierarch.uidname() ]
  102. )
  103. if len(prev_sup) > 0:
  104. for todel_sup in prev_sup: #This loop shoud be useless...but we never know
  105. todel_sup.delete()
  106. return lehierarch.insert({'lesup':lesup, 'lesub':self, 'nature':nature})
  107. ## @brief Link the LeObject with another one (rel2type relations)
  108. #
  109. # @note This methods asser that self is the superior and leo_tolink the subordinate
  110. #
  111. # @param leo_tolink LeObject : LeObject child instance to link with
  112. # @param **datas : Relation attributes (if any)
  113. # @return a relation id if success
  114. def link_with(self, leo_tolink, datas):
  115. # Fetch rel2type leapi class
  116. r2t = self.name2class('LeRel2Type')
  117. class_name = r2t.relname(self, leo_tolink.__class__)
  118. r2tcls = self.name2class(class_name)
  119. if not r2tcls:
  120. raise ValueError("No rel2type possible between a '%s' as superior and a '%s' as subordinate" % (self._leclass.__name__, leo_tolink.__class__.__name__))
  121. datas['lesup'] = self
  122. datas['lesub'] = leo_tolink
  123. return r2tcls.insert(datas, class_name)
  124. ## @brief Get the linked objects lodel_id
  125. # @param letype LeType : Filter the result with LeType child class (not instance)
  126. # @return a dict with LeType instance as key and dict{attr_name:attr_val...} as value
  127. # @todo unit tests
  128. def ___linked(self, letype):
  129. if leapi.letype.LeType not in letype.__bases__:
  130. raise ValueError("letype has to be a child class of LeType (not an instance) but %s found"%type(letype))
  131. if letype in self._linked_types.keys():
  132. get_sub = True
  133. elif self.__class__ in letype._linked_types.keys():
  134. get_sub = False
  135. else:
  136. raise ValueError("The two LeType classes %s and %s are not linked with a rel2type field"%(self.__class__.__name__, letype.__name__))
  137. return self._datasource.get_related(self, letype, get_sub)
  138. ## @brief Link this object with a LeObject as subordinate
  139. # @note shortcut for @ref leapi.leapi._LeObject.link_together()
  140. # @param lesub LeObject : The object to be linked with as subordinate
  141. # @param **rel_attr : keywords arguments for relations attributes
  142. # @return The relation_id if success else return False
  143. # @throw LeObjectError if the link is not valid
  144. # @throw LeObkectError if the link already exists
  145. # @throw AttributeError if an non existing relation attribute is given as argument
  146. # @throw ValueError if the relation attrivute value check fails
  147. # @see leapi.lefactory.LeFactory.link_together()
  148. def ___link(self, leo, **rel_attr):
  149. return leapi.lefactory.LeFactory.leobj_from_name('LeObject').link_together(self, leo, **rel_attr)
  150. ## @brief Returns linked subordinates in a rel2type given a wanted LeType child class
  151. # @param letype LeType(class) : The wanted LeType of result
  152. # @return A dict with LeType child class instance as key and dict {'id_relation':id, rel_attr_name:rel_attr_value, ...}
  153. # @throw LeObjectError if the relation is not possible
  154. # @see leapi.lefactory.LeFactory.linked_together()
  155. def ___linked_subordinates(self, letype):
  156. return leapi.lefactory.LeFactory.leobj_from_name('LeObject').linked_together(self, letype, True)
  157. ## @brief Remove a link with a subordinate
  158. # @param leo LeType : LeType child instance
  159. # @return True if a link has been deleted
  160. # @throw LeObjectError if the relation do not concern the current LeType
  161. def ___unlink_subordinate(self, id_relation):
  162. return leapi.lefactory.LeFactory.leobj_from_name('LeObject').linked_together(self, leo)
  163. ## @brief Remove a link bewteen this object and another
  164. # @param leo LeType : LeType child class instance
  165. # @todo unit tests
  166. def ___unlink(self, leo):
  167. if leo.__class__ in self._linked_types.keys():
  168. sup = self
  169. sub = leo
  170. elif self.__class__ in leo._linked_types.keys():
  171. sup = leo
  172. sub = self
  173. return self._datasource.del_related(sup, sub)
  174. ## @brief Add a superior
  175. # @param lesup LeType | LeRoot : LeType child class instance that will be the superior
  176. # @param nature str : The nature of the relation @ref EditorialModel.classtypes
  177. # @param rank str|int : The relation rank. Can be 'last', 'first' or an integer
  178. # @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
  179. # @return The relation ID or False if fails
  180. def ___superior_add(self, leo, nature, rank = 'last', replace_if_exists = False):
  181. return leapi.lefactory.LeFactory.leobj_from_name('LeObject').hierarchy_add(leo, self, nature, rank, replace_if_exists)
  182. ## @brief Delete a superior given a relation's natue
  183. # @param leo LeType | LeRoot : The superior to delete
  184. # @param nature str : The nature of the relation @ref EditorialModel.classtypes
  185. # @return True if deletion is a success
  186. def ___superior_del(self, leo, nature):
  187. return leapi.lefactory.leobj_from_name('LeObject').hierarchy_del(leo, self, nature)
  188. ## @brief Fetch superiors by depth
  189. # @return A list of LeObject ordered by depth (the first is the one with the bigger depth)
  190. def ___superiors(self):
  191. return leapi.lefactory.leobj_from_name('LeObject').hierarchy_get(self,nature, leo_is_sup = False)
  192. ## @brief Fetch subordinates ordered by rank
  193. # @return A list of LeObject ordered by rank
  194. def ___subordinates(self):
  195. return leapi.lefactory.leobj_from_name('LeObject').hierarchy_get(self,nature, leo_is_sup = True)
  196. ## @brief Update a LeType in db
  197. def ___db_update(self):
  198. return self.update(filters=[('lodel_id', '=', repr(self.lodel_id))], datas = self.datas)