1
0
mirror of https://github.com/django/django.git synced 2025-07-05 18:29:11 +00:00

queryset-refactor: Refactored the way values() works so that it works properly

across inherited models.

Completely by accident, this also allows values() queries to include fields
from related models, providing it is crossing a single-valued relation
(one-to-one, many-to-one). Many-to-many values() fields still aren't supported,
since that requires actual thinking. So this refs #5768.


git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@7230 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2008-03-12 12:41:58 +00:00
parent f3ed30f377
commit 428450b7a9
6 changed files with 56 additions and 37 deletions

View File

@ -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

View File

@ -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):
"""

View File

@ -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).

View File

@ -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``

View File

@ -250,6 +250,11 @@ FieldError: Cannot resolve keyword 'reporter_id' into field. Choices are: headli
>>> Reporter.objects.filter(article__reporter=r).distinct()
[<Reporter: John Smith>]
# 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()
[<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>, <Article: This is a test>, <Article: This is a test>]

View File

@ -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
"""}