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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. #-*- coding: utf-8 -*-
  2. ## @package EditorialModel.model
  3. # Contains the class managing and editorial model
  4. import EditorialModel
  5. from EditorialModel.migrationhandler.dummy import DummyMigrationHandler
  6. from EditorialModel.backend.dummy_backend import EmBackendDummy
  7. from EditorialModel.classes import EmClass
  8. from EditorialModel.fieldgroups import EmFieldGroup
  9. from EditorialModel.fields import EmField
  10. from EditorialModel.types import EmType
  11. from EditorialModel.exceptions import EmComponentCheckError, EmComponentNotExistError, MigrationHandlerChangeError
  12. import hashlib
  13. ## @brief Manages the Editorial Model
  14. class Model(object):
  15. components_class = [EmClass, EmType, EmFieldGroup, EmField]
  16. ## Constructor
  17. #
  18. # @param backend unknown: A backend object instanciated from one of the classes in the backend module
  19. # @param migration_handler : A migration handler
  20. def __init__(self, backend, migration_handler=None):
  21. if migration_handler is None:
  22. self.migration_handler = DummyMigrationHandler()
  23. elif issubclass(migration_handler.__class__, DummyMigrationHandler):
  24. self.migration_handler = migration_handler
  25. else:
  26. raise TypeError("migration_handler should be an instance from a subclass of DummyMigrationhandler")
  27. self.backend = None
  28. self.set_backend(backend)
  29. self._components = {'uids': {}, 'EmClass': [], 'EmType': [], 'EmField': [], 'EmFieldGroup': []}
  30. self.load()
  31. def __hash__(self):
  32. components_dump = ""
  33. for _, comp in self._components['uids'].items():
  34. components_dump += str(hash(comp))
  35. hashstring = hashlib.new('sha512')
  36. hashstring.update(components_dump.encode('utf-8'))
  37. return int(hashstring.hexdigest(), 16)
  38. def __eq__(self, other):
  39. return self.__hash__() == other.__hash__()
  40. @staticmethod
  41. ## Given a name return an EmComponent child class
  42. # @param class_name str : The name to identify an EmComponent class
  43. # @return A python class or False if the class_name is not a name of an EmComponent child class
  44. def emclass_from_name(class_name):
  45. for cls in Model.components_class:
  46. if cls.__name__ == class_name:
  47. return cls
  48. return False
  49. @staticmethod
  50. ## Given a python class return a name
  51. # @param em_class : The python class we want the name
  52. # @return A class name as string or False if cls is not an EmComponent child class
  53. def name_from_emclass(em_class):
  54. if em_class not in Model.components_class:
  55. return False
  56. return em_class.__name__
  57. ## Loads the structure of the Editorial Model
  58. #
  59. # Gets all the objects contained in that structure and creates a dict indexed by their uids
  60. # @todo Change the thrown exception when a components check fails
  61. # @throw ValueError When a component class don't exists
  62. def load(self):
  63. datas = self.backend.load()
  64. for uid, kwargs in datas.items():
  65. #Store and delete the EmComponent class name from datas
  66. cls_name = kwargs['component']
  67. del kwargs['component']
  68. cls = self.emclass_from_name(cls_name)
  69. if cls:
  70. kwargs['uid'] = uid
  71. # create a dict for the component and one indexed by uids, store instanciated component in it
  72. self._components['uids'][uid] = cls(model=self, **kwargs)
  73. self._components[cls_name].append(self._components['uids'][uid])
  74. else:
  75. raise ValueError("Unknow EmComponent class : '" + cls_name + "'")
  76. #Sorting by rank
  77. for component_class in Model.components_class:
  78. self.sort_components(component_class)
  79. #Check integrity
  80. loaded_comps = [(uid, component) for uid, component in self._components['uids'].items()]
  81. for uid, component in loaded_comps:
  82. try:
  83. component.check()
  84. except EmComponentCheckError as exception_object:
  85. raise EmComponentCheckError("The component with uid %d is not valid. Check returns the following error : \"%s\"" % (uid, str(exception_object)))
  86. #Everything is done. Indicating that the component initialisation is over
  87. component.init_ended()
  88. ## Saves data using the current backend
  89. # @param filename str | None : if None use the current backend file (provided at backend instanciation)
  90. def save(self, filename = None):
  91. return self.backend.save(self, filename)
  92. ## Given a EmComponent child class return a list of instances
  93. # @param cls EmComponent|str : A python class
  94. # @return a list of instances or False if the class is not an EmComponent child
  95. # @todo better implementation
  96. def components(self, cls=None):
  97. if isinstance(cls, str):
  98. cls = self.emclass_from_name(cls)
  99. if not cls:
  100. return False
  101. if cls is None:
  102. return [ self.component(uid) for uid in self._components['uids'] ]
  103. key_name = self.name_from_emclass(cls)
  104. return False if key_name is False else self._components[key_name]
  105. ## Return an EmComponent given an uid
  106. # @param uid int : An EmComponent uid
  107. # @return The corresponding instance or False if uid don't exists
  108. def component(self, uid):
  109. return False if uid not in self._components['uids'] else self._components['uids'][uid]
  110. ## @brief Search in all the editorial model for a component with a specific name
  111. # @param name str : the searched name
  112. # @param comp_cls str|EmComponent : filter on component type (see components() method)
  113. # @return a list of component with a specific name
  114. def component_from_name(self, name, comp_cls = None):
  115. if comp_cls == EmField or comp_cls == 'EmField':
  116. res = list()
  117. for field, fieldname in [ (f, f.name) for f in self.components('EmField')]:
  118. if fieldname == name:
  119. res.append(field)
  120. return res
  121. for comp,compname in [ (c, c.name) for c in self.components(comp_cls)]:
  122. if compname == name:
  123. return comp
  124. return False
  125. ## Sort components by rank in Model::_components
  126. # @param component_class pythonClass : The type of components to sort
  127. # @throw AttributeError if emclass is not valid
  128. # @warning disabled the test on component_class because of EmField new way of working
  129. def sort_components(self, component_class):
  130. #if component_class not in self.components_class:
  131. # raise AttributeError("Bad argument emclass : '" + str(component_class) + "', excpeting one of " + str(self.components_class))
  132. self._components[self.name_from_emclass(component_class)] = sorted(self.components(component_class), key=lambda comp: comp.rank)
  133. ## Return a new uid
  134. # @return a new uid
  135. def new_uid(self):
  136. used_uid = [int(uid) for uid in self._components['uids'].keys()]
  137. return sorted(used_uid)[-1] + 1 if len(used_uid) > 0 else 1
  138. ## Create a component from a component type and datas
  139. #
  140. # @note if datas does not contains a rank the new component will be added last
  141. # @note datas['rank'] can be an integer or two specials strings 'last' or 'first'
  142. #
  143. # @warning The uid parameter is designed to be used only by Model.load()
  144. # @param uid int|None : If given, don't generate a new uid
  145. # @param component_type str : a component type ( component_class, component_fieldgroup, component_field or component_type )
  146. # @param datas dict : the options needed by the component creation
  147. # @return The created EmComponent
  148. # @throw ValueError if datas['rank'] is not valid (too big or too small, not an integer nor 'last' or 'first' )
  149. # @todo Handle a raise from the migration handler
  150. # @todo Transform the datas arg in **datas ?
  151. def create_component(self, component_type, datas, uid=None):
  152. if not (uid is None) and (not isinstance(uid, int) or uid <= 0 or uid in self._components['uids']):
  153. raise ValueError("Invalid uid provided : %s"%repr(uid))
  154. if component_type not in [ n for n in self._components.keys() if n != 'uids' ]:
  155. raise ValueError("Invalid component_type rpovided")
  156. else:
  157. em_obj = self.emclass_from_name(component_type)
  158. rank = 'last'
  159. if 'rank' in datas:
  160. rank = datas['rank']
  161. del datas['rank']
  162. datas['uid'] = uid if uid else self.new_uid()
  163. em_component = em_obj(model=self, **datas)
  164. em_component.rank = em_component.get_max_rank() + 1 # Inserting last by default
  165. self._components['uids'][em_component.uid] = em_component
  166. self._components[component_type].append(em_component)
  167. if rank != 'last':
  168. em_component.set_rank(1 if rank == 'first' else rank)
  169. #everything done, indicating that initialisation is over
  170. em_component.init_ended()
  171. #register the creation in migration handler
  172. try:
  173. self.migration_handler.register_change(self, em_component.uid, None, em_component.attr_dump())
  174. except MigrationHandlerChangeError as exception_object:
  175. #Revert the creation
  176. self.components(em_component.__class__).remove(em_component)
  177. del self._components['uids'][em_component.uid]
  178. raise exception_object
  179. self.migration_handler.register_model_state(self, hash(self))
  180. if uid is None and component_type == 'EmClass':
  181. # !!! If uid is not None it means that we shouldn't create components automatically !!!
  182. self.add_default_class_fields(em_component.uid)
  183. #Checking the component
  184. em_component.check()
  185. return em_component
  186. ## @brief Add to a class (if not exists) the default fields
  187. #
  188. # @param class_uid int : An EmClass uid
  189. # @throw ValueError if class_uid in not an EmClass uid
  190. def add_default_class_fields(self, class_uid):
  191. if class_uid not in self._components['uids']:
  192. raise ValueError("The uid '%d' don't exists"%class_uid)
  193. emclass = self._components['uids'][class_uid]
  194. if not isinstance(emclass, EditorialModel.classes.EmClass):
  195. raise ValueError("The uid '%d' is not an EmClass uid"%class_uid)
  196. fgroup_name = EmClass.default_fieldgroup
  197. if fgroup_name not in [fg.name for fg in emclass.fieldgroups() ]:
  198. #Creating the default fieldgroup if not existing
  199. fg_datas = { 'name' : fgroup_name, 'class_id': emclass.uid }
  200. fgroup = self.create_component('EmFieldGroup', fg_datas)
  201. fgid = fgroup.uid
  202. else:
  203. for fg in emclass.fieldgroups():
  204. if fg.name == fgroup_name:
  205. fgid = fg.uid
  206. break
  207. default_fields = emclass.default_fields_list()
  208. for fname, fdatas in default_fields.items():
  209. if not (fname in [ f.name for f in emclass.fields() ]):
  210. #Adding the field
  211. fdatas['name'] = fname
  212. fdatas['fieldgroup_id'] = fgid
  213. self.create_component('EmField', fdatas)
  214. pass
  215. ## Delete a component
  216. # @param uid int : Component identifier
  217. # @throw EmComponentNotExistError
  218. # @todo unable uid check
  219. # @todo Handle a raise from the migration handler
  220. def delete_component(self, uid):
  221. em_component = self.component(uid)
  222. if not em_component:
  223. raise EmComponentNotExistError()
  224. if em_component.delete_check():
  225. #register the deletion in migration handler
  226. self.migration_handler.register_change(self, uid, self.component(uid).attr_dump(), None)
  227. # delete internal lists
  228. self._components[self.name_from_emclass(em_component.__class__)].remove(em_component)
  229. del self._components['uids'][uid]
  230. #Register the new EM state
  231. self.migration_handler.register_model_state(self, hash(self))
  232. return True
  233. return False
  234. ## Changes the current backend
  235. #
  236. # @param backend unknown: A backend object
  237. def set_backend(self, backend):
  238. if issubclass(backend.__class__, EmBackendDummy):
  239. self.backend = backend
  240. else:
  241. raise TypeError('Backend should be an instance of a EmBackednDummy subclass')
  242. ## Returns a list of all the EmClass objects of the model
  243. def classes(self):
  244. return list(self._components[self.name_from_emclass(EmClass)])
  245. ## Use a new migration handler, re-apply all the ME to this handler
  246. #
  247. # @param new_mh MigrationHandler: A migration_handler object
  248. # @warning : if a relational-attribute field (with 'rel_field_id') comes before it's relational field (with 'rel_to_type_id'), this will blow up
  249. def migrate_handler(self, new_mh):
  250. new_me = Model(EmBackendDummy(), new_mh)
  251. relations = {'fields_list': [], 'superiors_list': []}
  252. # re-create component one by one, in components_class[] order
  253. for cls in self.components_class:
  254. for component in self.components(cls):
  255. component_type = self.name_from_emclass(cls)
  256. component_dump = component.attr_dump()
  257. # Save relations between component to apply them later
  258. for relation in relations.keys():
  259. if relation in component_dump and component_dump[relation]:
  260. relations[relation].append((component.uid, component_dump[relation]))
  261. del component_dump[relation]
  262. new_me.create_component(component_type, component_dump, component.uid)
  263. # apply selected field to types
  264. for fields_list in relations['fields_list']:
  265. uid, fields = fields_list
  266. for field_id in fields:
  267. new_me.component(uid).select_field(new_me.component(field_id))
  268. # add superiors to types
  269. for superiors_list in relations['superiors_list']:
  270. uid, sup_list = superiors_list
  271. for nature, superiors_uid in sup_list.items():
  272. for superior_uid in superiors_uid:
  273. new_me.component(uid).add_superior(new_me.component(superior_uid), nature)
  274. del new_me
  275. self.migration_handler = new_mh