1
0
mirror of https://github.com/django/django.git synced 2025-07-06 10:49:17 +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): 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 Called by the _clone() method after initialising the rest of the
instance. 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: 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: 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) field_names = list(self._fields)
else: else:
fields = []
field_names = [] field_names = []
names = set(self.model._meta.get_all_field_names())
for f in self._fields: for f in self._fields:
if f in all: if f in names:
fields.append(all[f])
field_names.append(f) field_names.append(f)
elif not self.query.extra_select.has_key(f): elif not self.query.extra_select.has_key(f):
raise FieldDoesNotExist('%s has no field named %r' raise FieldDoesNotExist('%s has no field named %r'
% (self.model._meta.object_name, f)) % (self.model._meta.object_name, f))
else: # Default to all fields. else:
fields = self.model._meta.fields # Default to all fields.
field_names = [f.attname for f in 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.query.default_cols = False
self.field_names = field_names self.field_names = field_names

View File

@ -916,7 +916,8 @@ class Query(object):
if subtree: if subtree:
self.where.end_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 Compute the necessary table joins for the passage through the fields
given in 'names'. 'opts' is the Options class for the current model given in 'names'. 'opts' is the Options class for the current model
@ -939,9 +940,17 @@ class Query(object):
try: try:
field, model, direct, m2m = opts.get_field_by_name(name) field, model, direct, m2m = opts.get_field_by_name(name)
except FieldDoesNotExist: except FieldDoesNotExist:
names = opts.get_all_field_names() for f in opts.fields:
raise FieldError("Cannot resolve keyword %r into field. " if allow_explicit_fk and name == f.attname:
"Choices are: %s" % (name, ", ".join(names))) # 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): if not allow_many and (m2m or not direct):
for join in joins: for join in joins:
for alias in join: for alias in join:
@ -1102,17 +1111,17 @@ class Query(object):
""" """
return not (self.low_mark or self.high_mark) 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 Adds the given (model) fields to the select set. The field names are
the root model (the one given in self.model). added in the order specified.
""" """
for alias in self.tables: alias = self.get_initial_alias()
if self.alias_map[alias][ALIAS_REFCOUNT]: opts = self.get_meta()
break for name in field_names:
else: u1, target, u2, joins = self.setup_joins(name.split(LOOKUP_SEP),
alias = self.get_initial_alias() opts, alias, False, False, True)
self.select.extend([(alias, col) for col in columns]) self.select.append((joins[-1][-1], target.column))
def add_ordering(self, *ordering): def add_ordering(self, *ordering):
""" """

View File

@ -160,7 +160,7 @@ class UpdateQuery(Query):
query = self.clone(klass=Query) query = self.clone(klass=Query)
alias = '%s0' % self.alias_prefix alias = '%s0' % self.alias_prefix
query.change_alias(query.tables[0], alias) 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 # 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). # 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') >>> Blog.objects.values('id', 'name')
[{'id': 1, 'name': 'Beatles Blog'}] [{'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: A couple of subtleties that are worth mentioning:
* The ``values()`` method does not return anything for ``ManyToManyField`` * 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.objects.filter(article__reporter=r).distinct()
[<Reporter: John Smith>] [<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. # If you delete a reporter, his articles will be deleted.
>>> Article.objects.all() >>> 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>] [<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 from django.db import models
#
# Abstract base classes
#
class CommonInfo(models.Model): class CommonInfo(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
age = models.PositiveIntegerField() age = models.PositiveIntegerField()
@ -34,6 +38,10 @@ class Student(CommonInfo):
class Meta: class Meta:
pass pass
#
# Multi-table inheritance
#
class Place(models.Model): class Place(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
address = models.CharField(max_length=80) address = models.CharField(max_length=80)
@ -227,4 +235,9 @@ True
>>> r1.name >>> r1.name
u'Demon Puppies' 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
"""} """}