From 691def10a0197d83d2d108bd9043b0916d0f09b4 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Tue, 27 Aug 2019 12:58:11 +0200 Subject: [PATCH] Fixed #30727 -- Made Subquery pickle without evaluating their QuerySet. Subquery expression objects, when pickled, were evaluating the QuerySet objects saved in its _constructor_args attribute. --- django/db/models/expressions.py | 5 +++++ tests/queryset_pickle/tests.py | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 5f85b47423..1dd061c152 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -1019,6 +1019,11 @@ class Subquery(Expression): self.extra = extra super().__init__(output_field) + def __getstate__(self): + state = super().__getstate__() + state.pop('_constructor_args', None) + return state + def get_source_expressions(self): return [self.query] diff --git a/tests/queryset_pickle/tests.py b/tests/queryset_pickle/tests.py index e5cee5fd66..1b3f237819 100644 --- a/tests/queryset_pickle/tests.py +++ b/tests/queryset_pickle/tests.py @@ -172,6 +172,41 @@ class PickleabilityTestCase(TestCase): m2ms = pickle.loads(pickle.dumps(m2ms)) self.assertSequenceEqual(m2ms, [m2m]) + def test_pickle_exists_queryset_still_usable(self): + group = Group.objects.create(name='group') + Event.objects.create(title='event', group=group) + groups = Group.objects.annotate( + has_event=models.Exists( + Event.objects.filter(group_id=models.OuterRef('id')), + ), + ) + groups2 = pickle.loads(pickle.dumps(groups)) + self.assertSequenceEqual(groups2.filter(has_event=True), [group]) + + def test_pickle_exists_queryset_not_evaluated(self): + group = Group.objects.create(name='group') + Event.objects.create(title='event', group=group) + groups = Group.objects.annotate( + has_event=models.Exists( + Event.objects.filter(group_id=models.OuterRef('id')), + ), + ) + list(groups) # evaluate QuerySet. + with self.assertNumQueries(0): + self.assert_pickles(groups) + + def test_pickle_subquery_queryset_not_evaluated(self): + group = Group.objects.create(name='group') + Event.objects.create(title='event', group=group) + groups = Group.objects.annotate( + event_title=models.Subquery( + Event.objects.filter(group_id=models.OuterRef('id')).values('title'), + ), + ) + list(groups) # evaluate QuerySet. + with self.assertNumQueries(0): + self.assert_pickles(groups) + def test_annotation_with_callable_default(self): # Happening.when has a callable default of datetime.datetime.now. qs = Happening.objects.annotate(latest_time=models.Max('when'))