diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index a68f6e0290..b9095e503a 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -774,10 +774,20 @@ class SQLCompiler(object): # We only set this up here because # related_select_fields isn't populated until # execute_sql() has been called. + + # We also include types of fields of related models that + # will be included via select_related() for the benefit + # of MySQL/MySQLdb when boolean fields are involved + # (#15040). + + # This code duplicates the logic for the order of fields + # found in get_columns(). It would be nice to clean this up. if self.query.select_fields: - fields = self.query.select_fields + self.query.related_select_fields + fields = self.query.select_fields else: fields = self.query.model._meta.fields + fields = fields + self.query.related_select_fields + # If the field was deferred, exclude it from being passed # into `resolve_columns` because it wasn't selected. only_load = self.deferred_to_columns() diff --git a/tests/regressiontests/model_fields/models.py b/tests/regressiontests/model_fields/models.py index d9c123fb39..1d20f44fae 100644 --- a/tests/regressiontests/model_fields/models.py +++ b/tests/regressiontests/model_fields/models.py @@ -66,6 +66,11 @@ class BooleanModel(models.Model): bfield = models.BooleanField() string = models.CharField(max_length=10, default='abc') +class FksToBooleans(models.Model): + """Model wih FKs to models with {Null,}BooleanField's, #15040""" + bf = models.ForeignKey(BooleanModel) + nbf = models.ForeignKey(NullBooleanModel) + class RenamedField(models.Model): modelname = models.IntegerField(name="fieldname", choices=((1,'One'),)) diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py index 526aca4f98..a49894e36c 100644 --- a/tests/regressiontests/model_fields/tests.py +++ b/tests/regressiontests/model_fields/tests.py @@ -12,7 +12,8 @@ from django.utils import six from django.utils import unittest from .models import (Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post, - NullBooleanModel, BooleanModel, Document, RenamedField, VerboseNameField) + NullBooleanModel, BooleanModel, Document, RenamedField, VerboseNameField, + FksToBooleans) from .imagefield import (ImageFieldTests, ImageFieldTwoDimensionsTests, TwoImageFieldTests, ImageFieldNoDimensionsTests, @@ -218,6 +219,43 @@ class BooleanFieldTests(unittest.TestCase): select={'string_col': 'string'})[0] self.assertFalse(isinstance(b5.pk, bool)) + def test_select_related(self): + """ + Test type of boolean fields when retrieved via select_related() (MySQL, + #15040) + """ + bmt = BooleanModel.objects.create(bfield=True) + bmf = BooleanModel.objects.create(bfield=False) + nbmt = NullBooleanModel.objects.create(nbfield=True) + nbmf = NullBooleanModel.objects.create(nbfield=False) + + m1 = FksToBooleans.objects.create(bf=bmt, nbf=nbmt) + m2 = FksToBooleans.objects.create(bf=bmf, nbf=nbmf) + + # Test select_related('fk_field_name') + ma = FksToBooleans.objects.select_related('bf').get(pk=m1.id) + # verify types -- should't be 0/1 + self.assertIsInstance(ma.bf.bfield, bool) + self.assertIsInstance(ma.nbf.nbfield, bool) + # verify values + self.assertEqual(ma.bf.bfield, True) + self.assertEqual(ma.nbf.nbfield, True) + + # Test select_related() + mb = FksToBooleans.objects.select_related().get(pk=m1.id) + mc = FksToBooleans.objects.select_related().get(pk=m2.id) + # verify types -- shouldn't be 0/1 + self.assertIsInstance(mb.bf.bfield, bool) + self.assertIsInstance(mb.nbf.nbfield, bool) + self.assertIsInstance(mc.bf.bfield, bool) + self.assertIsInstance(mc.nbf.nbfield, bool) + # verify values + self.assertEqual(mb.bf.bfield, True) + self.assertEqual(mb.nbf.nbfield, True) + self.assertEqual(mc.bf.bfield, False) + self.assertEqual(mc.nbf.nbfield, False) + + class ChoicesTests(test.TestCase): def test_choices_and_field_display(self): """