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.

model.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. #-*- coding:utf-8 -*-
  2. import hashlib
  3. import importlib
  4. import copy
  5. from lodel.utils.mlstring import MlString
  6. from lodel.mlnamedobject.mlnamedobject import MlNamedObject
  7. from lodel.settings import import Settings
  8. from lodel.editorial_model.exceptions import EditorialModelError, assert_edit
  9. from lodel.logger import logger
  10. from lodel.settings.utils import SettingsError
  11. from lodel.leapi.datahandlers.base_classes import DataHandler
  12. from lodel.editorial_model.components import EmClass, EmField, EmGroup
  13. ## @brief Describe an editorial model
  14. #@ingroup lodel2_em
  15. class EditorialModel(MlNamedObject):
  16. ## @brief Create a new editorial model
  17. # @param name MlString|str|dict : the editorial model name
  18. # @param description MlString|str|dict : the editorial model description
  19. def __init__(self, name, description=None, display_name=None, help_text=None):
  20. self.name = MlString(name)
  21. self.description = MlString(description)
  22. ## @brief Stores all groups indexed by id
  23. self.__groups = dict()
  24. ## @brief Stores all classes indexed by id
  25. self.__classes = dict()
  26. #  @brief Stores all activated groups indexed by id
  27. self.__active_groups = dict()
  28. #  @brief Stores all activated classes indexed by id
  29. self.__active_classes = dict()
  30. self.__set_actives()
  31. if display_name is None:
  32. display_name = name
  33. if help_text is None:
  34. help_text = description
  35. super().__init__(display_name, help_text)
  36. ## @brief EmClass uids accessor
  37. #@return a copy of the dict containing all emclasses of the model if uid is None
  38. # else a copy the class with uid uid
  39. def all_classes(self, uid=None):
  40. if uid is None:
  41. return copy.copy(self.__classes)
  42. else:
  43. try:
  44. return copy.copy(self.__classes[uid])
  45. except KeyError:
  46. raise EditorialModelException("EmClass not found : '%s'" % uid)
  47. ## @brief EmClass uids accessor
  48. #@return the dict containing all emclasses of the model if uid is None
  49. # else the class with uid uid
  50. def all_classes_ref(self, uid=None):
  51. if uid is None:
  52. return self.__classes
  53. else:
  54. try:
  55. return self.__classes[uid]
  56. except KeyError:
  57. raise EditorialModelException("EmGroup not found : '%s'" % uid)
  58. ## @brief active EmClass uids accessor
  59. #@return a list of active class uids
  60. def active_classes_uids(self):
  61. return list(self.__active_classes.keys())
  62. ## @brief EmGroups accessor
  63. #@return a copy of the dict of the model's group if uid is None
  64. # else a copy of the group with uniq id uid
  65. def all_groups(self, uid=None):
  66. if uid is None:
  67. return copy.copy(self.__groups)
  68. else:
  69. try:
  70. return copy.copy(self.__groups[uid])
  71. except KeyError:
  72. raise EditorialModelException("EmGroup not found : '%s'" % uid)
  73. ## @brief EmGroups accessor
  74. #@return the dict of the model's group if uid is None
  75. # else the group with uniq id uid
  76. def all_groups_ref(self, uid=None):
  77. if uid is None:
  78. return self.__groups
  79. else:
  80. try:
  81. return self.__groups[uid]
  82. except KeyError:
  83. raise EditorialModelException("EmGroup not found : '%s'" % uid)
  84. ## @brief active EmClass uids accessor
  85. #@return a list of active group uids
  86. def active_groups_uids(self):
  87. return list(self.__active_groups.keys())
  88. ## @brief EmClass accessor
  89. #@param uid None | str : give this argument to get a specific EmClass
  90. #@return if uid is given returns an EmClass else returns an EmClass
  91. # iterator
  92. #@todo use Settings.editorialmodel.groups to determine which classes should
  93. # be returned
  94. def classes(self, uid=None):
  95. try:
  96. return self.__elt_getter(self.__active_classes,
  97. uid)
  98. except KeyError:
  99. raise EditorialModelException("EmClass not found : '%s'" % uid)
  100. ## @brief EmClass child list accessor
  101. #@param uid str : the EmClass uid
  102. #@return a set of EmClass
  103. def get_class_childs(self, uid):
  104. res = list()
  105. cur = self.classes(uid)
  106. for cls in self.classes():
  107. if cur in cls.parents_recc:
  108. res.append(cls)
  109. return set(res)
  110. ## @brief EmGroup getter
  111. # @param uid None | str : give this argument to get a specific EmGroup
  112. # @return if uid is given returns an EmGroup else returns an EmGroup iterator
  113. def groups(self, uid=None):
  114. try:
  115. return self.__elt_getter(self.__active_groups,
  116. uid)
  117. except KeyError:
  118. raise EditorialModelException("EmGroup not found : '%s'" % uid)
  119. ## @brief Private getter for __groups or __classes
  120. # @see classes() groups()
  121. def __elt_getter(self, elts, uid):
  122. return list(elts.values()) if uid is None else elts[uid]
  123. ## @brief Update the EditorialModel.__active_groups and
  124. # EditorialModel.__active_classes attibutes
  125. def __set_actives(self):
  126. if Settings.editorialmodel.editormode:
  127. logger.warning("All EM groups active because editormode in ON")
  128. # all groups & classes actives because we are in editor mode
  129. self.__active_groups = self.__groups
  130. self.__active_classes = self.__classes
  131. else:
  132. # determine groups first
  133. self.__active_groups = dict()
  134. self.__active_classes = dict()
  135. for agrp in Settings.editorialmodel.groups:
  136. if agrp not in self.__groups:
  137. raise SettingsError('Invalid group found in settings : %s' % agrp)
  138. logger.debug("Set group '%s' as active" % agrp)
  139. grp = self.__groups[agrp]
  140. self.__active_groups[grp.uid] = grp
  141. for acls in [cls for cls in grp.components() if isinstance(cls, EmClass)]:
  142. self.__active_classes[acls.uid] = acls
  143. if len(self.__active_groups) == 0:
  144. raise RuntimeError("No groups activated, abording...")
  145. if len(self.__active_classes) == 0:
  146. raise RuntimeError("No active class found. Abording")
  147. for clsname, acls in self.__active_classes.items():
  148. acls._set_active_fields(self.__active_groups)
  149. ## @brief EmField getter
  150. # @param uid str : An EmField uid represented by "CLASSUID.FIELDUID"
  151. # @return Fals or an EmField instance
  152. #
  153. # @todo delete it, useless...
  154. def field(self, uid=None):
  155. spl = uid.split('.')
  156. if len(spl) != 2:
  157. raise ValueError("Malformed EmField identifier : '%s'" % uid)
  158. cls_uid = spl[0]
  159. field_uid = spl[1]
  160. try:
  161. emclass = self.classes(cls_uid)
  162. except KeyError:
  163. return False
  164. try:
  165. return emclass.fields(field_uid)
  166. except KeyError:
  167. pass
  168. return False
  169. ## @brief Add a class to the editorial model
  170. # @param emclass EmClass : the EmClass instance to add
  171. # @return emclass
  172. def add_class(self, emclass):
  173. assert_edit()
  174. if not isinstance(emclass, EmClass):
  175. raise ValueError("<class EmClass> expected but got %s " % type(emclass))
  176. if emclass.uid in self.classes():
  177. raise EditorialModelException('Duplicated uid "%s"' % emclass.uid)
  178. self.__classes[emclass.uid] = emclass
  179. return emclass
  180. ## @brief Add a group to the editorial model
  181. # @param emgroup EmGroup : the EmGroup instance to add
  182. # @return emgroup
  183. def add_group(self, emgroup):
  184. assert_edit()
  185. if not isinstance(emgroup, EmGroup):
  186. raise ValueError("<class EmGroup> expected but got %s" % type(emgroup))
  187. if emgroup.uid in self.groups():
  188. raise EditorialModelException('Duplicated uid "%s"' % emgroup.uid)
  189. self.__groups[emgroup.uid] = emgroup
  190. return emgroup
  191. ## @brief Add a new EmClass to the editorial model
  192. #@param uid str : EmClass uid
  193. #@param **kwargs : EmClass constructor options (
  194. # see @ref lodel.editorial_model.component.EmClass.__init__() )
  195. def new_class(self, uid, **kwargs):
  196. assert_edit()
  197. return self.add_class(EmClass(uid, **kwargs))
  198. ## @brief Add a new EmGroup to the editorial model
  199. #@param uid str : EmGroup uid
  200. #@param *kwargs : EmGroup constructor keywords arguments (
  201. # see @ref lodel.editorial_model.component.EmGroup.__init__() )
  202. def new_group(self, uid, **kwargs):
  203. assert_edit()
  204. return self.add_group(EmGroup(uid, **kwargs))
  205. ## @brief Save a model
  206. # @param translator module : The translator module to use
  207. # @param **translator_args
  208. def save(self, translator, **translator_kwargs):
  209. assert_edit()
  210. if isinstance(translator, str):
  211. translator = self.translator_from_name(translator)
  212. return translator.save(self, **translator_kwargs)
  213. ## @brief Raise an error if lodel is not in EM edition mode
  214. @staticmethod
  215. def raise_if_ro():
  216. if not Settings.editorialmodel.editormode:
  217. raise EditorialModelError(
  218. "Lodel in not in EM editor mode. The EM is in read only state")
  219. ## @brief Load a model
  220. # @param translator module : The translator module to use
  221. # @param **translator_args
  222. @classmethod
  223. def load(cls, translator, **translator_kwargs):
  224. if isinstance(translator, str):
  225. translator = cls.translator_from_name(translator)
  226. res = translator.load(**translator_kwargs)
  227. res.__set_actives()
  228. return res
  229. ## @brief Return a translator module given a translator name
  230. # @param translator_name str : The translator name
  231. # @return the translator python module
  232. # @throw NameError if the translator does not exists
  233. @staticmethod
  234. def translator_from_name(translator_name):
  235. pkg_name = 'lodel.editorial_model.translator.%s' % translator_name
  236. try:
  237. mod = importlib.import_module(pkg_name)
  238. except ImportError:
  239. raise NameError("No translator named %s")
  240. return mod
  241. ## @brief Lodel hash
  242. def d_hash(self):
  243. payload = "%s%s" % (
  244. self.name,
  245. 'NODESC' if self.description is None else self.description.d_hash()
  246. )
  247. for guid in sorted(self.__groups):
  248. payload += str(self.__groups[guid].d_hash())
  249. for cuid in sorted(self.__classes):
  250. payload += str(self.__classes[cuid].d_hash())
  251. return int.from_bytes(
  252. hashlib.md5(bytes(payload, 'utf-8')).digest(),
  253. byteorder='big'
  254. )
  255. ## @brief Returns a list of all datahandlers
  256. # @return a list of all datahandlers
  257. @staticmethod
  258. def list_datahandlers():
  259. return DataHandler.list_data_handlers()