1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2026-03-21 10:32:02 +01:00
lodel2_mirror/EditorialModel/model.py
ArnAud e5d6caa6cf EmModel : new method migrate_handler()
use it to re-run ME creation in a new Migration Handler
2015-09-18 12:30:59 +02:00

231 lines
10 KiB
Python

#-*- coding: utf-8 -*-
## @file editorialmodel.py
# Manage instance of an editorial model
from EditorialModel.migrationhandler.dummy import DummyMigrationHandler
from EditorialModel.backend.dummy_backend import EmBackendDummy
from EditorialModel.classes import EmClass
from EditorialModel.fieldgroups import EmFieldGroup
from EditorialModel.fields import EmField
from EditorialModel.types import EmType
from EditorialModel.exceptions import EmComponentCheckError, EmComponentNotExistError, MigrationHandlerChangeError
import hashlib
## Manages the Editorial Model
class Model(object):
components_class = [EmClass, EmType, EmFieldGroup, EmField]
## Constructor
#
# @param backend unknown: A backend object instanciated from one of the classes in the backend module
def __init__(self, backend, migration_handler=None):
self.migration_handler = DummyMigrationHandler() if migration_handler is None else migration_handler
self.backend = backend
self._components = {'uids': {}, 'EmClass': [], 'EmType': [], 'EmField': [], 'EmFieldGroup': []}
self.load()
def __hash__(self):
components_dump = ""
for _, comp in self._components['uids'].items():
components_dump += str(hash(comp))
hashstring = hashlib.new('sha512')
hashstring.update(components_dump.encode('utf-8'))
return int(hashstring.hexdigest(), 16)
def __eq__(self, other):
return self.__hash__() == other.__hash__()
@staticmethod
## Given a name return an EmComponent child class
# @param class_name str : The name to identify an EmComponent class
# @return A python class or False if the class_name is not a name of an EmComponent child class
def emclass_from_name(class_name):
for cls in Model.components_class:
if cls.__name__ == class_name:
return cls
return False
@staticmethod
## Given a python class return a name
# @param cls : The python class we want the name
# @return A class name as string or False if cls is not an EmComponent child class
def name_from_emclass(em_class):
if em_class not in Model.components_class:
return False
return em_class.__name__
## Loads the structure of the Editorial Model
#
# Gets all the objects contained in that structure and creates a dict indexed by their uids
# @todo Change the thrown exception when a components check fails
# @throw ValueError When a component class don't exists
def load(self):
datas = self.backend.load()
for uid, kwargs in datas.items():
#Store and delete the EmComponent class name from datas
cls_name = kwargs['component']
del kwargs['component']
cls = self.emclass_from_name(cls_name)
if cls:
kwargs['uid'] = uid
# create a dict for the component and one indexed by uids, store instanciated component in it
self._components['uids'][uid] = cls(self, **kwargs)
self._components[cls_name].append(self._components['uids'][uid])
else:
raise ValueError("Unknow EmComponent class : '" + cls_name + "'")
#Sorting by rank
for component_class in Model.components_class:
self.sort_components(component_class)
#Check integrity
for uid, component in self._components['uids'].items():
try:
component.check()
except EmComponentCheckError as exception_object:
raise EmComponentCheckError("The component with uid %d is not valid. Check returns the following error : \"%s\"" % (uid, str(exception_object)))
#Everything is done. Indicating that the component initialisation is over
component.init_ended()
## Saves data using the current backend
def save(self):
return self.backend.save(self)
## Given a EmComponent child class return a list of instances
# @param cls EmComponent : A python class
# @return a list of instances or False if the class is not an EmComponent child
def components(self, cls):
key_name = self.name_from_emclass(cls)
return False if key_name is False else self._components[key_name]
## Return an EmComponent given an uid
# @param uid int : An EmComponent uid
# @return The corresponding instance or False if uid don't exists
def component(self, uid):
return False if uid not in self._components['uids'] else self._components['uids'][uid]
## Sort components by rank in Model::_components
# @param emclass pythonClass : The type of components to sort
# @throw AttributeError if emclass is not valid
def sort_components(self, component_class):
if component_class not in self.components_class:
raise AttributeError("Bad argument emclass : '" + component_class + "', excpeting one of " + str(self.components_class))
self._components[self.name_from_emclass(component_class)] = sorted(self.components(component_class), key=lambda comp: comp.rank)
## Return a new uid
# @return a new uid
def new_uid(self):
used_uid = [int(uid) for uid in self._components['uids'].keys()]
return sorted(used_uid)[-1] + 1 if len(used_uid) > 0 else 1
## Create a component from a component type and datas
#
# @note if datas does not contains a rank the new component will be added last
# @note datas['rank'] can be an integer or two specials strings 'last' or 'first'
# @param component_type str : a component type ( component_class, component_fieldgroup, component_field or component_type )
# @param datas dict : the options needed by the component creation
# @throw ValueError if datas['rank'] is not valid (too big or too small, not an integer nor 'last' or 'first' )
# @todo Handle a raise from the migration handler
def create_component(self, component_type, datas, uid=None):
em_obj = self.emclass_from_name(component_type)
rank = 'last'
if 'rank' in datas:
rank = datas['rank']
del datas['rank']
datas['uid'] = uid if uid else self.new_uid()
em_component = em_obj(self, **datas)
em_component.rank = em_component.get_max_rank() + 1 # Inserting last by default
self._components['uids'][em_component.uid] = em_component
self._components[component_type].append(em_component)
if rank != 'last':
em_component.set_rank(1 if rank == 'first' else rank)
#everything done, indicating that initialisation is over
em_component.init_ended()
#register the creation in migration handler
try:
self.migration_handler.register_change(self, em_component.uid, None, em_component.attr_dump)
except MigrationHandlerChangeError as exception_object:
#Revert the creation
self.components(em_component.__class__).remove(em_component)
del self._components['uids'][em_component.uid]
raise exception_object
self.migration_handler.register_model_state(self, hash(self))
return em_component
## Delete a component
# @param uid int : Component identifier
# @throw EmComponentNotExistError
# @todo unable uid check
# @todo Handle a raise from the migration handler
def delete_component(self, uid):
#register the deletion in migration handler
self.migration_handler.register_change(self, uid, self.component(uid).attr_dump, None)
em_component = self.component(uid)
if not em_component:
raise EmComponentNotExistError()
if em_component.delete_check():
self._components[self.name_from_emclass(em_component.__class__)].remove(em_component)
del self._components['uids'][uid]
#Register the new EM state
self.migration_handler.register_model_state(self, hash(self))
return True
## Changes the current backend
#
# @param backend unknown: A backend object
def set_backend(self, backend):
self.backend = backend
## Returns a list of all the EmClass objects of the model
def classes(self):
return list(self._components[self.name_from_emclass(EmClass)])
## Use a new migration handler, re-apply all the ME to this handler
#
# @param new_mh MigrationHandler: A migration_handler object
# @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
def migrate_handler(self, new_mh):
new_me = Model(EmBackendDummy(), new_mh)
relations = {'fields_list': [], 'subordinates_list': []}
# re-create component one by one, in components_class[] order
for cls in self.components_class:
for component in self.components(cls):
component_type = self.name_from_emclass(cls)
component_dump = component.attr_dump
del component_dump['model'], component_dump['_inited']
# Save relations between component to apply them later
for relation in relations.keys():
if relation in component_dump and component_dump[relation]:
relations[relation].append((component.uid, component_dump[relation]))
del component_dump[relation]
new_me.create_component(component_type, component_dump, component.uid)
# apply selected field to types
for fields_list in relations['fields_list']:
uid, fields = fields_list
for field_id in fields:
new_me.component(uid).select_field(new_me.component(field_id))
# add superiors to types
for subordinates_list in relations['subordinates_list']:
uid, sub_list = subordinates_list
for nature, subordinates in sub_list.items():
for subordinate_id in subordinates:
new_me.component(subordinate_id).add_superior(new_me.component(uid), nature)
self.migration_handler = new_mh