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/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -571,18 +571,15 @@ class QuerySet(object):
     # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
     ##################################################
 
-    def values(self, *fields):
-        return self._clone(klass=ValuesQuerySet, setup=True, _fields=fields)
+    def values(self, *fields, **aliased_fields):
+        return self._clone(klass=ValuesQuerySet, setup=True, _fields=fields, _aliased_fields=aliased_fields)
 
     def values_list(self, *fields, **kwargs):
         flat = kwargs.pop('flat', False)
-        if kwargs:
-            raise TypeError('Unexpected keyword arguments to values_list: %s'
-                    % (kwargs.keys(),))
-        if flat and len(fields) > 1:
+        if flat and len(fields) + len(kwargs) > 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)
+                _fields=fields, _aliased_fields=kwargs)
 
     def dates(self, field_name, kind, order='ASC'):
         """
@@ -736,7 +733,7 @@ class QuerySet(object):
 
         obj = self._clone()
 
-        obj._setup_aggregate_query(kwargs.keys())
+        obj._setup_aggregate_query((k,None) for k in kwargs)
 
         # Add the aggregates to the query
         for (alias, aggregate_expr) in kwargs.items():
@@ -904,7 +901,7 @@ class QuerySet(object):
         """
         opts = self.model._meta
         if self.query.group_by is None:
-            field_names = [f.attname for f in opts.fields]
+            field_names = [(f.attname,None) for f in opts.fields]
             self.query.add_fields(field_names, False)
             self.query.set_group_by()
 
@@ -924,14 +921,13 @@ class QuerySet(object):
     # empty" result.
     value_annotation = True
 
-
 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
+        # QuerySet.clone() will also set up the _fields and _aliased_fields attributes with the
         # names of the model fields to select.
 
     def iterator(self):
@@ -940,7 +936,7 @@ class ValuesQuerySet(QuerySet):
         field_names = self.field_names
         aggregate_names = self.query.aggregate_select.keys()
 
-        names = extra_names + field_names + aggregate_names
+        names = extra_names + [c_alias or c_name for c_name, c_alias in field_names] + aggregate_names
 
         for row in self.query.get_compiler(self.db).results_iter():
             yield dict(zip(names, row))
@@ -956,30 +952,32 @@ class ValuesQuerySet(QuerySet):
         self.query.clear_deferred_loading()
         self.query.clear_select_fields()
 
-        if self._fields:
+        if self._fields or self._aliased_fields:
             self.extra_names = []
             self.aggregate_names = []
+            all_fields = [(f,None) for f in self._fields]
+            all_fields.extend((f,a) for a,f in self._aliased_fields.items())
             if not self.query.extra and not self.query.aggregates:
                 # Short cut - if there are no extra or aggregates, then
                 # the values() clause must be just field names.
-                self.field_names = list(self._fields)
+                self.field_names = all_fields
             else:
                 self.query.default_cols = False
                 self.field_names = []
-                for f in self._fields:
+                for col_name, col_alias in all_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 f in self.query.extra:
-                        self.extra_names.append(f)
-                    elif f in self.query.aggregate_select:
-                        self.aggregate_names.append(f)
+                    if col_name in self.query.extra:
+                        self.extra_names.append((col_name,col_alias))
+                    elif col_name in self.query.aggregate_select:
+                        self.aggregate_names.append((col_name,col_alias))
                     else:
-                        self.field_names.append(f)
+                        self.field_names.append((col_name,col_alias))
         else:
             # Default to all fields.
             self.extra_names = None
-            self.field_names = [f.attname for f in self.model._meta.fields]
+            self.field_names = [(f.attname,None) for f in self.model._meta.fields]
             self.aggregate_names = None
 
         self.query.select = []
@@ -998,9 +996,12 @@ class ValuesQuerySet(QuerySet):
             # 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.aggregate_names = self.aggregate_names
+        if not hasattr(c, '_aliased_fields'):
+            # Same with _aliased_fields as _fields above
+            c._aliased_fields = self._aliased_fields.copy()
+        c.field_names = None if self.field_names is None else self.field_names[:]
+        c.extra_names = None if self.extra_names is None else self.extra_names[:]
+        c.aggregate_names = None if self.aggregate_names is None else self.aggregate_names[:]
         if setup and hasattr(c, '_setup_query'):
             c._setup_query()
         return c
