Fixed #27595 -- Made ForeignKey.get_col() follow target chains.

Previously, foreign relationships were followed only one level deep which
prevents foreign keys to foreign keys from being resolved appropriately.
This was causing issues such as improper database value conversion for
UUIDField on SQLite because the resolved expression's output field's
internal type wasn't correct. Added tests to make sure unlikely foreign
reference cycles don't cause recursion errors.

Refs #24343.

Thanks oyooyo for the report and Wayne Merry for the investigation.
This commit is contained in:
Simon Charette 2018-10-22 09:49:34 -04:00 committed by Tim Graham
parent 4269648c0f
commit 5e3463f6bc
3 changed files with 37 additions and 2 deletions

View File

@ -977,7 +977,13 @@ class ForeignKey(ForeignObject):
return converters
def get_col(self, alias, output_field=None):
return super().get_col(alias, output_field or self.target_field)
if output_field is None:
output_field = self.target_field
while isinstance(output_field, ForeignKey):
output_field = output_field.target_field
if output_field is self:
raise ValueError('Cannot resolve output_field.')
return super().get_col(alias, output_field)
class OneToOneField(ForeignKey):

View File

@ -103,3 +103,28 @@ class ForeignKeyTests(TestCase):
fk = models.ForeignKey(Foo, models.CASCADE)
self.assertEqual(Bar._meta.get_field('fk').to_python('1'), 1)
@isolate_apps('model_fields')
def test_fk_to_fk_get_col_output_field(self):
class Foo(models.Model):
pass
class Bar(models.Model):
foo = models.ForeignKey(Foo, models.CASCADE, primary_key=True)
class Baz(models.Model):
bar = models.ForeignKey(Bar, models.CASCADE, primary_key=True)
col = Baz._meta.get_field('bar').get_col('alias')
self.assertIs(col.output_field, Foo._meta.pk)
@isolate_apps('model_fields')
def test_recursive_fks_get_col(self):
class Foo(models.Model):
bar = models.ForeignKey('Bar', models.CASCADE, primary_key=True)
class Bar(models.Model):
foo = models.ForeignKey(Foo, models.CASCADE, primary_key=True)
with self.assertRaisesMessage(ValueError, 'Cannot resolve output_field'):
Foo._meta.get_field('bar').get_col('alias')

View File

@ -170,8 +170,12 @@ class TestAsPrimaryKey(TestCase):
self.assertEqual(r.uuid_fk, u2)
def test_two_level_foreign_keys(self):
gc = UUIDGrandchild()
# exercises ForeignKey.get_db_prep_value()
UUIDGrandchild().save()
gc.save()
self.assertIsInstance(gc.uuidchild_ptr_id, uuid.UUID)
gc.refresh_from_db()
self.assertIsInstance(gc.uuidchild_ptr_id, uuid.UUID)
class TestAsPrimaryKeyTransactionTests(TransactionTestCase):