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

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