暫無描述
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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. #-*- coding: utf-8 -*-
  2. ## @file editorialmodel.py
  3. # Manage instance of an 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. ## 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. def __init__(self, backend, migration_handler=None):
  20. self.migration_handler = DummyMigrationHandler() if migration_handler is None else migration_handler
  21. self.backend = backend
  22. self._components = {'uids': {}, 'EmClass': [], 'EmType': [], 'EmField': [], 'EmFieldGroup': []}
  23. self.load()
  24. def __hash__(self):
  25. components_dump = ""
  26. for _, comp in self._components['uids'].items():
  27. components_dump += str(hash(comp))
  28. hashstring = hashlib.new('sha512')
  29. hashstring.update(components_dump.encode('utf-8'))
  30. return int(hashstring.hexdigest(), 16)
  31. def __eq__(self, other):
  32. return self.__hash__() == other.__hash__()
  33. @staticmethod
  34. ## Given a name return an EmComponent child class
  35. # @param class_name str : The name to identify an EmComponent class
  36. # @return A python class or False if the class_name is not a name of an EmComponent child class
  37. def emclass_from_name(class_name):
  38. for cls in Model.components_class:
  39. if cls.__name__ == class_name:
  40. return cls
  41. return False
  42. @staticmethod
  43. ## Given a python class return a name
  44. # @param cls : The python class we want the name
  45. # @return A class name as string or False if cls is not an EmComponent child class
  46. # @todo réécrire le split, c'est pas bô
  47. def name_from_emclass(em_class):
  48. if em_class not in Model.components_class:
  49. spl = em_class.__module__.split('.')
  50. if spl[1] == 'fieldtypes':
  51. return 'EmField'
  52. return False
  53. return em_class.__name__
  54. ## Loads the structure of the Editorial Model
  55. #
  56. # Gets all the objects contained in that structure and creates a dict indexed by their uids
  57. # @todo Change the thrown exception when a components check fails
  58. # @throw ValueError When a component class don't exists
  59. def load(self):
  60. datas = self.backend.load()
  61. for uid, kwargs in datas.items():
  62. #Store and delete the EmComponent class name from datas
  63. cls_name = kwargs['component']
  64. del kwargs['component']
  65. if cls_name == 'EmField':
  66. #Special EmField process because of fieldtypes
  67. if not 'fieldtype' in kwargs:
  68. raise AttributeError("Missing 'fieldtype' from EmField instanciation")
  69. cls = EditorialModel.fields.EmField.get_field_class(kwargs['fieldtype'])
  70. else:
  71. cls = self.emclass_from_name(cls_name)
  72. if cls:
  73. kwargs['uid'] = uid
  74. # create a dict for the component and one indexed by uids, store instanciated component in it
  75. self._components['uids'][uid] = cls(model=self, **kwargs)
  76. self._components[cls_name].append(self._components['uids'][uid])
  77. else:
  78. raise ValueError("Unknow EmComponent class : '" + cls_name + "'")
  79. #Sorting by rank
  80. for component_class in Model.components_class:
  81. self.sort_components(component_class)
  82. #Check integrity
  83. for uid, component in self._components['uids'].items():
  84. try:
  85. component.check()
  86. except EmComponentCheckError as exception_object:
  87. raise EmComponentCheckError("The component with uid %d is not valid. Check returns the following error : \"%s\"" % (uid, str(exception_object)))
  88. #Everything is done. Indicating that the component initialisation is over
  89. component.init_ended()
  90. ## Saves data using the current backend
  91. # @param filename str | None : if None use the current backend file (provided at backend instanciation)
  92. def save(self, filename = None):
  93. return self.backend.save(self, filename)
  94. ## Given a EmComponent child class return a list of instances
  95. # @param cls EmComponent : A python class
  96. # @return a list of instances or False if the class is not an EmComponent child
  97. def components(self, cls=None):
  98. if cls is None:
  99. return [ self.component(uid) for uid in self._components['uids'] ]
  100. key_name = self.name_from_emclass(cls)
  101. return False if key_name is False else self._components[key_name]
  102. ## Return an EmComponent given an uid
  103. # @param uid int : An EmComponent uid
  104. # @return The corresponding instance or False if uid don't exists
  105. def component(self, uid):
  106. return False if uid not in self._components['uids'] else self._components['uids'][uid]
  107. ## Sort components by rank in Model::_components
  108. # @param emclass pythonClass : The type of components to sort
  109. # @throw AttributeError if emclass is not valid
  110. # @warning disabled the test on component_class because of EmField new way of working
  111. def sort_components(self, component_class):
  112. #if component_class not in self.components_class:
  113. # raise AttributeError("Bad argument emclass : '" + str(component_class) + "', excpeting one of " + str(self.components_class))
  114. self._components[self.name_from_emclass(component_class)] = sorted(self.components(component_class), key=lambda comp: comp.rank)
  115. ## Return a new uid
  116. # @return a new uid
  117. def new_uid(self):
  118. used_uid = [int(uid) for uid in self._components['uids'].keys()]
  119. return sorted(used_uid)[-1] + 1 if len(used_uid) > 0 else 1
  120. ## Create a component from a component type and datas
  121. #
  122. # @note if datas does not contains a rank the new component will be added last
  123. # @note datas['rank'] can be an integer or two specials strings 'last' or 'first'
  124. # @param component_type str : a component type ( component_class, component_fieldgroup, component_field or component_type )
  125. # @param datas dict : the options needed by the component creation
  126. # @throw ValueError if datas['rank'] is not valid (too big or too small, not an integer nor 'last' or 'first' )
  127. # @todo Handle a raise from the migration handler
  128. # @todo Transform the datas arg in **datas ?
  129. def create_component(self, component_type, datas, uid=None):
  130. if component_type == 'EmField':
  131. #special process for EmField
  132. if not 'fieldtype' in datas:
  133. raise AttributeError("Missing 'fieldtype' from EmField instanciation")
  134. em_obj = EditorialModel.fields.EmField.get_field_class(datas['fieldtype'])
  135. else:
  136. em_obj = self.emclass_from_name(component_type)
  137. rank = 'last'
  138. if 'rank' in datas:
  139. rank = datas['rank']
  140. del datas['rank']
  141. datas['uid'] = uid if uid else self.new_uid()
  142. em_component = em_obj(model=self, **datas)
  143. em_component.rank = em_component.get_max_rank() + 1 # Inserting last by default
  144. self._components['uids'][em_component.uid] = em_component
  145. self._components[component_type].append(em_component)
  146. if rank != 'last':
  147. em_component.set_rank(1 if rank == 'first' else rank)
  148. #everything done, indicating that initialisation is over
  149. em_component.init_ended()
  150. #register the creation in migration handler
  151. try:
  152. self.migration_handler.register_change(self, em_component.uid, None, em_component.attr_dump)
  153. except MigrationHandlerChangeError as exception_object:
  154. #Revert the creation
  155. self.components(em_component.__class__).remove(em_component)
  156. del self._components['uids'][em_component.uid]
  157. raise exception_object
  158. self.migration_handler.register_model_state(self, hash(self))
  159. return em_component
  160. ## Delete a component
  161. # @param uid int : Component identifier
  162. # @throw EmComponentNotExistError
  163. # @todo unable uid check
  164. # @todo Handle a raise from the migration handler
  165. def delete_component(self, uid):
  166. #register the deletion in migration handler
  167. self.migration_handler.register_change(self, uid, self.component(uid).attr_dump, None)
  168. em_component = self.component(uid)
  169. if not em_component:
  170. raise EmComponentNotExistError()
  171. if em_component.delete_check():
  172. self._components[self.name_from_emclass(em_component.__class__)].remove(em_component)
  173. del self._components['uids'][uid]
  174. #Register the new EM state
  175. self.migration_handler.register_model_state(self, hash(self))
  176. return True
  177. ## Changes the current backend
  178. #
  179. # @param backend unknown: A backend object
  180. def set_backend(self, backend):
  181. self.backend = backend
  182. ## Returns a list of all the EmClass objects of the model
  183. def classes(self):
  184. return list(self._components[self.name_from_emclass(EmClass)])
  185. ## Use a new migration handler, re-apply all the ME to this handler
  186. #
  187. # @param new_mh MigrationHandler: A migration_handler object
  188. # @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
  189. def migrate_handler(self, new_mh):
  190. new_me = Model(EmBackendDummy(), new_mh)
  191. relations = {'fields_list': [], 'superiors_list': []}
  192. # re-create component one by one, in components_class[] order
  193. for cls in self.components_class:
  194. for component in self.components(cls):
  195. component_type = self.name_from_emclass(cls)
  196. component_dump = component.attr_dump
  197. # Save relations between component to apply them later
  198. for relation in relations.keys():
  199. if relation in component_dump and component_dump[relation]:
  200. relations[relation].append((component.uid, component_dump[relation]))
  201. del component_dump[relation]
  202. new_me.create_component(component_type, component_dump, component.uid)
  203. # apply selected field to types
  204. for fields_list in relations['fields_list']:
  205. uid, fields = fields_list
  206. for field_id in fields:
  207. new_me.component(uid).select_field(new_me.component(field_id))
  208. # add superiors to types
  209. # TODO: debug, this add a superior to all types !
  210. for superiors_list in relations['superiors_list']:
  211. uid, sup_list = superiors_list
  212. for nature, superior_uid in sup_list.items():
  213. new_me.component(uid).add_superior(new_me.component(superior_uid), nature)
  214. self.migration_handler = new_mh