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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  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, 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': []}
  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:
  181. #Checking the component
  182. em_component.check()
  183. if component_type == 'EmClass':
  184. # !!! If uid is not None it means that we shouldn't create components automatically !!!
  185. self.add_default_class_fields(em_component.uid)
  186. return em_component
  187. ## @brief Add to a class (if not exists) the default fields
  188. #
  189. # @param class_uid int : An EmClass uid
  190. # @throw ValueError if class_uid in not an EmClass uid
  191. def add_default_class_fields(self, class_uid):
  192. if class_uid not in self._components['uids']:
  193. raise ValueError("The uid '%d' don't exists" % class_uid)
  194. emclass = self._components['uids'][class_uid]
  195. if not isinstance(emclass, EditorialModel.classes.EmClass):
  196. raise ValueError("The uid '%d' is not an EmClass uid" % class_uid)
  197. """
  198. fgroup_name = EmClass.default_fieldgroup
  199. if fgroup_name not in [fg.name for fg in emclass.fieldgroups() ]:
  200. #Creating the default fieldgroup if not existing
  201. fg_datas = { 'name' : fgroup_name, 'class_id': emclass.uid }
  202. fgroup = self.create_component('EmFieldGroup', fg_datas)
  203. fgid = fgroup.uid
  204. else:
  205. for fg in emclass.fieldgroups():
  206. if fg.name == fgroup_name:
  207. fgid = fg.uid
  208. break
  209. """
  210. default_fields = emclass.default_fields_list()
  211. for fname, fdatas in default_fields.items():
  212. if not (fname in [f.name for f in emclass.fields()]):
  213. #Adding the field
  214. fdatas['name'] = fname
  215. fdatas['class_id'] = class_uid
  216. self.create_component('EmField', fdatas)
  217. ## Delete a component
  218. # @param uid int : Component identifier
  219. # @throw EmComponentNotExistError
  220. # @todo unable uid check
  221. # @todo Handle a raise from the migration handler
  222. def delete_component(self, uid):
  223. em_component = self.component(uid)
  224. if not em_component:
  225. raise EmComponentNotExistError()
  226. if em_component.delete_check():
  227. #register the deletion in migration handler
  228. self.migration_handler.register_change(self, uid, self.component(uid).attr_dump(), None)
  229. # delete internal lists
  230. self._components[self.name_from_emclass(em_component.__class__)].remove(em_component)
  231. del self._components['uids'][uid]
  232. #Register the new EM state
  233. self.migration_handler.register_model_state(self, hash(self))
  234. return True
  235. return False
  236. ## Changes the current backend
  237. #
  238. # @param backend unknown: A backend object
  239. def set_backend(self, backend):
  240. if issubclass(backend.__class__, EmBackendDummy):
  241. self.backend = backend
  242. else:
  243. raise TypeError('Backend should be an instance of a EmBackednDummy subclass')
  244. ## Returns a list of all the EmClass objects of the model
  245. def classes(self):
  246. return list(self._components[self.name_from_emclass(EmClass)])
  247. ## Use a new migration handler, re-apply all the ME to this handler
  248. #
  249. # @param new_mh MigrationHandler: A migration_handler object
  250. # @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
  251. def migrate_handler(self, new_mh):
  252. new_me = Model(EmBackendDummy(), new_mh)
  253. relations = {'fields_list': [], 'superiors_list': []}
  254. # re-create component one by one, in components_class[] order
  255. for cls in self.components_class:
  256. for component in self.components(cls):
  257. component_type = self.name_from_emclass(cls)
  258. component_dump = component.attr_dump()
  259. # Save relations between component to apply them later
  260. for relation in relations.keys():
  261. if relation in component_dump and component_dump[relation]:
  262. relations[relation].append((component.uid, component_dump[relation]))
  263. del component_dump[relation]
  264. new_me.create_component(component_type, component_dump, component.uid)
  265. # apply selected field to types
  266. for fields_list in relations['fields_list']:
  267. uid, fields = fields_list
  268. for field_id in fields:
  269. new_me.component(uid).select_field(new_me.component(field_id))
  270. # add superiors to types
  271. for superiors_list in relations['superiors_list']:
  272. uid, sup_list = superiors_list
  273. for nature, superiors_uid in sup_list.items():
  274. for superior_uid in superiors_uid:
  275. new_me.component(uid).add_superior(new_me.component(superior_uid), nature)
  276. del new_me
  277. self.migration_handler = new_mh