@@ -1033,8 +1034,8 @@ class ValuesQuerySet(QuerySet):
         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)):
+        if (( (self._fields or self._aliased_fields) and len(self._fields) + len(self._aliased_fields) > 1) or
+                (not self._fields and not self._aliased_fields and len(self.model._meta.fields) > 1)):
             raise TypeError('Cannot use a multi-field %s as a filter value.'
                     % self.__class__.__name__)
 
@@ -1048,16 +1049,15 @@ class ValuesQuerySet(QuerySet):
         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)):
+        if (( (self._fields or self._aliased_fields) and len(self._fields) + len(self._aliased_fields) > 1) or
+                (not self._fields and not self._aliased_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
 
-
 class ValuesListQuerySet(ValuesQuerySet):
     def iterator(self):
-        if self.flat and len(self._fields) == 1:
+        if self.flat and len(self._fields) + len(self._aliased_fields) == 1:
             for row in self.query.get_compiler(self.db).results_iter():
                 yield row[0]
         elif not self.query.extra_select and not self.query.aggregate_select:
@@ -1071,12 +1071,13 @@ class ValuesListQuerySet(ValuesQuerySet):
             field_names = self.field_names
             aggregate_names = self.query.aggregate_select.keys()
 
-            names = extra_names + field_names + aggregate_names
+            names = extra_names + [c_alias or c_name for c_name,c_alias in field_names] + aggregate_names
 
             # If a field list has been specified, use it. Otherwise, use the
             # full list of fields, including extras and aggregates.
             if self._fields:
-                fields = list(self._fields) + filter(lambda f: f not in self._fields, aggregate_names)
+                fields = list(self._fields) + self._aliased_fields.keys()
+                fields.extend(f for f in aggregate_names if not (f in self._fields or f in self._aliased_fields))
             else:
                 fields = names
 
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 72948f9..6678ce2 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -187,25 +187,25 @@ class SQLCompiler(object):
             only_load = self.deferred_to_columns()
             for col in self.query.select:
                 if isinstance(col, (list, tuple)):
-                    alias, column = col
+                    alias, column, column_alias = col
                     table = self.query.alias_map[alias][TABLE_NAME]
                     if table in only_load and column not in only_load[table]:
                         continue
                     r = '%s.%s' % (qn(alias), qn(column))
-                    if with_aliases:
-                        if col[1] in col_aliases:
+                    a = r
+                    c = column
+                    if column_alias or with_aliases:
+                        if column_alias in col_aliases or column in col_aliases:
                             c_alias = 'Col%d' % len(col_aliases)
-                            result.append('%s AS %s' % (r, c_alias))
-                            aliases.add(c_alias)
-                            col_aliases.add(c_alias)
+                            a = c_alias
                         else:
-                            result.append('%s AS %s' % (r, qn2(col[1])))
-                            aliases.add(r)
-                            col_aliases.add(col[1])
-                    else:
-                        result.append(r)
-                        aliases.add(r)
-                        col_aliases.add(col[1])
+                            c_alias = column_alias or column
+                            a = column_alias or a
+                        r = '%s AS %s' % (r, qn2(c_alias))
+                        c = c_alias
+                    result.append(r)
+                    aliases.add(a)
+                    col_aliases.add(c)
                 else:
                     result.append(col.as_sql(qn, self.connection))
 
@@ -381,7 +381,8 @@ class SQLCompiler(object):
                 group_by.append((field, []))
                 continue
             col, order = get_order_dir(field, asc)
-            if col in self.query.aggregate_select:
+            if (col in self.query.aggregate_select or
+                    col in (a for _,_,a in self.query.select if a)):
                 result.append('%s %s' % (qn(col), order))
                 continue
             if '.' in field:
@@ -1017,7 +1018,7 @@ class SQLUpdateCompiler(SQLCompiler):
         query.bump_prefix()
         query.extra = {}
         query.select = []
-        query.add_fields([query.model._meta.pk.name])
+        query.add_fields([(query.model._meta.pk.name,None)])
         must_pre_select = count > 1 and not self.connection.features.update_can_self_select
 
         # Now we adjust the current query: reset the where clause and get rid
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index ed2bc06..323e45e 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -258,10 +258,7 @@ class Query(object):
         obj.tables = self.tables[:]
         obj.where = copy.deepcopy(self.where, memo=memo)
         obj.where_class = self.where_class
-        if self.group_by is None:
-            obj.group_by = None
-        else:
-            obj.group_by = self.group_by[:]
+        obj.group_by = None if self.group_by is None else self.group_by[:]
         obj.having = copy.deepcopy(self.having, memo=memo)
         obj.order_by = self.order_by[:]
         obj.low_mark, obj.high_mark = self.low_mark, self.high_mark
@@ -272,10 +269,7 @@ class Query(object):
         obj.select_related = self.select_related
         obj.related_select_cols = []
         obj.aggregates = copy.deepcopy(self.aggregates, memo=memo)
-        if self.aggregate_select_mask is None:
-            obj.aggregate_select_mask = None
-        else:
-            obj.aggregate_select_mask = self.aggregate_select_mask.copy()
+        obj.aggregate_select_mask = None if self.aggregate_select_mask is None else self.aggregate_select_mask.copy()
         # _aggregate_select_cache cannot be copied, as doing so breaks the
         # (necessary) state in which both aggregates and
         # _aggregate_select_cache point to the same underlying objects.
@@ -284,14 +278,8 @@ class Query(object):
         obj._aggregate_select_cache = None
         obj.max_depth = self.max_depth
         obj.extra = self.extra.copy()
-        if self.extra_select_mask is None:
-            obj.extra_select_mask = None
-        else:
-            obj.extra_select_mask = self.extra_select_mask.copy()
-        if self._extra_select_cache is None:
-            obj._extra_select_cache = None
-        else:
-            obj._extra_select_cache = self._extra_select_cache.copy()
+        obj.extra_select_mask = None if self.extra_select_mask is None else self.extra_select_mask.copy()
+        obj._extra_select_cache = None if self._extra_select_cache is None else self._extra_select_cache.copy()
         obj.extra_tables = self.extra_tables
         obj.extra_order_by = self.extra_order_by
         obj.deferred_loading = copy.deepcopy(self.deferred_loading, memo=memo)
@@ -433,7 +421,7 @@ class Query(object):
         q.select_fields = []
         q.default_cols = False
         q.select_related = False
-        q.set_extra_mask(('a',))
+        q.set_extra_mask([('a',None)])
         q.set_aggregate_mask(())
         q.clear_ordering(True)
         q.set_limits(high=1)
@@ -527,7 +515,8 @@ class Query(object):
         self.select = []
         for col in rhs.select:
             if isinstance(col, (list, tuple)):
-                self.select.append((change_map.get(col[0], col[0]), col[1]))
+                old_alias, column, column_alias = col
+                self.select.append((change_map.get(old_alias, old_alias), column, column_alias))
             else:
                 item = copy.deepcopy(col)
                 item.relabel_aliases(change_map)
@@ -756,8 +745,8 @@ class Query(object):
         for columns in [self.select, self.group_by or []]:
             for pos, col in enumerate(columns):
                 if isinstance(col, (list, tuple)):
-                    old_alias = col[0]
-                    columns[pos] = (change_map.get(old_alias, old_alias), col[1])
+                    old_alias, column, column_alias = col
+                    columns[pos] = (change_map.get(old_alias, old_alias), column, column_alias)
                 else:
                     col.relabel_aliases(change_map)
         for mapping in [self.aggregates]:
@@ -1159,8 +1148,8 @@ class Query(object):
             self.promote_alias_chain(table_it, table_promote or join_promote)
 
         if having_clause or force_having:
-            if (alias, col) not in self.group_by:
-                self.group_by.append((alias, col))
+            if (alias, col, None) not in self.group_by:
+                self.group_by.append((alias, col, None))
             self.having.add((Constraint(alias, col, field), lookup_type, value),
                 connector)
         else:
@@ -1548,7 +1537,7 @@ class Query(object):
         # since we are adding a IN <subquery> clause. This prevents the
         # database from tripping over IN (...,NULL,...) selects and returning
         # nothing
-        alias, col = query.select[0]
+        alias, col, _ = query.select[0]
         query.where.add((Constraint(alias, col, None), 'isnull', False), AND)
 
         self.add_filter(('%s__in' % prefix, query), negate=True, trim=True,
@@ -1627,7 +1616,7 @@ class Query(object):
         opts = self.get_meta()
 
         try:
-            for name in field_names:
+            for name, col_alias in field_names:
                 field, target, u2, joins, u3, u4 = self.setup_joins(
                         name.split(LOOKUP_SEP), opts, alias, False, allow_m2m,
                         True)
@@ -1641,7 +1630,7 @@ class Query(object):
                         col = join[LHS_JOIN_COL]
                         joins = joins[:-1]
                 self.promote_alias_chain(joins[1:])
-                self.select.append((final_alias, col))
+                self.select.append((final_alias, col, col_alias))
                 self.select_fields.append(field)
         except MultiJoin:
             raise FieldError("Invalid field name: '%s'" % name)
@@ -1707,7 +1696,9 @@ class Query(object):
             else:
                 assert len(self.select) == 1, \
                         "Cannot add count col with multiple cols in 'select': %r" % self.select
-                count = self.aggregates_module.Count(self.select[0])
+                sel = self.select[0]
+                sel = (sel[0],sel[1]) if isinstance(sel,(list,tuple)) else sel
+                count = self.aggregates_module.Count(sel)
         else:
             opts = self.model._meta
             if not self.select:
@@ -1719,7 +1710,9 @@ class Query(object):
                 assert len(self.select) == 1, \
                         "Cannot add count col with multiple cols in 'select'."
 
-                count = self.aggregates_module.Count(self.select[0], distinct=True)
+                sel = self.select[0]
+                sel = (sel[0],sel[1]) if isinstance(sel,(list,tuple)) else sel
+                count = self.aggregates_module.Count(sel, distinct=True)
             # Distinct handling is done in Count(), so don't do it at this
             # level.
             self.distinct = False
@@ -1875,10 +1868,11 @@ class Query(object):
         if self._aggregate_select_cache is not None:
             return self._aggregate_select_cache
         elif self.aggregate_select_mask is not None:
-            self._aggregate_select_cache = SortedDict([
-                (k,v) for k,v in self.aggregates.items()
-                if k in self.aggregate_select_mask
-            ])
+            self._aggregate_select_cache = SortedDict(
+                (a or k,v) for k,v in self.aggregates.items()
+                for n,a in self.aggregate_select_mask
+                if k==n
+            )
             return self._aggregate_select_cache
         else:
             return self.aggregates
@@ -1888,10 +1882,11 @@ class Query(object):
         if self._extra_select_cache is not None:
             return self._extra_select_cache
         elif self.extra_select_mask is not None:
-            self._extra_select_cache = SortedDict([
-                (k,v) for k,v in self.extra.items()
-                if k in self.extra_select_mask
-            ])
+            self._extra_select_cache = SortedDict(
+                (a or k,v) for k,v in self.extra.items()
+                for n,a in self.extra_select_mask
+                if k==n
+            )
             return self._extra_select_cache
         else:
             return self.extra
@@ -1931,7 +1926,7 @@ class Query(object):
             self.unref_alias(select_alias)
             select_alias = join_info[RHS_ALIAS]
             select_col = join_info[RHS_JOIN_COL]
-        self.select = [(select_alias, select_col)]
+        self.select = [(select_alias, select_col, None)]
         self.remove_inherited_models()
 
 
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index 7633555..e8cd96d 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -420,7 +420,7 @@ Examples::
 values
 ~~~~~~
 
-.. method:: values(*fields)
+.. method:: values(*fields,**aliased_fields)
 
 Returns a ``ValuesQuerySet`` — a ``QuerySet`` subclass that returns
 dictionaries when used as an iterable, rather than model-instance objects.
@@ -452,6 +452,16 @@ Example::
     >>> Blog.objects.values('id', 'name')
     [{'id': 1, 'name': 'Beatles Blog'}]
 
+Additionally, the ``values()`` method takes optional keyword arguments,
+``**aliased_fields``, which give aliases under which the ``SELECT`` retrieves the
+fields.  If used, the returned dictionary will contain the values of the fields
+under the corresponding alias provided.
+
+Example::
+
+    >>> Blog.objects.values('id', blog_name='name')
+    [{'id': 1, 'blog_name': 'Beatles Blog'}]
+
 A few subtleties that are worth mentioning:
 
 * If you have a field called ``foo`` that is a
@@ -526,7 +536,7 @@ and ``ManyToManyField`` attributes::
 values_list
 ~~~~~~~~~~~
 
-.. method:: values_list(*fields)
+.. method:: values_list(*fields,**aliased_fields)
 
 This is similar to ``values()`` except that instead of returning dictionaries,
 it returns tuples when iterated over. Each tuple contains the value from the
@@ -536,6 +546,14 @@ the first field, etc. For example::
     >>> Entry.objects.values_list('id', 'headline')
     [(1, u'First entry'), ...]
 
+Like ``values()``, you can assign the columns aliases by passing them in as
+keyword arguments (the exception being ``flat``, which is reserved; see below).
+However, while these aliases can be used by chained calls (say, to ``order_by``),
+by itself aliasing has little effect (though it does trickle down to the query):
+
+    >>> Entry.objects.values_list('id', hl='headline')
+    [(1, u'First entry'), ...]
+
 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,
 rather than one-tuples. An example should make the difference clearer::
diff --git a/tests/modeltests/lookup/tests.py b/tests/modeltests/lookup/tests.py
index 9c2b0c6..728bc46 100644
--- a/tests/modeltests/lookup/tests.py
+++ b/tests/modeltests/lookup/tests.py
@@ -252,6 +252,196 @@ class LookupTests(TestCase):
                 'pub_date': datetime(2005, 8, 1, 9, 0)
             }], transform=identity)
 
