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:
parent
4269648c0f
commit
5e3463f6bc
|
@ -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):
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue