django/tests/extra_regress/tests.py

432 lines
15 KiB
Python

import datetime
from django.contrib.auth.models import User
from django.test import TestCase
from .models import Order, RevisionableModel, TestObject
class ExtraRegressTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.u = User.objects.create_user(
username="fred",
password="secret",
email="fred@example.com"
)
def test_regression_7314_7372(self):
"""
Regression tests for #7314 and #7372
"""
rm = RevisionableModel.objects.create(
title='First Revision',
when=datetime.datetime(2008, 9, 28, 10, 30, 0)
)
self.assertEqual(rm.pk, rm.base.pk)
rm2 = rm.new_revision()
rm2.title = "Second Revision"
rm.when = datetime.datetime(2008, 9, 28, 14, 25, 0)
rm2.save()
self.assertEqual(rm2.title, 'Second Revision')
self.assertEqual(rm2.base.title, 'First Revision')
self.assertNotEqual(rm2.pk, rm.pk)
self.assertEqual(rm2.base.pk, rm.pk)
# Queryset to match most recent revision:
qs = RevisionableModel.objects.extra(
where=["%(table)s.id IN (SELECT MAX(rev.id) FROM %(table)s rev GROUP BY rev.base_id)" % {
'table': RevisionableModel._meta.db_table,
}]
)
self.assertQuerysetEqual(
qs, [('Second Revision', 'First Revision')],
transform=lambda r: (r.title, r.base.title)
)
# Queryset to search for string in title:
qs2 = RevisionableModel.objects.filter(title__contains="Revision")
self.assertQuerysetEqual(
qs2, [
('First Revision', 'First Revision'),
('Second Revision', 'First Revision'),
],
transform=lambda r: (r.title, r.base.title),
ordered=False
)
# Following queryset should return the most recent revision:
self.assertQuerysetEqual(
qs & qs2,
[('Second Revision', 'First Revision')],
transform=lambda r: (r.title, r.base.title),
ordered=False
)
def test_extra_stay_tied(self):
# Extra select parameters should stay tied to their corresponding
# select portions. Applies when portions are updated or otherwise
# moved around.
qs = User.objects.extra(select={'alpha': '%s', 'beta': "2", 'gamma': '%s'}, select_params=(1, 3))
qs = qs.extra(select={"beta": 4})
qs = qs.extra(select={"alpha": "%s"}, select_params=[5])
self.assertEqual(
list(qs.filter(id=self.u.id).values('alpha', 'beta', 'gamma')),
[{'alpha': 5, 'beta': 4, 'gamma': 3}]
)
def test_regression_7957(self):
"""
Regression test for #7957: Combining extra() calls should leave the
corresponding parameters associated with the right extra() bit. I.e.
internal dictionary must remain sorted.
"""
self.assertEqual(
(User.objects
.extra(select={"alpha": "%s"}, select_params=(1,))
.extra(select={"beta": "%s"}, select_params=(2,))[0].alpha),
1
)
self.assertEqual(
(User.objects
.extra(select={"beta": "%s"}, select_params=(1,))
.extra(select={"alpha": "%s"}, select_params=(2,))[0].alpha),
2
)
def test_regression_7961(self):
"""
Regression test for #7961: When not using a portion of an
extra(...) in a query, remove any corresponding parameters from the
query as well.
"""
self.assertEqual(
list(User.objects.extra(select={"alpha": "%s"}, select_params=(-6,))
.filter(id=self.u.id).values_list('id', flat=True)),
[self.u.id]
)
def test_regression_8063(self):
"""
Regression test for #8063: limiting a query shouldn't discard any
extra() bits.
"""
qs = User.objects.all().extra(where=['id=%s'], params=[self.u.id])
self.assertSequenceEqual(qs, [self.u])
self.assertSequenceEqual(qs[:1], [self.u])
def test_regression_8039(self):
"""
Regression test for #8039: Ordering sometimes removed relevant tables
from extra(). This test is the critical case: ordering uses a table,
but then removes the reference because of an optimization. The table
should still be present because of the extra() call.
"""
self.assertQuerysetEqual(
(Order.objects
.extra(where=["username=%s"], params=["fred"], tables=["auth_user"])
.order_by('created_by')),
[]
)
def test_regression_8819(self):
"""
Regression test for #8819: Fields in the extra(select=...) list
should be available to extra(order_by=...).
"""
self.assertSequenceEqual(
User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}).distinct(),
[self.u],
)
self.assertSequenceEqual(
User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}, order_by=['extra_field']),
[self.u],
)
self.assertSequenceEqual(
User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}, order_by=['extra_field']).distinct(),
[self.u],
)
def test_dates_query(self):
"""
When calling the dates() method on a queryset with extra selection
columns, we can (and should) ignore those columns. They don't change
the result and cause incorrect SQL to be produced otherwise.
"""
RevisionableModel.objects.create(
title='First Revision',
when=datetime.datetime(2008, 9, 28, 10, 30, 0)
)
self.assertSequenceEqual(
RevisionableModel.objects.extra(select={"the_answer": 'id'}).datetimes('when', 'month'),
[datetime.datetime(2008, 9, 1, 0, 0)],
)
def test_values_with_extra(self):
"""
Regression test for #10256... If there is a values() clause, Extra
columns are only returned if they are explicitly mentioned.
"""
obj = TestObject(first='first', second='second', third='third')
obj.save()
self.assertEqual(
list(
TestObject.objects
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
.values()
),
[{
'bar': 'second', 'third': 'third', 'second': 'second', 'whiz': 'third', 'foo': 'first',
'id': obj.pk, 'first': 'first'
}]
)
# Extra clauses after an empty values clause are still included
self.assertEqual(
list(
TestObject.objects
.values()
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
),
[{
'bar': 'second', 'third': 'third', 'second': 'second', 'whiz': 'third', 'foo': 'first',
'id': obj.pk, 'first': 'first'
}]
)
# Extra columns are ignored if not mentioned in the values() clause
self.assertEqual(
list(
TestObject.objects
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
.values('first', 'second')
),
[{'second': 'second', 'first': 'first'}]
)
# Extra columns after a non-empty values() clause are ignored
self.assertEqual(
list(
TestObject.objects
.values('first', 'second')
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
),
[{'second': 'second', 'first': 'first'}]
)
# Extra columns can be partially returned
self.assertEqual(
list(
TestObject.objects
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
.values('first', 'second', 'foo')
),
[{'second': 'second', 'foo': 'first', 'first': 'first'}]
)
# Also works if only extra columns are included
self.assertEqual(
list(
TestObject.objects
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
.values('foo', 'whiz')
),
[{'foo': 'first', 'whiz': 'third'}]
)
# Values list works the same way
# All columns are returned for an empty values_list()
self.assertEqual(
list(
TestObject.objects
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
.values_list()
),
[('first', 'second', 'third', obj.pk, 'first', 'second', 'third')]
)
# Extra columns after an empty values_list() are still included
self.assertEqual(
list(
TestObject.objects
.values_list()
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
),
[('first', 'second', 'third', obj.pk, 'first', 'second', 'third')]
)
# Extra columns ignored completely if not mentioned in values_list()
self.assertEqual(
list(
TestObject.objects
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
.values_list('first', 'second')
),
[('first', 'second')]
)
# Extra columns after a non-empty values_list() clause are ignored completely
self.assertEqual(
list(
TestObject.objects
.values_list('first', 'second')
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
),
[('first', 'second')]
)
self.assertEqual(
list(
TestObject.objects
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
.values_list('second', flat=True)
),
['second']
)
# Only the extra columns specified in the values_list() are returned
self.assertEqual(
list(
TestObject.objects
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
.values_list('first', 'second', 'whiz')
),
[('first', 'second', 'third')]
)
# ...also works if only extra columns are included
self.assertEqual(
list(
TestObject.objects
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
.values_list('foo', 'whiz')
),
[('first', 'third')]
)
self.assertEqual(
list(
TestObject.objects
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
.values_list('whiz', flat=True)
),
['third']
)
# ... and values are returned in the order they are specified
self.assertEqual(
list(
TestObject.objects
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
.values_list('whiz', 'foo')
),
[('third', 'first')]
)
self.assertEqual(
list(
TestObject.objects
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
.values_list('first', 'id')
),
[('first', obj.pk)]
)
self.assertEqual(
list(
TestObject.objects
.extra(select={'foo': 'first', 'bar': 'second', 'whiz': 'third'})
.values_list('whiz', 'first', 'bar', 'id')
),
[('third', 'first', 'second', obj.pk)]
)
def test_regression_10847(self):
"""
Regression for #10847: the list of extra columns can always be
accurately evaluated. Using an inner query ensures that as_sql() is
producing correct output without requiring full evaluation and
execution of the inner query.
"""
obj = TestObject(first='first', second='second', third='third')
obj.save()
self.assertEqual(
list(TestObject.objects.extra(select={'extra': 1}).values('pk')),
[{'pk': obj.pk}]
)
self.assertSequenceEqual(
TestObject.objects.filter(
pk__in=TestObject.objects.extra(select={'extra': 1}).values('pk')
),
[obj],
)
self.assertEqual(
list(TestObject.objects.values('pk').extra(select={'extra': 1})),
[{'pk': obj.pk}]
)
self.assertSequenceEqual(
TestObject.objects.filter(
pk__in=TestObject.objects.values('pk').extra(select={'extra': 1})
),
[obj],
)
self.assertSequenceEqual(
TestObject.objects.filter(pk=obj.pk) | TestObject.objects.extra(where=["id > %s"], params=[obj.pk]),
[obj],
)
def test_regression_17877(self):
"""
Extra WHERE clauses get correctly ANDed, even when they
contain OR operations.
"""
# Test Case 1: should appear in queryset.
t1 = TestObject.objects.create(first='a', second='a', third='a')
# Test Case 2: should appear in queryset.
t2 = TestObject.objects.create(first='b', second='a', third='a')
# Test Case 3: should not appear in queryset, bug case.
t = TestObject(first='a', second='a', third='b')
t.save()
# Test Case 4: should not appear in queryset.
t = TestObject(first='b', second='a', third='b')
t.save()
# Test Case 5: should not appear in queryset.
t = TestObject(first='b', second='b', third='a')
t.save()
# Test Case 6: should not appear in queryset, bug case.
t = TestObject(first='a', second='b', third='b')
t.save()
self.assertCountEqual(
TestObject.objects.extra(
where=["first = 'a' OR second = 'a'", "third = 'a'"],
),
[t1, t2],
)
def test_extra_values_distinct_ordering(self):
t1 = TestObject.objects.create(first='a', second='a', third='a')
t2 = TestObject.objects.create(first='a', second='b', third='b')
qs = TestObject.objects.extra(
select={'second_extra': 'second'}
).values_list('id', flat=True).distinct()
self.assertSequenceEqual(qs.order_by('second_extra'), [t1.pk, t2.pk])
self.assertSequenceEqual(qs.order_by('-second_extra'), [t2.pk, t1.pk])
# Note: the extra ordering must appear in select clause, so we get two
# non-distinct results here (this is on purpose, see #7070).
# Extra select doesn't appear in result values.
self.assertSequenceEqual(qs.order_by('-second_extra').values_list('first'), [('a',), ('a',)])