Author: phd
Date: Mon Oct 10 08:36:13 2011
New Revision: 4462
Log:
ForeignKey('Table', refColumn='refcol_id').
Added:
SQLObject/trunk/sqlobject/tests/test_ForeignKey.py
- copied, changed from r4459, SQLObject/trunk/sqlobject/tests/test_foreignKey.py
Deleted:
SQLObject/trunk/sqlobject/tests/test_foreignKey.py
Modified:
SQLObject/trunk/docs/News.txt
SQLObject/trunk/docs/TODO.txt
SQLObject/trunk/sqlobject/col.py
SQLObject/trunk/sqlobject/main.py
Modified: SQLObject/trunk/docs/News.txt
==============================================================================
--- SQLObject/trunk/docs/News.txt Mon Oct 10 08:35:13 2011 (r4461)
+++ SQLObject/trunk/docs/News.txt Mon Oct 10 08:36:13 2011 (r4462)
@@ -14,7 +14,12 @@
--------------------
* Strings are treated specially in Select to allow
- Select(['id, 'name'], where='value = 42'). Update allows string in WHERE.
+ Select(['id, 'name'], where='value = 42'). Update allows a string in
+ WHERE.
+
+* ForeignKey('Table', refColumn='refcol_id') to allow ForeignKey to
+ point to a non-id column; the referred column must be a unique integer
+ column.
* delColumn now accepts a ForeignKey's name without 'ID'.
Modified: SQLObject/trunk/docs/TODO.txt
==============================================================================
--- SQLObject/trunk/docs/TODO.txt Mon Oct 10 08:35:13 2011 (r4461)
+++ SQLObject/trunk/docs/TODO.txt Mon Oct 10 08:36:13 2011 (r4462)
@@ -1,9 +1,6 @@
TODO
----
-* ForeignKey('Table', otherColumn='other_id') to allow ForeignKey to
- point to a non-id column.
-
* RelatedJoin.hasOther(otherObject[.id])
* tableParamSQL::
Modified: SQLObject/trunk/sqlobject/col.py
==============================================================================
--- SQLObject/trunk/sqlobject/col.py Mon Oct 10 08:35:13 2011 (r4461)
+++ SQLObject/trunk/sqlobject/col.py Mon Oct 10 08:36:13 2011 (r4462)
@@ -768,6 +768,10 @@
# 3-03 @@: this should have a simplified constructor
# Should provide foreign key information for other DBs.
+ def __init__(self, **kw):
+ self.refColumn = kw.pop('refColumn', None)
+ super(SOKeyCol, self).__init__(**kw)
+
def _sqlType(self):
return self.key_type[self.soClass.sqlmeta.idType]
@@ -788,18 +792,18 @@
def __init__(self, **kw):
foreignKey = kw['foreignKey']
style = kw['soClass'].sqlmeta.style
- if not kw.get('name'):
- kw['name'] = style.instanceAttrToIDAttr(style.pythonClassToAttr(foreignKey))
- else:
+ if kw.get('name'):
kw['origName'] = kw['name']
kw['name'] = style.instanceAttrToIDAttr(kw['name'])
+ else:
+ kw['name'] = style.instanceAttrToIDAttr(style.pythonClassToAttr(foreignKey))
super(SOForeignKey, self).__init__(**kw)
def sqliteCreateSQL(self):
sql = SOKeyCol.sqliteCreateSQL(self)
other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
tName = other.sqlmeta.table
- idName = other.sqlmeta.idName
+ idName = self.refColumn or other.sqlmeta.idName
if self.cascade is not None:
if self.cascade == 'null':
action = 'ON DELETE SET NULL'
@@ -828,7 +832,7 @@
sTName = self.soClass.sqlmeta.table
other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
tName = other.sqlmeta.table
- idName = other.sqlmeta.idName
+ idName = self.refColumn or other.sqlmeta.idName
if self.cascade is not None:
if self.cascade == 'null':
action = 'ON DELETE SET NULL'
@@ -854,7 +858,7 @@
sTLocalName = sTName.split('.')[-1]
other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
tName = other.sqlmeta.table
- idName = other.sqlmeta.idName
+ idName = self.refColumn or other.sqlmeta.idName
if self.cascade is not None:
if self.cascade == 'null':
action = 'ON DELETE SET NULL'
@@ -883,7 +887,7 @@
sql = SOKeyCol.sybaseCreateSQL(self)
other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
tName = other.sqlmeta.table
- idName = other.sqlmeta.idName
+ idName = self.refColumn or other.sqlmeta.idName
reference = ('REFERENCES %(tName)s(%(idName)s) ' %
{'tName':tName,
'idName':idName})
@@ -898,7 +902,7 @@
sql = SOKeyCol.mssqlCreateSQL(self, connection)
other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
tName = other.sqlmeta.table
- idName = other.sqlmeta.idName
+ idName = self.refColumn or other.sqlmeta.idName
reference = ('REFERENCES %(tName)s(%(idName)s) ' %
{'tName':tName,
'idName':idName})
@@ -915,7 +919,7 @@
#I assume that foreign key name is identical to the id of the reference table
sql = ' '.join([fidName, self._maxdbType()])
tName = other.sqlmeta.table
- idName = other.sqlmeta.idName
+ idName = self.refColumn or other.sqlmeta.idName
sql=sql + ',' + '\n'
sql=sql + 'FOREIGN KEY (%s) REFERENCES %s(%s)'%(fidName,tName,idName)
return sql
Modified: SQLObject/trunk/sqlobject/main.py
==============================================================================
--- SQLObject/trunk/sqlobject/main.py Mon Oct 10 08:35:13 2011 (r4461)
+++ SQLObject/trunk/sqlobject/main.py Mon Oct 10 08:36:13 2011 (r4462)
@@ -396,10 +396,10 @@
if sqlmeta.cacheValues:
# self._SO_class_className is a reference
# to the class in question.
- getter = eval('lambda self: self._SO_foreignKey(self._SO_loadValue(%r), self._SO_class_%s)' % (instanceName(name), column.foreignKey))
+ getter = eval('lambda self: self._SO_foreignKey(self._SO_loadValue(%r), self._SO_class_%s, %s)' % (instanceName(name), column.foreignKey, column.refColumn and repr(column.refColumn)))
else:
# Same non-caching version as above.
- getter = eval('lambda self: self._SO_foreignKey(self._SO_getValue(%s), self._SO_class_%s)' % (repr(name), column.foreignKey))
+ getter = eval('lambda self: self._SO_foreignKey(self._SO_getValue(%s), self._SO_class_%s, %s)' % (repr(name), column.foreignKey, column.refColumn and repr(column.refColumn)))
setattr(soClass, rawGetterName(origName), getter)
# And we set the _get_columnName version
@@ -410,7 +410,7 @@
if not column.immutable:
# The setter just gets the ID of the object,
# and then sets the real column.
- setter = eval('lambda self, val: setattr(self, %s, self._SO_getID(val))' % (repr(name)))
+ setter = eval('lambda self, val: setattr(self, %s, self._SO_getID(val, %s))' % (repr(name), column.refColumn and repr(column.refColumn)))
setattr(soClass, rawSetterName(origName), setter)
if not hasattr(soClass, setterName(origName)):
setattr(soClass, setterName(origName), setter)
@@ -1170,13 +1170,17 @@
value = column.to_python(value, self._SO_validatorState)
return value
- def _SO_foreignKey(self, id, joinClass):
- if id is None:
+ def _SO_foreignKey(self, value, joinClass, idName=None):
+ if value is None:
return None
- elif self.sqlmeta._perConnection:
- return joinClass.get(id, connection=self._connection)
+ if self.sqlmeta._perConnection:
+ connection = self._connection
else:
- return joinClass.get(id)
+ connection = None
+ if idName is None: # Get by id
+ return joinClass.get(value, connection=connection)
+ return joinClass.select(
+ getattr(joinClass.q, idName)==value, connection=connection).getOne()
def __init__(self, **kw):
# If we are the outmost constructor of a hiearchy of
@@ -1303,8 +1307,8 @@
func(self)
_postponed_local.postponed_calls.append(_send_RowCreatedSignal)
- def _SO_getID(self, obj):
- return getID(obj)
+ def _SO_getID(self, obj, refColumn=None):
+ return getID(obj, refColumn)
@classmethod
def _findAlternateID(cls, name, dbName, value, connection=None):
@@ -1701,9 +1705,9 @@
## Utility functions (for external consumption)
########################################
-def getID(obj):
+def getID(obj, refColumn=None):
if isinstance(obj, SQLObject):
- return obj.id
+ return getattr(obj, refColumn or 'id')
elif isinstance(obj, int):
return obj
elif isinstance(obj, long):
Copied and modified: SQLObject/trunk/sqlobject/tests/test_ForeignKey.py (from r4459, SQLObject/trunk/sqlobject/tests/test_foreignKey.py)
==============================================================================
--- SQLObject/trunk/sqlobject/tests/test_foreignKey.py Thu Sep 29 09:06:55 2011 (r4459, copy source)
+++ SQLObject/trunk/sqlobject/tests/test_ForeignKey.py Mon Oct 10 08:36:13 2011 (r4462)
@@ -4,6 +4,7 @@
class TestComposerKey(SQLObject):
name = StringCol()
+ id2 = IntCol(default=None, unique=True)
class TestWorkKey(SQLObject):
class sqlmeta:
@@ -15,6 +16,10 @@
class TestWorkKey2(SQLObject):
title = StringCol()
+class TestOtherColumn(SQLObject):
+ key1 = ForeignKey('TestComposerKey', default=None)
+ key2 = ForeignKey('TestComposerKey', refColumn='id2', default=None)
+
def test1():
setupClass([TestComposerKey, TestWorkKey])
@@ -69,3 +74,13 @@
InstalledTestDatabase.drop(TestWorkKey)
setupClass([TestComposerKey, TestWorkKey2], force=True)
TestWorkKey2.sqlmeta.addColumn(ForeignKey('TestComposerKey'), changeSchema=True)
+
+def test_otherColumn():
+ setupClass([TestComposerKey, TestOtherColumn])
+ test_composer1 = TestComposerKey(name='Test1')
+ test_composer2 = TestComposerKey(name='Test2', id2=2)
+ test_fkey = TestOtherColumn(key1=test_composer1)
+ test_other = TestOtherColumn(key2=test_composer2.id2)
+ getConnection().cache.clear()
+ assert test_fkey.key1 == test_composer1
+ assert test_other.key2 == test_composer2
|