From c54d73ae01cd787315ba030da17e266c49f386b3 Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Sat, 14 Feb 2015 19:37:12 +0000 Subject: [PATCH] [1.8.x] Fixed #24343 -- Ensure db converters are used for foreign keys. Joint effort between myself, Josh, Anssi and Shai. Conflicts: django/db/models/query.py tests/model_fields/models.py Backport of 4755f8fc25331c739a6f932cc8aba0cc9e62e352 from master. --- django/db/models/expressions.py | 13 ++++++++----- django/db/models/fields/__init__.py | 10 +++++----- django/db/models/fields/related.py | 14 ++++++++++++++ django/db/models/query.py | 6 +++--- django/db/models/sql/query.py | 2 +- .../test_nōn_ascii_path/app/__init__.py | 0 tests/model_fields/models.py | 4 ++++ tests/model_fields/test_uuid.py | 10 +++++++++- 8 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 tests/migrations/test_nōn_ascii_path/app/__init__.py diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 8e5c4aa533d..2e41803a1a5 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -585,10 +585,10 @@ class Random(ExpressionNode): class Col(ExpressionNode): - def __init__(self, alias, target, source=None): - if source is None: - source = target - super(Col, self).__init__(output_field=source) + def __init__(self, alias, target, output_field=None): + if output_field is None: + output_field = target + super(Col, self).__init__(output_field=output_field) self.alias, self.target = alias, target def __repr__(self): @@ -606,7 +606,10 @@ class Col(ExpressionNode): return [self] def get_db_converters(self, connection): - return self.output_field.get_db_converters(connection) + if self.target == self.output_field: + return self.output_field.get_db_converters(connection) + return (self.output_field.get_db_converters(connection) + + self.target.get_db_converters(connection)) class Ref(ExpressionNode): diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 31b0f26141c..19f090863b2 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -333,12 +333,12 @@ class Field(RegisterLookupMixin): ] return [] - def get_col(self, alias, source=None): - if source is None: - source = self - if alias != self.model._meta.db_table or source != self: + def get_col(self, alias, output_field=None): + if output_field is None: + output_field = self + if alias != self.model._meta.db_table or output_field != self: from django.db.models.expressions import Col - return Col(alias, self, source) + return Col(alias, self, output_field) else: return self.cached_col diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index a8e4a3156de..3410e5c994f 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -1991,6 +1991,20 @@ class ForeignKey(ForeignObject): def db_parameters(self, connection): return {"type": self.db_type(connection), "check": []} + def convert_empty_strings(self, value, connection, context): + if (not value) and isinstance(value, six.string_types): + return None + return value + + def get_db_converters(self, connection): + converters = super(ForeignKey, self).get_db_converters(connection) + if connection.features.interprets_empty_strings_as_nulls: + converters += [self.convert_empty_strings] + return converters + + def get_col(self, alias, output_field=None): + return super(ForeignKey, self).get_col(alias, output_field or self.related_field) + class OneToOneField(ForeignKey): """ diff --git a/django/db/models/query.py b/django/db/models/query.py index 0ec3cfd7303..b0552057724 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -243,7 +243,7 @@ class QuerySet(object): model_cls = klass_info['model'] select_fields = klass_info['select_fields'] model_fields_start, model_fields_end = select_fields[0], select_fields[-1] + 1 - init_list = [f[0].output_field.attname + init_list = [f[0].target.attname for f in select[model_fields_start:model_fields_end]] if len(init_list) != len(model_cls._meta.concrete_fields): init_set = set(init_list) @@ -1699,7 +1699,7 @@ class RelatedPopulator(object): self.cols_start = select_fields[0] self.cols_end = select_fields[-1] + 1 self.init_list = [ - f[0].output_field.attname for f in select[self.cols_start:self.cols_end] + f[0].target.attname for f in select[self.cols_start:self.cols_end] ] self.reorder_for_init = None else: @@ -1708,7 +1708,7 @@ class RelatedPopulator(object): ] reorder_map = [] for idx in select_fields: - field = select[idx][0].output_field + field = select[idx][0].target init_pos = model_init_attnames.index(field.attname) reorder_map.append((init_pos, field.attname, idx)) reorder_map.sort() diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index f99b58b84f6..344b2986880 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1542,7 +1542,7 @@ class Query(object): # database from tripping over IN (...,NULL,...) selects and returning # nothing col = query.select[0] - select_field = col.field + select_field = col.target alias = col.alias if self.is_nullable(select_field): lookup_class = select_field.get_lookup('isnull') diff --git a/tests/migrations/test_nōn_ascii_path/app/__init__.py b/tests/migrations/test_nōn_ascii_path/app/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py index 5f580624ba6..3d25c896071 100644 --- a/tests/model_fields/models.py +++ b/tests/model_fields/models.py @@ -369,3 +369,7 @@ class NullableUUIDModel(models.Model): class PrimaryKeyUUIDModel(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4) + + +class RelatedToUUIDModel(models.Model): + uuid_fk = models.ForeignKey('PrimaryKeyUUIDModel') diff --git a/tests/model_fields/test_uuid.py b/tests/model_fields/test_uuid.py index f06f4d02a7f..28e11914a19 100644 --- a/tests/model_fields/test_uuid.py +++ b/tests/model_fields/test_uuid.py @@ -5,7 +5,9 @@ from django.core import exceptions, serializers from django.db import models from django.test import TestCase -from .models import NullableUUIDModel, PrimaryKeyUUIDModel, UUIDModel +from .models import ( + NullableUUIDModel, PrimaryKeyUUIDModel, RelatedToUUIDModel, UUIDModel, +) class TestSaveLoad(TestCase): @@ -121,3 +123,9 @@ class TestAsPrimaryKey(TestCase): self.assertTrue(u1_found) self.assertTrue(u2_found) self.assertEqual(PrimaryKeyUUIDModel.objects.count(), 2) + + def test_underlying_field(self): + pk_model = PrimaryKeyUUIDModel.objects.create() + RelatedToUUIDModel.objects.create(uuid_fk=pk_model) + related = RelatedToUUIDModel.objects.get() + self.assertEqual(related.uuid_fk.pk, related.uuid_fk_id)