diff options
| author | Niclas Olofsson <n@niclasolofsson.se> | 2014-12-04 21:47:48 +0100 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2014-12-24 14:54:30 -0500 |
| commit | 3daa9d60be6672841ceed5bb812b6fb25950dc16 (patch) | |
| tree | 1771ecd7ab15fd5795bb605c9d7e710ed0b86935 /django/db/models/sql | |
| parent | b27db97b236569d059fa824c3078e06adf8b4fae (diff) | |
Fixed #10414 -- Made select_related() fail on invalid field names.
Diffstat (limited to 'django/db/models/sql')
| -rw-r--r-- | django/db/models/sql/compiler.py | 55 |
1 files changed, 46 insertions, 9 deletions
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index c70dd8c5bd..e8948cb9e1 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -1,3 +1,4 @@ +from itertools import chain import warnings from django.core.exceptions import FieldError @@ -599,6 +600,14 @@ class SQLCompiler(object): (for example, cur_depth=1 means we are looking at models with direct connections to the root model). """ + def _get_field_choices(): + direct_choices = (f.name for (f, _) in opts.get_fields_with_model() if f.rel) + reverse_choices = ( + f.field.related_query_name() + for f in opts.get_all_related_objects() if f.field.unique + ) + return chain(direct_choices, reverse_choices) + if not restricted and self.query.max_depth and cur_depth > self.query.max_depth: # We've recursed far enough; bail out. return @@ -611,6 +620,7 @@ class SQLCompiler(object): # Setup for the case when only particular related fields should be # included in the related selection. + fields_found = set() if requested is None: if isinstance(self.query.select_related, dict): requested = self.query.select_related @@ -619,6 +629,24 @@ class SQLCompiler(object): restricted = False for f, model in opts.get_fields_with_model(): + fields_found.add(f.name) + + if restricted: + next = requested.get(f.name, {}) + if not f.rel: + # If a non-related field is used like a relation, + # or if a single non-relational field is given. + if next or (cur_depth == 1 and f.name in requested): + raise FieldError( + "Non-relational field given in select_related: '%s'. " + "Choices are: %s" % ( + f.name, + ", ".join(_get_field_choices()) or '(none)', + ) + ) + else: + next = False + # The get_fields_with_model() returns None for fields that live # in the field's local model. So, for those fields we want to use # the f.model - that is the field's local model. @@ -632,13 +660,9 @@ class SQLCompiler(object): columns, _ = self.get_default_columns(start_alias=alias, opts=f.rel.to._meta, as_pairs=True) self.query.related_select_cols.extend( - SelectInfo((col[0], col[1].column), col[1]) for col in columns) - if restricted: - next = requested.get(f.name, {}) - else: - next = False - self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1, - next, restricted) + SelectInfo((col[0], col[1].column), col[1]) for col in columns + ) + self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1, next, restricted) if restricted: related_fields = [ @@ -651,8 +675,10 @@ class SQLCompiler(object): only_load.get(model), reverse=True): continue - _, _, _, joins, _ = self.query.setup_joins( - [f.related_query_name()], opts, root_alias) + related_field_name = f.related_query_name() + fields_found.add(related_field_name) + + _, _, _, joins, _ = self.query.setup_joins([related_field_name], opts, root_alias) alias = joins[-1] from_parent = (opts.model if issubclass(model, opts.model) else None) @@ -664,6 +690,17 @@ class SQLCompiler(object): self.fill_related_selections(model._meta, alias, cur_depth + 1, next, restricted) + fields_not_found = set(requested.keys()).difference(fields_found) + if fields_not_found: + invalid_fields = ("'%s'" % s for s in fields_not_found) + raise FieldError( + 'Invalid field name(s) given in select_related: %s. ' + 'Choices are: %s' % ( + ', '.join(invalid_fields), + ', '.join(_get_field_choices()) or '(none)', + ) + ) + def deferred_to_columns(self): """ Converts the self.deferred_loading data structure to mapping of table |
