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

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