Ticket #16735: column_alias.diff
File column_alias.diff, 33.0 KB (added by , 13 years ago) |
---|
-
django/db/models/query.py
From 5d851b8effbbfd6b8189a8d43e64a0001a8f65fd Mon Sep 17 00:00:00 2001 From: Nate Bragg <jonathan.bragg@alum.rpi.edu> Date: Sun, 15 Jan 2012 19:48:48 -0500 Subject: [PATCH] Adds column aliases to values. Also some general cleanup. --- django/db/models/query.py | 65 +++++++------- django/db/models/sql/compiler.py | 31 +++--- django/db/models/sql/query.py | 67 ++++++------- docs/ref/models/querysets.txt | 22 ++++- tests/modeltests/lookup/tests.py | 190 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 290 insertions(+), 85 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 41c24c7..6b987ba 100644
a b class QuerySet(object): 571 571 # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS # 572 572 ################################################## 573 573 574 def values(self, *fields ):575 return self._clone(klass=ValuesQuerySet, setup=True, _fields=fields )574 def values(self, *fields, **aliased_fields): 575 return self._clone(klass=ValuesQuerySet, setup=True, _fields=fields, _aliased_fields=aliased_fields) 576 576 577 577 def values_list(self, *fields, **kwargs): 578 578 flat = kwargs.pop('flat', False) 579 if kwargs: 580 raise TypeError('Unexpected keyword arguments to values_list: %s' 581 % (kwargs.keys(),)) 582 if flat and len(fields) > 1: 579 if flat and len(fields) + len(kwargs) > 1: 583 580 raise TypeError("'flat' is not valid when values_list is called with more than one field.") 584 581 return self._clone(klass=ValuesListQuerySet, setup=True, flat=flat, 585 _fields=fields )582 _fields=fields, _aliased_fields=kwargs) 586 583 587 584 def dates(self, field_name, kind, order='ASC'): 588 585 """ … … class QuerySet(object): 736 733 737 734 obj = self._clone() 738 735 739 obj._setup_aggregate_query( kwargs.keys())736 obj._setup_aggregate_query((k,None) for k in kwargs) 740 737 741 738 # Add the aggregates to the query 742 739 for (alias, aggregate_expr) in kwargs.items(): … … class QuerySet(object): 904 901 """ 905 902 opts = self.model._meta 906 903 if self.query.group_by is None: 907 field_names = [ f.attnamefor f in opts.fields]904 field_names = [(f.attname,None) for f in opts.fields] 908 905 self.query.add_fields(field_names, False) 909 906 self.query.set_group_by() 910 907 … … class QuerySet(object): 924 921 # empty" result. 925 922 value_annotation = True 926 923 927 928 924 class ValuesQuerySet(QuerySet): 929 925 def __init__(self, *args, **kwargs): 930 926 super(ValuesQuerySet, self).__init__(*args, **kwargs) 931 927 # select_related isn't supported in values(). (FIXME -#3358) 932 928 self.query.select_related = False 933 929 934 # QuerySet.clone() will also set up the _fields a ttributewith the930 # QuerySet.clone() will also set up the _fields and _aliased_fields attributes with the 935 931 # names of the model fields to select. 936 932 937 933 def iterator(self): … … class ValuesQuerySet(QuerySet): 940 936 field_names = self.field_names 941 937 aggregate_names = self.query.aggregate_select.keys() 942 938 943 names = extra_names + field_names+ aggregate_names939 names = extra_names + [c_alias or c_name for c_name, c_alias in field_names] + aggregate_names 944 940 945 941 for row in self.query.get_compiler(self.db).results_iter(): 946 942 yield dict(zip(names, row)) … … class ValuesQuerySet(QuerySet): 956 952 self.query.clear_deferred_loading() 957 953 self.query.clear_select_fields() 958 954 959 if self._fields :955 if self._fields or self._aliased_fields: 960 956 self.extra_names = [] 961 957 self.aggregate_names = [] 958 all_fields = [(f,None) for f in self._fields] 959 all_fields.extend((f,a) for a,f in self._aliased_fields.items()) 962 960 if not self.query.extra and not self.query.aggregates: 963 961 # Short cut - if there are no extra or aggregates, then 964 962 # the values() clause must be just field names. 965 self.field_names = list(self._fields)963 self.field_names = all_fields 966 964 else: 967 965 self.query.default_cols = False 968 966 self.field_names = [] 969 for f in self._fields:967 for col_name, col_alias in all_fields: 970 968 # we inspect the full extra_select list since we might 971 969 # be adding back an extra select item that we hadn't 972 970 # had selected previously. 973 if fin self.query.extra:974 self.extra_names.append( f)975 elif fin self.query.aggregate_select:976 self.aggregate_names.append( f)971 if col_name in self.query.extra: 972 self.extra_names.append((col_name,col_alias)) 973 elif col_name in self.query.aggregate_select: 974 self.aggregate_names.append((col_name,col_alias)) 977 975 else: 978 self.field_names.append( f)976 self.field_names.append((col_name,col_alias)) 979 977 else: 980 978 # Default to all fields. 981 979 self.extra_names = None 982 self.field_names = [ f.attnamefor f in self.model._meta.fields]980 self.field_names = [(f.attname,None) for f in self.model._meta.fields] 983 981 self.aggregate_names = None 984 982 985 983 self.query.select = [] … … class ValuesQuerySet(QuerySet): 998 996 # Only clone self._fields if _fields wasn't passed into the cloning 999 997 # call directly. 1000 998 c._fields = self._fields[:] 1001 c.field_names = self.field_names 1002 c.extra_names = self.extra_names 1003 c.aggregate_names = self.aggregate_names 999 if not hasattr(c, '_aliased_fields'): 1000 # Same with _aliased_fields as _fields above 1001 c._aliased_fields = self._aliased_fields.copy() 1002 c.field_names = None if self.field_names is None else self.field_names[:] 1003 c.extra_names = None if self.extra_names is None else self.extra_names[:] 1004 c.aggregate_names = None if self.aggregate_names is None else self.aggregate_names[:] 1004 1005 if setup and hasattr(c, '_setup_query'): 1005 1006 c._setup_query() 1006 1007 return c … … class ValuesQuerySet(QuerySet): 1033 1034 returned). This differs from QuerySet.as_sql(), where the column to 1034 1035 select is set up by Django. 1035 1036 """ 1036 if (( self._fields and len(self._fields) > 1) or1037 (not self._fields and len(self.model._meta.fields) > 1)):1037 if (( (self._fields or self._aliased_fields) and len(self._fields) + len(self._aliased_fields) > 1) or 1038 (not self._fields and not self._aliased_fields and len(self.model._meta.fields) > 1)): 1038 1039 raise TypeError('Cannot use a multi-field %s as a filter value.' 1039 1040 % self.__class__.__name__) 1040 1041 … … class ValuesQuerySet(QuerySet): 1048 1049 Validates that we aren't trying to do a query like 1049 1050 value__in=qs.values('value1', 'value2'), which isn't valid. 1050 1051 """ 1051 if (( self._fields and len(self._fields) > 1) or1052 (not self._fields and len(self.model._meta.fields) > 1)):1052 if (( (self._fields or self._aliased_fields) and len(self._fields) + len(self._aliased_fields) > 1) or 1053 (not self._fields and not self._aliased_fields and len(self.model._meta.fields) > 1)): 1053 1054 raise TypeError('Cannot use a multi-field %s as a filter value.' 1054 1055 % self.__class__.__name__) 1055 1056 return self 1056 1057 1057 1058 1058 class ValuesListQuerySet(ValuesQuerySet): 1059 1059 def iterator(self): 1060 if self.flat and len(self._fields) == 1:1060 if self.flat and len(self._fields) + len(self._aliased_fields) == 1: 1061 1061 for row in self.query.get_compiler(self.db).results_iter(): 1062 1062 yield row[0] 1063 1063 elif not self.query.extra_select and not self.query.aggregate_select: … … class ValuesListQuerySet(ValuesQuerySet): 1071 1071 field_names = self.field_names 1072 1072 aggregate_names = self.query.aggregate_select.keys() 1073 1073 1074 names = extra_names + field_names+ aggregate_names1074 names = extra_names + [c_alias or c_name for c_name,c_alias in field_names] + aggregate_names 1075 1075 1076 1076 # If a field list has been specified, use it. Otherwise, use the 1077 1077 # full list of fields, including extras and aggregates. 1078 1078 if self._fields: 1079 fields = list(self._fields) + filter(lambda f: f not in self._fields, aggregate_names) 1079 fields = list(self._fields) + self._aliased_fields.keys() 1080 fields.extend(f for f in aggregate_names if not (f in self._fields or f in self._aliased_fields)) 1080 1081 else: 1081 1082 fields = names 1082 1083 -
django/db/models/sql/compiler.py
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 72948f9..6678ce2 100644
a b class SQLCompiler(object): 187 187 only_load = self.deferred_to_columns() 188 188 for col in self.query.select: 189 189 if isinstance(col, (list, tuple)): 190 alias, column = col190 alias, column, column_alias = col 191 191 table = self.query.alias_map[alias][TABLE_NAME] 192 192 if table in only_load and column not in only_load[table]: 193 193 continue 194 194 r = '%s.%s' % (qn(alias), qn(column)) 195 if with_aliases: 196 if col[1] in col_aliases: 195 a = r 196 c = column 197 if column_alias or with_aliases: 198 if column_alias in col_aliases or column in col_aliases: 197 199 c_alias = 'Col%d' % len(col_aliases) 198 result.append('%s AS %s' % (r, c_alias)) 199 aliases.add(c_alias) 200 col_aliases.add(c_alias) 200 a = c_alias 201 201 else: 202 result.append('%s AS %s' % (r, qn2(col[1])))203 a liases.add(r)204 col_aliases.add(col[1])205 else:206 207 aliases.add(r)208 col_aliases.add(col[1])202 c_alias = column_alias or column 203 a = column_alias or a 204 r = '%s AS %s' % (r, qn2(c_alias)) 205 c = c_alias 206 result.append(r) 207 aliases.add(a) 208 col_aliases.add(c) 209 209 else: 210 210 result.append(col.as_sql(qn, self.connection)) 211 211 … … class SQLCompiler(object): 381 381 group_by.append((field, [])) 382 382 continue 383 383 col, order = get_order_dir(field, asc) 384 if col in self.query.aggregate_select: 384 if (col in self.query.aggregate_select or 385 col in (a for _,_,a in self.query.select if a)): 385 386 result.append('%s %s' % (qn(col), order)) 386 387 continue 387 388 if '.' in field: … … class SQLUpdateCompiler(SQLCompiler): 1017 1018 query.bump_prefix() 1018 1019 query.extra = {} 1019 1020 query.select = [] 1020 query.add_fields([ query.model._meta.pk.name])1021 query.add_fields([(query.model._meta.pk.name,None)]) 1021 1022 must_pre_select = count > 1 and not self.connection.features.update_can_self_select 1022 1023 1023 1024 # Now we adjust the current query: reset the where clause and get rid -
django/db/models/sql/query.py
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index ed2bc06..323e45e 100644
a b class Query(object): 258 258 obj.tables = self.tables[:] 259 259 obj.where = copy.deepcopy(self.where, memo=memo) 260 260 obj.where_class = self.where_class 261 if self.group_by is None: 262 obj.group_by = None 263 else: 264 obj.group_by = self.group_by[:] 261 obj.group_by = None if self.group_by is None else self.group_by[:] 265 262 obj.having = copy.deepcopy(self.having, memo=memo) 266 263 obj.order_by = self.order_by[:] 267 264 obj.low_mark, obj.high_mark = self.low_mark, self.high_mark … … class Query(object): 272 269 obj.select_related = self.select_related 273 270 obj.related_select_cols = [] 274 271 obj.aggregates = copy.deepcopy(self.aggregates, memo=memo) 275 if self.aggregate_select_mask is None: 276 obj.aggregate_select_mask = None 277 else: 278 obj.aggregate_select_mask = self.aggregate_select_mask.copy() 272 obj.aggregate_select_mask = None if self.aggregate_select_mask is None else self.aggregate_select_mask.copy() 279 273 # _aggregate_select_cache cannot be copied, as doing so breaks the 280 274 # (necessary) state in which both aggregates and 281 275 # _aggregate_select_cache point to the same underlying objects. … … class Query(object): 284 278 obj._aggregate_select_cache = None 285 279 obj.max_depth = self.max_depth 286 280 obj.extra = self.extra.copy() 287 if self.extra_select_mask is None: 288 obj.extra_select_mask = None 289 else: 290 obj.extra_select_mask = self.extra_select_mask.copy() 291 if self._extra_select_cache is None: 292 obj._extra_select_cache = None 293 else: 294 obj._extra_select_cache = self._extra_select_cache.copy() 281 obj.extra_select_mask = None if self.extra_select_mask is None else self.extra_select_mask.copy() 282 obj._extra_select_cache = None if self._extra_select_cache is None else self._extra_select_cache.copy() 295 283 obj.extra_tables = self.extra_tables 296 284 obj.extra_order_by = self.extra_order_by 297 285 obj.deferred_loading = copy.deepcopy(self.deferred_loading, memo=memo) … … class Query(object): 433 421 q.select_fields = [] 434 422 q.default_cols = False 435 423 q.select_related = False 436 q.set_extra_mask( ('a',))424 q.set_extra_mask([('a',None)]) 437 425 q.set_aggregate_mask(()) 438 426 q.clear_ordering(True) 439 427 q.set_limits(high=1) … … class Query(object): 527 515 self.select = [] 528 516 for col in rhs.select: 529 517 if isinstance(col, (list, tuple)): 530 self.select.append((change_map.get(col[0], col[0]), col[1])) 518 old_alias, column, column_alias = col 519 self.select.append((change_map.get(old_alias, old_alias), column, column_alias)) 531 520 else: 532 521 item = copy.deepcopy(col) 533 522 item.relabel_aliases(change_map) … … class Query(object): 756 745 for columns in [self.select, self.group_by or []]: 757 746 for pos, col in enumerate(columns): 758 747 if isinstance(col, (list, tuple)): 759 old_alias = col[0]760 columns[pos] = (change_map.get(old_alias, old_alias), col [1])748 old_alias, column, column_alias = col 749 columns[pos] = (change_map.get(old_alias, old_alias), column, column_alias) 761 750 else: 762 751 col.relabel_aliases(change_map) 763 752 for mapping in [self.aggregates]: … … class Query(object): 1159 1148 self.promote_alias_chain(table_it, table_promote or join_promote) 1160 1149 1161 1150 if having_clause or force_having: 1162 if (alias, col ) not in self.group_by:1163 self.group_by.append((alias, col ))1151 if (alias, col, None) not in self.group_by: 1152 self.group_by.append((alias, col, None)) 1164 1153 self.having.add((Constraint(alias, col, field), lookup_type, value), 1165 1154 connector) 1166 1155 else: … … class Query(object): 1548 1537 # since we are adding a IN <subquery> clause. This prevents the 1549 1538 # database from tripping over IN (...,NULL,...) selects and returning 1550 1539 # nothing 1551 alias, col = query.select[0]1540 alias, col, _ = query.select[0] 1552 1541 query.where.add((Constraint(alias, col, None), 'isnull', False), AND) 1553 1542 1554 1543 self.add_filter(('%s__in' % prefix, query), negate=True, trim=True, … … class Query(object): 1627 1616 opts = self.get_meta() 1628 1617 1629 1618 try: 1630 for name in field_names:1619 for name, col_alias in field_names: 1631 1620 field, target, u2, joins, u3, u4 = self.setup_joins( 1632 1621 name.split(LOOKUP_SEP), opts, alias, False, allow_m2m, 1633 1622 True) … … class Query(object): 1641 1630 col = join[LHS_JOIN_COL] 1642 1631 joins = joins[:-1] 1643 1632 self.promote_alias_chain(joins[1:]) 1644 self.select.append((final_alias, col ))1633 self.select.append((final_alias, col, col_alias)) 1645 1634 self.select_fields.append(field) 1646 1635 except MultiJoin: 1647 1636 raise FieldError("Invalid field name: '%s'" % name) … … class Query(object): 1707 1696 else: 1708 1697 assert len(self.select) == 1, \ 1709 1698 "Cannot add count col with multiple cols in 'select': %r" % self.select 1710 count = self.aggregates_module.Count(self.select[0]) 1699 sel = self.select[0] 1700 sel = (sel[0],sel[1]) if isinstance(sel,(list,tuple)) else sel 1701 count = self.aggregates_module.Count(sel) 1711 1702 else: 1712 1703 opts = self.model._meta 1713 1704 if not self.select: … … class Query(object): 1719 1710 assert len(self.select) == 1, \ 1720 1711 "Cannot add count col with multiple cols in 'select'." 1721 1712 1722 count = self.aggregates_module.Count(self.select[0], distinct=True) 1713 sel = self.select[0] 1714 sel = (sel[0],sel[1]) if isinstance(sel,(list,tuple)) else sel 1715 count = self.aggregates_module.Count(sel, distinct=True) 1723 1716 # Distinct handling is done in Count(), so don't do it at this 1724 1717 # level. 1725 1718 self.distinct = False … … class Query(object): 1875 1868 if self._aggregate_select_cache is not None: 1876 1869 return self._aggregate_select_cache 1877 1870 elif self.aggregate_select_mask is not None: 1878 self._aggregate_select_cache = SortedDict([ 1879 (k,v) for k,v in self.aggregates.items() 1880 if k in self.aggregate_select_mask 1881 ]) 1871 self._aggregate_select_cache = SortedDict( 1872 (a or k,v) for k,v in self.aggregates.items() 1873 for n,a in self.aggregate_select_mask 1874 if k==n 1875 ) 1882 1876 return self._aggregate_select_cache 1883 1877 else: 1884 1878 return self.aggregates … … class Query(object): 1888 1882 if self._extra_select_cache is not None: 1889 1883 return self._extra_select_cache 1890 1884 elif self.extra_select_mask is not None: 1891 self._extra_select_cache = SortedDict([ 1892 (k,v) for k,v in self.extra.items() 1893 if k in self.extra_select_mask 1894 ]) 1885 self._extra_select_cache = SortedDict( 1886 (a or k,v) for k,v in self.extra.items() 1887 for n,a in self.extra_select_mask 1888 if k==n 1889 ) 1895 1890 return self._extra_select_cache 1896 1891 else: 1897 1892 return self.extra … … class Query(object): 1931 1926 self.unref_alias(select_alias) 1932 1927 select_alias = join_info[RHS_ALIAS] 1933 1928 select_col = join_info[RHS_JOIN_COL] 1934 self.select = [(select_alias, select_col )]1929 self.select = [(select_alias, select_col, None)] 1935 1930 self.remove_inherited_models() 1936 1931 1937 1932 -
docs/ref/models/querysets.txt
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 7633555..e8cd96d 100644
a b Examples:: 420 420 values 421 421 ~~~~~~ 422 422 423 .. method:: values(*fields )423 .. method:: values(*fields,**aliased_fields) 424 424 425 425 Returns a ``ValuesQuerySet`` — a ``QuerySet`` subclass that returns 426 426 dictionaries when used as an iterable, rather than model-instance objects. … … Example:: 452 452 >>> Blog.objects.values('id', 'name') 453 453 [{'id': 1, 'name': 'Beatles Blog'}] 454 454 455 Additionally, the ``values()`` method takes optional keyword arguments, 456 ``**aliased_fields``, which give aliases under which the ``SELECT`` retrieves the 457 fields. If used, the returned dictionary will contain the values of the fields 458 under the corresponding alias provided. 459 460 Example:: 461 462 >>> Blog.objects.values('id', blog_name='name') 463 [{'id': 1, 'blog_name': 'Beatles Blog'}] 464 455 465 A few subtleties that are worth mentioning: 456 466 457 467 * If you have a field called ``foo`` that is a … … and ``ManyToManyField`` attributes:: 526 536 values_list 527 537 ~~~~~~~~~~~ 528 538 529 .. method:: values_list(*fields )539 .. method:: values_list(*fields,**aliased_fields) 530 540 531 541 This is similar to ``values()`` except that instead of returning dictionaries, 532 542 it returns tuples when iterated over. Each tuple contains the value from the … … the first field, etc. For example:: 536 546 >>> Entry.objects.values_list('id', 'headline') 537 547 [(1, u'First entry'), ...] 538 548 549 Like ``values()``, you can assign the columns aliases by passing them in as 550 keyword arguments (the exception being ``flat``, which is reserved; see below). 551 However, while these aliases can be used by chained calls (say, to ``order_by``), 552 by itself aliasing has little effect (though it does trickle down to the query): 553 554 >>> Entry.objects.values_list('id', hl='headline') 555 [(1, u'First entry'), ...] 556 539 557 If you only pass in a single field, you can also pass in the ``flat`` 540 558 parameter. If ``True``, this will mean the returned results are single values, 541 559 rather than one-tuples. An example should make the difference clearer:: -
tests/modeltests/lookup/tests.py
diff --git a/tests/modeltests/lookup/tests.py b/tests/modeltests/lookup/tests.py index 9c2b0c6..728bc46 100644
a b class LookupTests(TestCase): 252 252 'pub_date': datetime(2005, 8, 1, 9, 0) 253 253 }], transform=identity) 254 254 255 def test_values_aliases(self): 256 # values() returns a list of dictionaries instead of object instances -- 257 # and you can specify which fields you want to retrieve. 258 identity = lambda x:x 259 self.assertQuerysetEqual(Article.objects.values(title='headline'), 260 [ 261 {'title': u'Article 5'}, 262 {'title': u'Article 6'}, 263 {'title': u'Article 4'}, 264 {'title': u'Article 2'}, 265 {'title': u'Article 3'}, 266 {'title': u'Article 7'}, 267 {'title': u'Article 1'}, 268 ], 269 transform=identity) 270 self.assertQuerysetEqual( 271 Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).values(primary_key='id'), 272 [{'primary_key': self.a2.id}, {'primary_key': self.a3.id}, {'primary_key': self.a7.id}], 273 transform=identity) 274 self.assertQuerysetEqual(Article.objects.values(primary_key='id', title='headline'), 275 [ 276 {'primary_key': self.a5.id, 'title': 'Article 5'}, 277 {'primary_key': self.a6.id, 'title': 'Article 6'}, 278 {'primary_key': self.a4.id, 'title': 'Article 4'}, 279 {'primary_key': self.a2.id, 'title': 'Article 2'}, 280 {'primary_key': self.a3.id, 'title': 'Article 3'}, 281 {'primary_key': self.a7.id, 'title': 'Article 7'}, 282 {'primary_key': self.a1.id, 'title': 'Article 1'}, 283 ], 284 transform=identity) 285 # You can use values() with iterator() for memory savings, 286 # because iterator() uses database-level iteration. 287 self.assertQuerysetEqual(Article.objects.values(primary_key='id', title='headline').iterator(), 288 [ 289 {'primary_key': self.a5.id, 'title': 'Article 5'}, 290 {'primary_key': self.a6.id, 'title': 'Article 6'}, 291 {'primary_key': self.a4.id, 'title': 'Article 4'}, 292 {'primary_key': self.a2.id, 'title': 'Article 2'}, 293 {'primary_key': self.a3.id, 'title': 'Article 3'}, 294 {'primary_key': self.a7.id, 'title': 'Article 7'}, 295 {'primary_key': self.a1.id, 'title': 'Article 1'}, 296 ], 297 transform=identity) 298 # The values() method works with "extra" fields specified in extra(select). 299 self.assertQuerysetEqual( 300 Article.objects.extra(select={'id_plus_one': 'id + 1'}).values('id_plus_one', primary_key='id'), 301 [ 302 {'primary_key': self.a5.id, 'id_plus_one': self.a5.id + 1}, 303 {'primary_key': self.a6.id, 'id_plus_one': self.a6.id + 1}, 304 {'primary_key': self.a4.id, 'id_plus_one': self.a4.id + 1}, 305 {'primary_key': self.a2.id, 'id_plus_one': self.a2.id + 1}, 306 {'primary_key': self.a3.id, 'id_plus_one': self.a3.id + 1}, 307 {'primary_key': self.a7.id, 'id_plus_one': self.a7.id + 1}, 308 {'primary_key': self.a1.id, 'id_plus_one': self.a1.id + 1}, 309 ], 310 transform=identity) 311 data = { 312 'id_plus_one': 'id+1', 313 'id_plus_two': 'id+2', 314 'id_plus_three': 'id+3', 315 'id_plus_four': 'id+4', 316 'id_plus_five': 'id+5', 317 'id_plus_six': 'id+6', 318 'id_plus_seven': 'id+7', 319 'id_plus_eight': 'id+8', 320 } 321 self.assertQuerysetEqual( 322 Article.objects.filter(id=self.a1.id).extra(select=data).values(**dict([(k+'_',k) for k in data.keys()])), 323 [{ 324 'id_plus_one_': self.a1.id + 1, 325 'id_plus_two_': self.a1.id + 2, 326 'id_plus_three_': self.a1.id + 3, 327 'id_plus_four_': self.a1.id + 4, 328 'id_plus_five_': self.a1.id + 5, 329 'id_plus_six_': self.a1.id + 6, 330 'id_plus_seven_': self.a1.id + 7, 331 'id_plus_eight_': self.a1.id + 8, 332 }], transform=identity) 333 # You can specify fields from forward and reverse relations, just like filter(). 334 self.assertQuerysetEqual( 335 Article.objects.values('headline', a_name='author__name'), 336 [ 337 {'headline': self.a5.headline, 'a_name': self.au2.name}, 338 {'headline': self.a6.headline, 'a_name': self.au2.name}, 339 {'headline': self.a4.headline, 'a_name': self.au1.name}, 340 {'headline': self.a2.headline, 'a_name': self.au1.name}, 341 {'headline': self.a3.headline, 'a_name': self.au1.name}, 342 {'headline': self.a7.headline, 'a_name': self.au2.name}, 343 {'headline': self.a1.headline, 'a_name': self.au1.name}, 344 ], transform=identity) 345 self.assertQuerysetEqual( 346 Author.objects.values('name', headline='article__headline').order_by('name', 'headline'), 347 [ 348 {'name': self.au1.name, 'headline': self.a1.headline}, 349 {'name': self.au1.name, 'headline': self.a2.headline}, 350 {'name': self.au1.name, 'headline': self.a3.headline}, 351 {'name': self.au1.name, 'headline': self.a4.headline}, 352 {'name': self.au2.name, 'headline': self.a5.headline}, 353 {'name': self.au2.name, 'headline': self.a6.headline}, 354 {'name': self.au2.name, 'headline': self.a7.headline}, 355 ], transform=identity) 356 self.assertQuerysetEqual( 357 Author.objects.values('name', headline='article__headline', tag_name='article__tag__name').order_by('name', 'headline', 'tag_name'), 358 [ 359 {'name': self.au1.name, 'headline': self.a1.headline, 'tag_name': self.t1.name}, 360 {'name': self.au1.name, 'headline': self.a2.headline, 'tag_name': self.t1.name}, 361 {'name': self.au1.name, 'headline': self.a3.headline, 'tag_name': self.t1.name}, 362 {'name': self.au1.name, 'headline': self.a3.headline, 'tag_name': self.t2.name}, 363 {'name': self.au1.name, 'headline': self.a4.headline, 'tag_name': self.t2.name}, 364 {'name': self.au2.name, 'headline': self.a5.headline, 'tag_name': self.t2.name}, 365 {'name': self.au2.name, 'headline': self.a5.headline, 'tag_name': self.t3.name}, 366 {'name': self.au2.name, 'headline': self.a6.headline, 'tag_name': self.t3.name}, 367 {'name': self.au2.name, 'headline': self.a7.headline, 'tag_name': self.t3.name}, 368 ], transform=identity) 369 # However, an exception FieldDoesNotExist will be thrown if you specify 370 # a non-existent field name in values() (a field that is neither in the 371 # model nor in extra(select)). 372 self.assertRaises(FieldError, 373 Article.objects.extra(select={'id_plus_one': 'id + 1'}).values, 374 'id', id_incremented='id_plus_two') 375 376 def test_values_list_aliases(self): 377 # values_list() is similar to values(), except that the results are 378 # returned as a list of tuples, rather than a list of dictionaries. 379 # Within each tuple, the order of the elements is the same as the order 380 # of fields in the values_list() call. 381 identity = lambda x:x 382 self.assertQuerysetEqual(Article.objects.values_list(alias_doesnt_matter='headline'), 383 [ 384 (u'Article 5',), 385 (u'Article 6',), 386 (u'Article 4',), 387 (u'Article 2',), 388 (u'Article 3',), 389 (u'Article 7',), 390 (u'Article 1',), 391 ], transform=identity) 392 self.assertQuerysetEqual(Article.objects.values_list(id_alias='id').order_by('id_alias'), 393 [(self.a1.id,), (self.a2.id,), (self.a3.id,), (self.a4.id,), (self.a5.id,), (self.a6.id,), (self.a7.id,)], 394 transform=identity) 395 self.assertQuerysetEqual( 396 Article.objects.values_list(id_alias='id', flat=True).order_by('id_alias'), 397 [self.a1.id, self.a2.id, self.a3.id, self.a4.id, self.a5.id, self.a6.id, self.a7.id], 398 transform=identity) 399 self.assertQuerysetEqual( 400 Article.objects.extra(select={'id_plus_one': 'id+1'}) 401 .order_by('id').values_list(alias_doesnt_matter='id'), 402 [(self.a1.id,), (self.a2.id,), (self.a3.id,), (self.a4.id,), (self.a5.id,), (self.a6.id,), (self.a7.id,)], 403 transform=identity) 404 self.assertQuerysetEqual( 405 Article.objects.extra(select={'id_plus_one': 'id+1'}) 406 .order_by('id').values_list('id_plus_one', alias_doesnt_matter='id'), 407 [ 408 (self.a1.id+1, self.a1.id), 409 (self.a2.id+1, self.a2.id), 410 (self.a3.id+1, self.a3.id), 411 (self.a4.id+1, self.a4.id), 412 (self.a5.id+1, self.a5.id), 413 (self.a6.id+1, self.a6.id), 414 (self.a7.id+1, self.a7.id) 415 ], 416 transform=identity) 417 self.assertQuerysetEqual( 418 Article.objects.extra(select={'id_plus_one': 'id+1'}) 419 .order_by('id').values_list('id', alias_doesnt_matter='id_plus_one'), 420 [ 421 (self.a1.id, self.a1.id+1), 422 (self.a2.id, self.a2.id+1), 423 (self.a3.id, self.a3.id+1), 424 (self.a4.id, self.a4.id+1), 425 (self.a5.id, self.a5.id+1), 426 (self.a6.id, self.a6.id+1), 427 (self.a7.id, self.a7.id+1) 428 ], 429 transform=identity) 430 self.assertQuerysetEqual( 431 Author.objects.values_list('name', headline='article__headline', tag_name='article__tag__name').order_by('name', 'headline', 'tag_name'), 432 [ 433 (self.au1.name, self.a1.headline, self.t1.name), 434 (self.au1.name, self.a2.headline, self.t1.name), 435 (self.au1.name, self.a3.headline, self.t1.name), 436 (self.au1.name, self.a3.headline, self.t2.name), 437 (self.au1.name, self.a4.headline, self.t2.name), 438 (self.au2.name, self.a5.headline, self.t2.name), 439 (self.au2.name, self.a5.headline, self.t3.name), 440 (self.au2.name, self.a6.headline, self.t3.name), 441 (self.au2.name, self.a7.headline, self.t3.name), 442 ], transform=identity) 443 self.assertRaises(TypeError, Article.objects.values_list, 'id', hl='headline', flat=True) 444 255 445 def test_values_list(self): 256 446 # values_list() is similar to values(), except that the results are 257 447 # returned as a list of tuples, rather than a list of dictionaries.