Skip to content
Projeler
Gruplar
Parçacıklar
Yardım
Yükleniyor...
Oturum aç / Kaydol
Gezinmeyi değiştir
D
django
Proje
Proje
Ayrıntılar
Etkinlik
Cycle Analytics
Depo (repository)
Depo (repository)
Dosyalar
Kayıtlar (commit)
Dallar (branch)
Etiketler
Katkıda bulunanlar
Grafik
Karşılaştır
Grafikler
Konular (issue)
0
Konular (issue)
0
Liste
Pano
Etiketler
Kilometre Taşları
Birleştirme (merge) Talepleri
0
Birleştirme (merge) Talepleri
0
CI / CD
CI / CD
İş akışları (pipeline)
İşler
Zamanlamalar
Grafikler
Paketler
Paketler
Wiki
Wiki
Parçacıklar
Parçacıklar
Üyeler
Üyeler
Collapse sidebar
Close sidebar
Etkinlik
Grafik
Grafikler
Yeni bir konu (issue) oluştur
İşler
Kayıtlar (commit)
Konu (issue) Panoları
Kenar çubuğunu aç
Batuhan Osman TASKAYA
django
Commits
f3c95621
Kaydet (Commit)
f3c95621
authored
Agu 04, 2017
tarafından
Sergey Fedoseev
Kaydeden (comit)
Tim Graham
Eyl 06, 2017
Dosyalara gözat
Seçenekler
Dosyalara Gözat
İndir
Eposta Yamaları
Sade Fark
Fixed #15648 -- Allowed QuerySet.values_list() to return a namedtuple.
üst
a027447f
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
101 additions
and
5 deletions
+101
-5
query.py
django/db/models/query.py
+46
-4
querysets.txt
docs/ref/models/querysets.txt
+14
-1
2.0.txt
docs/releases/2.0.txt
+3
-0
tests.py
tests/queries/tests.py
+38
-0
No files found.
django/db/models/query.py
Dosyayı görüntüle @
f3c95621
...
@@ -6,8 +6,9 @@ import copy
...
@@ -6,8 +6,9 @@ import copy
import
operator
import
operator
import
sys
import
sys
import
warnings
import
warnings
from
collections
import
OrderedDict
from
collections
import
OrderedDict
,
namedtuple
from
contextlib
import
suppress
from
contextlib
import
suppress
from
functools
import
lru_cache
from
itertools
import
chain
from
itertools
import
chain
from
django.conf
import
settings
from
django.conf
import
settings
...
@@ -137,6 +138,34 @@ class ValuesListIterable(BaseIterable):
...
@@ -137,6 +138,34 @@ class ValuesListIterable(BaseIterable):
return
compiler
.
results_iter
(
tuple_expected
=
True
)
return
compiler
.
results_iter
(
tuple_expected
=
True
)
class
NamedValuesListIterable
(
ValuesListIterable
):
"""
Iterable returned by QuerySet.values_list(named=True) that yields a
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
):
queryset
=
self
.
queryset
if
queryset
.
_fields
:
names
=
queryset
.
_fields
else
:
query
=
queryset
.
query
names
=
list
(
query
.
extra_select
)
names
.
extend
(
query
.
values_select
)
names
.
extend
(
query
.
annotation_select
)
tuple_class
=
self
.
create_namedtuple_class
(
*
names
)
new
=
tuple
.
__new__
for
row
in
super
()
.
__iter__
():
yield
new
(
tuple_class
,
row
)
class
FlatValuesListIterable
(
BaseIterable
):
class
FlatValuesListIterable
(
BaseIterable
):
"""
"""
Iterable returned by QuerySet.values_list(flat=True) that yields single
Iterable returned by QuerySet.values_list(flat=True) that yields single
...
@@ -712,22 +741,35 @@ class QuerySet:
...
@@ -712,22 +741,35 @@ class QuerySet:
clone
.
_iterable_class
=
ValuesIterable
clone
.
_iterable_class
=
ValuesIterable
return
clone
return
clone
def
values_list
(
self
,
*
fields
,
flat
=
False
):
def
values_list
(
self
,
*
fields
,
flat
=
False
,
named
=
False
):
if
flat
and
named
:
raise
TypeError
(
"'flat' and 'named' can't be used together."
)
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."
)
field_names
=
{
f
for
f
in
fields
if
not
hasattr
(
f
,
'resolve_expression'
)}
_fields
=
[]
_fields
=
[]
expressions
=
{}
expressions
=
{}
counter
=
1
for
field
in
fields
:
for
field
in
fields
:
if
hasattr
(
field
,
'resolve_expression'
):
if
hasattr
(
field
,
'resolve_expression'
):
field_id
=
str
(
id
(
field
))
field_id_prefix
=
getattr
(
field
,
'default_alias'
,
field
.
__class__
.
__name__
.
lower
())
while
True
:
field_id
=
field_id_prefix
+
str
(
counter
)
counter
+=
1
if
field_id
not
in
field_names
:
break
expressions
[
field_id
]
=
field
expressions
[
field_id
]
=
field
_fields
.
append
(
field_id
)
_fields
.
append
(
field_id
)
else
:
else
:
_fields
.
append
(
field
)
_fields
.
append
(
field
)
clone
=
self
.
_values
(
*
_fields
,
**
expressions
)
clone
=
self
.
_values
(
*
_fields
,
**
expressions
)
clone
.
_iterable_class
=
FlatValuesListIterable
if
flat
else
ValuesListIterable
clone
.
_iterable_class
=
(
NamedValuesListIterable
if
named
else
FlatValuesListIterable
if
flat
else
ValuesListIterable
)
return
clone
return
clone
def
dates
(
self
,
field_name
,
kind
,
order
=
'ASC'
):
def
dates
(
self
,
field_name
,
kind
,
order
=
'ASC'
):
...
...
docs/ref/models/querysets.txt
Dosyayı görüntüle @
f3c95621
...
@@ -626,7 +626,7 @@ You can also refer to fields on related models with reverse relations through
...
@@ -626,7 +626,7 @@ You can also refer to fields on related models with reverse relations through
``values_list()``
``values_list()``
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
.. method:: values_list(*fields, flat=False)
.. method:: values_list(*fields, flat=False
, named=False
)
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
...
@@ -651,6 +651,15 @@ rather than one-tuples. An example should make the difference clearer::
...
@@ -651,6 +651,15 @@ rather than one-tuples. An example should make the difference clearer::
It is an error to pass in ``flat`` when there is more than one field.
It is an error to pass in ``flat`` when there is more than one field.
You can pass ``named=True`` to get results as a
:func:`~python:collections.namedtuple`::
>>> Entry.objects.values_list('id', 'headline', named=True)
<QuerySet [Row(id=1, headline='First entry'), ...]>
Using a named tuple may make use of the results more readable, at the expense
of a small performance penalty for transforming the results into a named tuple.
If you don't pass any values to ``values_list()``, it will return all the
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.
fields in the model, in the order they were declared.
...
@@ -688,6 +697,10 @@ not having any author::
...
@@ -688,6 +697,10 @@ not having any author::
Support for expressions in ``*fields`` was added.
Support for expressions in ``*fields`` was added.
.. versionchanged:: 2.0
The ``named`` parameter was added.
``dates()``
``dates()``
~~~~~~~~~~~
~~~~~~~~~~~
...
...
docs/releases/2.0.txt
Dosyayı görüntüle @
f3c95621
...
@@ -291,6 +291,9 @@ Models
...
@@ -291,6 +291,9 @@ Models
* Added support for expressions in :attr:`Meta.ordering
* Added support for expressions in :attr:`Meta.ordering
<django.db.models.Options.ordering>`.
<django.db.models.Options.ordering>`.
* The new ``named`` parameter of :meth:`.QuerySet.values_list` allows fetching
results as named tuples.
Pagination
Pagination
~~~~~~~~~~
~~~~~~~~~~
...
...
tests/queries/tests.py
Dosyayı görüntüle @
f3c95621
...
@@ -2248,6 +2248,44 @@ class ValuesQuerysetTests(TestCase):
...
@@ -2248,6 +2248,44 @@ class ValuesQuerysetTests(TestCase):
with
self
.
assertRaisesMessage
(
FieldError
,
msg
):
with
self
.
assertRaisesMessage
(
FieldError
,
msg
):
Tag
.
objects
.
values_list
(
'name__foo'
)
Tag
.
objects
.
values_list
(
'name__foo'
)
def
test_named_values_list_flat
(
self
):
msg
=
"'flat' and 'named' can't be used together."
with
self
.
assertRaisesMessage
(
TypeError
,
msg
):
Number
.
objects
.
values_list
(
'num'
,
flat
=
True
,
named
=
True
)
def
test_named_values_list_bad_field_name
(
self
):
msg
=
"Type names and field names must be valid identifiers: '1'"
with
self
.
assertRaisesMessage
(
ValueError
,
msg
):
Number
.
objects
.
extra
(
select
=
{
'1'
:
'num+1'
})
.
values_list
(
'1'
,
named
=
True
)
.
first
()
def
test_named_values_list_with_fields
(
self
):
qs
=
Number
.
objects
.
extra
(
select
=
{
'num2'
:
'num+1'
})
.
annotate
(
Count
(
'id'
))
values
=
qs
.
values_list
(
'num'
,
'num2'
,
named
=
True
)
.
first
()
self
.
assertEqual
(
type
(
values
)
.
__name__
,
'Row'
)
self
.
assertEqual
(
values
.
_fields
,
(
'num'
,
'num2'
))
self
.
assertEqual
(
values
.
num
,
72
)
self
.
assertEqual
(
values
.
num2
,
73
)
def
test_named_values_list_without_fields
(
self
):
qs
=
Number
.
objects
.
extra
(
select
=
{
'num2'
:
'num+1'
})
.
annotate
(
Count
(
'id'
))
values
=
qs
.
values_list
(
named
=
True
)
.
first
()
self
.
assertEqual
(
type
(
values
)
.
__name__
,
'Row'
)
self
.
assertEqual
(
values
.
_fields
,
(
'num2'
,
'id'
,
'num'
,
'id__count'
))
self
.
assertEqual
(
values
.
num
,
72
)
self
.
assertEqual
(
values
.
num2
,
73
)
self
.
assertEqual
(
values
.
id__count
,
1
)
def
test_named_values_list_expression_with_default_alias
(
self
):
expr
=
Count
(
'id'
)
values
=
Number
.
objects
.
annotate
(
id__count1
=
expr
)
.
values_list
(
expr
,
'id__count1'
,
named
=
True
)
.
first
()
self
.
assertEqual
(
values
.
_fields
,
(
'id__count2'
,
'id__count1'
))
def
test_named_values_list_expression
(
self
):
expr
=
F
(
'num'
)
+
1
qs
=
Number
.
objects
.
annotate
(
combinedexpression1
=
expr
)
.
values_list
(
expr
,
'combinedexpression1'
,
named
=
True
)
values
=
qs
.
first
()
self
.
assertEqual
(
values
.
_fields
,
(
'combinedexpression2'
,
'combinedexpression1'
))
class
QuerySetSupportsPythonIdioms
(
TestCase
):
class
QuerySetSupportsPythonIdioms
(
TestCase
):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment