django/tests/extra_regress/tests.py

438 lines
16 KiB
Python
Raw Normal View History

import datetime
from collections import OrderedDict
from django.contrib.auth.models import User
from django.test import TestCase
from .models import Order, RevisionableModel, TestObject
class ExtraRegressTests(TestCase):
def setUp(self):
self.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,
}]
)
2016-04-08 10:04:45 +08:00
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")
2016-04-08 10:04:45 +08:00
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:
2016-04-08 10:04:45 +08:00
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(
2013-11-03 03:37:48 +08:00
select=OrderedDict((("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(
2013-12-09 01:20:06 +08:00
(User.objects
.extra(select={"alpha": "%s"}, select_params=(1,))
.extra(select={"beta": "%s"}, select_params=(2,))[0].alpha),
1
)
self.assertEqual(
2013-12-09 01:20:06 +08:00
(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(
2016-04-08 10:04:45 +08:00
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.assertQuerysetEqual(qs, ['<User: fred>'])
self.assertQuerysetEqual(qs[:1], ['<User: fred>'])
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(
2013-12-09 01:20:06 +08:00
(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.assertQuerysetEqual(
User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}).distinct(),
['<User: fred>']
)
self.assertQuerysetEqual(
User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}, order_by=['extra_field']),
['<User: fred>']
)
self.assertQuerysetEqual(
User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}, order_by=['extra_field']).distinct(),
['<User: fred>']
)
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(
Fixed #17260 -- Added time zone aware aggregation and lookups. Thanks Carl Meyer for the review. Squashed commit of the following: commit 4f290bdb60b7d8534abf4ca901bd0844612dcbda Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Wed Feb 13 21:21:30 2013 +0100 Used '0:00' instead of 'UTC' which doesn't always exist in Oracle. Thanks Ian Kelly for the suggestion. commit 01b6366f3ce67d57a58ca8f25e5be77911748638 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Wed Feb 13 13:38:43 2013 +0100 Made tzname a parameter of datetime_extract/trunc_sql. This is required to work around a bug in Oracle. commit 924a144ef8a80ba4daeeafbe9efaa826566e9d02 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Wed Feb 13 14:47:44 2013 +0100 Added support for parameters in SELECT clauses. commit b4351d2890cd1090d3ff2d203fe148937324c935 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Mon Feb 11 22:30:22 2013 +0100 Documented backwards incompatibilities in the two previous commits. commit 91ef84713c81bd455f559dacf790e586d08cacb9 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Mon Feb 11 09:42:31 2013 +0100 Used QuerySet.datetimes for the admin's date_hierarchy. commit 0d0de288a5210fa106cd4350961eb2006535cc5c Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Mon Feb 11 09:29:38 2013 +0100 Used QuerySet.datetimes in date-based generic views. commit 9c0859ff7c0b00734afe7fc15609d43d83215072 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Feb 10 21:43:25 2013 +0100 Implemented QuerySet.datetimes on Oracle. commit 68ab511a4ffbd2b811bf5da174d47e4dd90f28fc Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Feb 10 21:43:14 2013 +0100 Implemented QuerySet.datetimes on MySQL. commit 22d52681d347a8cdf568dc31ed032cbc61d049ef Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Feb 10 21:42:29 2013 +0100 Implemented QuerySet.datetimes on SQLite. commit f6800fd04c93722b45f9236976389e0b2fe436f5 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Feb 10 21:43:03 2013 +0100 Implemented QuerySet.datetimes on PostgreSQL. commit 0c829c23f4cf4d6804cadcc93032dd4c26b8c65e Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Feb 10 21:41:08 2013 +0100 Added datetime-handling infrastructure in the ORM layers. commit 104d82a7778cf3f0f5d03dfa53709c26df45daad Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Mon Feb 11 10:05:55 2013 +0100 Updated null_queries tests to avoid clashing with the __second lookup. commit c01bbb32358201b3ac8cb4291ef87b7612a2b8e6 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Feb 10 23:07:41 2013 +0100 Updated tests of .dates(). Replaced .dates() by .datetimes() for DateTimeFields. Replaced dates with datetimes in the expected output for DateFields. commit 50fb7a52462fecf0127b38e7f3df322aeb287c43 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Feb 10 21:40:09 2013 +0100 Updated and added tests for QuerySet.datetimes. commit a8451a5004c437190e264667b1e6fb8acc3c1eeb Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Feb 10 22:34:46 2013 +0100 Documented the new time lookups and updated the date lookups. commit 29413eab2bd1d5e004598900c0dadc0521bbf4d3 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Feb 10 16:15:49 2013 +0100 Documented QuerySet.datetimes and updated QuerySet.dates.
2013-02-10 23:15:49 +08:00
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=OrderedDict((('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=OrderedDict((('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=OrderedDict((('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=OrderedDict((('foo', 'first'), ('bar', 'second'), ('whiz', 'third'))))
),
[{'second': 'second', 'first': 'first'}]
)
# Extra columns can be partially returned
self.assertEqual(
list(
TestObject.objects
.extra(select=OrderedDict((('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=OrderedDict((('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=OrderedDict((('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=OrderedDict((('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=OrderedDict((('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=OrderedDict((('foo', 'first'), ('bar', 'second'), ('whiz', 'third'))))
),
[('first', 'second')]
)
self.assertEqual(
list(
TestObject.objects
.extra(select=OrderedDict((('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=OrderedDict((('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=OrderedDict((('foo', 'first'), ('bar', 'second'), ('whiz', 'third'))))
.values_list('foo', 'whiz')
),
[('first', 'third')]
)
self.assertEqual(
list(
TestObject.objects
.extra(select=OrderedDict((('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=OrderedDict((('foo', 'first'), ('bar', 'second'), ('whiz', 'third'))))
.values_list('whiz', 'foo')
),
[('third', 'first')]
)
self.assertEqual(
list(
TestObject.objects
.extra(select=OrderedDict((('foo', 'first'), ('bar', 'second'), ('whiz', 'third'))))
.values_list('first', 'id')
),
[('first', obj.pk)]
)
self.assertEqual(
list(
TestObject.objects
.extra(select=OrderedDict((('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.assertQuerysetEqual(
TestObject.objects.filter(
pk__in=TestObject.objects.extra(select={'extra': 1}).values('pk')
),
['<TestObject: TestObject: first,second,third>']
)
self.assertEqual(
list(TestObject.objects.values('pk').extra(select={'extra': 1})),
[{'pk': obj.pk}]
)
self.assertQuerysetEqual(
TestObject.objects.filter(
pk__in=TestObject.objects.values('pk').extra(select={'extra': 1})
),
['<TestObject: TestObject: first,second,third>']
)
self.assertQuerysetEqual(
TestObject.objects.filter(pk=obj.pk) | TestObject.objects.extra(where=["id > %s"], params=[obj.pk]),
['<TestObject: TestObject: first,second,third>']
)
def test_regression_17877(self):
"""
Extra WHERE clauses get correctly ANDed, even when they
contain OR operations.
"""
# Test Case 1: should appear in queryset.
t = TestObject(first='a', second='a', third='a')
t.save()
# Test Case 2: should appear in queryset.
t = TestObject(first='b', second='a', third='a')
t.save()
# 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.assertQuerysetEqual(
TestObject.objects.extra(
where=["first = 'a' OR second = 'a'", "third = 'a'"],
),
['<TestObject: TestObject: a,a,a>', '<TestObject: TestObject: b,a,a>'],
ordered=False
)
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',)])