1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2026-03-17 08:42:01 +01:00

Merge branch 'migration_handler':

Add the possibility to change the migartion handler of a Model
It will send changes one by one to the new migartion handler
so it can re-create the Editorial Model
This commit is contained in:
ArnAud 2015-09-18 12:31:57 +02:00
commit 6388eb6701
4 changed files with 165 additions and 131 deletions

View file

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
## @package EditorialModel.backend.dummy_backend
# @brief Gives an empty backend
#
# Allows an editorial model to use an empty backend
# Mostly for migration and test purpose
## Manages a Json file based backend structure
class EmBackendDummy(object):
def load(self):
data = {}
return data
def save(self):
return True

View file

@ -4,6 +4,7 @@
# 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
@ -15,7 +16,7 @@ import hashlib
## Manages the Editorial Model
class Model(object):
components_class = [EmClass, EmField, EmFieldGroup, EmType]
components_class = [EmClass, EmType, EmFieldGroup, EmField]
## Constructor
#
@ -129,7 +130,7 @@ class Model(object):
# @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):
def create_component(self, component_type, datas, uid=None):
em_obj = self.emclass_from_name(component_type)
@ -138,13 +139,13 @@ class Model(object):
rank = datas['rank']
del datas['rank']
datas['uid'] = self.new_uid()
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
em_component.rank = em_component.get_max_rank() + 1 # Inserting last by default
self._components['uids'][em_component.uid] = em_component
self._components[self.name_from_emclass(em_component.__class__)].append(em_component)
self._components[component_type].append(em_component)
if rank != 'last':
em_component.set_rank(1 if rank == 'first' else rank)
@ -193,3 +194,39 @@ class Model(object):
## 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
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
# TODO: debug, this add a superior to all types !
for superiors_list in relations['superiors_list']:
uid, sup_list = superiors_list
for nature, superior_uid in sup_list.items():
new_me.component(uid).add_superior(new_me.component(superior_uid), nature)
self.migration_handler = new_mh

View file

