Fixed #25871 -- Added expressions support to QuerySet.values().
This commit is contained in:
parent
d4eefc7e2a
commit
39f35d4b9d
|
@ -678,14 +678,17 @@ class QuerySet(object):
|
||||||
using = self.db
|
using = self.db
|
||||||
return RawQuerySet(raw_query, model=self.model, params=params, translations=translations, using=using)
|
return RawQuerySet(raw_query, model=self.model, params=params, translations=translations, using=using)
|
||||||
|
|
||||||
def _values(self, *fields):
|
def _values(self, *fields, **expressions):
|
||||||
clone = self._clone()
|
clone = self._clone()
|
||||||
|
if expressions:
|
||||||
|
clone = clone.annotate(**expressions)
|
||||||
clone._fields = fields
|
clone._fields = fields
|
||||||
clone.query.set_values(fields)
|
clone.query.set_values(fields)
|
||||||
return clone
|
return clone
|
||||||
|
|
||||||
def values(self, *fields):
|
def values(self, *fields, **expressions):
|
||||||
clone = self._values(*fields)
|
fields += tuple(expressions)
|
||||||
|
clone = self._values(*fields, **expressions)
|
||||||
clone._iterable_class = ValuesIterable
|
clone._iterable_class = ValuesIterable
|
||||||
return clone
|
return clone
|
||||||
|
|
||||||
|
@ -697,7 +700,17 @@ class QuerySet(object):
|
||||||
if flat and len(fields) > 1:
|
if flat and len(fields) > 1:
|
||||||
raise TypeError("'flat' is not valid when values_list is called with more than one field.")
|
raise TypeError("'flat' is not valid when values_list is called with more than one field.")
|
||||||
|
|
||||||
clone = self._values(*fields)
|
_fields = []
|
||||||
|
expressions = {}
|
||||||
|
for field in fields:
|
||||||
|
if hasattr(field, 'resolve_expression'):
|
||||||
|
field_id = str(id(field))
|
||||||
|
expressions[field_id] = field
|
||||||
|
_fields.append(field_id)
|
||||||
|
else:
|
||||||
|
_fields.append(field)
|
||||||
|
|
||||||
|
clone = self._values(*_fields, **expressions)
|
||||||
clone._iterable_class = FlatValuesListIterable if flat else ValuesListIterable
|
clone._iterable_class = FlatValuesListIterable if flat else ValuesListIterable
|
||||||
return clone
|
return clone
|
||||||
|
|
||||||
|
|
|
@ -506,7 +506,7 @@ Examples (those after the first will only work on PostgreSQL)::
|
||||||
``values()``
|
``values()``
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
.. method:: values(*fields)
|
.. method:: values(*fields, **expressions)
|
||||||
|
|
||||||
Returns a ``QuerySet`` that returns dictionaries, rather than model instances,
|
Returns a ``QuerySet`` that returns dictionaries, rather than model instances,
|
||||||
when used as an iterable.
|
when used as an iterable.
|
||||||
|
@ -538,6 +538,23 @@ Example::
|
||||||
>>> Blog.objects.values('id', 'name')
|
>>> Blog.objects.values('id', 'name')
|
||||||
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>
|
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>
|
||||||
|
|
||||||
|
The ``values()`` method also takes optional keyword arguments,
|
||||||
|
``**expressions``, which are passed through to :meth:`annotate`::
|
||||||
|
|
||||||
|
>>> from django.db.models.functions import Lower
|
||||||
|
>>> Blog.objects.values(lower_name=Lower('name'))
|
||||||
|
<QuerySet [{'lower_name': 'beatles blog'}]>
|
||||||
|
|
||||||
|
An aggregate within a ``values()`` clause is applied before other arguments
|
||||||
|
within the same ``values()`` clause. If you need to group by another value,
|
||||||
|
add it to an earlier ``values()`` clause instead. For example::
|
||||||
|
|
||||||
|
>>> from django.db.models import Count
|
||||||
|
>>> Blog.objects.values('author', entries=Count('entry'))
|
||||||
|
<QuerySet [{'author': 1, 'entries': 20}, {'author': 1, 'entries': 13}]>
|
||||||
|
>>> Blog.objects.values('author').annotate(entries=Count('entry'))
|
||||||
|
<QuerySet [{'author': 1, 'entries': 33}]>
|
||||||
|
|
||||||
A few subtleties that are worth mentioning:
|
A few subtleties that are worth mentioning:
|
||||||
|
|
||||||
* If you have a field called ``foo`` that is a
|
* If you have a field called ``foo`` that is a
|
||||||
|
@ -603,6 +620,10 @@ You can also refer to fields on related models with reverse relations through
|
||||||
pronounced if you include multiple such fields in your ``values()`` query,
|
pronounced if you include multiple such fields in your ``values()`` query,
|
||||||
in which case all possible combinations will be returned.
|
in which case all possible combinations will be returned.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.11
|
||||||
|
|
||||||
|
Support for ``**expressions`` was added.
|
||||||
|
|
||||||
``values_list()``
|
``values_list()``
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -610,11 +631,14 @@ You can also refer to fields on related models with reverse relations through
|
||||||
|
|
||||||
This is similar to ``values()`` except that instead of returning dictionaries,
|
This is similar to ``values()`` except that instead of returning dictionaries,
|
||||||
it returns tuples when iterated over. Each tuple contains the value from the
|
it returns tuples when iterated over. Each tuple contains the value from the
|
||||||
respective field passed into the ``values_list()`` call — so the first item is
|
respective field or expression passed into the ``values_list()`` call — so the
|
||||||
the first field, etc. For example::
|
first item is the first field, etc. For example::
|
||||||
|
|
||||||
>>> Entry.objects.values_list('id', 'headline')
|
>>> Entry.objects.values_list('id', 'headline')
|
||||||
[(1, 'First entry'), ...]
|
[(1, 'First entry'), ...]
|
||||||
|
>>> from django.db.models.functions import Lower
|
||||||
|
>>> Entry.objects.values_list('id', Lower('headline'))
|
||||||
|
[(1, 'first entry'), ...]
|
||||||
|
|
||||||
If you only pass in a single field, you can also pass in the ``flat``
|
If you only pass in a single field, you can also pass in the ``flat``
|
||||||
parameter. If ``True``, this will mean the returned results are single values,
|
parameter. If ``True``, this will mean the returned results are single values,
|
||||||
|
@ -661,6 +685,10 @@ not having any author::
|
||||||
>>> Entry.objects.values_list('authors')
|
>>> Entry.objects.values_list('authors')
|
||||||
[('Noam Chomsky',), ('George Orwell',), (None,)]
|
[('Noam Chomsky',), ('George Orwell',), (None,)]
|
||||||
|
|
||||||
|
.. versionchanged:: 1.11
|
||||||
|
|
||||||
|
Support for expressions in ``*fields`` was added.
|
||||||
|
|
||||||
``dates()``
|
``dates()``
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -227,6 +227,9 @@ Models
|
||||||
to truncate :class:`~django.db.models.DateTimeField` to its time component
|
to truncate :class:`~django.db.models.DateTimeField` to its time component
|
||||||
and exposed it through the :lookup:`time` lookup.
|
and exposed it through the :lookup:`time` lookup.
|
||||||
|
|
||||||
|
* Added support for expressions in :meth:`.QuerySet.values` and
|
||||||
|
:meth:`~.QuerySet.values_list`.
|
||||||
|
|
||||||
Requests and Responses
|
Requests and Responses
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db.models.aggregates import Sum
|
||||||
|
from django.db.models.expressions import F
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from .models import Company, Employee
|
||||||
|
|
||||||
|
|
||||||
|
class ValuesExpressionsTests(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
Company.objects.create(
|
||||||
|
name='Example Inc.', num_employees=2300, num_chairs=5,
|
||||||
|
ceo=Employee.objects.create(firstname='Joe', lastname='Smith', salary=10)
|
||||||
|
)
|
||||||
|
Company.objects.create(
|
||||||
|
name='Foobar Ltd.', num_employees=3, num_chairs=4,
|
||||||
|
ceo=Employee.objects.create(firstname='Frank', lastname='Meyer', salary=20)
|
||||||
|
)
|
||||||
|
Company.objects.create(
|
||||||
|
name='Test GmbH', num_employees=32, num_chairs=1,
|
||||||
|
ceo=Employee.objects.create(firstname='Max', lastname='Mustermann', salary=30)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_values_expression(self):
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
Company.objects.values(salary=F('ceo__salary')),
|
||||||
|
[{'salary': 10}, {'salary': 20}, {'salary': 30}],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_values_expression_group_by(self):
|
||||||
|
# values() applies annotate() first, so values selected are grouped by
|
||||||
|
# id, not firstname.
|
||||||
|
Employee.objects.create(firstname='Joe', lastname='Jones', salary=2)
|
||||||
|
joes = Employee.objects.filter(firstname='Joe')
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
joes.values('firstname', sum_salary=Sum('salary')).order_by('sum_salary'),
|
||||||
|
[{'firstname': 'Joe', 'sum_salary': 2}, {'firstname': 'Joe', 'sum_salary': 10}],
|
||||||
|
)
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
joes.values('firstname').annotate(sum_salary=Sum('salary')),
|
||||||
|
[{'firstname': 'Joe', 'sum_salary': 12}]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_chained_values_with_expression(self):
|
||||||
|
Employee.objects.create(firstname='Joe', lastname='Jones', salary=2)
|
||||||
|
joes = Employee.objects.filter(firstname='Joe').values('firstname')
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
joes.values('firstname', sum_salary=Sum('salary')),
|
||||||
|
[{'firstname': 'Joe', 'sum_salary': 12}]
|
||||||
|
)
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
joes.values(sum_salary=Sum('salary')),
|
||||||
|
[{'sum_salary': 12}]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_values_list_expression(self):
|
||||||
|
companies = Company.objects.values_list('name', F('ceo__salary'))
|
||||||
|
self.assertSequenceEqual(companies, [('Example Inc.', 10), ('Foobar Ltd.', 20), ('Test GmbH', 30)])
|
||||||
|
|
||||||
|
def test_values_list_expression_flat(self):
|
||||||
|
companies = Company.objects.values_list(F('ceo__salary'), flat=True)
|
||||||
|
self.assertSequenceEqual(companies, (10, 20, 30))
|
Loading…
Reference in New Issue