1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2025-11-25 23:06:55 +01:00
lodel2_mirror/EditorialModel/model.py

330 lines
14 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#-*- coding: utf-8 -*-
## @package EditorialModel.model
# Contains the class managing and editorial model
import EditorialModel
from DataSource.dummy.migrationhandler import DummyMigrationHandler
from EditorialModel.backend.dummy_backend import EmBackendDummy
from EditorialModel.classes import EmClass
from EditorialModel.fields import EmField
from EditorialModel.types import EmType
from EditorialModel.exceptions import EmComponentCheckError, EmComponentNotExistError, MigrationHandlerChangeError
import hashlib
## @brief Manages the Editorial Model
class Model(object):
components_class = [EmClass, EmType, EmField]
## Constructor
#
# @param backend unknown: A backend object instanciated from one of the classes in the backend module
# @param migration_handler : A migration handler
def __init__(self, backend, migration_handler=None):
if migration_handler is None:
self.migration_handler = DummyMigrationHandler()
elif issubclass(migration_handler.__class__, DummyMigrationHandler):
self.migration_handler = migration_handler
else:
raise TypeError("migration_handler should be an instance from a subclass of DummyMigrationhandler")
self.backend = None
self.set_backend(backend)
self._components = {'uids': {}, 'EmClass': [], 'EmType': [], 'EmField': []}
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 em_class : 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(model=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
loaded_comps = [(uid, component) for uid, component in self._components['uids'].items()]
for uid, component in loaded_comps:
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
# @param filename str | None : if None use the current backend file (provided at backend instanciation)
def save(self, filename=None):
return self.backend.save(self, filename)
## Given a EmComponent child class return a list of instances
# @param cls EmComponent|str : A python class
# @return a list of instances or False if the class is not an EmComponent child
# @todo better implementation
def components(self, cls=None):
if isinstance(cls, str):
cls = self.emclass_from_name(cls)
if not cls:
return False
if cls is None:
return [self.component(uid) for uid in self._components['uids']]
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]
## @brief Search in all the editorial model for a component with a specific name
# @param name str : the searched name
# @param comp_cls str|EmComponent : filter on component type (see components() method)
# @return a list of component with a specific name
def component_from_name(self, name, comp_cls=None):
if comp_cls == EmField or comp_cls == 'EmField':
res = list()
for field, fieldname in [(f, f.name) for f in self.components('EmField')]:
if fieldname == name:
res.append(field)
return res
for comp, compname in [(c, c.name) for c in self.components(comp_cls)]:
if compname == name:
return comp
return False
## Sort components by rank in Model::_components
# @param component_class pythonClass : The type of components to sort
# @throw AttributeError if emclass is not valid
# @warning disabled the test on component_class because of EmField new way of working
def sort_components(self, component_class):
#if component_class not in self.components_class:
# raise AttributeError("Bad argument emclass : '" + str(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'
#
# @warning The uid parameter is designed to be used only by Model.load()
# @param uid int|None : If given, don't generate a new uid
# @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
# @return The created EmComponent
# @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
# @todo Transform the datas arg in **datas ?
def create_component(self, component_type, datas, uid=None):
if not (uid is None) and (not isinstance(uid, int) or uid <= 0 or uid in self._components['uids']):
raise ValueError("Invalid uid provided : %s" % repr(uid))
if component_type not in [n for n in self._components.keys() if n != 'uids']:
raise ValueError("Invalid component_type rpovided")
else:
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(model=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))
if uid is None:
#Checking the component
em_component.check()
if component_type == 'EmClass':
# !!! If uid is not None it means that we shouldn't create components automatically !!!
self.add_default_class_fields(em_component.uid)
return em_component
## @brief Add to a class (if not exists) the default fields
#
# @param class_uid int : An EmClass uid
# @throw ValueError if class_uid in not an EmClass uid
def add_default_class_fields(self, class_uid):
if class_uid not in self._components['uids']:
raise ValueError("The uid '%d' don't exists" % class_uid)
emclass = self._components['uids'][class_uid]
if not isinstance(emclass, EditorialModel.classes.EmClass):
raise ValueError("The uid '%d' is not an EmClass uid" % class_uid)
"""
fgroup_name = EmClass.default_fieldgroup
if fgroup_name not in [fg.name for fg in emclass.fieldgroups() ]:
#Creating the default fieldgroup if not existing
fg_datas = { 'name' : fgroup_name, 'class_id': emclass.uid }
fgroup = self.create_component('EmFieldGroup', fg_datas)
fgid = fgroup.uid
else:
for fg in emclass.fieldgroups():
if fg.name == fgroup_name:
fgid = fg.uid
break
"""
default_fields = emclass.default_fields_list()
for fname, fdatas in default_fields.items():
if not (fname in [f.name for f in emclass.fields()]):
#Adding the field
fdatas['name'] = fname
fdatas['class_id'] = class_uid
self.create_component('EmField', fdatas)
## 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):
em_component = self.component(uid)
if not em_component:
raise EmComponentNotExistError()
if em_component.delete_check():
#register the deletion in migration handler
self.migration_handler.register_change(self, uid, self.component(uid).attr_dump(), None)
# delete internal lists
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
return False
## Changes the current backend
#
# @param backend unknown: A backend object
def set_backend(self, backend):
if issubclass(backend.__class__, EmBackendDummy):
self.backend = backend
else:
raise TypeError('Backend should be an instance of a EmBackednDummy subclass')
## 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': [], 'superiors_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()
# 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 superiors_list in relations['superiors_list']:
uid, sup_list = superiors_list
for nature, superiors_uid in sup_list.items():
for superior_uid in superiors_uid:
new_me.component(uid).add_superior(new_me.component(superior_uid), nature)
del new_me
self.migration_handler = new_mh