@ -12,7 +12,7 @@
"component":"EmField", "name":"titre", "string":"{\"fre\":\"Titre\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "fieldtype":"varchar", "fieldgroup_id":"3", "rel_to_type_id":"", "rel_field_id":"", "optional":0, "internal":"", "icon":"0"
},
"5" : {
"component":"EmType", "name":"article", "string":"{\"fre\":\"Article\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "class_id":"1", "icon":"0", "sortcolumn":"rank", "fields_list":[7]
"component":"EmType", "name":"article", "string":"{\"fre\":\"Article\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "class_id":"1", "icon":"0", "sortcolumn":"rank", "fields_list":[7], "superiors_list":{"parent":14}
},
"6" : {
"component":"EmType", "name":"personne", "string":"{\"fre\":\"Personne\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "class_id":"2", "icon":"0", "sortcolumn":"rank", "fields_list":[10]
@ -30,16 +30,16 @@
"component":"EmField", "name":"prenom", "string":"{\"fre\":\"Preom\"}", "help_text":"{}", "rank":"2", "date_update":"", "date_create":"", "fieldtype":"varchar", "fieldgroup_id":"8", "rel_to_type_id":"", "rel_field_id":"", "optional":1, "internal":"", "icon":"0"
},
"11" : {
"component":"EmField", "name":"auteur", "string":"{\"fre\":\"Auteur\"}", "help_text":"{}", "rank":"3", "date_update":"", "date_create":"", "fieldtype":"varchar", "fieldgroup_id":"17", "rel_to_type_id":"6", "rel_field_id":"", "optional":1, "internal":"", "icon":"0"
"component":"EmField", "name":"auteur", "string":"{\"fre\":\"Auteur\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "fieldtype":"varchar", "fieldgroup_id":"17", "rel_to_type_id":"6", "rel_field_id":"", "optional":1, "internal":"", "icon":"0"
},
"12" : {
"component":"EmField", "name":"adresse", "string":"{\"fre\":\"Adresse\"}", "help_text":"{}", "rank":"4", "date_update":"", "date_create":"", "fieldtype":"varchar", "fieldgroup_id":"17", "rel_to_type_id":"", "rel_field_id":"11", "optional":0, "internal":"", "icon":"0"
"component":"EmField", "name":"adresse", "string":"{\"fre\":\"Adresse\"}", "help_text":"{}", "rank":"2", "date_update":"", "date_create":"", "fieldtype":"varchar", "fieldgroup_id":"17", "rel_to_type_id":"", "rel_field_id":"11", "optional":0, "internal":"", "icon":"0"
},
"13" : {
"component":"EmClass", "name":"publication", "string":"{\"fre\":\"Publication\"}", "help_text":"{}", "rank":"2", "date_update":"", "date_create":"", "classtype":"entity", "icon":"0", "sortcolumn":"rank"
},
"14" : {
"component":"EmType", "name":"rubrique", "string":"{\"fre\":\"Rubrique\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "class_id":"13", "icon":"0", "sortcolumn":"rank", "subordinates_list":{"parent":[5,14]}
"component":"EmType", "name":"rubrique", "string":"{\"fre\":\"Rubrique\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "class_id":"13", "icon":"0", "sortcolumn":"rank", "superiors_list":{"parent":14}
},
"15" : {
"component":"EmFieldGroup", "name":"info", "string":"{\"fre\":\"Info\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "class_id":"13"

View file

@ -25,7 +25,7 @@ class EmType(EmComponent):
## Instanciate a new EmType
# @todo define and check types for icon and sortcolumn
# @todo better check self.subordinates
def __init__(self, model, uid, name, class_id, fields_list = [], subordinates_list = {}, icon = '0', sortcolumn = 'rank', string = None, help_text = None, date_update = None, date_create = None, rank = None):
def __init__(self, model, uid, name, class_id, fields_list = [], superiors_list = {}, icon = '0', sortcolumn = 'rank', string = None, help_text = None, date_update = None, date_create = None, rank = None):
self.class_id = class_id
self.check_type('class_id', int)
self.fields_list = fields_list
@ -34,14 +34,13 @@ class EmType(EmComponent):
if not isinstance(l_uid, int):
raise AttributeError("Excepted fields_list to be a list of integers, but found a +"+str(type(l_uid))+" in it")
self.subordinates_list = subordinates_list
self.check_type('subordinates_list', dict)
for nature, uids in self.subordinates_list.items():
self.superiors_list = superiors_list
self.check_type('superiors_list', dict)
for nature, sup_uid in self.superiors_list.items():
if nature not in [EmNature.PARENT, EmNature.TRANSLATION, EmNature.IDENTITY]:
raise AttributeError("Nature '%s' of subordinates is not allowed !" % nature)
for uid in uids:
if not isinstance(uid, int):
raise AttributeError("Excepted subordinates of nature '%s' to be a list int !" % nature)
if not isinstance(sup_uid, int):
raise AttributeError("Excepted subordinates of nature '%s' to be an int !" % nature)
self.icon = icon
self.sortcolumn = sortcolumn
@ -79,7 +78,7 @@ class EmType(EmComponent):
# @todo Check if the type is not linked by any EmClass
# @todo Check if there is no other ''non-deletion'' conditions
def delete_check(self):
if sum(self.subordinates_list) > 0:
if len(self.subordinates()) > 0:
return False
#Delete all relation with superiors
for nature, sups in self.superiors().items():
@ -110,52 +109,61 @@ class EmType(EmComponent):
# Indicates that an optional field is used
#
# @param field EmField: The optional field to select
# @return True if success False if failed
#
# @throw TypeError if field is not an EmField instance
# @throw ValueError if field is not optional or is not associated with this type
# @see EmType::_opt_field_act()
# @throw MigrationHandlerChangeError if migration handler is not happy with the change
# @see EmType::_change_field_list()
def select_field(self, field):
if field.uid in self.fields_list:
return True
return self._change_field_list(field, True)
self._change_field_list(field, True)
## Unselect_field (Function)
#
# Indicates that an optional field will not be used
#
# @param field EmField: The optional field to unselect
# @return True if success False if fails
#
# @throw TypeError if field is not an EmField instance
# @throw ValueError if field is not optional or is not associated with this type
# @see EmType::_opt_field_act()
# @throw MigrationHandlerChangeError if migration handler is not happy with the change
# @see EmType::_change_field_list()
def unselect_field(self, field):
if field.uid not in self.fields_list:
return True
return self._change_field_list(field, False)
self._change_field_list(field, False)
## @brief Select or unselect an optional field
# @param field EmField: The EmField to select or unselect
# @param select bool: If True select field, else unselect it
# @return True if success False if fails
#
# @throw TypeError if field is not an EmField instance
# @throw ValueError if field is not optional or is not associated with this type
def _change_field_list(self, field, add=True): # TODO voir si on conserve l'argument "select"
# @throw MigrationHandlerChangeError if migration handler is not happy with the change
def _change_field_list(self, field, select=True):
if not isinstance(field, EmField):
raise TypeError("Excepted <class EmField> as field argument. But got " + str(type(field)))
if not field in self.em_class.fields():
raise ValueError("This field is not part of this type")
raise ValueError("This field " + str(field) + "is not part of the type " + str(self))
if not field.optional:
raise ValueError("This field is not optional")
if add:
self.fields_list.append(field.uid)
else:
self.fields_list.remove(field.uid)
try:
if select:
self.fields_list.append(field.uid)
self.model.migration_handler.register_change(self.model, self.uid, None, {'fields_list': field.uid})
else:
self.fields_list.remove(field.uid)
self.model.migration_handler.register_change(self.model, self.uid, {'fields_list': field.uid}, None)
except MigrationHandlerChangeError as exception_object:
if select:
self.fields_list.remove(field.uid)
else:
self.fields_list.append(field.uid)
raise exception_object
return True
self.model.migration_handler.register_model_state(self.model, hash(self.model))
## Get the list of associated hooks
# @note Not conceptualized yet
@ -186,22 +194,22 @@ class EmType(EmComponent):
# EmType instance
# @throw RuntimeError if a nature fetched from db is not valid
def subordinates(self):
return { nature: [ self.model.component(tuid) for tuid in self.subordinates_list[nature] ] for nature in self.subordinates_list }
subordinates = {}
for em_type in self.model.components(EmType):
for nature, superior_uid in em_type.superiors_list.items():
if self.uid == superior_uid:
if nature in subordinates:
subordinates[nature].append(em_type)
else:
subordinates[nature] = [em_type]
return subordinates
## @brief Get the list of superiors by relation's nature
# Get a list of EmType that are superiors of this type
# @return Return a dict with relation nature as keys and an EmType as value
# @throw RuntimeError if a nature has multiple superiors
def superiors(self):
superiors = {}
for em_type in self.model.components(EmType):
for nature, sub_uids in em_type.subordinates_list.items():
if self.uid in sub_uids:
if nature in superiors:
raise RuntimeError("Multiple superiors found for relation of nature '%s' for EmType %d"%(nature, self.uid))
else:
superiors[nature] = em_type
return superiors
return { nature:self.model.component(superior_uid) for nature, superior_uid in self.superiors_list.items() }
## Add a superior in the type hierarchy
# @param em_type EmType: An EmType instance
@ -212,11 +220,6 @@ class EmType(EmComponent):
# @throw ValueError when relation_nature isn't reconized or not allowed for this type
# @throw ValueError when relation_nature don't allow to link this types together
def add_superior(self, em_type, relation_nature):
if not isinstance(em_type, EmType) or not isinstance(relation_nature, str):
raise TypeError("Excepted <class EmType> and <class str> as em_type argument. But got : " + str(type(em_type)) + " " + str(type(relation_nature)))
if relation_nature not in EmClassType.natures(self.classtype['name']):
raise ValueError("Invalid nature for add_superior : '" + relation_nature + "'. Allowed relations for this type are " + str(EmClassType.natures(self.classtype['name'])))
#Checking that this relation is allowed by the nature of the relation
att = self.classtype['hierarchy'][relation_nature]['attach']
if att == 'classtype':
@ -225,65 +228,44 @@ class EmType(EmComponent):
elif self.name != em_type.name:
raise ValueError("Not allowed to put a different em_type as superior in a relation of nature '" + relation_nature + "'")
# TODO Réimplémenter
# conn = self.db_engine.connect()
# htable = self._table_hierarchy
# values = {'subordinate_id': self.uid, 'superior_id': em_type.uid, 'nature': relation_nature}
# req = htable.insert(values=values)
#
# try:
# conn.execute(req)
# except sql.exc.IntegrityError:
# ret = False
# else:
# ret = True
# finally:
# conn.close()
#
# return ret
self._change_superiors_list(em_type, relation_nature, True)
## Delete a superior in the type hierarchy
# @param em_type EmType: An EmType instance
# @param relation_nature str: The name of the relation's nature
# @throw TypeError when em_type isn't an EmType instance
# @throw ValueError when relation_nature isn't reconized or not allowed for this type
def del_superior(self, em_type, relation_nature):
if not isinstance(em_type, EmType):
raise TypeError("Excepted <class EmType> as argument. But got : " + str(type(em_type)))
self._change_superiors_list(em_type, relation_nature, False)
## Apply changes to the superiors_list
# @param em_type EmType: An EmType instance
# @param relation_nature str: The name of the relation's nature
# @param add bool: Add or delete relation
def _change_superiors_list(self, em_type, relation_nature, add=True):
# check instance of parameters
if not isinstance(em_type, EmType) or not isinstance(relation_nature, str):
raise TypeError("Excepted <class EmType> and <class str> as em_type argument. But got : " + str(type(em_type)) + " " + str(type(relation_nature)))
# check if relation_nature is valid for this type
if relation_nature not in EmClassType.natures(self.classtype['name']):
raise ValueError("Invalid nature for add_superior : '" + relation_nature + "'. Allowed relations for this type are " + str(EmClassType.natures(self.classtype['name'])))
# TODO Réimplémenter
# conn = self.db_engine.connect()
# htable = self._table_hierarchy
# req = htable.delete(htable.c.superior_id == em_type.uid and htable.c.nature == relation_nature)
# conn.execute(req)
# conn.close()
try:
if add:
self.superiors_list[relation_nature] = em_type.uid
self.model.migration_handler.register_change(self.model, self.uid, None, {'superiors_list': {relation_nature: em_type.uid}})
else:
del self.superiors_list[relation_nature]
self.model.migration_handler.register_change(self.model, self.uid, {'superiors_list': {relation_nature: em_type.uid}}, None)
except MigrationHandlerChangeError as exception_object:
if add:
del self.superiors_list[relation_nature]
else:
self.superiors_list[relation_nature] = em_type.uid
raise exception_object
## @brief Get the list of linked type
# Types are linked with special fields called relation_to_type fields
# @return a list of EmType
# @see EmFields
def linked_types(self):
return self._linked_types_db() # TODO changer l'appel
self.model.migration_handler.register_model_state(self.model, hash(self.model))
## @brief Return the list of all the types linked to this type, should they be superiors or subordinates
# @return A list of EmType objects
# def _linked_types_db(self):
# conn = self.db_engine.connect()
# htable = self._table_hierarchy
# req = htable.select(htable.c.superior_id, htable.c.subordinate_id)
# req = req.where(sql.or_(htable.c.subordinate_id == self.uid, htable.c.superior_id == self.uid))
#
# res = conn.execute(req)
# rows = res.fetchall()
# conn.close()
#
# rows = dict(zip(rows.keys(), rows))
# result = []
# for row in rows:
# result.append(EmType(row['subordinate_id'] if row['superior_id'] == self.uid else row['superior_id']))
#
# return result
## Checks if the EmType is valid
# @throw EmComponentCheckError if check fails
def check(self):
@ -293,52 +275,50 @@ class EmType(EmComponent):
raise EmComponentCheckError("class_id contains an uid that does not exists '%d'" % self.class_id)
if not isinstance(em_class, EditorialModel.classes.EmClass):
raise EmComponentCheckError("class_id contains an uid from a component that is not an EmClass but a %s" % str(type(emc_class)))
for i,fuid in enumerate(self.fields_list):
field = self.model.component(fuid)
for i,f_uid in enumerate(self.fields_list):
field = self.model.component(f_uid)
if not field:
raise EmComponentCheckError("The element %d of selected_field is a non existing uid '%d'"%(i, fuid))
raise EmComponentCheckError("The element %d of selected_field is a non existing uid '%d'"%(i, f_uid))
if not isinstance(field, EmField):
raise EmComponentCheckError("The element %d of selected_field is not an EmField but a %s" % (i, str(type(field)) ))
if not field.optional:
raise EmComponentCheckError("The element %d of selected_field is an EmField not optional" % i )
if field.fieldgroup_id not in [ fg.uid for fg in self.fieldgroups() ]:
raise EmComponentCheckErrro("The element %d of selected_field is an EmField that is part of an EmFieldGroup that is not associated with this EmType" % i)
for nature in self.subordinates_list:
for i, tuid in enumerate(self.subordinates_list[nature]):
em_type = self.model.component(tuid)
if not em_type:
raise EmComponentCheckError("The element %d of subordinates contains a non existing uid '%d'" % (i, tuid))
if not isinstance(em_type, EmType):
raise EmComponentCheckError("The element %d of subordinates contains a component that is not an EmType but a %s" % (i, str(type(em_type))))
if nature not in EmClassType.natures(self.em_class.classtype):
raise EmComponentCheckError("The relation nature '%s' of the element %d of subordinates is not valid for this EmType classtype '%s'", (nature, i, self.classtype) )
nat_spec = getattr(EmClassType, self.em_class.classtype)['hierarchy'][nature]
if nat_spec['attach'] == 'classtype':
if self.classtype != em_type.classtype:
raise EmComponentCheckError("The element %d of subordinates is of '%s' classtype. But the current type is of '%s' classtype, and relation nature '%s' require two EmType of same classtype" % (i, em_type.classtype, self.classtype, nature) )
elif nat_spec['attach'] == 'type':
if self.uid != em_type.uid:
raise EmComponentCheckError("The element %d of subordinates is a different EmType. But the relation nature '%s' require the same EmType" % (i, nature))
for nature, sup_uid in self.superiors_list.items():
em_type = self.model.component(sup_uid)
if not em_type:
raise EmComponentCheckError("The superior is a non existing uid '%d'" % (sup_uid))
if not isinstance(em_type, EmType):
raise EmComponentCheckError("The superior is a component that is not an EmType but a %s" % (str(type(em_type))))
if nature not in EmClassType.natures(self.em_class.classtype):
raise EmComponentCheckError("The relation nature '%s' of the superior is not valid for this EmType classtype '%s'", (nature, self.classtype) )
nat_spec = getattr(EmClassType, self.em_class.classtype)['hierarchy'][nature]
if nat_spec['attach'] == 'classtype':
if self.classtype != em_type.classtype:
raise EmComponentCheckError("The superior is of '%s' classtype. But the current type is of '%s' classtype, and relation nature '%s' require two EmType of same classtype" % (em_type.classtype, self.classtype, nature) )
elif nat_spec['attach'] == 'type':
if self.uid != em_type.uid:
raise EmComponentCheckError("The superior is a different EmType. But the relation nature '%s' require the same EmType" % (nature))
else:
raise NotImplementedError("The nature['attach'] '%s' is not implemented in this check !" % nat_spec['attach'])
if 'max_depth' in nat_spec and nat_spec['max_depth'] > 0:
depth = 1
cur_type = em_type
while depth >= nat_spec['max_depth']:
depth +=1
if len(cur_type.subordinates()[nature]) == 0:
break
else:
raise NotImplementedError("The nature['attach'] '%s' is not implemented in this check !" % nat_spec['attach'])
if 'max_depth' in nat_spec and nat_spec['max_depth'] > 0:
depth = 1
cur_type = em_type
while depth >= nat_spec['max_depth']:
depth +=1
if len(cur_type.subordinates()[nature]) == 0:
break
else:
raise EmComponentCheckError("The relation with the element %d of subordinates has a depth superior than the maximum depth ( %d ) allowed by the relation's nature ( '%s' )" %( i, nat_spec['max_depth'], nature) )
raise EmComponentCheckError("The relation with the element %d of subordinates has a depth superior than the maximum depth ( %d ) allowed by the relation's nature ( '%s' )" %( i, nat_spec['max_depth'], nature) )
for nature in self.subordinates():
nat_spec = getattr(EmClassType, self.em_class.classtype)['hierarchy'][nature]
if 'max_child' in nat_spec and nat_spec['max_child'] > 0:
if len(self.subordinates()[nature]) > nat_spec['max_child']:
raise EmComponentCheckError("The EmType has more child than allowed in the relation's nature : %d > %d" (len(self.subordinates()[nature], nat_spec['max_child'])))
#pass