Refs #24267 -- Implemented lookups for related fields

Previously related fields didn't implement get_lookup, instead
related fields were treated specially. This commit removed some of
the special handling. In particular, related fields return Lookup
instances now, too.

Other notable changes in this commit is removal of support for
annotations in names_to_path().
This commit is contained in:
Anssi Kääriäinen 2015-02-02 13:48:30 +02:00 committed by Tim Graham
parent 8654c6a732
commit b68212f539
6 changed files with 219 additions and 94 deletions

View file

@ -17,7 +17,8 @@ from django.db import DEFAULT_DB_ALIAS, connections
from django.db.models.aggregates import Count
from django.db.models.constants import LOOKUP_SEP
from django.db.models.expressions import Col, Ref
from django.db.models.query_utils import Q, PathInfo, refs_aggregate
from django.db.models.fields.related_lookups import MultiColSource
from django.db.models.query_utils import Q, PathInfo, refs_expression
from django.db.models.sql.constants import (
INNER, LOUTER, ORDER_DIR, ORDER_PATTERN, QUERY_TERMS, SINGLE,
)
@ -1006,7 +1007,7 @@ class Query(object):
"""
lookup_splitted = lookup.split(LOOKUP_SEP)
if self._annotations:
aggregate, aggregate_lookups = refs_aggregate(lookup_splitted, self.annotations)
aggregate, aggregate_lookups = refs_expression(lookup_splitted, self.annotations)
if aggregate:
return aggregate_lookups, (), aggregate
_, field, _, lookup_parts = self.names_to_path(lookup_splitted, self.get_meta())
@ -1157,24 +1158,26 @@ class Query(object):
if can_reuse is not None:
can_reuse.update(join_list)
used_joins = set(used_joins).union(set(join_list))
# Process the join list to see if we can remove any non-needed joins from
# the far end (fewer tables in a query is better).
targets, alias, join_list = self.trim_joins(sources, join_list, path)
if hasattr(field, 'get_lookup_constraint'):
# For now foreign keys get special treatment. This should be
# refactored when composite fields lands.
condition = field.get_lookup_constraint(self.where_class, alias, targets, sources,
lookups, value)
lookup_type = lookups[-1]
else:
assert(len(targets) == 1)
if hasattr(targets[0], 'as_sql'):
# handle Expressions as annotations
col = targets[0]
if field.is_relation:
# No support for transforms for relational fields
assert len(lookups) == 1
lookup_class = field.get_lookup(lookups[0])
# Undo the changes done in setup_joins() if hasattr(final_field, 'field') branch
# This hack is needed as long as the field.rel isn't like a real field.
if field.get_path_info()[-1].target_fields != sources:
target_field = field.rel
else:
col = targets[0].get_col(alias, field)
target_field = field
if len(targets) == 1:
lhs = targets[0].get_col(alias, target_field)
else:
lhs = MultiColSource(alias, targets, sources, target_field)
condition = lookup_class(lhs, value)
lookup_type = lookup_class.lookup_name
else:
col = targets[0].get_col(alias, field)
condition = self.build_lookup(lookups, col, value)
lookup_type = condition.lookup_name
@ -1284,14 +1287,6 @@ class Query(object):
)
model = field.model._meta.concrete_model
except FieldDoesNotExist:
# is it an annotation?
if self._annotations and name in self._annotations:
field, model = self._annotations[name], None
if not field.contains_aggregate:
# Local non-relational field.
final_field = field
targets = (field,)
break
# We didn't find the current field, so move position back
# one step.
pos -= 1
@ -1985,7 +1980,7 @@ def is_reverse_o2o(field):
A little helper to check if the given field is reverse-o2o. The field is
expected to be some sort of relation field or related object.
"""
return not hasattr(field, 'rel') and field.field.unique
return field.is_relation and field.one_to_one and not field.concrete
class JoinPromoter(object):