On Wed, Dec 23, 2015 at 11:09:35PM +0100, Oleg Broytman <p...@phdru.name> wrote:
> 1. Postgres. Until now SQLObject used to create ForeignKeys of type
> idType of the current table, i.e. usually INT. I fixed that (by adding a
> lookup of idType of the referenced table), but that a big
> backward-incompatible change and I don't want to apply it to bugfix
> releases. So I think the patches will go to 2.2.0b2 and 3.0.0a2.

   BTW, see the patches (attached).

> > > Cheers!
> > > 
> > > - n
> > > 
> > > -- 
> > > Dr. Nathan Edwards                      n...@georgetown.edu
> > > Department of Biochemistry and Molecular & Cellular Biology
> > >             Georgetown University Medical Center
> > >                 Room 1217, Harris Building,
> > >         3300 Whitehaven St, NW, Washington DC 20007
> > >            Phone: 202-687-7042, Fax: 202-687-0057

Oleg.
-- 
     Oleg Broytman            http://phdru.name/            p...@phdru.name
           Programmers don't die, they just GOSUB without RETURN.
>From 278c209f056fda12f5c7b4a79d0d18a6879a098e Mon Sep 17 00:00:00 2001
From: Oleg Broytman <p...@phdru.name>
Date: Thu, 24 Dec 2015 00:44:58 +0300
Subject: [PATCH 1/3] Fix ForeignKey column type

For ForeignKey, the type of column is determined by the other side
(the type of id column of the referenced table).
---
 sqlobject/col.py | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/sqlobject/col.py b/sqlobject/col.py
index 1b1efc7..1eacf10 100644
--- a/sqlobject/col.py
+++ b/sqlobject/col.py
@@ -796,16 +796,19 @@ class SOKeyCol(SOCol):
         self.refColumn = kw.pop('refColumn', None)
         super(SOKeyCol, self).__init__(**kw)
 
+    def _idType(self):
+        return self.soClass.sqlmeta.idType
+
     def _sqlType(self):
-        return self.key_type[self.soClass.sqlmeta.idType]
+        return self.key_type[self._idType()]
 
     def _sybaseType(self):
         key_type = {int: "NUMERIC(18,0) NULL", str: "TEXT"}
-        return key_type[self.soClass.sqlmeta.idType]
+        return key_type[self._idType()]
 
     def _mssqlType(self):
         key_type = {int: "INT NULL", str: "TEXT"}
-        return key_type[self.soClass.sqlmeta.idType]
+        return key_type[self._idType()]
 
 class KeyCol(Col):
 
@@ -823,6 +826,10 @@ class SOForeignKey(SOKeyCol):
             kw['name'] = style.instanceAttrToIDAttr(style.pythonClassToAttr(foreignKey))
         super(SOForeignKey, self).__init__(**kw)
 
+    def _idType(self):
+        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
+        return other.sqlmeta.idType
+
     def sqliteCreateSQL(self):
         sql = SOKeyCol.sqliteCreateSQL(self)
         other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
-- 
2.1.4

>From e35d2fec433c94fa2714d0e300f299b85cc67090 Mon Sep 17 00:00:00 2001
From: Oleg Broytman <p...@phdru.name>
Date: Thu, 24 Dec 2015 00:47:39 +0300
Subject: [PATCH 2/3] Add a helper to test mutually referencing tables

---
 sqlobject/tests/dbtest.py | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)

diff --git a/sqlobject/tests/dbtest.py b/sqlobject/tests/dbtest.py
index 5b884dc..1473e7b 100644
--- a/sqlobject/tests/dbtest.py
+++ b/sqlobject/tests/dbtest.py
@@ -6,7 +6,7 @@ import logging
 import os
 import re
 import sys
-from py.test import raises
+from py.test import raises, skip
 import sqlobject
 import sqlobject.conftest as conftest
 
@@ -320,6 +320,23 @@ def setupLogging():
     logger = logging.getLogger()
     logger.addHandler(hdlr)
 
+def setupCyclicClasses(*classes):
+    if not supports('dropTableCascade'):
+        skip("dropTableCascade isn't supported")
+    conn = getConnection()
+    for soClass in classes:
+        soClass.setConnection(conn)
+        soClass.dropTable(ifExists=True, cascade=True)
+
+    constraints = []
+    for soClass in classes:
+        constraints += soClass.createTable(ifNotExists=True,
+                                           applyConstraints=False)
+    for constraint in constraints:
+        conn.query(constraint)
+
 __all__ = ['getConnection', 'getConnectionURI', 'setupClass', 'Dummy', 'raises',
            'inserts', 'supports', 'deprecated_module',
-           'setup_module', 'teardown_module', 'setupLogging']
+           'setup_module', 'teardown_module', 'setupLogging',
+           'setupCyclicClasses',
+           ]
-- 
2.1.4

