Fixed #24211 -- Removed ValuesQuerySet() and ValuesListQuerySet().

Thanks Anssi Kääriäinen, Marc Tamlyn, and Tim Graham for the reviews.
This commit is contained in:
Loic Bistuer 2015-01-30 14:26:13 +07:00
parent dbabf43920
commit 4c3bfe9053
10 changed files with 264 additions and 349 deletions

View File

@ -192,7 +192,6 @@ class RelatedGeoModelTest(TestCase):
def test07_values(self):
"Testing values() and values_list() and GeoQuerySets."
# GeoQuerySet and GeoValuesQuerySet, and GeoValuesListQuerySet respectively.
gqs = Location.objects.all()
gvqs = Location.objects.values()
gvlqs = Location.objects.values_list()
@ -264,7 +263,7 @@ class RelatedGeoModelTest(TestCase):
"Testing `Count` aggregate use with the `GeoManager` on non geo-fields. See #11087."
# Should only be one author (Trevor Paglen) returned by this query, and
# the annotation should have 3 for the number of books, see #11087.
# Also testing with a `GeoValuesQuerySet`, see #11489.
# Also testing with a values(), see #11489.
qs = Author.objects.annotate(num_books=Count('books')).filter(num_books__gt=1)
vqs = Author.objects.values('name').annotate(num_books=Count('books')).filter(num_books__gt=1)
self.assertEqual(1, len(qs))

View File

