Fixed #30628 -- Adjusted expression identity to differentiate bound fields.

Expressions referring to different bound fields should not be
considered equal.

Thanks Julien Enselme for the detailed report.

Regression in bc7e288ca9.
This commit is contained in:
Simon Charette 2019-07-09 17:26:37 -04:00 committed by Mariusz Felisiak
parent 1bbf77bea5
commit ee6e93ec87
6 changed files with 37 additions and 5 deletions

View File

@ -376,7 +376,10 @@ class BaseExpression:
identity = [self.__class__] identity = [self.__class__]
for arg, value in arguments: for arg, value in arguments:
if isinstance(value, fields.Field): if isinstance(value, fields.Field):
value = type(value) if value.name and value.model:
value = (value.model._meta.label, value.name)
else:
value = type(value)
else: else:
value = make_hashable(value) value = make_hashable(value)
identity.append((arg, value)) identity.append((arg, value))

View File

@ -9,4 +9,6 @@ Django 2.2.4 fixes several bugs in 2.2.3.
Bugfixes Bugfixes
======== ========
* ... * Fixed a regression in Django 2.2 when ordering a ``QuerySet.union()``,
``intersection()``, or ``difference()`` by a field type present more than
once results in the wrong ordering being used (:ticket:`30628`).

View File

@ -21,7 +21,7 @@ from django.db.models.functions import (
from django.db.models.sql import constants from django.db.models.sql import constants
from django.db.models.sql.datastructures import Join from django.db.models.sql.datastructures import Join
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
from django.test.utils import Approximate from django.test.utils import Approximate, isolate_apps
from .models import ( from .models import (
UUID, UUIDPK, Company, Employee, Experiment, Number, RemoteEmployee, UUID, UUIDPK, Company, Employee, Experiment, Number, RemoteEmployee,
@ -898,6 +898,7 @@ class ExpressionsTests(TestCase):
) )
@isolate_apps('expressions')
class SimpleExpressionTests(SimpleTestCase): class SimpleExpressionTests(SimpleTestCase):
def test_equal(self): def test_equal(self):
@ -911,6 +912,15 @@ class SimpleExpressionTests(SimpleTestCase):
Expression(models.CharField()) Expression(models.CharField())
) )
class TestModel(models.Model):
field = models.IntegerField()
other_field = models.IntegerField()
self.assertNotEqual(
Expression(TestModel._meta.get_field('field')),
Expression(TestModel._meta.get_field('other_field')),
)
def test_hash(self): def test_hash(self):
self.assertEqual(hash(Expression()), hash(Expression())) self.assertEqual(hash(Expression()), hash(Expression()))
self.assertEqual( self.assertEqual(
@ -922,6 +932,15 @@ class SimpleExpressionTests(SimpleTestCase):
hash(Expression(models.CharField())), hash(Expression(models.CharField())),
) )
class TestModel(models.Model):
field = models.IntegerField()
other_field = models.IntegerField()
self.assertNotEqual(
hash(Expression(TestModel._meta.get_field('field'))),
hash(Expression(TestModel._meta.get_field('other_field'))),
)
class ExpressionsNumericTests(TestCase): class ExpressionsNumericTests(TestCase):

View File

@ -148,6 +148,7 @@ class Cover(models.Model):
class Number(models.Model): class Number(models.Model):
num = models.IntegerField() num = models.IntegerField()
other_num = models.IntegerField(null=True)
def __str__(self): def __str__(self):
return str(self.num) return str(self.num)

View File

@ -9,7 +9,7 @@ from .models import Number, ReservedName
class QuerySetSetOperationTests(TestCase): class QuerySetSetOperationTests(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
Number.objects.bulk_create(Number(num=i) for i in range(10)) Number.objects.bulk_create(Number(num=i, other_num=10 - i) for i in range(10))
def number_transform(self, value): def number_transform(self, value):
return value.num return value.num
@ -251,3 +251,10 @@ class QuerySetSetOperationTests(TestCase):
qs1 = Number.objects.all() qs1 = Number.objects.all()
qs2 = Number.objects.intersection(Number.objects.filter(num__gt=1)) qs2 = Number.objects.intersection(Number.objects.filter(num__gt=1))
self.assertEqual(qs1.difference(qs2).count(), 2) self.assertEqual(qs1.difference(qs2).count(), 2)
def test_order_by_same_type(self):
qs = Number.objects.all()
union = qs.union(qs)
numbers = list(range(10))
self.assertNumbersEqual(union.order_by('num'), numbers)
self.assertNumbersEqual(union.order_by('other_num'), reversed(numbers))

View File

@ -2335,7 +2335,7 @@ class ValuesQuerysetTests(TestCase):
qs = Number.objects.extra(select={'num2': 'num+1'}).annotate(Count('id')) qs = Number.objects.extra(select={'num2': 'num+1'}).annotate(Count('id'))
values = qs.values_list(named=True).first() values = qs.values_list(named=True).first()
self.assertEqual(type(values).__name__, 'Row') self.assertEqual(type(values).__name__, 'Row')
self.assertEqual(values._fields, ('num2', 'id', 'num', 'id__count')) self.assertEqual(values._fields, ('num2', 'id', 'num', 'other_num', 'id__count'))
self.assertEqual(values.num, 72) self.assertEqual(values.num, 72)
self.assertEqual(values.num2, 73) self.assertEqual(values.num2, 73)
self.assertEqual(values.id__count, 1) self.assertEqual(values.id__count, 1)