diff -rcN SQLObject.orig/sqlobject/main.py SQLObject/sqlobject/main.py *** SQLObject.orig/sqlobject/main.py Mon Nov 22 13:56:27 2004 --- SQLObject/sqlobject/main.py Mon Nov 22 14:38:45 2004 *************** *** 4,9 **** --- 4,13 ---- SQLObject is a object-relational mapper. See SQLObject.html or SQLObject.txt for more. + Modified by + Daniel Savard, Xsoli Inc 7 Feb 2004 + - Added support for simple table inheritance. + This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the *************** *** 80,86 **** value.setName(attr) implicitIndexes.append(value) del d[attr] ! continue # We *don't* want to inherit _table, so we make sure it # is defined in this class (not a superclass) --- 84,90 ---- value.setName(attr) implicitIndexes.append(value) del d[attr] ! continue # We *don't* want to inherit _table, so we make sure it # is defined in this class (not a superclass) *************** *** 102,111 **** --- 106,149 ---- newClass = type.__new__(cls, className, bases, d) newClass._SO_finishedClassCreation = False + #DSM: Need to keep the name of the class for easy access later + newClass._className = className + newClass._childClasses = {} + #DSM: Need to know very soon if the class is a child of an + #DSM: inheritable class. If so, we keep a link to our parent class. + for cls in bases: + if hasattr(cls, '_inheritable') and cls._inheritable: + newClass._parentClass = cls + newClass._parent = None + cls._childClasses[className] = newClass + # We append to _columns, but we don't want to change the # superclass's _columns list, so we make a copy if necessary if not d.has_key('_columns'): newClass._columns = newClass._columns[:] + #DSM: If this class is a child of a parent class, we need to do some + #DSM: attribute check and a a foreign key to the parent. + if newClass._parentClass: + #DSM: First, look for invalid column name: + #DSM: reserved ones or same as a parent + parentCols = [column.name for column in newClass._columns] + for column in implicitColumns: + cname = column.name + if cname in ['childName']: + raise AttributeError, \ + "The column name '%s' is reserved" % cname + if cname in parentCols: + raise AttributeError, "The column '%s' is already " \ + "defined in an inheritable parent" % cname + #DSM: Remove columns if inherited from an inheritable class + #DSM: as we don't want them. All we want is a foreign key + #DSM: that will link to our parent + newClass._columns = [] + #DSM: If this is inheritable, add some default columns + # to be able to link to children + if hasattr(newClass, '_inheritable') and newClass._inheritable: + newClass._columns.append( + col.StringCol(name='childName',default=None)) newClass._columns.extend(implicitColumns) if not d.has_key('_joins'): newClass._joins = newClass._joins[:] *************** *** 164,169 **** --- 202,215 ---- # SQL where-clause generation. See the sql module for # more. newClass.q = sqlbuilder.SQLObjectTable(newClass) + #DSM: If we are a child, get the q magic from the parent + currentClass = newClass + while currentClass._parentClass: + currentClass = currentClass._parentClass + for column in currentClass._columns: + if type(column) == col.ForeignKey: continue + setattr(newClass.q, column.name, + getattr(currentClass.q, column.name)) # We have to check if there are columns in the inherited # _columns where the attribute has been set to None in this *************** *** 324,329 **** --- 370,383 ---- # it's set (default 1). _cacheValues = True + #DSM: The _inheritable attribute controls wheter the class can by + #DSM: inherited 'logically' with a foreignKey and back reference. + _inheritable = False # Does this class is inheritable + _parentClass = None # A reference to the parent class + _parent = None # A reference to the parent instance + _childClasses = {} # Reference to child classes + childName = None # Children name (to be able to get a subclass) + # The _defaultOrder is used by SelectResults _defaultOrder = None *************** *** 352,361 **** # aren't using integer IDs _idType = int ! def get(cls, id, connection=None, selectResults=None): assert id is not None, 'None is not a possible id for %s' % cls.__name ! id = cls._idType(id) if connection is None: --- 406,415 ---- # aren't using integer IDs _idType = int ! def get(cls, id, connection=None, selectResults=None, childUpdate=False): assert id is not None, 'None is not a possible id for %s' % cls.__name ! id = cls._idType(id) if connection is None: *************** *** 374,379 **** --- 428,447 ---- cache.put(id, cls, val) finally: cache.finishPut(cls) + #DSM: If we are updating a child, we should never return a child... + if childUpdate: return val + #DSM: If this class has a child, return the child + if hasattr(val, 'childName'): + childName = val.childName + if childName is not None: + return val._childClasses[childName].get(id) + #DSM: Now, we know we are alone or the last child in a family... + #DSM: It's time to find our parents + inst = val + while inst._parentClass and not inst._parent: + inst._parent = inst._parentClass.get(id, childUpdate=True) + inst = inst._parent + #DSM: We can now return ourself return val get = classmethod(get) *************** *** 383,389 **** cls._SO_indexList.append(index) addIndex = classmethod(addIndex) ! def addColumn(cls, columnDef, changeSchema=False, connection=None): column = columnDef.withClass(cls) name = column.name assert name != 'id', "The 'id' column is implicit, and should not be defined as a column" --- 451,474 ---- cls._SO_indexList.append(index) addIndex = classmethod(addIndex) ! def addColumn(cls, columnDef, changeSchema=False, childUpdate=False): ! #DSM: Try to add parent properties to the current class ! #DSM: Only do this once if possible at object creation and once for ! #DSM: each new dynamic column to refresh the current class ! if childUpdate or cls._parentClass: ! for col in cls._parentClass._columns: ! cname = col.name ! if cname == 'childName': continue ! setattr(cls, getterName(cname), eval( ! 'lambda self: self._parent.%s' % cname)) ! if not col.kw.has_key('immutable') or not col.kw['immutable']: ! setattr(cls, setterName(cname), eval( ! 'lambda self, val: setattr(self._parent, %s, val)' ! % repr(cname))) ! if childUpdate: ! makeProperties(cls) ! return ! column = columnDef.withClass(cls) name = column.name assert name != 'id', "The 'id' column is implicit, and should not be defined as a column" *************** *** 415,421 **** # Here if the _get_columnName method isn't in the # definition, we add it with the default # _SO_get_columnName definition. ! if not hasattr(cls, getterName(name)): setattr(cls, getterName(name), getter) cls._SO_plainGetters[name] = 1 --- 500,506 ---- # Here if the _get_columnName method isn't in the # definition, we add it with the default # _SO_get_columnName definition. ! if not hasattr(cls, getterName(name)) or (name == 'childName'): setattr(cls, getterName(name), getter) cls._SO_plainGetters[name] = 1 *************** *** 433,439 **** setattr(cls, '_SO_toPython_%s' % name, column.toPython) setattr(cls, rawSetterName(name), setter) # Then do the aliasing ! if not hasattr(cls, setterName(name)): setattr(cls, setterName(name), setter) # We keep track of setters that haven't been # overridden, because we can combine these --- 518,524 ---- setattr(cls, '_SO_toPython_%s' % name, column.toPython) setattr(cls, rawSetterName(name), setter) # Then do the aliasing ! if not hasattr(cls, setterName(name)) or name == 'childName': setattr(cls, setterName(name), setter) # We keep track of setters that haven't been # overridden, because we can combine these *************** *** 498,503 **** --- 583,593 ---- if cls._SO_finishedClassCreation: makeProperties(cls) + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic column as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addColumn(columnDef, childUpdate=True) + addColumn = classmethod(addColumn) def addColumnsFromDatabase(cls, connection=None): *************** *** 546,556 **** conn.delColumn(cls._table, column) if cls._SO_finishedClassCreation: ! unmakeProperties(cls) delColumn = classmethod(delColumn) ! def addJoin(cls, joinDef): # The name of the method we'll create. If it's # automatically generated, it's generated by the # join class. --- 636,671 ---- conn.delColumn(cls._table, column) if cls._SO_finishedClassCreation: ! delattr(cls, name) ! ! #DSM: Update each child class if needed ! #DSM: and delete properties for this column ! for c in cls._childClasses.values(): ! delattr(c, name) delColumn = classmethod(delColumn) ! def addJoin(cls, joinDef, childUpdate=False): ! #DSM: Try to add parent properties to the current class ! #DSM: Only do this once if possible at object creation and once for ! #DSM: each new dynamic join to refresh the current class ! if childUpdate or cls._parentClass: ! for jdef in cls._parentClass._joins: ! join = jdef.withClass(cls) ! jname = join.joinMethodName ! jarn = join.addRemoveName ! setattr(cls, getterName(jname), ! eval('lambda self: self._parent.%s' % jname)) ! if hasattr(join, 'remove'): ! setattr(cls, 'remove' + jarn, ! eval('lambda self,o: self._parent.remove%s(o)' % jarn)) ! if hasattr(join, 'add'): ! setattr(cls, 'add' + jarn, ! eval('lambda self,o: self._parent.add%s(o)' % jarn)) ! if childUpdate: ! makeProperties(cls) ! return ! # The name of the method we'll create. If it's # automatically generated, it's generated by the # join class. *************** *** 599,604 **** --- 714,724 ---- if cls._SO_finishedClassCreation: makeProperties(cls) + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic join as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addJoin(joinDef, childUpdate=True) + addJoin = classmethod(addJoin) def delJoin(cls, joinDef): *************** *** 624,630 **** delattr(cls, 'add' + join.addRemovePrefix) if cls._SO_finishedClassCreation: ! unmakeProperties(cls) delJoin = classmethod(delJoin) --- 744,755 ---- delattr(cls, 'add' + join.addRemovePrefix) if cls._SO_finishedClassCreation: ! delattr(cls, meth) ! ! #DSM: Update each child class if needed ! #DSM: and delete properties for this join ! for c in cls._childClasses.values(): ! delattr(c, meth) delJoin = classmethod(delJoin) *************** *** 865,870 **** --- 990,1012 ---- def _create(self, id, **kw): + #DSM: If we were called by a children class, + #DSM: we must retreive the properties dictionary. + #DSM: Note: we can't use the ** call paremeter directly + #DSM: as we must be able to delete items from the dictionary + #DSM: (and our children must know that the items were removed!) + fromChild = False + if kw.has_key('kw'): + kw = kw['kw'] + fromChild = True + #DSM: If we are the children of an inheritable class, + #DSM: we must first create our parent + if self._parentClass: + #kw['childName'] = inst._className + self._parent = self._parentClass(kw=kw) + self._parent.childName = self._className + id = self._parent.id + self._SO_creating = True self._SO_createValues = {} self._SO_validatorState = SQLObjectState(self) *************** *** 898,904 **** for name, value in kw.items(): if name in self._SO_plainSetters: forDB[name] = value ! else: others[name] = value # We take all the straight-to-DB values and use set() to --- 1040,1050 ---- for name, value in kw.items(): if name in self._SO_plainSetters: forDB[name] = value ! #DSM: If this is a call from the child, ! #DSM: we must remove the parameter for the database ! if fromChild: del kw[name] ! elif not fromChild: ! #DSM: Only use other items if this isn't a call from the child others[name] = value # We take all the straight-to-DB values and use set() to *************** *** 1094,1099 **** --- 1240,1248 ---- def destroySelf(self): # Kills this object. Kills it dead! + #DSM: If this object has parents, recursivly kill them + if hasattr(self, '_parent') and self._parent: + self._parent.destroySelf() depends = [] klass = self.__class__ depends = self._SO_depends() *************** *** 1187,1192 **** --- 1336,1374 ---- self.clause = clause tablesDict = sqlbuilder.tablesUsedDict(self.clause) tablesDict[sourceClass._table] = 1 + #DSM: if this class has a parent, we need to link it + #DSM: and be sure the parent is in the table list. + #DSM: The following code is before clauseTables + #DSM: because if the user uses clauseTables + #DSM: (and normal string SELECT), he must know what he wants + #DSM: and will do himself the relationship between classes. + if type(self.clause) is not str: + tableRegistry = {} + for registryClass in classregistry.registry(sourceClass._registry).allClasses(): + if registryClass._table in tablesDict: + #DSM: By default, no parent are needed for the clauses + tableRegistry[registryClass] = registryClass + currentClass = registryClass + while currentClass._parentClass: + currentClass = currentClass._parentClass + if tableRegistry.has_key(currentClass): + #DSM: Must keep the last parent needed + #DSM: (to limit the number of join needed) + tableRegistry[registryClass] = currentClass + #DSM: Remove this class as it is a parent one + #DSM: of a needed children + del tableRegistry[currentClass] + #DSM: Table registry contains only the last children + #DSM: or standalone classes + parentClause = [] + for (currentClass, minParentClass) in tableRegistry.items(): + while currentClass != minParentClass and currentClass._parentClass: + parentClass = currentClass._parentClass + parentClause.append(currentClass.q.id == parentClass.q.id) + currentClass = parentClass + tablesDict[currentClass._table] = 1 + self.clause = reduce(sqlbuilder.AND, parentClause, clause) + if clauseTables: for table in clauseTables: tablesDict[table] = 1 *************** *** 1315,1321 **** def count(self): """ Counting elements of current select results """ assert not self.ops.get('distinct'), "It is not currently supported to count distinct objects" ! count = self.accumulate('COUNT(*)') if self.ops.get('start'): count -= self.ops['start'] --- 1497,1503 ---- def count(self): """ Counting elements of current select results """ assert not self.ops.get('distinct'), "It is not currently supported to count distinct objects" ! count = self.accumulate('COUNT(*)') if self.ops.get('start'): count -= self.ops['start']