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.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",)] )