From 6af0b45f3fb60e71555895b21de49062940e4a00 Mon Sep 17 00:00:00 2001 From: Yann Date: Thu, 2 Jul 2015 14:35:43 +0200 Subject: [PATCH] Code factorization on EmType select and unselect fields. Added comparison capabilities to EmComponent (and childs) Implemented __hash__ and __eq__ for EmComponent (based on class name and uid) to test equality betwenn two instance. Allows to use "instance in instance_list" syntax --- Database/sqlutils.py | 5 +- EditorialModel/components.py | 10 +++ EditorialModel/fields_types.py | 105 ------------------------------ EditorialModel/test/test_field.py | 1 - EditorialModel/test/test_types.py | 28 ++++++-- EditorialModel/types.py | 75 ++++++++++++++++----- 6 files changed, 96 insertions(+), 128 deletions(-) delete mode 100644 EditorialModel/fields_types.py diff --git a/Database/sqlutils.py b/Database/sqlutils.py index 1665d08..7fa1ea3 100644 --- a/Database/sqlutils.py +++ b/Database/sqlutils.py @@ -81,15 +81,16 @@ def meta(engine): ## Return an sqlalchemy table given an EmComponent child class # @warning Except a class type not an instance -# @param em_class : An EmComponent child class +# @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 getTable(cls): +def get_table(cls): from EditorialModel.components import EmComponent #dirty circula inclusion hack if not issubclass(cls, EmComponent) or cls.table == 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) ## 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/components.py b/EditorialModel/components.py index 2d80595..d35de58 100644 --- a/EditorialModel/components.py +++ b/EditorialModel/components.py @@ -106,6 +106,16 @@ class EmComponent(object): self._fields[name].from_python(value) else: object.__setattr__(self, name, value) + + ## @brief Hash function that allows to compare two EmComponent + # @return EmComponent+ClassName+uid + def __hash__(self): + return "EmComponent"+self.__class__.__name__+str(self.uid) + + ## @brief Test if two EmComponent are "equals" + # @return True or False + def __eq__(self, other): + return self.__class__ == other.__class__ and self.uid == other.uid ## Lookup in the database properties of the object to populate the properties # @throw EmComponentNotExistError if the instance is not anymore stored in database diff --git a/EditorialModel/fields_types.py b/EditorialModel/fields_types.py deleted file mode 100644 index 05bb3c2..0000000 --- a/EditorialModel/fields_types.py +++ /dev/null @@ -1,105 +0,0 @@ -#-*- coding: utf-8 -*- - -from EditorialModel.fieldtypes import EmField_integer -from EditorialModel.components import EmComponent - -from Database import sqlutils -import sqlalchemy as sqla - -import logging - -logger = logging.getLogger('Lodel2.EditorialModel') - - -## Em_Field_Type (Class) -# -# Represents an association between a field and a type -class Em_Field_Type(object): - - table = 'em_field_type' - _fields = [('type_id', EmField_integer), ('field_id', EmField_integer)] - - ## __init__ (Function) - # - # Instanciates an Em_Field_Type object with data fetched from the database - # - # @param type_id integer: Identifier of the type - # @param field_id integer: Identifier of the field - def __init__(self, type_id, field_id): - self.table = Em_Field_Type.table - self._fields = self.__class__._fields - self.type_id = type_id - self.field_id = field_id - - ## Create (Function) - # - # Creates a relation between a field and a type - # - # @static - # - # @param emType EmType: Object representing the Type - # @param emField EmField: Object representing the Field - # @return Em_Field_Type object - @classmethod - def create(cls, em_type, em_field): - values = { - 'type_id': em_type.uid, - 'field_id': em_field.uid - } - - created_relation = Em_Field_Type._create_db(**values) - return created_relation - - @classmethod - def _create_db(cls, **kwargs): - dbe = EmComponent.db_engine() - conn = dbe.connect() - table = sqla.Table(cls.table, sqlutils.meta(dbe)) - req = table.insert(kwargs) - res = conn.execute(req) - conn.close() - return Em_Field_Type(kwargs['type_id'], kwargs['field_id']) - - ## Delete (Function) - # - # Deletes a relation between a field and a type - # - # @return Boolean - def delete(self): - return self._delete_db() - - def _delete_db(self): - dbe = EmComponent.db_engine() - table = sqla.Table(self.table, sqlutils.meta(dbe)) - req = table.delete().where(table.c.type_id == self.type_id).where(table.c.field_id == self.field_id) - conn = dbe.connect() - try: - conn.execute(req) - res = True - except: - res = False - conn.close() - - return res - - ## Exists (Function) - # - # Checks if a the relation exists in the database - # - # @return True if success, False if failure - def exists(self): - return self._exists_db() - - ## _ExistsDb (Function) - # - # Queries the database to see if a relation exists or not - # - # @return True if success, False if failure - def _exists_db(self): - dbe = EmComponent.db_engine() - table = sqla.Table(self.table, sqlutils.meta(dbe)) - req = table.select().where(table.c.type_id == self.type_id).where(table.c.field_id == self.field_id) - conn = dbe.connect() - res = conn.execute(req).fetchall() - conn.close() - return len(res) > 0 diff --git a/EditorialModel/test/test_field.py b/EditorialModel/test/test_field.py index 6142fb8..82e0232 100644 --- a/EditorialModel/test/test_field.py +++ b/EditorialModel/test/test_field.py @@ -12,7 +12,6 @@ from EditorialModel.classes import EmClass from EditorialModel.classtypes import EmClassType from EditorialModel.types import EmType from EditorialModel.fieldgroups import EmFieldGroup -from EditorialModel.fields_types import Em_Field_Type from EditorialModel.test.utils import * from EditorialModel.fieldtypes import * diff --git a/EditorialModel/test/test_types.py b/EditorialModel/test/test_types.py index d43f2ec..556fd97 100644 --- a/EditorialModel/test/test_types.py +++ b/EditorialModel/test/test_types.py @@ -12,7 +12,6 @@ from EditorialModel.classtypes import EmClassType, EmNature from EditorialModel.components import EmComponent, EmComponentNotExistError from EditorialModel.fieldgroups import EmFieldGroup from EditorialModel.fieldtypes import * -from EditorialModel.fields_types import Em_Field_Type from EditorialModel.fields import EmField from EditorialModel.test.utils import * from Database import sqlutils @@ -44,8 +43,11 @@ def setUpModule(): EmType.create(name='type7', em_class=emclass4) emfieldgroup = EmFieldGroup.create(name='fieldgroup1', em_class=emclass1) + emfieldgroup2 = EmFieldGroup.create(name='fieldgroup2', em_class=emclass2) emfieldtype = get_field_type('integer') EmField.create(name='field1', fieldgroup=emfieldgroup, fieldtype=emfieldtype, rel_to_type_id=emtype.uid) + EmField.create(name='field2', fieldgroup=emfieldgroup2, fieldtype=emfieldtype, optional=True) + EmField.create(name='field3', fieldgroup=emfieldgroup2, fieldtype=emfieldtype, optional=False) saveDbState(TEST_TYPE_DBNAME) @@ -69,18 +71,34 @@ class TypeTestCase(TestCase): self.emfieldgroup = EmFieldGroup('fieldgroup1') self.emfieldtype = get_field_type('integer') self.emfield = EmField('field1') + self.emfield2 = EmField('field2') + self.emfield3 = EmField('field3') + pass + class TestSelectField(TypeTestCase): def testSelectField(self): """ Testing optionnal field selection """ - self.emtype.select_field(self.emfield) - self.assertIsNotNone(Em_Field_Type(self.emtype.uid, self.emfield.uid)) + self.emtype.select_field(self.emfield2) + #a bit queick and dirty + self.assertIn(self.emfield2, self.emtype.selected_fields()) + pass def testUnselectField(self): """ Testing optionnal field unselection """ - self.emtype.unselect_field(self.emfield) - self.assertFalse(Em_Field_Type(self.emtype.uid, self.emfield.uid).exists()) + self.emtype.select_field(self.emfield2) + self.emtype.unselect_field(self.emfield2) + self.assertNotIn(self.emfield2, self.emtype.selected_fields()) + pass + + def testSelectFieldInvalid(self): + """ Testing optionnal field selection with invalid fields """ + with self.assertRaises(ValueError, msg="But the field was not optionnal"): + self.emtype.select_field(self.emfield3) + with self.assertRaises(ValueError, msg="But the field was not part of this type"): + self.emtype.select_field(self.emfield) + pass class TestLinkedTypes(TypeTestCase): @unittest.skip("Not yet implemented") diff --git a/EditorialModel/types.py b/EditorialModel/types.py index caf883b..122ff6b 100644 --- a/EditorialModel/types.py +++ b/EditorialModel/types.py @@ -1,9 +1,9 @@ #-*- coding: utf-8 -*- -from EditorialModel.fields_types import Em_Field_Type from Database import sqlutils import sqlalchemy as sql +import EditorialModel from EditorialModel.components import EmComponent, EmComponentNotExistError from EditorialModel.fieldgroups import EmFieldGroup from EditorialModel.classtypes import EmNature, EmClassType @@ -68,37 +68,82 @@ class EmType(EmComponent): return [ EmFieldGroup(row['uid']) for row in rows ] - ## Get the list of associated fields + ## Get the list of all Emfield possibly associated with this type # @return A list of EmField instance - # @todo handle optionnal fields - def fields(self): + def all_fields(self): res = [] - for fguid in self.field_groups(): - res += EmFieldGroup(fguid).fields() + for fieldgroup in self.field_groups(): + res += fieldgroup.fields() return res - pass + + ## Return selected optional field + # @return A list of EmField instance + def selected_fields(self): + dbe = self.db_engine() + meta = sqlutils.meta(dbe) + conn = dbe.connect() + + table = sql.Table('em_field_type', meta) + res = conn.execute(table.select().where(table.c.type_id == self.uid)) + + return [ EditorialModel.fields.EmField(row['field_id']) for row in res.fetchall()] + + ## Return the list of associated fields + # @return A list of EmField instance + def fields(self): + result = list() + for field in self.all_fields(): + if not field.optional: + result.append(field) + return result+selected_fields ## Select_field (Function) # # Indicates that an optional field is used # # @param field EmField: The optional field to select - # @throw ValueError, TypeError + # @return True if success False if failed + # @throw TypeError # @todo change exception type and define return value and raise condition def select_field(self, field): - Em_Field_Type.create(self, field) - + return self._opt_field_act(field, True) + ## Unselect_field (Function) # # Indicates that an optional field will not be used # # @param field EmField: The optional field to unselect - # @throw ValueError, TypeError - # @todo change exception type and define return value and raise condition + # @return True if success False if fails + # @throw TypeError def unselect_field(self, field): - emFieldType = Em_Field_Type(self.uid, field.uid) - emFieldType.delete() - del emFieldType + return self._opt_field_act(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 _opt_field_act(self, field, select=True): + if not field in self.all_fields(): + raise ValueError("This field is not part of this type") + if not field.optional: + raise ValueError("This field is not optional") + + dbe = self.db_engine() + meta = sqlutils.meta(dbe) + conn = dbe.connect() + + table = sql.Table('em_field_type', meta) + if select: + req = table.insert({'type_id': self.uid, 'field_id': field.uid}) + else: + req = table.delete().where(table.c.type_id == self.uid and table.c.field_id == field.uid) + + res = conn.execute(req) + conn.close() + return bool(res) ## Get the list of associated hooks # @note Not conceptualized yet