diff --git a/django/db/models/fields/related_lookups.py b/django/db/models/fields/related_lookups.py index de2564fb0d..9d4baa6d03 100644 --- a/django/db/models/fields/related_lookups.py +++ b/django/db/models/fields/related_lookups.py @@ -83,6 +83,25 @@ class RelatedIn(In): else: return super(RelatedIn, self).as_sql(compiler, connection) + def __getstate__(self): + """ + Prevent pickling a query with an __in=inner_qs lookup from evaluating + inner_qs. + """ + from django.db.models.query import QuerySet # Avoid circular import + state = self.__dict__.copy() + if isinstance(self.rhs, QuerySet): + state['rhs'] = (self.rhs.__class__, self.rhs.query) + return state + + def __setstate__(self, state): + self.__dict__.update(state) + if isinstance(self.rhs, tuple): + queryset_class, query = self.rhs + queryset = queryset_class() + queryset.query = query + self.rhs = queryset + class RelatedLookupMixin(object): def get_prep_lookup(self): diff --git a/tests/queryset_pickle/tests.py b/tests/queryset_pickle/tests.py index 3c24f204c9..4a28e58da5 100644 --- a/tests/queryset_pickle/tests.py +++ b/tests/queryset_pickle/tests.py @@ -153,3 +153,42 @@ class PickleabilityTestCase(TestCase): msg = "Pickled queryset instance's Django version 1.0 does not match the current version %s." % get_version() with self.assertRaisesMessage(RuntimeWarning, msg): pickle.loads(pickle.dumps(qs)) + + +class InLookupTests(TestCase): + + @classmethod + def setUpTestData(cls): + for i in range(1, 3): + group = Group.objects.create(name='Group {}'.format(i)) + cls.e1 = Event.objects.create(title='Event 1', group=group) + + def test_in_lookup_queryset_evaluation(self): + """ + Neither pickling nor unpickling a QuerySet.query with an __in=inner_qs + lookup should evaluate inner_qs. + """ + events = Event.objects.filter(group__in=Group.objects.all()) + + with self.assertNumQueries(0): + dumped = pickle.dumps(events.query) + + with self.assertNumQueries(0): + reloaded = pickle.loads(dumped) + reloaded_events = Event.objects.none() + reloaded_events.query = reloaded + + self.assertSequenceEqual(reloaded_events, [self.e1]) + + def test_in_lookup_query_evaluation(self): + events = Event.objects.filter(group__in=Group.objects.values('id').query) + + with self.assertNumQueries(0): + dumped = pickle.dumps(events.query) + + with self.assertNumQueries(0): + reloaded = pickle.loads(dumped) + reloaded_events = Event.objects.none() + reloaded_events.query = reloaded + + self.assertSequenceEqual(reloaded_events, [self.e1])