+    def test_values_aliases(self):
+        # values() returns a list of dictionaries instead of object instances --
+        # and you can specify which fields you want to retrieve.
+        identity = lambda x:x
+        self.assertQuerysetEqual(Article.objects.values(title='headline'),
+            [
+                {'title': u'Article 5'},
+                {'title': u'Article 6'},
+                {'title': u'Article 4'},
+                {'title': u'Article 2'},
+                {'title': u'Article 3'},
+                {'title': u'Article 7'},
+                {'title': u'Article 1'},
+            ],
+            transform=identity)
+        self.assertQuerysetEqual(
+            Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).values(primary_key='id'),
+            [{'primary_key': self.a2.id}, {'primary_key': self.a3.id}, {'primary_key': self.a7.id}],
+            transform=identity)
+        self.assertQuerysetEqual(Article.objects.values(primary_key='id', title='headline'),
+            [
+                {'primary_key': self.a5.id, 'title': 'Article 5'},
+                {'primary_key': self.a6.id, 'title': 'Article 6'},
+                {'primary_key': self.a4.id, 'title': 'Article 4'},
+                {'primary_key': self.a2.id, 'title': 'Article 2'},
+                {'primary_key': self.a3.id, 'title': 'Article 3'},
+                {'primary_key': self.a7.id, 'title': 'Article 7'},
+                {'primary_key': self.a1.id, 'title': 'Article 1'},
+            ],
+            transform=identity)
+        # You can use values() with iterator() for memory savings,
+        # because iterator() uses database-level iteration.
+        self.assertQuerysetEqual(Article.objects.values(primary_key='id', title='headline').iterator(),
+            [
+                {'primary_key': self.a5.id, 'title': 'Article 5'},
+                {'primary_key': self.a6.id, 'title': 'Article 6'},
+                {'primary_key': self.a4.id, 'title': 'Article 4'},
+                {'primary_key': self.a2.id, 'title': 'Article 2'},
+                {'primary_key': self.a3.id, 'title': 'Article 3'},
+                {'primary_key': self.a7.id, 'title': 'Article 7'},
+                {'primary_key': self.a1.id, 'title': 'Article 1'},
+            ],
+            transform=identity)
+        # The values() method works with "extra" fields specified in extra(select).
+        self.assertQuerysetEqual(
+            Article.objects.extra(select={'id_plus_one': 'id + 1'}).values('id_plus_one', primary_key='id'),
+            [
+                {'primary_key': self.a5.id, 'id_plus_one': self.a5.id + 1},
+                {'primary_key': self.a6.id, 'id_plus_one': self.a6.id + 1},
+                {'primary_key': self.a4.id, 'id_plus_one': self.a4.id + 1},
+                {'primary_key': self.a2.id, 'id_plus_one': self.a2.id + 1},
+                {'primary_key': self.a3.id, 'id_plus_one': self.a3.id + 1},
+                {'primary_key': self.a7.id, 'id_plus_one': self.a7.id + 1},
+                {'primary_key': self.a1.id, 'id_plus_one': self.a1.id + 1},
+            ],
+            transform=identity)
+        data = {
+            'id_plus_one': 'id+1',
+            'id_plus_two': 'id+2',
+            'id_plus_three': 'id+3',
+            'id_plus_four': 'id+4',
+            'id_plus_five': 'id+5',
+            'id_plus_six': 'id+6',
+            'id_plus_seven': 'id+7',
+            'id_plus_eight': 'id+8',
+        }
+        self.assertQuerysetEqual(
+            Article.objects.filter(id=self.a1.id).extra(select=data).values(**dict([(k+'_',k) for k in data.keys()])),
+            [{
+                'id_plus_one_': self.a1.id + 1,
+                'id_plus_two_': self.a1.id + 2,
+                'id_plus_three_': self.a1.id + 3,
+                'id_plus_four_': self.a1.id + 4,
+                'id_plus_five_': self.a1.id + 5,
+                'id_plus_six_': self.a1.id + 6,
+                'id_plus_seven_': self.a1.id + 7,
+                'id_plus_eight_': self.a1.id + 8,
+            }], transform=identity)
+        # You can specify fields from forward and reverse relations, just like filter().
+        self.assertQuerysetEqual(
+            Article.objects.values('headline', a_name='author__name'),
+            [
+                {'headline': self.a5.headline, 'a_name': self.au2.name},
+                {'headline': self.a6.headline, 'a_name': self.au2.name},
+                {'headline': self.a4.headline, 'a_name': self.au1.name},
+                {'headline': self.a2.headline, 'a_name': self.au1.name},
+                {'headline': self.a3.headline, 'a_name': self.au1.name},
+                {'headline': self.a7.headline, 'a_name': self.au2.name},
+                {'headline': self.a1.headline, 'a_name': self.au1.name},
+            ], transform=identity)
+        self.assertQuerysetEqual(
+            Author.objects.values('name', headline='article__headline').order_by('name', 'headline'),
+            [
+                {'name': self.au1.name, 'headline': self.a1.headline},
+                {'name': self.au1.name, 'headline': self.a2.headline},
+                {'name': self.au1.name, 'headline': self.a3.headline},
+                {'name': self.au1.name, 'headline': self.a4.headline},
+                {'name': self.au2.name, 'headline': self.a5.headline},
+                {'name': self.au2.name, 'headline': self.a6.headline},
+                {'name': self.au2.name, 'headline': self.a7.headline},
+            ], transform=identity)
+        self.assertQuerysetEqual(
+            Author.objects.values('name', headline='article__headline', tag_name='article__tag__name').order_by('name', 'headline', 'tag_name'),
+            [
+                {'name': self.au1.name, 'headline': self.a1.headline, 'tag_name': self.t1.name},
+                {'name': self.au1.name, 'headline': self.a2.headline, 'tag_name': self.t1.name},
+                {'name': self.au1.name, 'headline': self.a3.headline, 'tag_name': self.t1.name},
+                {'name': self.au1.name, 'headline': self.a3.headline, 'tag_name': self.t2.name},
+                {'name': self.au1.name, 'headline': self.a4.headline, 'tag_name': self.t2.name},
+                {'name': self.au2.name, 'headline': self.a5.headline, 'tag_name': self.t2.name},
+                {'name': self.au2.name, 'headline': self.a5.headline, 'tag_name': self.t3.name},
+                {'name': self.au2.name, 'headline': self.a6.headline, 'tag_name': self.t3.name},
+                {'name': self.au2.name, 'headline': self.a7.headline, 'tag_name': self.t3.name},
+            ], transform=identity)
+        # However, an exception FieldDoesNotExist will be thrown if you specify
+        # a non-existent field name in values() (a field that is neither in the
+        # model nor in extra(select)).
+        self.assertRaises(FieldError,
+            Article.objects.extra(select={'id_plus_one': 'id + 1'}).values,
+            'id', id_incremented='id_plus_two')
+
+    def test_values_list_aliases(self):
+        # values_list() is similar to values(), except that the results are
+        # returned as a list of tuples, rather than a list of dictionaries.
+        # Within each tuple, the order of the elements is the same as the order
+        # of fields in the values_list() call.
+        identity = lambda x:x
+        self.assertQuerysetEqual(Article.objects.values_list(alias_doesnt_matter='headline'),
+            [
+                (u'Article 5',),
+                (u'Article 6',),
+                (u'Article 4',),
+                (u'Article 2',),
+                (u'Article 3',),
+                (u'Article 7',),
+                (u'Article 1',),
+            ], transform=identity)
+        self.assertQuerysetEqual(Article.objects.values_list(id_alias='id').order_by('id_alias'),
+            [(self.a1.id,), (self.a2.id,), (self.a3.id,), (self.a4.id,), (self.a5.id,), (self.a6.id,), (self.a7.id,)],
+            transform=identity)
+        self.assertQuerysetEqual(
+            Article.objects.values_list(id_alias='id', flat=True).order_by('id_alias'),
+            [self.a1.id, self.a2.id, self.a3.id, self.a4.id, self.a5.id, self.a6.id, self.a7.id],
+            transform=identity)
+        self.assertQuerysetEqual(
+            Article.objects.extra(select={'id_plus_one': 'id+1'})
+                           .order_by('id').values_list(alias_doesnt_matter='id'),
+            [(self.a1.id,), (self.a2.id,), (self.a3.id,), (self.a4.id,), (self.a5.id,), (self.a6.id,), (self.a7.id,)],
+            transform=identity)
+        self.assertQuerysetEqual(
+            Article.objects.extra(select={'id_plus_one': 'id+1'})
+                           .order_by('id').values_list('id_plus_one', alias_doesnt_matter='id'),
+            [
+                (self.a1.id+1, self.a1.id),
+                (self.a2.id+1, self.a2.id),
+                (self.a3.id+1, self.a3.id),
+                (self.a4.id+1, self.a4.id),
+                (self.a5.id+1, self.a5.id),
+                (self.a6.id+1, self.a6.id),
+                (self.a7.id+1, self.a7.id)
+            ],
+            transform=identity)
+        self.assertQuerysetEqual(
+            Article.objects.extra(select={'id_plus_one': 'id+1'})
+                           .order_by('id').values_list('id', alias_doesnt_matter='id_plus_one'),
+            [
+                (self.a1.id, self.a1.id+1),
+                (self.a2.id, self.a2.id+1),
+                (self.a3.id, self.a3.id+1),
+                (self.a4.id, self.a4.id+1),
+                (self.a5.id, self.a5.id+1),
+                (self.a6.id, self.a6.id+1),
+                (self.a7.id, self.a7.id+1)
+            ],
+            transform=identity)
+        self.assertQuerysetEqual(
+            Author.objects.values_list('name', headline='article__headline', tag_name='article__tag__name').order_by('name', 'headline', 'tag_name'),
+            [
+                (self.au1.name, self.a1.headline, self.t1.name),
+                (self.au1.name, self.a2.headline, self.t1.name),
+                (self.au1.name, self.a3.headline, self.t1.name),
+                (self.au1.name, self.a3.headline, self.t2.name),
+                (self.au1.name, self.a4.headline, self.t2.name),
+                (self.au2.name, self.a5.headline, self.t2.name),
+                (self.au2.name, self.a5.headline, self.t3.name),
+                (self.au2.name, self.a6.headline, self.t3.name),
+                (self.au2.name, self.a7.headline, self.t3.name),
+            ], transform=identity)
+        self.assertRaises(TypeError, Article.objects.values_list, 'id', hl='headline', flat=True)
+
     def test_values_list(self):
         # values_list() is similar to values(), except that the results are
         # returned as a list of tuples, rather than a list of dictionaries.
-- 
1.7.5.4

