Fixed #31843 -- Fixed pickling named values from QuerySet.values_list().

This commit is contained in:
Kwist 2020-08-31 11:54:34 +03:00 committed by Mariusz Felisiak
parent 3a9f192b13
commit 981a072dd4
4 changed files with 26 additions and 11 deletions

View File

@ -934,6 +934,7 @@ answer newbie questions, and generally made Django that much better:
Vincent Foley <vfoleybourgon@yahoo.ca> Vincent Foley <vfoleybourgon@yahoo.ca>
Vinny Do <vdo.code@gmail.com> Vinny Do <vdo.code@gmail.com>
Vitaly Babiy <vbabiy86@gmail.com> Vitaly Babiy <vbabiy86@gmail.com>
Vitaliy Yelnik <velnik@gmail.com>
Vladimir Kuzma <vladimirkuzma.ch@gmail.com> Vladimir Kuzma <vladimirkuzma.ch@gmail.com>
Vlado <vlado@labath.org> Vlado <vlado@labath.org>
Vsevolod Solovyov Vsevolod Solovyov

View File

@ -5,8 +5,6 @@ The main QuerySet implementation. This provides the public API for the ORM.
import copy import copy
import operator import operator
import warnings import warnings
from collections import namedtuple
from functools import lru_cache
from itertools import chain from itertools import chain
import django import django
@ -23,7 +21,7 @@ from django.db.models.expressions import Case, Expression, F, Value, When
from django.db.models.functions import Cast, Trunc from django.db.models.functions import Cast, Trunc
from django.db.models.query_utils import FilteredRelation, Q from django.db.models.query_utils import FilteredRelation, Q
from django.db.models.sql.constants import CURSOR, GET_ITERATOR_CHUNK_SIZE from django.db.models.sql.constants import CURSOR, GET_ITERATOR_CHUNK_SIZE
from django.db.models.utils import resolve_callables from django.db.models.utils import create_namedtuple_class, resolve_callables
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property, partition from django.utils.functional import cached_property, partition
@ -148,13 +146,6 @@ class NamedValuesListIterable(ValuesListIterable):
namedtuple for each row. namedtuple for each row.
""" """
@staticmethod
@lru_cache()
def create_namedtuple_class(*names):
# Cache namedtuple() with @lru_cache() since it's too slow to be
# called for every QuerySet evaluation.
return namedtuple('Row', names)
def __iter__(self): def __iter__(self):
queryset = self.queryset queryset = self.queryset
if queryset._fields: if queryset._fields:
@ -162,7 +153,7 @@ class NamedValuesListIterable(ValuesListIterable):
else: else:
query = queryset.query query = queryset.query
names = [*query.extra_select, *query.values_select, *query.annotation_select] names = [*query.extra_select, *query.values_select, *query.annotation_select]
tuple_class = self.create_namedtuple_class(*names) tuple_class = create_namedtuple_class(*names)
new = tuple.__new__ new = tuple.__new__
for row in super().__iter__(): for row in super().__iter__():
yield new(tuple_class, row) yield new(tuple_class, row)

View File

@ -1,3 +1,7 @@
import functools
from collections import namedtuple
def make_model_tuple(model): def make_model_tuple(model):
""" """
Take a model or a string of the form "app_label.ModelName" and return a Take a model or a string of the form "app_label.ModelName" and return a
@ -28,3 +32,17 @@ def resolve_callables(mapping):
""" """
for k, v in mapping.items(): for k, v in mapping.items():
yield k, v() if callable(v) else v yield k, v() if callable(v) else v
def unpickle_named_row(names, values):
return create_namedtuple_class(*names)(*values)
@functools.lru_cache()
def create_namedtuple_class(*names):
# Cache type() with @lru_cache() since it's too slow to be called for every
# QuerySet evaluation.
def __reduce__(self):
return unpickle_named_row, (names, tuple(self))
return type('Row', (namedtuple('Row', names),), {'__reduce__': __reduce__})

View File

@ -2408,6 +2408,11 @@ class ValuesQuerysetTests(TestCase):
values = qs.first() values = qs.first()
self.assertEqual(values._fields, ('combinedexpression2', 'combinedexpression1')) self.assertEqual(values._fields, ('combinedexpression2', 'combinedexpression1'))
def test_named_values_pickle(self):
value = Number.objects.values_list('num', 'other_num', named=True).get()
self.assertEqual(value, (72, None))
self.assertEqual(pickle.loads(pickle.dumps(value)), value)
class QuerySetSupportsPythonIdioms(TestCase): class QuerySetSupportsPythonIdioms(TestCase):