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:
parent
f3ed30f377
commit
428450b7a9
@ -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
|
||||
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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).
|
||||
|
@ -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``
|
||||
|
@ -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>]
|
||||
|
@ -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
|
||||
|
||||
"""}
|
||||
|
Loading…
x
Reference in New Issue
Block a user