@ -12,7 +12,7 @@ from django.core import exceptions
from django.db import (connections, router, transaction, IntegrityError,
DJANGO_VERSION_PICKLE_KEY)
from django.db.models.constants import LOOKUP_SEP
from django.db.models.fields import AutoField, Empty
from django.db.models.fields import AutoField
from django.db.models.query_utils import Q, deferred_class_factory, InvalidQuery
from django.db.models.deletion import Collector
from django.db.models.sql.constants import CURSOR
@ -34,16 +34,130 @@ REPR_OUTPUT_SIZE = 20
EmptyResultSet = sql.EmptyResultSet
def _pickle_queryset(class_bases, class_dict):
"""
Used by `__reduce__` to create the initial version of the `QuerySet` class
onto which the output of `__getstate__` will be applied.
class BaseIterator(object):
def __init__(self, queryset):
self.queryset = queryset
See `__reduce__` for more details.
class ModelIterator(BaseIterator):
"""
new = Empty()
new.__class__ = type(class_bases[0].__name__, class_bases, class_dict)
return new
Iterator that yields a model instance for each row.
"""
def __iter__(self):
queryset = self.queryset
db = queryset.db
compiler = queryset.query.get_compiler(using=db)
# Execute the query. This will also fill compiler.select, klass_info,
# and annotations.
results = compiler.execute_sql()
select, klass_info, annotation_col_map = (compiler.select, compiler.klass_info,
compiler.annotation_col_map)
if klass_info is None:
return
model_cls = klass_info['model']
select_fields = klass_info['select_fields']
model_fields_start, model_fields_end = select_fields[0], select_fields[-1] + 1
init_list = [f[0].output_field.attname
for f in select[model_fields_start:model_fields_end]]
if len(init_list) != len(model_cls._meta.concrete_fields):
init_set = set(init_list)
skip = [f.attname for f in model_cls._meta.concrete_fields
if f.attname not in init_set]
model_cls = deferred_class_factory(model_cls, skip)
related_populators = get_related_populators(klass_info, select, db)
for row in compiler.results_iter(results):
obj = model_cls.from_db(db, init_list, row[model_fields_start:model_fields_end])
if related_populators:
for rel_populator in related_populators:
rel_populator.populate(row, obj)
if annotation_col_map:
for attr_name, col_pos in annotation_col_map.items():
setattr(obj, attr_name, row[col_pos])
# Add the known related objects to the model, if there are any
if queryset._known_related_objects:
for field, rel_objs in queryset._known_related_objects.items():
# Avoid overwriting objects loaded e.g. by select_related
if hasattr(obj, field.get_cache_name()):
continue
pk = getattr(obj, field.get_attname())
try:
rel_obj = rel_objs[pk]
except KeyError:
pass # may happen in qs1 | qs2 scenarios
else:
setattr(obj, field.name, rel_obj)
yield obj
class ValuesIterator(BaseIterator):
"""
Iterator returned by QuerySet.values() that yields a dict
for each row.
"""
def __iter__(self):
queryset = self.queryset
query = queryset.query
compiler = query.get_compiler(queryset.db)
field_names = list(query.values_select)
extra_names = list(query.extra_select)
annotation_names = list(query.annotation_select)
# extra(select=...) cols are always at the start of the row.
names = extra_names + field_names + annotation_names
for row in compiler.results_iter():
yield dict(zip(names, row))
class ValuesListIterator(BaseIterator):
"""
Iterator returned by QuerySet.values_lists(flat=False)
that yields a tuple for each row.
"""
def __iter__(self):
queryset = self.queryset
query = queryset.query
compiler = query.get_compiler(queryset.db)
if not query.extra_select and not query.annotation_select:
for row in compiler.results_iter():
yield tuple(row)
else:
field_names = list(query.values_select)
extra_names = list(query.extra_select)
annotation_names = list(query.annotation_select)
# extra(select=...) cols are always at the start of the row.
names = extra_names + field_names + annotation_names
if queryset._fields:
# Reorder according to fields.
fields = list(queryset._fields) + [f for f in annotation_names if f not in queryset._fields]
else:
fields = names
for row in compiler.results_iter():
data = dict(zip(names, row))
yield tuple(data[f] for f in fields)
class FlatValuesListIterator(BaseIterator):
"""
Iterator returned by QuerySet.values_lists(flat=True) that
yields single values.
"""
def __iter__(self):
queryset = self.queryset
compiler = queryset.query.get_compiler(queryset.db)
for row in compiler.results_iter():
yield row[0]
class QuerySet(object):
@ -61,7 +175,9 @@ class QuerySet(object):
self._for_write = False
self._prefetch_related_lookups = []
self._prefetch_done = False
self._known_related_objects = {} # {rel_field, {pk: rel_obj}}
self._known_related_objects = {} # {rel_field, {pk: rel_obj}}
self._iterator_class = ModelIterator
self._fields = None
def as_manager(cls):
# Address the circular dependency between `Queryset` and `Manager`.
@ -115,26 +231,6 @@ class QuerySet(object):
self.__dict__.update(state)
def __reduce__(self):
"""
Used by pickle to deal with the types that we create dynamically when
specialized queryset such as `ValuesQuerySet` are used in conjunction
with querysets that are *subclasses* of `QuerySet`.
See `_clone` implementation for more details.
"""
if hasattr(self, '_specialized_queryset_class'):
class_bases = (
self._specialized_queryset_class,
self._base_queryset_class,
)
class_dict = {
'_specialized_queryset_class': self._specialized_queryset_class,
'_base_queryset_class': self._base_queryset_class,
}
return _pickle_queryset, (class_bases, class_dict), self.__getstate__()
return super(QuerySet, self).__reduce__()
def __repr__(self):
data = list(self[:REPR_OUTPUT_SIZE + 1])
if len(data) > REPR_OUTPUT_SIZE:
@ -232,50 +328,7 @@ class QuerySet(object):
An iterator over the results from applying this QuerySet to the
database.
"""
db = self.db
compiler = self.query.get_compiler(using=db)
# Execute the query. This will also fill compiler.select, klass_info,
# and annotations.
results = compiler.execute_sql()
select, klass_info, annotation_col_map = (compiler.select, compiler.klass_info,
compiler.annotation_col_map)
if klass_info is None:
return
model_cls = klass_info['model']
select_fields = klass_info['select_fields']
model_fields_start, model_fields_end = select_fields[0], select_fields[-1] + 1
init_list = [f[0].output_field.attname
for f in select[model_fields_start:model_fields_end]]
if len(init_list) != len(model_cls._meta.concrete_fields):
init_set = set(init_list)
skip = [f.attname for f in model_cls._meta.concrete_fields
if f.attname not in init_set]
model_cls = deferred_class_factory(model_cls, skip)
related_populators = get_related_populators(klass_info, select, db)
for row in compiler.results_iter(results):
obj = model_cls.from_db(db, init_list, row[model_fields_start:model_fields_end])
if related_populators:
for rel_populator in related_populators:
rel_populator.populate(row, obj)
if annotation_col_map:
for attr_name, col_pos in annotation_col_map.items():
setattr(obj, attr_name, row[col_pos])
# Add the known related objects to the model, if there are any
if self._known_related_objects:
for field, rel_objs in self._known_related_objects.items():
# Avoid overwriting objects loaded e.g. by select_related
if hasattr(obj, field.get_cache_name()):
continue
pk = getattr(obj, field.get_attname())
try:
rel_obj = rel_objs[pk]
except KeyError:
pass # may happen in qs1 | qs2 scenarios
else:
setattr(obj, field.name, rel_obj)
yield obj
return self._iterator_class(self)
def aggregate(self, *args, **kwargs):
"""
@ -518,6 +571,9 @@ class QuerySet(object):
assert self.query.can_filter(), \
"Cannot use 'limit' or 'offset' with delete."
if self._fields is not None:
raise TypeError("Cannot call delete() after .values() or .values_list()")
del_query = self._clone()
# The delete is actually 2 queries - one to find related objects,
@ -600,18 +656,64 @@ class QuerySet(object):
params=params, translations=translations,
using=using)
def _values(self, *fields):
clone = self._clone()
clone._fields = fields
query = clone.query
query.select_related = False
query.clear_deferred_loading()
query.clear_select_fields()
if query.group_by is True:
query.add_fields((f.attname for f in self.model._meta.concrete_fields), False)
query.set_group_by()
query.clear_select_fields()
if fields:
field_names = []
extra_names = []
annotation_names = []
if not query._extra and not query._annotations:
# Shortcut - if there are no extra or annotations, then
# the values() clause must be just field names.
field_names = list(fields)
else:
query.default_cols = False
for f in fields:
if f in query.extra_select:
extra_names.append(f)
elif f in query.annotation_select:
annotation_names.append(f)
else:
field_names.append(f)
query.set_extra_mask(extra_names)
query.set_annotation_mask(annotation_names)
else:
field_names = [f.attname for f in self.model._meta.concrete_fields]
query.values_select = field_names
query.add_fields(field_names, True)
return clone
def values(self, *fields):
return self._clone(klass=ValuesQuerySet, setup=True, _fields=fields)
clone = self._values(*fields)
clone._iterator_class = ValuesIterator
return clone
def values_list(self, *fields, **kwargs):
flat = kwargs.pop('flat', False)
if kwargs:
raise TypeError('Unexpected keyword arguments to values_list: %s'
% (list(kwargs),))
if flat and len(fields) > 1:
raise TypeError("'flat' is not valid when values_list is called with more than one field.")
return self._clone(klass=ValuesListQuerySet, setup=True, flat=flat,
_fields=fields)
clone = self._values(*fields)
clone._iterator_class = FlatValuesListIterator if flat else ValuesListIterator
return clone
def dates(self, field_name, kind, order='ASC'):
"""
@ -779,26 +881,26 @@ class QuerySet(object):
annotations[arg.default_alias] = arg
annotations.update(kwargs)
obj = self._clone()
names = getattr(self, '_fields', None)
clone = self._clone()
names = self._fields
if names is None:
names = {f.name for f in self.model._meta.get_fields()}
# Add the annotations to the query
for alias, annotation in annotations.items():
if alias in names:
raise ValueError("The annotation '%s' conflicts with a field on "
"the model." % alias)
obj.query.add_annotation(annotation, alias, is_summary=False)
# expressions need to be added to the query before we know if they contain aggregates
added_aggregates = []
for alias, annotation in obj.query.annotations.items():
if alias in annotations and annotation.contains_aggregate:
added_aggregates.append(alias)
if added_aggregates:
obj._setup_aggregate_query(list(added_aggregates))
clone.query.add_annotation(annotation, alias, is_summary=False)
return obj
for alias, annotation in clone.query.annotations.items():
if alias in annotations and annotation.contains_aggregate:
if clone._fields is None:
clone.query.group_by = True
else:
clone.query.set_group_by()
break
return clone
def order_by(self, *field_names):
"""
@ -848,6 +950,8 @@ class QuerySet(object):
parameter, in which case all deferrals are removed (None acts as a
reset option).
"""
if self._fields is not None:
raise TypeError("Cannot call defer() after .values() or .values_list()")
clone = self._clone()
if fields == (None,):
clone.query.clear_deferred_loading()
@ -861,6 +965,8 @@ class QuerySet(object):
method and that are not already specified as deferred are loaded
immediately when the queryset is evaluated.
"""
if self._fields is not None:
raise TypeError("Cannot call only() after .values() or .values_list()")
if fields == (None,):
# Can only pass None to defer(), not only(), as the rest option.
# That won't stop people trying to do this, so let's be explicit.
@ -934,29 +1040,19 @@ class QuerySet(object):
self.model._base_manager._insert(batch, fields=fields,
using=self.db)
def _clone(self, klass=None, setup=False, **kwargs):
if klass is None:
klass = self.__class__
elif not issubclass(self.__class__, klass):
base_queryset_class = getattr(self, '_base_queryset_class', self.__class__)
class_bases = (klass, base_queryset_class)
class_dict = {
'_base_queryset_class': base_queryset_class,
'_specialized_queryset_class': klass,
}
klass = type(klass.__name__, class_bases, class_dict)
def _clone(self, **kwargs):
query = self.query.clone()
if self._sticky_filter:
query.filter_is_sticky = True
c = klass(model=self.model, query=query, using=self._db, hints=self._hints)
c._for_write = self._for_write
c._prefetch_related_lookups = self._prefetch_related_lookups[:]
c._known_related_objects = self._known_related_objects
c.__dict__.update(kwargs)
if setup and hasattr(c, '_setup_query'):
c._setup_query()
return c
clone = self.__class__(model=self.model, query=query, using=self._db, hints=self._hints)
clone._for_write = self._for_write
clone._prefetch_related_lookups = self._prefetch_related_lookups[:]
clone._known_related_objects = self._known_related_objects
clone._iterator_class = self._iterator_class
clone._fields = self._fields
clone.__dict__.update(kwargs)
return clone
def _fetch_all(self):
if self._result_cache is None:
@ -980,11 +1076,14 @@ class QuerySet(object):
def _merge_sanity_check(self, other):
"""
Checks that we are merging two comparable QuerySet classes. By default
this does nothing, but see the ValuesQuerySet for an example of where
it's useful.
Checks that we are merging two comparable QuerySet classes.
"""
pass
if self._fields is not None and (
set(self.query.values_select) != set(other.query.values_select) or
set(self.query.extra_select) != set(other.query.extra_select) or
set(self.query.annotation_select) != set(other.query.annotation_select)):
raise TypeError("Merging '%s' classes must involve the same values in each case."
% self.__class__.__name__)
def _merge_known_related_objects(self, other):
"""
@ -993,23 +1092,29 @@ class QuerySet(object):
for field, objects in other._known_related_objects.items():
self._known_related_objects.setdefault(field, {}).update(objects)
def _setup_aggregate_query(self, aggregates):
"""
Prepare the query for computing a result that contains aggregate annotations.
"""
if self.query.group_by is None:
self.query.group_by = True
def _prepare(self):
if self._fields is not None:
# values() queryset can only be used as nested queries
# if they are set up to select only a single field.
if len(self._fields or self.model._meta.concrete_fields) > 1:
raise TypeError('Cannot use multi-field values as a filter value.')
return self
def _as_sql(self, connection):
"""
Returns the internal query's SQL and parameters (as a tuple).
"""
obj = self.values("pk")
if obj._db is None or connection == connections[obj._db]:
return obj.query.get_compiler(connection=connection).as_nested_sql()
if self._fields is not None:
# values() queryset can only be used as nested queries
# if they are set up to select only a single field.
if len(self._fields or self.model._meta.concrete_fields) > 1:
raise TypeError('Cannot use multi-field values as a filter value.')
clone = self._clone()
else:
clone = self.values('pk')
if clone._db is None or connection == connections[clone._db]:
return clone.query.get_compiler(connection=connection).as_nested_sql()
raise ValueError("Can't do subqueries with queries on different DBs.")
# When used as part of a nested query, a queryset will never be an "always
@ -1035,6 +1140,9 @@ class QuerySet(object):
def is_compatible_query_object_type(self, opts):
model = self.model
return (
# We trust that users of values() know what they are doing.
self._fields is not None or
# Otherwise check that models are compatible.
model == opts.concrete_model or
opts.concrete_model in model._meta.get_parent_list() or
model in opts.get_parent_list()
@ -1057,195 +1165,6 @@ class EmptyQuerySet(six.with_metaclass(InstanceCheckMeta)):
raise TypeError("EmptyQuerySet can't be instantiated")
class ValuesQuerySet(QuerySet):
def __init__(self, *args, **kwargs):
super(ValuesQuerySet, self).__init__(*args, **kwargs)
# select_related isn't supported in values(). (FIXME -#3358)
self.query.select_related = False
# QuerySet.clone() will also set up the _fields attribute with the
# names of the model fields to select.
def only(self, *fields):
raise NotImplementedError("ValuesQuerySet does not implement only()")
def defer(self, *fields):
raise NotImplementedError("ValuesQuerySet does not implement defer()")
def iterator(self):
# Purge any extra columns that haven't been explicitly asked for
extra_names = list(self.query.extra_select)
field_names = self.field_names
annotation_names = list(self.query.annotation_select)
names = extra_names + field_names + annotation_names
for row in self.query.get_compiler(self.db).results_iter():
yield dict(zip(names, row))
def delete(self):
# values().delete() doesn't work currently - make sure it raises an
# user friendly error.
raise TypeError("Queries with .values() or .values_list() applied "
"can't be deleted")
def _setup_query(self):
"""
Constructs the field_names list that the values query will be
retrieving.
Called by the _clone() method after initializing the rest of the
instance.
"""
if self.query.group_by is True:
self.query.add_fields([f.attname for f in self.model._meta.concrete_fields], False)
self.query.set_group_by()
self.query.clear_deferred_loading()
self.query.clear_select_fields()
if self._fields:
self.extra_names = []
self.annotation_names = []
if not self.query._extra and not self.query._annotations:
# Short cut - if there are no extra or annotations, then
# the values() clause must be just field names.
self.field_names = list(self._fields)
else:
self.query.default_cols = False
self.field_names = []
for f in self._fields:
# we inspect the full extra_select list since we might
# be adding back an extra select item that we hadn't
# had selected previously.
if self.query._extra and f in self.query._extra:
self.extra_names.append(f)
elif f in self.query.annotation_select:
self.annotation_names.append(f)
else:
self.field_names.append(f)
else:
# Default to all fields.
self.extra_names = None
self.field_names = [f.attname for f in self.model._meta.concrete_fields]
self.annotation_names = None
self.query.select = []
if self.extra_names is not None:
self.query.set_extra_mask(self.extra_names)
self.query.add_fields(self.field_names, True)
if self.annotation_names is not None:
self.query.set_annotation_mask(self.annotation_names)
def _clone(self, klass=None, setup=False, **kwargs):
"""
Cloning a ValuesQuerySet preserves the current fields.
"""
c = super(ValuesQuerySet, self)._clone(klass, **kwargs)
if not hasattr(c, '_fields'):
# Only clone self._fields if _fields wasn't passed into the cloning
# call directly.
c._fields = self._fields[:]
c.field_names = self.field_names
c.extra_names = self.extra_names
c.annotation_names = self.annotation_names
if setup and hasattr(c, '_setup_query'):
c._setup_query()
return c
def _merge_sanity_check(self, other):
super(ValuesQuerySet, self)._merge_sanity_check(other)
if (set(self.extra_names) != set(other.extra_names) or
set(self.field_names) != set(other.field_names) or
self.annotation_names != other.annotation_names):
raise TypeError("Merging '%s' classes must involve the same values in each case."
% self.__class__.__name__)
def _setup_aggregate_query(self, aggregates):
"""
Prepare the query for computing a result that contains aggregate annotations.
"""
self.query.set_group_by()
if self.annotation_names is not None:
self.annotation_names.extend(aggregates)
self.query.set_annotation_mask(self.annotation_names)
super(ValuesQuerySet, self)._setup_aggregate_query(aggregates)
def _as_sql(self, connection):
"""
For ValuesQuerySet (and subclasses like ValuesListQuerySet), they can
only be used as nested queries if they're already set up to select only
a single field (in which case, that is the field column that is
returned). This differs from QuerySet.as_sql(), where the column to
select is set up by Django.
"""
if ((self._fields and len(self._fields) > 1) or
(not self._fields and len(self.model._meta.fields) > 1)):
raise TypeError('Cannot use a multi-field %s as a filter value.'
% self.__class__.__name__)
obj = self._clone()
if obj._db is None or connection == connections[obj._db]:
return obj.query.get_compiler(connection=connection).as_nested_sql()
raise ValueError("Can't do subqueries with queries on different DBs.")
def _prepare(self):
"""
Validates that we aren't trying to do a query like
value__in=qs.values('value1', 'value2'), which isn't valid.
"""
if ((self._fields and len(self._fields) > 1) or
(not self._fields and len(self.model._meta.fields) > 1)):
raise TypeError('Cannot use a multi-field %s as a filter value.'
% self.__class__.__name__)
return self
def is_compatible_query_object_type(self, opts):
"""
ValueQuerySets do not need to be checked for compatibility.
We trust that users of ValueQuerySets know what they are doing.
"""
return True
class ValuesListQuerySet(ValuesQuerySet):
def iterator(self):
compiler = self.query.get_compiler(self.db)
if self.flat and len(self._fields) == 1:
for row in compiler.results_iter():
yield row[0]
elif not self.query.extra_select and not self.query.annotation_select:
for row in compiler.results_iter():
yield tuple(row)
else:
# When extra(select=...) or an annotation is involved, the extra
# cols are always at the start of the row, and we need to reorder
# the fields to match the order in self._fields.
extra_names = list(self.query.extra_select)
field_names = self.field_names
annotation_names = list(self.query.annotation_select)
names = extra_names + field_names + annotation_names
# If a field list has been specified, use it. Otherwise, use the
# full list of fields, including extras and annotations.
if self._fields:
fields = list(self._fields) + [f for f in annotation_names if f not in self._fields]
else:
fields = names
for row in compiler.results_iter():
data = dict(zip(names, row))
yield tuple(data[f] for f in fields)
def _clone(self, *args, **kwargs):
clone = super(ValuesListQuerySet, self)._clone(*args, **kwargs)
if not hasattr(clone, "flat"):
# Only assign flat if the clone didn't already get it from kwargs
clone.flat = self.flat
return clone
class RawQuerySet(object):
"""
Provides an iterator which converts the results of raw SQL queries into

View File

@ -148,7 +148,14 @@ class Query(object):
self.distinct_fields = []
self.select_for_update = False
self.select_for_update_nowait = False
self.select_related = False
# Arbitrary limit for select_related to prevents infinite recursion.
self.max_depth = 5
# Holds the selects defined by a call to values() or values_list()
# excluding annotation_select and extra_select.
self.values_select = []
# SQL annotation-related attributes
# The _annotations will be an OrderedDict when used. Due to the cost
@ -158,10 +165,6 @@ class Query(object):
self.annotation_select_mask = None
self._annotation_select_cache = None
# Arbitrary maximum limit for select_related. Prevents infinite
# recursion. Can be changed by the depth parameter to select_related().
self.max_depth = 5
# These are for extensions. The contents are more or less appended
# verbatim to the appropriate clause.
# The _extra attribute is an OrderedDict, lazily created similarly to
@ -273,6 +276,7 @@ class Query(object):
obj.select_for_update = self.select_for_update
obj.select_for_update_nowait = self.select_for_update_nowait
obj.select_related = self.select_related
obj.values_select = self.values_select[:]
obj._annotations = self._annotations.copy() if self._annotations is not None else None
if self.annotation_select_mask is None:
obj.annotation_select_mask = None
@ -1616,6 +1620,7 @@ class Query(object):
columns.
"""
self.select = []
self.values_select = []
def add_select(self, col):
self.default_cols = False

View File

@ -204,7 +204,7 @@ class SubqueryConstraint(object):
if query._db and connection.alias != query._db:
raise ValueError("Can't do subqueries with queries on different DBs.")
# Do not override already existing values.
if not hasattr(query, 'field_names'):
if query._fields is None:
query = query.values(*self.targets)
else:
query = query._clone()

View File

@ -514,8 +514,8 @@ values
.. method:: values(*fields)
Returns a ``ValuesQuerySet`` — a ``QuerySet`` subclass that returns
dictionaries when used as an iterable, rather than model-instance objects.
Returns a ``QuerySet`` that returns dictionaries, rather than model instances,
when used as an iterable.
Each of those dictionaries represents an object, with the keys corresponding to
the attribute names of model objects.
@ -585,14 +585,12 @@ A few subtleties that are worth mentioning:
:meth:`defer()` after ``values()`` was allowed, but it either crashed or
returned incorrect results.
A ``ValuesQuerySet`` is useful when you know you're only going to need values
from a small number of the available fields and you won't need the
functionality of a model instance object. It's more efficient to select only
the fields you need to use.
It is useful when you know you're only going to need values from a small number
of the available fields and you won't need the functionality of a model
instance object. It's more efficient to select only the fields you need to use.
Finally, note that a ``ValuesQuerySet`` is a subclass of ``QuerySet`` and it
implements most of the same methods. You can call ``filter()`` on it,
``order_by()``, etc. That means that these two calls are identical::
Finally, note that you can call ``filter()``, ``order_by()``, etc. after the
``values()`` call, that means that these two calls are identical::
Blog.objects.values().order_by('id')
Blog.objects.order_by('id').values()
@ -645,11 +643,6 @@ It is an error to pass in ``flat`` when there is more than one field.
If you don't pass any values to ``values_list()``, it will return all the
fields in the model, in the order they were declared.
Note that this method returns a ``ValuesListQuerySet``. This class behaves
like a list. Most of the time this is enough, but if you require an actual
Python list object, you can simply call ``list()`` on it, which will evaluate
the queryset.
dates
~~~~~
@ -2280,10 +2273,10 @@ This queryset will be evaluated as subselect statement::
SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')
If you pass in a ``ValuesQuerySet`` or ``ValuesListQuerySet`` (the result of
calling ``values()`` or ``values_list()`` on a queryset) as the value to an
``__in`` lookup, you need to ensure you are only extracting one field in the
result. For example, this will work (filtering on the blog names)::
If you pass in a ``QuerySet`` resulting from ``values()`` or ``values_list()``
as the value to an ``__in`` lookup, you need to ensure you are only extracting
one field in the result. For example, this will work (filtering on the blog
names)::
inner_qs = Blog.objects.filter(name__contains='Ch').values('name')
entries = Entry.objects.filter(blog__name__in=inner_qs)

View File

@ -178,6 +178,8 @@ Miscellaneous
.. _`httplib.responses`: https://docs.python.org/2/library/httplib.html#httplib.responses
* ``ValuesQuerySet`` and ``ValuesListQuerySet`` have been removed.
.. _deprecated-features-1.9:
Features deprecated in 1.9

View File

@ -511,7 +511,7 @@ class AggregationTests(TestCase):
# Regression for #14707 -- If you're using a values query set, some potential conflicts are avoided.
# age is a field on Author, so it shouldn't be allowed as an aggregate.
# But age isn't included in the ValuesQuerySet, so it is.
# But age isn't included in values(), so it is.
results = Author.objects.values('name').annotate(age=Count('book_contact_set')).order_by('name')
self.assertEqual(len(results), 9)
self.assertEqual(results[0]['name'], 'Adrian Holovaty')

View File

@ -10,7 +10,7 @@ from django.db import DatabaseError
from django.db.models.fields import Field
from django.db.models.fields.related import ForeignObjectRel
from django.db.models.manager import BaseManager
from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet, MAX_GET_RESULTS
from django.db.models.query import QuerySet, EmptyQuerySet, MAX_GET_RESULTS
from django.test import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature
from django.utils import six
from django.utils.translation import ugettext_lazy
@ -383,7 +383,6 @@ class ModelTest(TestCase):
with self.assertNumQueries(0):
qs = Article.objects.none().values_list('pk')
self.assertIsInstance(qs, EmptyQuerySet)
self.assertIsInstance(qs, ValuesListQuerySet)
self.assertEqual(len(qs), 0)
def test_emptyqs_customqs(self):

View File

@ -753,12 +753,12 @@ class Queries1Tests(BaseQuerysetTest):
# Multi-valued values() and values_list() querysets should raise errors.
self.assertRaisesMessage(
TypeError,
'Cannot use a multi-field ValuesQuerySet as a filter value.',
'Cannot use multi-field values as a filter value.',
lambda: Tag.objects.filter(name__in=Tag.objects.filter(parent=self.t1).values('name', 'id'))
)
self.assertRaisesMessage(
TypeError,
'Cannot use a multi-field ValuesListQuerySet as a filter value.',
'Cannot use multi-field values as a filter value.',
lambda: Tag.objects.filter(name__in=Tag.objects.filter(parent=self.t1).values_list('name', 'id'))
)
@ -1288,13 +1288,12 @@ class Queries3Tests(BaseQuerysetTest):
)
def test_ticket22023(self):
# only() and defer() are not applicable for ValuesQuerySet
with self.assertRaisesMessage(NotImplementedError,
"ValuesQuerySet does not implement only()"):
with self.assertRaisesMessage(TypeError,
"Cannot call only() after .values() or .values_list()"):
Valid.objects.values().only()
with self.assertRaisesMessage(NotImplementedError,
"ValuesQuerySet does not implement defer()"):
with self.assertRaisesMessage(TypeError,
"Cannot call defer() after .values() or .values_list()"):
Valid.objects.values().defer()

View File

@ -99,8 +99,7 @@ class PickleabilityTestCase(TestCase):
def test_specialized_queryset(self):
self.assert_pickles(Happening.objects.values('name'))
self.assert_pickles(Happening.objects.values('name').dates('when', 'year'))
# ValuesQuerySet with related field (#14515)
# With related field (#14515)
self.assert_pickles(
Event.objects.select_related('group').order_by('title').values_list('title', 'group__name')
)