Нет описания
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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