diff --git a/django/db/models/query.py b/django/db/models/query.py index c686c51ea2..59b6e64954 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -478,44 +478,29 @@ class ValuesQuerySet(QuerySet): def _setup_query(self): """ - Sets up any special features of the query attribute. + Constructs the field_names list that the values query will be + retrieving. Called by the _clone() method after initialising the rest of the instance. """ - # Construct two objects: - # - fields is a list of Field objects to fetch. - # - field_names is a list of field names, which will be the keys in - # the resulting dictionaries. - # 'fields' is used to configure the query, whilst field_names is stored - # in this object for use by iterator(). if self._fields: - opts = self.model._meta - all = dict([(field.column, field) for field in opts.fields]) - for field in opts.fields: - all[field.name] = field if not self.query.extra_select: - try: - fields = [all[f] for f in self._fields] - except KeyError, e: - raise FieldDoesNotExist('%s has no field named %r' - % (opts.object_name, e.args[0])) field_names = list(self._fields) else: - fields = [] field_names = [] + names = set(self.model._meta.get_all_field_names()) for f in self._fields: - if f in all: - fields.append(all[f]) + if f in names: field_names.append(f) elif not self.query.extra_select.has_key(f): raise FieldDoesNotExist('%s has no field named %r' % (self.model._meta.object_name, f)) - else: # Default to all fields. - fields = self.model._meta.fields - field_names = [f.attname for f in fields] + else: + # Default to all fields. + field_names = [f.attname for f in self.model._meta.fields] - self.query.add_local_columns([f.column for f in fields]) + self.query.add_fields(field_names) self.query.default_cols = False self.field_names = field_names diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 491e5b2f5b..f0e2318a74 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -916,7 +916,8 @@ class Query(object): if subtree: self.where.end_subtree() - def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True): + def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True, + allow_explicit_fk=False): """ Compute the necessary table joins for the passage through the fields given in 'names'. 'opts' is the Options class for the current model @@ -939,9 +940,17 @@ class Query(object): try: field, model, direct, m2m = opts.get_field_by_name(name) except FieldDoesNotExist: - names = opts.get_all_field_names() - raise FieldError("Cannot resolve keyword %r into field. " - "Choices are: %s" % (name, ", ".join(names))) + for f in opts.fields: + if allow_explicit_fk and name == f.attname: + # XXX: A hack to allow foo_id to work in values() for + # backwards compatibility purposes. If we dropped that + # feature, this could be removed. + field, model, direct, m2m = opts.get_field_by_name(f.name) + break + else: + names = opts.get_all_field_names() + raise FieldError("Cannot resolve keyword %r into field. " + "Choices are: %s" % (name, ", ".join(names))) if not allow_many and (m2m or not direct): for join in joins: for alias in join: @@ -1102,17 +1111,17 @@ class Query(object): """ return not (self.low_mark or self.high_mark) - def add_local_columns(self, columns): + def add_fields(self, field_names): """ - Adds the given column names to the select set, assuming they come from - the root model (the one given in self.model). + Adds the given (model) fields to the select set. The field names are + added in the order specified. """ - for alias in self.tables: - if self.alias_map[alias][ALIAS_REFCOUNT]: - break - else: - alias = self.get_initial_alias() - self.select.extend([(alias, col) for col in columns]) + alias = self.get_initial_alias() + opts = self.get_meta() + for name in field_names: + u1, target, u2, joins = self.setup_joins(name.split(LOOKUP_SEP), + opts, alias, False, False, True) + self.select.append((joins[-1][-1], target.column)) def add_ordering(self, *ordering): """ diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 374109d355..5fb42de85b 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -160,7 +160,7 @@ class UpdateQuery(Query): query = self.clone(klass=Query) alias = '%s0' % self.alias_prefix query.change_alias(query.tables[0], alias) - self.add_local_columns([query.model._meta.pk.column]) + self.add_fields([query.model._meta.pk.name]) # Now we adjust the current query: reset the where clause and get rid # of all the tables we don't need (since they're in the sub-select). diff --git a/docs/db-api.txt b/docs/db-api.txt index e626d1da86..af5ba8ce49 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -596,6 +596,13 @@ Example:: >>> Blog.objects.values('id', 'name') [{'id': 1, 'name': 'Beatles Blog'}] +You can also retrieve values from across ``ForeignKey`` relations by using +double underscores to separate the field names, just as when calling the +``filter()`` command. For example:: + + >>> Entry.objects.values('blog__name').distinct() + [{'name': 'Beatles Blog'}] + A couple of subtleties that are worth mentioning: * The ``values()`` method does not return anything for ``ManyToManyField`` diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py index c55b0e3e7c..d2c8e87fc1 100644 --- a/tests/modeltests/many_to_one/models.py +++ b/tests/modeltests/many_to_one/models.py @@ -250,6 +250,11 @@ FieldError: Cannot resolve keyword 'reporter_id' into field. Choices are: headli >>> Reporter.objects.filter(article__reporter=r).distinct() [] +# It's possible to use values() calls across many-to-one relations. +>>> d = {'reporter__first_name': u'John', 'reporter__last_name': u'Smith'} +>>> list(Article.objects.filter(reporter=r).distinct().values('reporter__first_name', 'reporter__last_name')) == [d] +True + # If you delete a reporter, his articles will be deleted. >>> Article.objects.all() [, , , , ] diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py index d6ff586ee3..3f13dabc72 100644 --- a/tests/modeltests/model_inheritance/models.py +++ b/tests/modeltests/model_inheritance/models.py @@ -14,6 +14,10 @@ Both styles are demonstrated here. from django.db import models +# +# Abstract base classes +# + class CommonInfo(models.Model): name = models.CharField(max_length=50) age = models.PositiveIntegerField() @@ -34,6 +38,10 @@ class Student(CommonInfo): class Meta: pass +# +# Multi-table inheritance +# + class Place(models.Model): name = models.CharField(max_length=50) address = models.CharField(max_length=80) @@ -227,4 +235,9 @@ True >>> r1.name u'Demon Puppies' +# The values() command also works on fields from parent models. +>>> d = {'rating': 4, 'name': u'Ristorante Miron'} +>>> list(ItalianRestaurant.objects.values('name', 'rating')) == [d] +True + """}