Loading django/db/models/options.py +4 −3 Original line number Diff line number Diff line Loading @@ -404,12 +404,13 @@ class Options(object): for f, model in self.get_all_related_objects_with_model(): cache[f.field.related_query_name()] = (f, model, False, False) for f, model in self.get_m2m_with_model(): cache[f.name] = (f, model, True, True) cache[f.name] = cache[f.attname] = (f, model, True, True) for f, model in self.get_fields_with_model(): cache[f.name] = (f, model, True, False) cache[f.name] = cache[f.attname] = (f, model, True, False) for f in self.virtual_fields: if hasattr(f, 'related'): cache[f.name] = (f.related, None if f.model == self.model else f.model, True, False) cache[f.name] = cache[f.attname] = ( f.related, None if f.model == self.model else f.model, True, False) if app_cache_ready(): self._name_map = cache return cache Loading django/db/models/sql/query.py +8 −21 Original line number Diff line number Diff line Loading @@ -1091,8 +1091,7 @@ class Query(object): try: field, sources, opts, join_list, path = self.setup_joins( parts, opts, alias, can_reuse, allow_many, allow_explicit_fk=True) parts, opts, alias, can_reuse, allow_many,) if can_reuse is not None: can_reuse.update(join_list) except MultiJoin as e: Loading Loading @@ -1237,15 +1236,14 @@ class Query(object): len(q_object.children)) return target_clause def names_to_path(self, names, opts, allow_many, allow_explicit_fk): def names_to_path(self, names, opts, allow_many): """ Walks the names path and turns them PathInfo tuples. Note that a single name in 'names' can generate multiple PathInfos (m2m for example). 'names' is the path of names to travle, 'opts' is the model Options we start the name resolving from, 'allow_many' and 'allow_explicit_fk' are as for setup_joins(). start the name resolving from, 'allow_many' is as for setup_joins(). Returns a list of PathInfo tuples. In addition returns the final field (the last used join field), and target (which is a field guaranteed to Loading @@ -1258,14 +1256,6 @@ class Query(object): try: field, model, direct, m2m = opts.get_field_by_name(name) except FieldDoesNotExist: 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: available = opts.get_all_field_names() + list(self.aggregate_select) raise FieldError("Cannot resolve keyword %r into field. " "Choices are: %s" % (name, ", ".join(available))) Loading Loading @@ -1314,7 +1304,7 @@ class Query(object): return path, final_field, targets def setup_joins(self, names, opts, alias, can_reuse=None, allow_many=True, allow_explicit_fk=False, outer_if_first=False): outer_if_first=False): """ Compute the necessary table joins for the passage through the fields given in 'names'. 'opts' is the Options class for the current model Loading @@ -1329,9 +1319,6 @@ class Query(object): If 'allow_many' is False, then any reverse foreign key seen will generate a MultiJoin exception. The 'allow_explicit_fk' controls if field.attname is allowed in the lookups. Returns the final field involved in the joins, the target field (used for any 'where' constraint), the final 'opts' value, the joins and the field path travelled to generate the joins. Loading @@ -1345,7 +1332,7 @@ class Query(object): joins = [alias] # First, generate the path for the names path, final_field, targets = self.names_to_path( names, opts, allow_many, allow_explicit_fk) names, opts, allow_many) # Then, add the path to the query's joins. Note that we can't trim # joins at this stage - we will need the information about join type # of the trimmed joins. Loading tests/lookup/tests.py +2 −1 Original line number Diff line number Diff line Loading @@ -470,7 +470,8 @@ class LookupTests(TestCase): self.fail('FieldError not raised') except FieldError as ex: self.assertEqual(str(ex), "Cannot resolve keyword 'pub_date_year' " "into field. Choices are: author, headline, id, pub_date, tag") "into field. Choices are: author, author_id, headline, " "id, pub_date, tag") try: Article.objects.filter(headline__starts='Article') self.fail('FieldError not raised') Loading Loading
django/db/models/options.py +4 −3 Original line number Diff line number Diff line Loading @@ -404,12 +404,13 @@ class Options(object): for f, model in self.get_all_related_objects_with_model(): cache[f.field.related_query_name()] = (f, model, False, False) for f, model in self.get_m2m_with_model(): cache[f.name] = (f, model, True, True) cache[f.name] = cache[f.attname] = (f, model, True, True) for f, model in self.get_fields_with_model(): cache[f.name] = (f, model, True, False) cache[f.name] = cache[f.attname] = (f, model, True, False) for f in self.virtual_fields: if hasattr(f, 'related'): cache[f.name] = (f.related, None if f.model == self.model else f.model, True, False) cache[f.name] = cache[f.attname] = ( f.related, None if f.model == self.model else f.model, True, False) if app_cache_ready(): self._name_map = cache return cache Loading
django/db/models/sql/query.py +8 −21 Original line number Diff line number Diff line Loading @@ -1091,8 +1091,7 @@ class Query(object): try: field, sources, opts, join_list, path = self.setup_joins( parts, opts, alias, can_reuse, allow_many, allow_explicit_fk=True) parts, opts, alias, can_reuse, allow_many,) if can_reuse is not None: can_reuse.update(join_list) except MultiJoin as e: Loading Loading @@ -1237,15 +1236,14 @@ class Query(object): len(q_object.children)) return target_clause def names_to_path(self, names, opts, allow_many, allow_explicit_fk): def names_to_path(self, names, opts, allow_many): """ Walks the names path and turns them PathInfo tuples. Note that a single name in 'names' can generate multiple PathInfos (m2m for example). 'names' is the path of names to travle, 'opts' is the model Options we start the name resolving from, 'allow_many' and 'allow_explicit_fk' are as for setup_joins(). start the name resolving from, 'allow_many' is as for setup_joins(). Returns a list of PathInfo tuples. In addition returns the final field (the last used join field), and target (which is a field guaranteed to Loading @@ -1258,14 +1256,6 @@ class Query(object): try: field, model, direct, m2m = opts.get_field_by_name(name) except FieldDoesNotExist: 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: available = opts.get_all_field_names() + list(self.aggregate_select) raise FieldError("Cannot resolve keyword %r into field. " "Choices are: %s" % (name, ", ".join(available))) Loading Loading @@ -1314,7 +1304,7 @@ class Query(object): return path, final_field, targets def setup_joins(self, names, opts, alias, can_reuse=None, allow_many=True, allow_explicit_fk=False, outer_if_first=False): outer_if_first=False): """ Compute the necessary table joins for the passage through the fields given in 'names'. 'opts' is the Options class for the current model Loading @@ -1329,9 +1319,6 @@ class Query(object): If 'allow_many' is False, then any reverse foreign key seen will generate a MultiJoin exception. The 'allow_explicit_fk' controls if field.attname is allowed in the lookups. Returns the final field involved in the joins, the target field (used for any 'where' constraint), the final 'opts' value, the joins and the field path travelled to generate the joins. Loading @@ -1345,7 +1332,7 @@ class Query(object): joins = [alias] # First, generate the path for the names path, final_field, targets = self.names_to_path( names, opts, allow_many, allow_explicit_fk) names, opts, allow_many) # Then, add the path to the query's joins. Note that we can't trim # joins at this stage - we will need the information about join type # of the trimmed joins. Loading
tests/lookup/tests.py +2 −1 Original line number Diff line number Diff line Loading @@ -470,7 +470,8 @@ class LookupTests(TestCase): self.fail('FieldError not raised') except FieldError as ex: self.assertEqual(str(ex), "Cannot resolve keyword 'pub_date_year' " "into field. Choices are: author, headline, id, pub_date, tag") "into field. Choices are: author, author_id, headline, " "id, pub_date, tag") try: Article.objects.filter(headline__starts='Article') self.fail('FieldError not raised') Loading