diff --git a/django/db/models/query.py b/django/db/models/query.py
index 44acadf..ea7c282 100644
a
|
b
|
The main QuerySet implementation. This provides the public API for the ORM.
|
3 | 3 | """ |
4 | 4 | |
5 | 5 | import copy |
| 6 | from collections import namedtuple |
6 | 7 | import itertools |
7 | 8 | import sys |
8 | 9 | |
… |
… |
class ValuesQuerySet(QuerySet):
|
1056 | 1057 | |
1057 | 1058 | |
1058 | 1059 | class ValuesListQuerySet(ValuesQuerySet): |
| 1060 | namedtuple_cache = {} # A map of fieldnames -> tuple to use |
| 1061 | def cached_tuple(self, names): |
| 1062 | names = tuple(names) |
| 1063 | try: |
| 1064 | return self.namedtuple_cache[names] |
| 1065 | except KeyError: |
| 1066 | self.namedtuple_cache[names] = namedtuple('ValuesListRow', |
| 1067 | ' '.join(names)) |
| 1068 | return self.namedtuple_cache[names] |
| 1069 | |
1059 | 1070 | def iterator(self): |
1060 | 1071 | if self.flat and len(self._fields) == 1: |
1061 | 1072 | for row in self.query.get_compiler(self.db).results_iter(): |
1062 | 1073 | yield row[0] |
1063 | 1074 | elif not self.query.extra_select and not self.query.aggregate_select: |
| 1075 | ret_tuple = self.cached_tuple(self.field_names) |
1064 | 1076 | for row in self.query.get_compiler(self.db).results_iter(): |
1065 | | yield tuple(row) |
| 1077 | yield ret_tuple(*row) |
1066 | 1078 | else: |
1067 | 1079 | # When extra(select=...) or an annotation is involved, the extra |
1068 | 1080 | # cols are always at the start of the row, and we need to reorder |
… |
… |
class ValuesListQuerySet(ValuesQuerySet):
|
1080 | 1092 | else: |
1081 | 1093 | fields = names |
1082 | 1094 | |
| 1095 | ret_tuple = self.cached_tuple(fields) |
1083 | 1096 | for row in self.query.get_compiler(self.db).results_iter(): |
1084 | | data = dict(zip(names, row)) |
1085 | | yield tuple([data[f] for f in fields]) |
| 1097 | data = dict(zip(names, row)) |
| 1098 | yield ret_tuple(*[data[k] for k in fields]) |
1086 | 1099 | |
1087 | 1100 | def _clone(self, *args, **kwargs): |
1088 | 1101 | clone = super(ValuesListQuerySet, self)._clone(*args, **kwargs) |
diff --git a/tests/regressiontests/extra_regress/tests.py b/tests/regressiontests/extra_regress/tests.py
index 67efb42..c2962dc 100644
a
|
b
|
class ExtraRegressTests(TestCase):
|
213 | 213 | |
214 | 214 | # Values list works the same way |
215 | 215 | # All columns are returned for an empty values_list() |
| 216 | lst = list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list()) |
216 | 217 | self.assertEqual( |
217 | | list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list()), |
| 218 | lst, |
218 | 219 | [(u'first', u'second', u'third', obj.pk, u'first', u'second', u'third')] |
219 | 220 | ) |
| 221 | # Check that the namedtuple names are correctly assigned |
| 222 | self.assertEqual(lst[0].foo, u'first') |
| 223 | self.assertEqual(lst[0].bar, u'second') |
| 224 | self.assertEqual(lst[0].id, obj.pk) |
| 225 | self.assertEqual(lst[0].third, u'third') |
220 | 226 | |
221 | 227 | # Extra columns after an empty values_list() are still included |
| 228 | lst = list(TestObject.objects.values_list().extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))) |
222 | 229 | self.assertEqual( |
223 | | list(TestObject.objects.values_list().extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), |
| 230 | lst, |
224 | 231 | [(u'first', u'second', u'third', obj.pk, u'first', u'second', u'third')] |
225 | 232 | ) |
| 233 | # Check that the namedtuple names are correctly assigned |
| 234 | self.assertEqual(lst[0].foo, u'first') |
| 235 | self.assertEqual(lst[0].bar, u'second') |
| 236 | self.assertEqual(lst[0].id, obj.pk) |
| 237 | self.assertEqual(lst[0].third, u'third') |
226 | 238 | |
227 | 239 | # Extra columns ignored completely if not mentioned in values_list() |
| 240 | lst = list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second')) |
228 | 241 | self.assertEqual( |
229 | | list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second')), |
230 | | [(u'first', u'second')] |
| 242 | lst, [(u'first', u'second')] |
231 | 243 | ) |
| 244 | # Check that the namedtuple names are correctly assigned |
| 245 | self.assertEqual(lst[0].first, u'first') |
| 246 | self.assertEqual(lst[0].second, u'second') |
232 | 247 | |
233 | 248 | # Extra columns after a non-empty values_list() clause are ignored completely |
234 | 249 | self.assertEqual( |
diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py
index ded3e8f..03dc9fe 100644
a
|
b
|
class ValuesQuerysetTests(BaseQuerysetTest):
|
1639 | 1639 | qs, [72] |
1640 | 1640 | ) |
1641 | 1641 | |
| 1642 | def test_values_list_namedtuples(self): |
| 1643 | Number.objects.create(num=72) |
| 1644 | qs = Number.objects.values_list("num") |
| 1645 | self.assertEqual(qs[0][0], 72) |
| 1646 | self.assertEqual(qs[0].num, 72) |
| 1647 | |
1642 | 1648 | |
1643 | 1649 | class WeirdQuerysetSlicingTests(BaseQuerysetTest): |
1644 | 1650 | def setUp(self): |