diff --git a/Database/sqlutils.py b/Database/sqlutils.py index 0b1d767..dbab71f 100644 --- a/Database/sqlutils.py +++ b/Database/sqlutils.py @@ -5,6 +5,7 @@ import logging as logger import sqlalchemy as sqla from django.conf import settings +import EditorialModel os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Lodel.settings") @@ -76,17 +77,12 @@ def meta(engine): # @param cls : An EmComponent child class # @return An sqlalchemy table # @throw TypeError if em_instance is an EmComponent or not an EmComponent child class (or an instance) -def get_table(cls): - from EditorialModel.components import EmComponent # dirty circula inclusion hack - if not issubclass(cls, EmComponent) or cls.table is None: - raise TypeError("Excepting an EmComponent child class not an " + str(cls)) - engine = cls.db_engine() - return sqla.Table(cls.table, meta(engine)) - - -def getTable(cls): - return get_table(cls) - +# @todo Move this function as an instance method ? +def get_table(self): + if not issubclass(self.__class__, EditorialModel.components.EmComponent) or self.table is None: + raise TypeError("Excepting an EmComponent child class not an " + str(self.__class__)) + engine = self.db_engine + return sqla.Table(self.table, meta(engine)) ## This function is intended to execute ddl defined in sqlalter # @warning There is a dirty workaround here, DDL should returns only one query, but DropColumn for sqlite has to return 4 queries (rename, create, insert, drop). There is a split on the compiled SQL to extract and execute one query at a time diff --git a/EditorialModel/classes.py b/EditorialModel/classes.py index 846b6a4..bee7abd 100644 --- a/EditorialModel/classes.py +++ b/EditorialModel/classes.py @@ -44,7 +44,7 @@ class EmClass(EmComponent): #Create a new entry in the em_class table result = super(EmClass, cls).create(name=name, classtype=classtype, icon=icon, sortcolumn=sortcolumn, **em_component_args) - dbe = result.db_engine() + dbe = result.db_engine conn = dbe.connect() #Create a new table storing LodelObjects of this EmClass @@ -69,7 +69,7 @@ class EmClass(EmComponent): if len(fieldgroups) > 0: return False - dbe = self.__class__.db_engine() + dbe = self.db_engine meta = sqlutils.meta(dbe) #Here we have to give a connection class_table = sql.Table(self.name, meta) @@ -88,7 +88,7 @@ class EmClass(EmComponent): ## Isolate SQL for EmClass::fieldgroups # @return An array of dict (sqlalchemy fetchall) def _fieldgroups_db(self): - dbe = self.__class__.db_engine() + dbe = self.db_engine emfg = sql.Table(EditorialModel.fieldgroups.EmFieldGroup.table, sqlutils.meta(dbe)) req = emfg.select().where(emfg.c.class_id == self.uid) @@ -116,7 +116,7 @@ class EmClass(EmComponent): ## Isolate SQL for EmCLass::types # @return An array of dict (sqlalchemy fetchall) def _types_db(self): - dbe = self.__class__.db_engine() + dbe = self.db_engine emtype = sql.Table(EditorialModel.types.EmType.table, sqlutils.meta(dbe)) req = emtype.select().where(emtype.c.class_id == self.uid) conn = dbe.connect() @@ -134,7 +134,7 @@ class EmClass(EmComponent): def _link_type_db(self, table_name): # Create a new table storing additionnal fields for the relation between the linked type and this EmClass - conn = self.__class__.db_engine().connect() + conn = self.db_engine.connect() meta = sql.MetaData() emlinketable = sql.Table(table_name, meta, sql.Column('uid', sql.VARCHAR(50), primary_key=True)) emlinketable.create(conn) @@ -146,7 +146,7 @@ class EmClass(EmComponent): return self._linked_types_db() def _linked_types_db(self): - dbe = self.__class__.db_engine() + dbe = self.db_engine meta = sql.MetaData() meta.reflect(dbe) diff --git a/EditorialModel/components.py b/EditorialModel/components.py index b1b0b69..c2e98e4 100644 --- a/EditorialModel/components.py +++ b/EditorialModel/components.py @@ -53,7 +53,14 @@ class EmComponent(object): # @param id_or_name int|str: name or id of the object # @throw TypeError if id_or_name is not an integer nor a string # @throw NotImplementedError if called with EmComponent - def __init__(self, id_or_name): + def __init__(self, id_or_name, dbconf = 'default'): + + self.dbconf = dbconf + if self.dbconf: + self.db_engine = sqlutils.get_engine(dbconf) + else: + self.db_engine = False + if type(self) == EmComponent: raise NotImplementedError('Abstract class') @@ -129,15 +136,15 @@ class EmComponent(object): super(EmComponent, self).__setattr__('deleted', False) - @classmethod + #@classmethod ## Shortcut that return the sqlAlchemy engine - def db_engine(cls): - return sqlutils.get_engine(cls.dbconf) + #def db_engine(cls): + # return sqlutils.get_engine(cls.dbconf) ## Do the query on the database for EmComponent::populate() # @throw EmComponentNotExistError if the instance is not anymore stored in database def _populate_db(self): - dbe = self.__class__.db_engine() + dbe = self.db_engine component = sql.Table(self.table, sqlutils.meta(dbe)) req = sql.sql.select([component]) @@ -159,7 +166,7 @@ class EmComponent(object): ## Insert a new component in the database # # This function create and assign a new UID and handle the date_create and date_update values - # + # @warning There is a mandatory argument dbconf that indicate wich database configuration to use # @param **kwargs : Names arguments representing object properties # @return An instance of the created component # @throw TypeError if an element of kwargs isn't a valid object propertie or if a mandatory argument is missing @@ -194,21 +201,27 @@ class EmComponent(object): # if cls._fields[name].notNull and cls._fields[name].default == None: # raise TypeError("Missing argument : "+name) + if 'dbconf' in kwargs: + if not kwargs['db_engine']: + raise NotImplementedError("Its a nonsense to call create with no database") + dbconf = kwargs['dbconf'] + else: + dbconf = 'default' + dbe = sqlutils.get_engine(dbconf) - kwargs['uid'] = cls.new_uid() + kwargs['uid'] = cls.new_uid(dbe) kwargs['date_update'] = kwargs['date_create'] = datetime.datetime.utcnow() - dbe = cls.db_engine() conn = dbe.connect() - kwargs['rank'] = cls.get_max_rank( kwargs[cls.ranked_in] )+1 + kwargs['rank'] = cls._get_max_rank( kwargs[cls.ranked_in], dbe )+1 table = sql.Table(cls.table, sqlutils.meta(dbe)) req = table.insert(kwargs) if not conn.execute(req): raise RuntimeError("Unable to create the "+cls.__class__.__name__+" EmComponent ") conn.close() - return cls(kwargs['name']) + return cls(kwargs['name'], dbconf) ## Write the representation of the component in the database # @return bool @@ -232,7 +245,7 @@ class EmComponent(object): # @throw RunTimeError if it was unable to do the Db update def _save_db(self, values): """ Do the query on the db """ - dbe = self.__class__.db_engine() + dbe = self.db_engine component = sql.Table(self.table, sqlutils.meta(dbe)) req = sql.update(component, values=values).where(component.c.uid == self.uid) @@ -247,7 +260,7 @@ class EmComponent(object): # @throw RunTimeError if it was unable to do the deletion def delete(self): # - dbe = self.__class__.db_engine() + dbe = self.db_engine component = sql.Table(self.table, sqlutils.meta(dbe)) req = component.delete().where(component.c.uid == self.uid) conn = dbe.connect() @@ -260,13 +273,11 @@ class EmComponent(object): super(EmComponent, self).__setattr__('deleted', True) return True - ## get_max_rank - # Retourne le rank le plus élevé pour le groupe de component au quel apartient l'objet actuelle + ## @brief Get the maximum rank given an EmComponent child class and a ranked_in filter # @param ranked_in_value mixed: The rank "family" - # @param return -1 if no EmComponent found else return an integer >= 0 + # @return -1 if no EmComponent found else return an integer >= 0 @classmethod - def get_max_rank(cls, ranked_in_value): - dbe = cls.db_engine() + def _get_max_rank(cls, ranked_in_value, dbe): component = sql.Table(cls.table, sqlutils.meta(dbe)) req = sql.sql.select([component.c.rank]).where(getattr(component.c, cls.ranked_in) == ranked_in_value).order_by(component.c.rank.desc()) c = dbe.connect() @@ -278,6 +289,12 @@ class EmComponent(object): else: return -1 + ## Only make a call to the class method + # @return A positive integer or -1 if no components + # @see EmComponent::_get_max_rank() + def get_max_rank(self, ranked_in_value): + return self.__class__._get_max_rank(ranked_in_value, self.db_engine) + ## Set a new rank for this component # @note This function assume that ranks are properly set from 1 to x with no gap # @param new_rank int: The new rank @@ -298,9 +315,9 @@ class EmComponent(object): limits = [ self.rank + ( 1 if mod > 0 else -1), new_rank ] #The range of modified ranks limits.sort() - dbe = self.db_engine() + dbe = self.db_engine conn = dbe.connect() - table = sqlutils.get_table(self.__class__) + table = sqlutils.get_table(self) #Selecting the components that will be modified req = table.select().where( getattr(table.c, self.ranked_in) == getattr(self, self.ranked_in)).where(table.c.rank >= limits[0]).where(table.c.rank <= limits[1]) @@ -358,11 +375,11 @@ class EmComponent(object): # # Use the class property table # @return A new uid (an integer) - def new_uid(cls): + def new_uid(cls, db_engine): if cls.table is None: raise NotImplementedError("Abstract method") - dbe = cls.db_engine() + dbe = db_engine uidtable = sql.Table('uids', sqlutils.meta(dbe)) conn = dbe.connect() diff --git a/EditorialModel/fieldgroups.py b/EditorialModel/fieldgroups.py index af63f51..e89e07b 100644 --- a/EditorialModel/fieldgroups.py +++ b/EditorialModel/fieldgroups.py @@ -5,6 +5,7 @@ from EditorialModel.classes import EmClass import EditorialModel.fieldtypes as ftypes from Database import sqlutils +import sqlalchemy as sql import EditorialModel @@ -43,9 +44,10 @@ class EmFieldGroup(EmComponent): ## Get the list of associated fields # @return A list of EmField instance def fields(self): - field_table = sqlutils.getTable(EditorialModel.fields.EmField) + meta = sqlutils.meta(self.db_engine) + field_table = sql.Table(EditorialModel.fields.EmField.table, meta) req = field_table.select(field_table.c.uid).where(field_table.c.fieldgroup_id == self.uid) - conn = self.__class__.db_engine().connect() + conn = self.db_engine.connect() res = conn.execute(req) rows = res.fetchall() conn.close() diff --git a/EditorialModel/fields.py b/EditorialModel/fields.py index b44f9e2..a7e215f 100644 --- a/EditorialModel/fields.py +++ b/EditorialModel/fields.py @@ -71,11 +71,11 @@ class EmField(EmComponent): # @return bool : True if deleted False if deletion aborded # @todo Check if unconditionnal deletion is correct def delete(self): - dbe = self.__class__.db_engine() + dbe = self.db_engine class_table = sql.Table(self.get_class_table(), sqlutils.meta(dbe)) field_col = sql.Column(self.name) ddl = DropColumn(class_table, field_col) - sqlutils.ddl_execute(ddl, self.__class__.db_engine()) + sqlutils.ddl_execute(ddl, self.db_engine) return super(EmField, self).delete() ## add_field_column_to_class_table (Function) @@ -84,7 +84,7 @@ class EmField(EmComponent): # # @return True in case of success, False if not def add_field_column_to_class_table(self): - dbe = self.db_engine() + dbe = self.db_engine fieldtype = get_field_type(self.fieldtype) new_column = sql.Column(name=self.name, **(fieldtype.sqlalchemy_args())) class_table = sql.Table(self.get_class_table(), sqlutils.meta(dbe)) @@ -103,9 +103,10 @@ class EmField(EmComponent): # @return An EmClass instance def get_class(self): # - dbe = self.db_engine() + dbe = self.db_engine + meta = sqlutils.meta(dbe) conn = dbe.connect() - fieldgroup_table = sqlutils.getTable(EmFieldGroup) + fieldgroup_table = sql.Table(EmFieldGroup.table, meta) req = fieldgroup_table.select().where(fieldgroup_table.c.uid == self.fieldgroup_id) res = conn.execute(req) row = res.fetchone() diff --git a/EditorialModel/test/test_component.py b/EditorialModel/test/test_component.py index 8abaf47..69480f4 100644 --- a/EditorialModel/test/test_component.py +++ b/EditorialModel/test/test_component.py @@ -240,7 +240,7 @@ class TestUid(ComponentTestCase): def test_newuid(self): """ Test valid calls for new_uid method """ for _ in range(10): - nuid = EmTestComp.new_uid() + nuid = EmTestComp.new_uid(self.dber) conn = self.dber.connect() tuid = sqla.Table('uids', sqlutils.meta(self.dber)) @@ -257,7 +257,7 @@ class TestUid(ComponentTestCase): def test_newuid_abstract(self): """ Test not valit call for new_uid method """ with self.assertRaises(NotImplementedError): - EmComponent.new_uid() + EmComponent.new_uid(self.dber) pass #=======================# @@ -432,11 +432,11 @@ class TestCreate(ComponentTestCase): pass def testGetMaxRank(self): - old = EmTestComp.get_max_rank('f') + old = EmTestComp._get_max_rank('f', self.dber) EmTestComp.create(name="foobartest", rank_fam = 'f') - n = EmTestComp.get_max_rank('f') + n = EmTestComp._get_max_rank('f', self.dber) self.assertEqual(old+1, n, "Excepted value was "+str(old+1)+" but got "+str(n)) - self.assertEqual(EmTestComp.get_max_rank('z'), -1) + self.assertEqual(EmTestComp._get_max_rank('z', self.dber), -1) pass #====================# diff --git a/EditorialModel/test/test_field.py b/EditorialModel/test/test_field.py index 82e0232..f523d43 100644 --- a/EditorialModel/test/test_field.py +++ b/EditorialModel/test/test_field.py @@ -79,7 +79,7 @@ class FieldTestCase(TestCase): # @param field EmField: EmField object # @return Number of found records def _get_field_records_Db(self,field): - dbe = EmComponent.db_engine() + dbe = field.db_engine fieldtable = sqla.Table(EmField.table, sqlutils.meta(dbe)) conn = dbe.connect() req = fieldtable.select().where(fieldtable.c.uid==field.uid).where(fieldtable.c.name==field.name) @@ -103,7 +103,8 @@ class FieldTestCase(TestCase): # @param table_name str: Name of the table # @return list of columns def _get_table_columns_Db(self, table_name): - table = sqla.Table(table_name, sqlutils.meta(EmComponent.db_engine())) + dbe = self.testClass.db_engine + table = sqla.Table(table_name, sqlutils.meta(dbe)) return table.c ## TestField (Class) diff --git a/EditorialModel/test/test_fieldgroups.py b/EditorialModel/test/test_fieldgroups.py index 487b778..3a09d1a 100644 --- a/EditorialModel/test/test_fieldgroups.py +++ b/EditorialModel/test/test_fieldgroups.py @@ -67,6 +67,7 @@ class TestInit(FieldGroupTestCase): def setUp(self): super(TestInit, self).setUp() + dbe = sqlutils.get_engine() conn = sqlutils.get_engine().connect() ent1 = EmClass('entity1') @@ -76,9 +77,9 @@ class TestInit(FieldGroupTestCase): self.creadate = datetime.datetime.utcnow() #Test fieldgroup self.tfg = [ - { 'uid': EmFieldGroup.new_uid(), 'name': 'fg1', 'string': '{"fr":"Super Fieldgroup"}', 'help': '{"en":"help"}', 'rank': 0 , 'class_id': ent1.uid, 'date_create' : self.creadate, 'date_update': self.creadate}, - { 'uid': EmFieldGroup.new_uid(), 'name': 'fg2', 'string': '{"fr":"Super Fieldgroup"}', 'help': '{"en":"help"}', 'rank': 1 , 'class_id': ent1.uid, 'date_create': self.creadate, 'date_update': self.creadate}, - { 'uid': EmFieldGroup.new_uid(), 'name': 'fg3', 'string': '{"fr":"Super Fieldgroup"}', 'help': '{"en":"help"}', 'rank': 2 , 'class_id': idx1.uid, 'date_create': self.creadate, 'date_update': self.creadate}, + { 'uid': EmFieldGroup.new_uid(dbe), 'name': 'fg1', 'string': '{"fr":"Super Fieldgroup"}', 'help': '{"en":"help"}', 'rank': 0 , 'class_id': ent1.uid, 'date_create' : self.creadate, 'date_update': self.creadate}, + { 'uid': EmFieldGroup.new_uid(dbe), 'name': 'fg2', 'string': '{"fr":"Super Fieldgroup"}', 'help': '{"en":"help"}', 'rank': 1 , 'class_id': ent1.uid, 'date_create': self.creadate, 'date_update': self.creadate}, + { 'uid': EmFieldGroup.new_uid(dbe), 'name': 'fg3', 'string': '{"fr":"Super Fieldgroup"}', 'help': '{"en":"help"}', 'rank': 2 , 'class_id': idx1.uid, 'date_create': self.creadate, 'date_update': self.creadate}, ] req = sqla.Table('em_fieldgroup', sqlutils.meta(sqlutils.get_engine())).insert(self.tfg) diff --git a/EditorialModel/types.py b/EditorialModel/types.py index 916e438..153b33c 100644 --- a/EditorialModel/types.py +++ b/EditorialModel/types.py @@ -49,7 +49,7 @@ class EmType(EmComponent): # @return sqlalchemy em_type_hierarchy table object # @todo Don't hardcode table name def _table_hierarchy(self): - return sql.Table(self.__class__.table_hierarchy, sqlutils.meta(self.db_engine())) + return sql.Table(self.__class__.table_hierarchy, sqlutils.meta(self.db_engine)) @property ## Return the EmClassType of the type @@ -76,9 +76,10 @@ class EmType(EmComponent): ## Get the list of associated fieldgroups # @return A list of EmFieldGroup instance def field_groups(self): - fg_table = sqlutils.getTable(EmFieldGroup) + meta = sqlutils.meta(self.db_engine) + fg_table = sql.Table(EmFieldGroup.table, meta) req = fg_table.select(fg_table.c.uid).where(fg_table.c.class_id == self.class_id) - conn = self.__class__.db_engine().connect() + conn = self.db_engine.connect() res = conn.execute(req) rows = res.fetchall() conn.close() @@ -96,7 +97,7 @@ class EmType(EmComponent): ## Return selected optional field # @return A list of EmField instance def selected_fields(self): - dbe = self.db_engine() + dbe = self.db_engine meta = sqlutils.meta(dbe) conn = dbe.connect() @@ -155,7 +156,7 @@ class EmType(EmComponent): if not field.optional: raise ValueError("This field is not optional") - dbe = self.db_engine() + dbe = self.db_engine meta = sqlutils.meta(dbe) conn = dbe.connect() @@ -216,8 +217,9 @@ class EmType(EmComponent): # @throw RunTimeError if a nature fetched from db is not valid # @see EmType::subordinates(), EmType::superiors() def _sub_or_sup(self, sup=True): - conn = self.db_engine().connect() + conn = self.db_engine.connect() htable = self._table_hierarchy + type_table = sqlutils.get_table(self) req = htable.select() if sup: @@ -265,7 +267,7 @@ 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 + "'") - conn = self.db_engine().connect() + 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) @@ -290,7 +292,7 @@ class EmType(EmComponent): 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']))) - conn = self.db_engine().connect() + 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) @@ -306,7 +308,7 @@ class EmType(EmComponent): ## @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() + 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))