Fixed #31943 -- Fixed recreating QuerySet.values()/values_list() when using a pickled Query.
This commit is contained in:
parent
84609b3205
commit
5362e08624
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue