Ticket #16735: column_alias.diff

File column_alias.diff, 33.0 KB (added by Nate Bragg, 12 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):  
    571571    # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
    572572    ##################################################
    573573
    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)
    576576
    577577    def values_list(self, *fields, **kwargs):
    578578        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:
    583580            raise TypeError("'flat' is not valid when values_list is called with more than one field.")
    584581        return self._clone(klass=ValuesListQuerySet, setup=True, flat=flat,
    585                 _fields=fields)
     582                _fields=fields, _aliased_fields=kwargs)
    586583
    587584    def dates(self, field_name, kind, order='ASC'):
    588585        """
    class QuerySet(object):  
    736733
    737734        obj = self._clone()
    738735
    739         obj._setup_aggregate_query(kwargs.keys())
     736        obj._setup_aggregate_query((k,None) for k in kwargs)
    740737
    741738        # Add the aggregates to the query
    742739        for (alias, aggregate_expr) in kwargs.items():
    class QuerySet(object):  
    904901        """
    905902        opts = self.model._meta
    906903        if self.query.group_by is None:
    907             field_names = [f.attname for f in opts.fields]
     904            field_names = [(f.attname,None) for f in opts.fields]
    908905            self.query.add_fields(field_names, False)
    909906            self.query.set_group_by()
    910907
    class QuerySet(object):  
    924921    # empty" result.
    925922    value_annotation = True
    926923
    927 
    928924class ValuesQuerySet(QuerySet):
    929925    def __init__(self, *args, **kwargs):
    930926        super(ValuesQuerySet, self).__init__(*args, **kwargs)
    931927        # select_related isn't supported in values(). (FIXME -#3358)
    932928        self.query.select_related = False
    933929
    934         # QuerySet.clone() will also set up the _fields attribute with the
     930        # QuerySet.clone() will also set up the _fields and _aliased_fields attributes with the
    935931        # names of the model fields to select.
    936932
    937933    def iterator(self):
    class ValuesQuerySet(QuerySet):  
    940936        field_names = self.field_names
    941937        aggregate_names = self.query.aggregate_select.keys()
    942938
    943         names = extra_names + field_names + aggregate_names
     939        names = extra_names + [c_alias or c_name for c_name, c_alias in field_names] + aggregate_names
    944940
    945941        for row in self.query.get_compiler(self.db).results_iter():
    946942            yield dict(zip(names, row))
    class ValuesQuerySet(QuerySet):  
    956952        self.query.clear_deferred_loading()
    957953        self.query.clear_select_fields()
    958954
    959         if self._fields:
     955        if self._fields or self._aliased_fields:
    960956            self.extra_names = []
    961957            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())
    962960            if not self.query.extra and not self.query.aggregates:
    963961                # Short cut - if there are no extra or aggregates, then
    964962                # the values() clause must be just field names.
    965                 self.field_names = list(self._fields)
     963                self.field_names = all_fields
    966964            else:
    967965                self.query.default_cols = False
    968966                self.field_names = []
    969                 for f in self._fields:
     967                for col_name, col_alias in all_fields:
    970968                    # we inspect the full extra_select list since we might
    971969                    # be adding back an extra select item that we hadn't
    972970                    # had selected previously.
    973                     if f in self.query.extra:
    974                         self.extra_names.append(f)
    975                     elif f in 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))
    977975                    else:
    978                         self.field_names.append(f)
     976                        self.field_names.append((col_name,col_alias))
    979977        else:
    980978            # Default to all fields.
    981979            self.extra_names = None
    982             self.field_names = [f.attname for f in self.model._meta.fields]
     980            self.field_names = [(f.attname,None) for f in self.model._meta.fields]
    983981            self.aggregate_names = None
    984982
    985983        self.query.select = []
    class ValuesQuerySet(QuerySet):  
    998996            # Only clone self._fields if _fields wasn't passed into the cloning
    999997            # call directly.
    1000998            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[:]
    10041005        if setup and hasattr(c, '_setup_query'):
    10051006            c._setup_query()
    10061007        return c
    class ValuesQuerySet(QuerySet):  
    10331034        returned). This differs from QuerySet.as_sql(), where the column to
    10341035        select is set up by Django.
    10351036        """
    1036         if ((self._fields and len(self._fields) > 1) or
    1037                 (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)):
    10381039            raise TypeError('Cannot use a multi-field %s as a filter value.'
    10391040                    % self.__class__.__name__)
    10401041
    class ValuesQuerySet(QuerySet):  
    10481049        Validates that we aren't trying to do a query like
    10491050        value__in=qs.values('value1', 'value2'), which isn't valid.
    10501051        """
    1051         if ((self._fields and len(self._fields) > 1) or
    1052                 (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)):
    10531054            raise TypeError('Cannot use a multi-field %s as a filter value.'
    10541055                    % self.__class__.__name__)
    10551056        return self
    10561057
    1057 
    10581058class ValuesListQuerySet(ValuesQuerySet):
    10591059    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:
    10611061            for row in self.query.get_compiler(self.db).results_iter():
    10621062                yield row[0]
    10631063        elif not self.query.extra_select and not self.query.aggregate_select:
    class ValuesListQuerySet(ValuesQuerySet):  
    10711071            field_names = self.field_names
    10721072            aggregate_names = self.query.aggregate_select.keys()
    10731073
    1074             names = extra_names + field_names + aggregate_names
     1074            names = extra_names + [c_alias or c_name for c_name,c_alias in field_names] + aggregate_names
    10751075
    10761076            # If a field list has been specified, use it. Otherwise, use the
    10771077            # full list of fields, including extras and aggregates.
    10781078            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))
    10801081            else:
    10811082                fields = names
    10821083
  • 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):  
    187187            only_load = self.deferred_to_columns()
    188188            for col in self.query.select:
    189189                if isinstance(col, (list, tuple)):
    190                     alias, column = col
     190                    alias, column, column_alias = col
    191191                    table = self.query.alias_map[alias][TABLE_NAME]
    192192                    if table in only_load and column not in only_load[table]:
    193193                        continue
    194194                    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:
    197199                            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
    201201                        else:
    202                             result.append('%s AS %s' % (r, qn2(col[1])))
    203                             aliases.add(r)
    204                             col_aliases.add(col[1])
    205                     else:
    206                         result.append(r)
    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)
    209209                else:
    210210                    result.append(col.as_sql(qn, self.connection))
    211211
    class SQLCompiler(object):  
    381381                group_by.append((field, []))
    382382                continue
    383383            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)):
    385386                result.append('%s %s' % (qn(col), order))
    386387                continue
    387388            if '.' in field:
    class SQLUpdateCompiler(SQLCompiler):  
    10171018        query.bump_prefix()
    10181019        query.extra = {}
    10191020        query.select = []
    1020         query.add_fields([query.model._meta.pk.name])
     1021        query.add_fields([(query.model._meta.pk.name,None)])
    10211022        must_pre_select = count > 1 and not self.connection.features.update_can_self_select
    10221023
    10231024        # 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):  
    258258        obj.tables = self.tables[:]
    259259        obj.where = copy.deepcopy(self.where, memo=memo)
    260260        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[:]
    265262        obj.having = copy.deepcopy(self.having, memo=memo)
    266263        obj.order_by = self.order_by[:]
    267264        obj.low_mark, obj.high_mark = self.low_mark, self.high_mark
    class Query(object):  
    272269        obj.select_related = self.select_related
    273270        obj.related_select_cols = []
    274271        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()
    279273        # _aggregate_select_cache cannot be copied, as doing so breaks the
    280274        # (necessary) state in which both aggregates and
    281275        # _aggregate_select_cache point to the same underlying objects.
    class Query(object):  
    284278        obj._aggregate_select_cache = None
    285279        obj.max_depth = self.max_depth
    286280        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()
    295283        obj.extra_tables = self.extra_tables
    296284        obj.extra_order_by = self.extra_order_by
    297285        obj.deferred_loading = copy.deepcopy(self.deferred_loading, memo=memo)
    class Query(object):  
    433421        q.select_fields = []
    434422        q.default_cols = False
    435423        q.select_related = False
    436         q.set_extra_mask(('a',))
     424        q.set_extra_mask([('a',None)])
    437425        q.set_aggregate_mask(())
    438426        q.clear_ordering(True)
    439427        q.set_limits(high=1)
    class Query(object):  
    527515        self.select = []
    528516        for col in rhs.select:
    529517            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))
    531520            else:
    532521                item = copy.deepcopy(col)
    533522                item.relabel_aliases(change_map)
    class Query(object):  
    756745        for columns in [self.select, self.group_by or []]:
    757746            for pos, col in enumerate(columns):
    758747                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)
    761750                else:
    762751                    col.relabel_aliases(change_map)
    763752        for mapping in [self.aggregates]:
    class Query(object):  
    11591148            self.promote_alias_chain(table_it, table_promote or join_promote)
    11601149
    11611150        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))
    11641153            self.having.add((Constraint(alias, col, field), lookup_type, value),
    11651154                connector)
    11661155        else:
    class Query(object):  
    15481537        # since we are adding a IN <subquery> clause. This prevents the
    15491538        # database from tripping over IN (...,NULL,...) selects and returning
    15501539        # nothing
    1551         alias, col = query.select[0]
     1540        alias, col, _ = query.select[0]
    15521541        query.where.add((Constraint(alias, col, None), 'isnull', False), AND)
    15531542
    15541543        self.add_filter(('%s__in' % prefix, query), negate=True, trim=True,
    class Query(object):  
    16271616        opts = self.get_meta()
    16281617
    16291618        try:
    1630             for name in field_names:
     1619            for name, col_alias in field_names:
    16311620                field, target, u2, joins, u3, u4 = self.setup_joins(
    16321621                        name.split(LOOKUP_SEP), opts, alias, False, allow_m2m,
    16331622                        True)
    class Query(object):  
    16411630                        col = join[LHS_JOIN_COL]
    16421631                        joins = joins[:-1]
    16431632                self.promote_alias_chain(joins[1:])
    1644                 self.select.append((final_alias, col))
     1633                self.select.append((final_alias, col, col_alias))
    16451634                self.select_fields.append(field)
    16461635        except MultiJoin:
    16471636            raise FieldError("Invalid field name: '%s'" % name)
    class Query(object):  
    17071696            else:
    17081697                assert len(self.select) == 1, \
    17091698                        "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)
    17111702        else:
    17121703            opts = self.model._meta
    17131704            if not self.select:
    class Query(object):  
    17191710                assert len(self.select) == 1, \
    17201711                        "Cannot add count col with multiple cols in 'select'."
    17211712
    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)
    17231716            # Distinct handling is done in Count(), so don't do it at this
    17241717            # level.
    17251718            self.distinct = False
    class Query(object):  
    18751868        if self._aggregate_select_cache is not None:
    18761869            return self._aggregate_select_cache
    18771870        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            )
    18821876            return self._aggregate_select_cache
    18831877        else:
    18841878            return self.aggregates
    class Query(object):  
    18881882        if self._extra_select_cache is not None:
    18891883            return self._extra_select_cache
    18901884        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            )
    18951890            return self._extra_select_cache
    18961891        else:
    18971892            return self.extra
    class Query(object):  
    19311926            self.unref_alias(select_alias)
    19321927            select_alias = join_info[RHS_ALIAS]
    19331928            select_col = join_info[RHS_JOIN_COL]
    1934         self.select = [(select_alias, select_col)]
     1929        self.select = [(select_alias, select_col, None)]
    19351930        self.remove_inherited_models()
    19361931
    19371932
  • 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::  
    420420values
    421421~~~~~~
    422422
    423 .. method:: values(*fields)
     423.. method:: values(*fields,**aliased_fields)
    424424
    425425Returns a ``ValuesQuerySet`` — a ``QuerySet`` subclass that returns
    426426dictionaries when used as an iterable, rather than model-instance objects.
    Example::  
    452452    >>> Blog.objects.values('id', 'name')
    453453    [{'id': 1, 'name': 'Beatles Blog'}]
    454454
     455Additionally, the ``values()`` method takes optional keyword arguments,
     456``**aliased_fields``, which give aliases under which the ``SELECT`` retrieves the
     457fields.  If used, the returned dictionary will contain the values of the fields
     458under the corresponding alias provided.
     459
     460Example::
     461
     462    >>> Blog.objects.values('id', blog_name='name')
     463    [{'id': 1, 'blog_name': 'Beatles Blog'}]
     464
    455465A few subtleties that are worth mentioning:
    456466
    457467* If you have a field called ``foo`` that is a
    and ``ManyToManyField`` attributes::  
    526536values_list
    527537~~~~~~~~~~~
    528538
    529 .. method:: values_list(*fields)
     539.. method:: values_list(*fields,**aliased_fields)
    530540
    531541This is similar to ``values()`` except that instead of returning dictionaries,
    532542it returns tuples when iterated over. Each tuple contains the value from the
    the first field, etc. For example::  
    536546    >>> Entry.objects.values_list('id', 'headline')
    537547    [(1, u'First entry'), ...]
    538548
     549Like ``values()``, you can assign the columns aliases by passing them in as
     550keyword arguments (the exception being ``flat``, which is reserved; see below).
     551However, while these aliases can be used by chained calls (say, to ``order_by``),
     552by 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
    539557If you only pass in a single field, you can also pass in the ``flat``
    540558parameter. If ``True``, this will mean the returned results are single values,
    541559rather 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):  
    252252                'pub_date': datetime(2005, 8, 1, 9, 0)
    253253            }], transform=identity)
    254254
     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
    255445    def test_values_list(self):
    256446        # values_list() is similar to values(), except that the results are
    257447        # returned as a list of tuples, rather than a list of dictionaries.
Back to Top