Fixed #31943 -- Fixed recreating QuerySet.values()/values_list() when using a pickled Query.

This commit is contained in:
Hasan Ramezani 2020-09-10 14:34:04 +02:00 committed by Mariusz Felisiak
parent 84609b3205
commit 5362e08624
3 changed files with 39 additions and 1 deletions

View File

@ -210,6 +210,8 @@ class QuerySet:
@query.setter @query.setter
def query(self, value): def query(self, value):
if value.values_select:
self._iterable_class = ValuesIterable
self._query = value self._query = value
def as_manager(cls): def as_manager(cls):

View File

@ -106,6 +106,20 @@ the query construction and is not part of the public API. However, it is safe
(and fully supported) to pickle and unpickle the attribute's contents as (and fully supported) to pickle and unpickle the attribute's contents as
described here. described here.
.. admonition:: Restrictions on ``QuerySet.values_list()``
If you recreate :meth:`QuerySet.values_list` using the pickled ``query``
attribute, it will be converted to :meth:`QuerySet.values`::
>>> import pickle
>>> qs = Blog.objects.values_list('id', 'name')
>>> qs
<QuerySet [(1, 'Beatles Blog')]>
>>> reloaded_qs = Blog.objects.all()
>>> reloaded_qs.query = pickle.loads(pickle.dumps(qs.query))
>>> reloaded_qs
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>
.. admonition:: You can't share pickles between versions .. admonition:: You can't share pickles between versions
Pickles of ``QuerySets`` are only valid for the version of Django that Pickles of ``QuerySets`` are only valid for the version of Django that

View File

@ -11,7 +11,7 @@ from .models import Container, Event, Group, Happening, M2MModel, MyEvent
class PickleabilityTestCase(TestCase): class PickleabilityTestCase(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
Happening.objects.create() # make sure the defaults are working (#20158) cls.happening = Happening.objects.create() # make sure the defaults are working (#20158)
def assert_pickles(self, qs): def assert_pickles(self, qs):
self.assertEqual(list(pickle.loads(pickle.dumps(qs))), list(qs)) self.assertEqual(list(pickle.loads(pickle.dumps(qs))), list(qs))
@ -224,6 +224,28 @@ class PickleabilityTestCase(TestCase):
qs = Happening.objects.annotate(latest_time=models.Max('when')) qs = Happening.objects.annotate(latest_time=models.Max('when'))
self.assert_pickles(qs) self.assert_pickles(qs)
def test_annotation_values(self):
qs = Happening.objects.values('name').annotate(latest_time=models.Max('when'))
reloaded = Happening.objects.all()
reloaded.query = pickle.loads(pickle.dumps(qs.query))
self.assertEqual(
reloaded.get(),
{'name': 'test', 'latest_time': self.happening.when},
)
def test_annotation_values_list(self):
# values_list() is reloaded to values() when using a pickled query.
tests = [
Happening.objects.values_list('name'),
Happening.objects.values_list('name', flat=True),
Happening.objects.values_list('name', named=True),
]
for qs in tests:
with self.subTest(qs._iterable_class.__name__):
reloaded = Happening.objects.all()
reloaded.query = pickle.loads(pickle.dumps(qs.query))
self.assertEqual(reloaded.get(), {'name': 'test'})
def test_filter_deferred(self): def test_filter_deferred(self):
qs = Happening.objects.all() qs = Happening.objects.all()
qs._defer_next_filter = True qs._defer_next_filter = True