summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorAnssi Kääriäinen <akaariai@gmail.com>2012-06-26 18:08:42 +0300
committerAnssi Kääriäinen <akaariai@gmail.com>2012-06-26 18:08:42 +0300
commitb6c356b7bb97f3d6d4831b31e67868313bbbc090 (patch)
tree1cd4c587080703c5b86b13349858509ce145ca4c /django
parent531878302735e6a2b36d82b584947bbf8eae8111 (diff)
Fixed #17485 -- Made defer work with select_related
This commit tackles a couple of issues. First, in certain cases there were some mixups if field.attname or field.name should be deferred. Field.attname is now always used. Another issue tackled is a case where field is both deferred by .only(), and selected by select_related. This case is now an error. A lot of thanks to koniiiik (Michal Petrucha) for the patch, and to Andrei Antoukh for review.
Diffstat (limited to 'django')
-rw-r--r--django/db/models/query.py7
-rw-r--r--django/db/models/query_utils.py13
-rw-r--r--django/db/models/sql/compiler.py7
-rw-r--r--django/db/models/sql/query.py12
4 files changed, 29 insertions, 10 deletions
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 755820c3b0..0f1d87c642 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -1296,7 +1296,7 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
# Build the list of fields that *haven't* been requested
for field, model in klass._meta.get_fields_with_model():
if field.name not in load_fields:
- skip.add(field.name)
+ skip.add(field.attname)
elif local_only and model is not None:
continue
else:
@@ -1327,7 +1327,7 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
related_fields = []
for f in klass._meta.fields:
- if select_related_descend(f, restricted, requested):
+ if select_related_descend(f, restricted, requested, load_fields):
if restricted:
next = requested[f.name]
else:
@@ -1339,7 +1339,8 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
reverse_related_fields = []
if restricted:
for o in klass._meta.get_all_related_objects():
- if o.field.unique and select_related_descend(o.field, restricted, requested, reverse=True):
+ if o.field.unique and select_related_descend(o.field, restricted, requested,
+ only_load.get(o.model), reverse=True):
next = requested[o.field.related_query_name()]
klass_info = get_klass_info(o.model, max_depth=max_depth, cur_depth=cur_depth+1,
requested=next, only_load=only_load, local_only=True)
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py
index a7c176fd8f..60bdb2bcb4 100644
--- a/django/db/models/query_utils.py
+++ b/django/db/models/query_utils.py
@@ -126,18 +126,19 @@ class DeferredAttribute(object):
return None
-def select_related_descend(field, restricted, requested, reverse=False):
+def select_related_descend(field, restricted, requested, load_fields, reverse=False):
"""
Returns True if this field should be used to descend deeper for
select_related() purposes. Used by both the query construction code
(sql.query.fill_related_selections()) and the model instance creation code
- (query.get_cached_row()).
+ (query.get_klass_info()).
Arguments:
* field - the field to be checked
* restricted - a boolean field, indicating if the field list has been
manually restricted using a requested clause)
* requested - The select_related() dictionary.
+ * load_fields - the set of fields to be loaded on this model
* reverse - boolean, True if we are checking a reverse select related
"""
if not field.rel:
@@ -151,6 +152,14 @@ def select_related_descend(field, restricted, requested, reverse=False):
return False
if not restricted and field.null:
return False
+ if load_fields:
+ if field.name not in load_fields:
+ if restricted and field.name in requested:
+ raise InvalidQuery("Field %s.%s cannot be both deferred"
+ " and traversed using select_related"
+ " at the same time." %
+ (field.model._meta.object_name, field.name))
+ return False
return True
# This function is needed because data descriptors must be defined on a class
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 5801b2f428..d44cdfe4a4 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -596,6 +596,7 @@ class SQLCompiler(object):
if avoid_set is None:
avoid_set = set()
orig_dupe_set = dupe_set
+ only_load = self.query.get_loaded_field_names()
# Setup for the case when only particular related fields should be
# included in the related selection.
@@ -607,7 +608,8 @@ class SQLCompiler(object):
restricted = False
for f, model in opts.get_fields_with_model():
- if not select_related_descend(f, restricted, requested):
+ if not select_related_descend(f, restricted, requested,
+ only_load.get(model or self.query.model)):
continue
# The "avoid" set is aliases we want to avoid just for this
# particular branch of the recursion. They aren't permanently
@@ -680,7 +682,8 @@ class SQLCompiler(object):
if o.field.unique
]
for f, model in related_fields:
- if not select_related_descend(f, restricted, requested, reverse=True):
+ if not select_related_descend(f, restricted, requested,
+ only_load.get(model), reverse=True):
continue
# The "avoid" set is aliases we want to avoid just for this
# particular branch of the recursion. They aren't permanently
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 7f331bfe7f..8fbba3dbc9 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -1845,9 +1845,15 @@ class Query(object):
If no fields are marked for deferral, returns an empty dictionary.
"""
- collection = {}
- self.deferred_to_data(collection, self.get_loaded_field_names_cb)
- return collection
+ # We cache this because we call this function multiple times
+ # (compiler.fill_related_selections, query.iterator)
+ try:
+ return self._loaded_field_names_cache
+ except AttributeError:
+ collection = {}
+ self.deferred_to_data(collection, self.get_loaded_field_names_cb)
+ self._loaded_field_names_cache = collection
+ return collection
def get_loaded_field_names_cb(self, target, model, fields):
"""