>From 9fa57a00f4109659e184c250a6d3363516ebd706 Mon Sep 17 00:00:00 2001
From: Oleg Broytman <p...@phdru.name>
Date: Thu, 24 Dec 2015 00:49:20 +0300
Subject: [PATCH 3/3] Add ForeignKeyValidator

Validate foreign keys using idType of the referenced column.
---
 sqlobject/col.py                   | 27 +++++++++++++++++++++++++++
 sqlobject/tests/test_ForeignKey.py | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 60 insertions(+)

diff --git a/sqlobject/col.py b/sqlobject/col.py
index 1eacf10..eeeac93 100644
--- a/sqlobject/col.py
+++ b/sqlobject/col.py
@@ -814,6 +814,29 @@ class KeyCol(Col):
 
     baseClass = SOKeyCol
 
+class ForeignKeyValidator(SOValidator):
+
+    def __init__(self, *args, **kw):
+        super(ForeignKeyValidator, self).__init__(*args, **kw)
+        self.fkIDType = None
+
+    def from_python(self, value, state):
+        if value is None:
+            return None
+        # Avoid importing the main module to get the SQLObject class for isinstance
+        if hasattr(value, 'sqlmeta'):
+            return value
+        if self.fkIDType is None:
+            otherTable = findClass(self.soCol.foreignKey, self.soCol.soClass.sqlmeta.registry)
+            self.fkIDType = otherTable.sqlmeta.idType
+        try:
+            value = self.fkIDType(value)
+            return value
+        except (ValueError, TypeError), e:
+            pass
+        raise validators.Invalid("expected a %r for the ForeignKey '%s', got %s %r instead" % \
+                (self.fkIDType, self.name, type(value), value), value, state)
+
 class SOForeignKey(SOKeyCol):
 
     def __init__(self, **kw):
@@ -826,6 +849,10 @@ class SOForeignKey(SOKeyCol):
             kw['name'] = style.instanceAttrToIDAttr(style.pythonClassToAttr(foreignKey))
         super(SOForeignKey, self).__init__(**kw)
 
+    def createValidators(self):
+        return [ForeignKeyValidator(name=self.name)] + \
+               super(SOForeignKey, self).createValidators()
+
     def _idType(self):
         other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
         return other.sqlmeta.idType
diff --git a/sqlobject/tests/test_ForeignKey.py b/sqlobject/tests/test_ForeignKey.py
index 44a1929..30c4127 100644
--- a/sqlobject/tests/test_ForeignKey.py
+++ b/sqlobject/tests/test_ForeignKey.py
@@ -1,3 +1,4 @@
+from formencode import validators
 from sqlobject import *
 from sqlobject.tests.dbtest import *
 from sqlobject.tests.dbtest import InstalledTestDatabase
@@ -84,3 +85,35 @@ def test_otherColumn():
     getConnection().cache.clear()
     assert test_fkey.key1 == test_composer1
     assert test_other.key2 == test_composer2
+
+class TestFKValidationA(SQLObject):
+    name = StringCol()
+    bfk = ForeignKey("TestFKValidationB")
+    cfk = ForeignKey("TestFKValidationC", default=None)
+
+class TestFKValidationB(SQLObject):
+    name = StringCol()
+    afk = ForeignKey("TestFKValidationA")
+
+class TestFKValidationC(SQLObject):
+    class sqlmeta:
+        idType = str
+    name = StringCol()
+
+def test_foreignkey_validation():
+    setupCyclicClasses(TestFKValidationA, TestFKValidationB, TestFKValidationC)
+    a = TestFKValidationA(name="testa", bfk=None)
+    b = TestFKValidationB(name="testb", afk=a)
+    c = TestFKValidationC(id='testc', name="testc")
+    a.bfk = b
+    a.cfk = c
+    assert a.bfk == b
+    assert a.cfk == c
+    assert b.afk == a
+
+    raises(validators.Invalid,
+           TestFKValidationA, name="testa", bfk='testb', cfk='testc')
+
+    a = TestFKValidationA(name="testa", bfk=1, cfk='testc')
+    assert a.bfkID == 1
+    assert a.cfkID == 'testc'
-- 
2.1.4

------------------------------------------------------------------------------
_______________________________________________
sqlobject-discuss mailing list
sqlobject-discuss@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/sqlobject-discuss

Reply via email to