Ticket #11670: 11670.field-lookup-collisions.6.diff
File 11670.field-lookup-collisions.6.diff, 37.1 KB (added by , 13 years ago) |
---|
-
django/contrib/admin/options.py
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 6be2a64..da24b40 100644
a b from django.core.exceptions import PermissionDenied, ValidationError 13 13 from django.core.paginator import Paginator 14 14 from django.core.urlresolvers import reverse 15 15 from django.db import models, transaction, router 16 from django.db.models.related import RelatedObject 17 from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist 16 from django.db.models.fields import BLANK_CHOICE_DASH 18 17 from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS 19 18 from django.http import Http404, HttpResponse, HttpResponseRedirect 20 19 from django.shortcuts import get_object_or_404 … … class BaseModelAdmin(object): 233 232 if k == lookup and v == value: 234 233 return True 235 234 236 parts = lookup.split(LOOKUP_SEP) 235 parts, fields, _ = model._meta.resolve_lookup_path(lookup) 236 parts = list(parts) 237 num_parts = len(parts) 238 num_fields = len(fields) 237 239 238 # Last term in lookup is a query term (__exact, __startswith etc) 239 # This term can be ignored. 240 if len(parts) > 1 and parts[-1] in QUERY_TERMS: 240 if not num_fields: 241 # Lookups on non-existants fields are ok, since they're ignored 242 # later. 243 return True 244 245 elif num_parts > 1 and parts[-1] in QUERY_TERMS: 241 246 parts.pop() 247 num_parts += -1 242 248 243 249 # Special case -- foo__id__exact and foo__id queries are implied 244 250 # if foo has been specificially included in the lookup list; so 245 251 # drop __id if it is the last part. However, first we need to find 246 252 # the pk attribute name. 247 pk_attr_name = None 248 for part in parts[:-1]: 249 field, _, _, _ = model._meta.get_field_by_name(part) 250 if hasattr(field, 'rel'): 251 model = field.rel.to 252 pk_attr_name = model._meta.pk.name 253 elif isinstance(field, RelatedObject): 254 model = field.model 255 pk_attr_name = model._meta.pk.name 256 else: 257 pk_attr_name = None 258 if pk_attr_name and len(parts) > 1 and parts[-1] == pk_attr_name: 253 if (num_fields == num_parts and 254 parts[-1] == fields[-1][0].model._meta.pk.name): 259 255 parts.pop() 256 num_parts += -1 260 257 261 try: 262 self.model._meta.get_field_by_name(parts[0]) 263 except FieldDoesNotExist: 264 # Lookups on non-existants fields are ok, since they're ignored 265 # later. 258 if num_parts == 1: 266 259 return True 267 else: 268 if len(parts) == 1: 269 return True 270 clean_lookup = LOOKUP_SEP.join(parts) 271 return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy 260 261 clean_lookup = LOOKUP_SEP.join(parts) 262 return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy 272 263 273 264 def has_add_permission(self, request): 274 265 """ -
django/contrib/admin/util.py
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 7204a12..3c5d1ab 100644
a b 1 1 from django.db import models 2 2 from django.db.models.sql.constants import LOOKUP_SEP 3 3 from django.db.models.deletion import Collector 4 from django.db.models.related import RelatedObject 4 from django.db.models.related import RelatedObject, get_model_from_relation, NotRelationField 5 5 from django.forms.forms import pretty_name 6 6 from django.utils import formats 7 7 from django.utils.html import escape … … def display_for_field(value, field): 302 302 else: 303 303 return smart_unicode(value) 304 304 305 306 class NotRelationField(Exception):307 pass308 309 310 def get_model_from_relation(field):311 if isinstance(field, models.related.RelatedObject):312 return field.model313 elif getattr(field, 'rel'): # or isinstance?314 return field.rel.to315 else:316 raise NotRelationField317 318 319 305 def reverse_field_path(model, path): 320 306 """ Create a reversed field path. 321 307 … … def reverse_field_path(model, path): 343 329 related_name = field.field.name 344 330 parent = field.model 345 331 reversed_path.insert(0, related_name) 346 return (parent, LOOKUP_SEP.join(reversed_path)) 347 348 349 def get_fields_from_path(model, path): 350 """ Return list of Fields given path relative to model. 351 352 e.g. (ModelX, "user__groups__name") -> [ 353 <django.db.models.fields.related.ForeignKey object at 0x...>, 354 <django.db.models.fields.related.ManyToManyField object at 0x...>, 355 <django.db.models.fields.CharField object at 0x...>, 356 ] 357 """ 358 pieces = path.split(LOOKUP_SEP) 359 fields = [] 360 for piece in pieces: 361 if fields: 362 parent = get_model_from_relation(fields[-1]) 363 else: 364 parent = model 365 fields.append(parent._meta.get_field_by_name(piece)[0]) 366 return fields 332 return parent, LOOKUP_SEP.join(reversed_path) 367 333 368 334 369 335 def remove_trailing_data_field(fields): … … def get_limit_choices_to_from_path(model, path): 381 347 If final model in path is linked via a ForeignKey or ManyToManyField which 382 348 has a `limit_choices_to` attribute, return it as a Q object. 383 349 """ 384 385 fields = get_fields_from_path(model, path)350 _, fields, _ = model._meta.resolve_lookup_path(path) 351 fields = [field[0] for field in fields] 386 352 fields = remove_trailing_data_field(fields) 387 353 limit_choices_to = ( 388 354 fields and hasattr(fields[-1], 'rel') and -
django/contrib/admin/validation.py
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index 733f89d..511a644 100644
a b from django.db.models.fields import FieldDoesNotExist 4 4 from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model, 5 5 _get_foreign_key) 6 6 from django.contrib.admin import ListFilter, FieldListFilter 7 from django.contrib.admin.util import get_fields_from_path, NotRelationField8 7 from django.contrib.admin.options import (flatten_fieldsets, BaseModelAdmin, 9 8 HORIZONTAL, VERTICAL) 10 9 … … def validate(cls, model): 84 83 # item is option #1 85 84 field = item 86 85 # Validate the field string 87 try: 88 get_fields_from_path(model, field) 89 except (NotRelationField, FieldDoesNotExist): 86 _, _, last_field = model._meta.resolve_lookup_path(field) 87 if not last_field: 90 88 raise ImproperlyConfigured("'%s.list_filter[%d]' refers to '%s'" 91 89 " which does not refer to a Field." 92 90 % (cls.__name__, idx, field)) -
django/contrib/admin/views/main.py
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 616b249..0b52452 100644
a b from django.utils.http import urlencode 10 10 11 11 from django.contrib.admin import FieldListFilter 12 12 from django.contrib.admin.options import IncorrectLookupParameters 13 from django.contrib.admin.util import quote , get_fields_from_path13 from django.contrib.admin.util import quote 14 14 15 15 # Changelist settings 16 16 ALL_VAR = 'all' … … class ChangeList(object): 105 105 field, field_list_filter_class = list_filter, FieldListFilter.create 106 106 if not isinstance(field, models.Field): 107 107 field_path = field 108 field = get_fields_from_path(self.model, field_path)[-1]109 spec = field_list_filter_class(field , request, cleaned_params,108 _, _, field = self.model._meta.resolve_lookup_path(field_path) 109 spec = field_list_filter_class(field[0], request, cleaned_params, 110 110 self.model, self.model_admin, field_path=field_path) 111 111 if spec and spec.has_output(): 112 112 filter_specs.append(spec) -
django/contrib/gis/db/models/sql/where.py
diff --git a/django/contrib/gis/db/models/sql/where.py b/django/contrib/gis/db/models/sql/where.py index 0e15222..f36217d 100644
a b 1 from django.db.models.fields import FieldDoesNotExist2 from django.db.models.sql.constants import LOOKUP_SEP3 1 from django.db.models.sql.expressions import SQLEvaluator 4 2 from django.db.models.sql.where import Constraint, WhereNode 5 3 from django.contrib.gis.db.models.fields import GeometryField … … class GeoWhereNode(WhereNode): 58 56 'address__point'. 59 57 60 58 If a GeometryField exists according to the given lookup on the model 61 options, it will be returned. Otherwise returns None.59 options, it will be returned. Otherwise returns False. 62 60 """ 63 # This takes into account the situation where the lookup is a 64 # lookup to a related geographic field, e.g., 'address__point'. 65 field_list = lookup.split(LOOKUP_SEP) 66 67 # Reversing so list operates like a queue of related lookups, 68 # and popping the top lookup. 69 field_list.reverse() 70 fld_name = field_list.pop() 71 72 try: 73 geo_fld = opts.get_field(fld_name) 74 # If the field list is still around, then it means that the 75 # lookup was for a geometry field across a relationship -- 76 # thus we keep on getting the related model options and the 77 # model field associated with the next field in the list 78 # until there's no more left. 79 while len(field_list): 80 opts = geo_fld.rel.to._meta 81 geo_fld = opts.get_field(field_list.pop()) 82 except (FieldDoesNotExist, AttributeError): 83 return False 84 85 # Finally, make sure we got a Geographic field and return. 86 if isinstance(geo_fld, GeometryField): 87 return geo_fld 61 _, _, field = opts.resolve_lookup_path(lookup) 62 if field[0] and isinstance(field[0], GeometryField): 63 return field[0] 88 64 else: 89 65 return False -
django/db/models/options.py
diff --git a/django/db/models/options.py b/django/db/models/options.py index 0cd52a3..0f48510 100644
a b 1 from copy import copy 1 2 import re 2 3 from bisect import bisect 3 4 5 from django.core.exceptions import FieldError 6 from django.db.models.sql.constants import LOOKUP_SEP 4 7 from django.conf import settings 5 from django.db.models.related import RelatedObject 8 from django.db.models.related import RelatedObject, get_model_from_relation, NotRelationField 6 9 from django.db.models.fields.related import ManyToManyRel 7 10 from django.db.models.fields import AutoField, FieldDoesNotExist 8 11 from django.db.models.fields.proxy import OrderWrt … … class Options(object): 54 57 # from *other* models. Needed for some admin checks. Internal use only. 55 58 self.related_fkey_lookups = [] 56 59 60 self._resolve_lookup_path_cache = {} 61 57 62 def contribute_to_class(self, cls, name): 58 63 from django.db import connection 59 64 from django.db.backends.util import truncate_name … … class Options(object): 495 500 Returns the index of the primary key field in the self.fields list. 496 501 """ 497 502 return self.fields.index(self.pk) 503 504 def resolve_lookup_path(self, path, allow_explicit_fk=False, query=None): 505 """ 506 Resolves the given lookup path and returns a tuple (parts, fields, 507 last_field) where parts is a tuple containing the lookup parts split by 508 LOOKUP_SEP; fields is a tuple of information (as returned by 509 get_field_by_name()) for all the fields that have been resolved 510 traversing the relations; and last_field is the information (also as 511 returned by get_field_by_name()) for the last part of the lookup, if 512 that part actually is a field, or None if it isn't. Note that only 513 tuples are returned so that the calling code explicitly has to 514 convert them to lists if it wants to modify them -- this is required 515 since the results are cached and we want to preserve them. 516 """ 517 # Look in the cache first. 518 if path in self._resolve_lookup_path_cache: 519 return self._resolve_lookup_path_cache[path] 520 521 parts = tuple(path.split(LOOKUP_SEP)) 522 if not parts: 523 raise FieldError("Cannot parse lookup path %r" % path) 524 525 num_parts = len(parts) 526 fields = () 527 last_field = None 528 529 # Traverse the lookup query to distinguish related fields from 530 # lookup types and aggregates. 531 try: 532 opts = self 533 for counter, field_name in enumerate(parts): 534 if field_name == 'pk': 535 field_name = opts.pk.name 536 field_info = opts.get_field_by_name(field_name) 537 fields += (field_info,) 538 if (counter + 1) < num_parts: 539 # If we haven't reached the end of the list of 540 # lookups yet, then let's attempt to continue 541 # traversing relations. 542 related_model = get_model_from_relation(field_info[0]) 543 opts = related_model._meta 544 # We have reached the end of the query and the last lookup 545 # is a field. 546 last_field = fields[-1] 547 except FieldDoesNotExist: 548 if (allow_explicit_fk and 549 field_name not in query.query_terms and 550 field_name not in query.aggregate_select.keys() and 551 field_name not in query.aggregates.keys() and 552 LOOKUP_SEP.join(parts) not in query.aggregates.keys()): 553 # XXX: A hack to allow foo_id to work in values() for 554 # backwards compatibility purposes. If we dropped that 555 # feature, this could be removed. 556 for f in opts.fields: 557 if field_name == f.attname: 558 field_info = opts.get_field_by_name(f.name) 559 fields += (field_info,) 560 break 561 else: 562 raise FieldError("Cannot resolve keyword %r into field. " 563 "Choices are: %s" % ( 564 field_name, 565 ", ".join(opts.get_all_field_names()))) 566 # The traversing didn't reach the end because at least one of 567 # the lookups wasn't a field. 568 except NotRelationField: 569 # The traversing didn't reach the end because at least one of 570 # the lookups wasn't a relation field. 571 pass 572 # Cache the result. 573 self._resolve_lookup_path_cache[path] = ( 574 parts, fields, last_field) 575 return self._resolve_lookup_path_cache[path] -
django/db/models/related.py
diff --git a/django/db/models/related.py b/django/db/models/related.py index 90995d7..ad1fe47 100644
a b class RelatedObject(object): 36 36 {'%s__isnull' % self.parent_model._meta.module_name: False}) 37 37 lst = [(x._get_pk_val(), smart_unicode(x)) for x in queryset] 38 38 return first_choice + lst 39 39 40 40 def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): 41 41 # Defer to the actual field definition for db prep 42 42 return self.field.get_db_prep_lookup(lookup_type, value, … … class RelatedObject(object): 67 67 68 68 def get_cache_name(self): 69 69 return "_%s_cache" % self.get_accessor_name() 70 71 class NotRelationField(Exception): 72 pass 73 74 def get_model_from_relation(field): 75 if isinstance(field, RelatedObject): 76 return field.model 77 elif getattr(field, 'rel'): # or isinstance? 78 return field.rel.to 79 else: 80 raise NotRelationField 81 No newline at end of file -
django/db/models/sql/compiler.py
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 6bf7de2..45d76a1 100644
a b class SQLCompiler(object): 383 383 The 'name' is of the form 'field1__field2__...__fieldN'. 384 384 """ 385 385 name, order = get_order_dir(name, default_order) 386 pieces = name.split(LOOKUP_SEP)387 386 if not alias: 388 387 alias = self.query.get_initial_alias() 389 field, target, opts, joins, last, extra = self.query.setup_joins( pieces,390 388 field, target, opts, joins, last, extra = self.query.setup_joins( 389 name, opts, alias, False) 391 390 alias = joins[-1] 392 391 col = target.column 393 392 if not field.rel: -
django/db/models/sql/expressions.py
diff --git a/django/db/models/sql/expressions.py b/django/db/models/sql/expressions.py index 1bbf742..dde46ea 100644
a b class SQLEvaluator(object): 36 36 def prepare_leaf(self, node, query, allow_joins): 37 37 if not allow_joins and LOOKUP_SEP in node.name: 38 38 raise FieldError("Joined field references are not permitted in this query") 39 40 field_list = node.name.split(LOOKUP_SEP)41 if (len( field_list) == 1 and39 opts = query.get_meta() 40 parts, _, _ = opts.resolve_lookup_path(node.name) 41 if (len(parts) == 1 and 42 42 node.name in query.aggregate_select.keys()): 43 43 self.contains_aggregate = True 44 44 self.cols[node] = query.aggregate_select[node.name] 45 45 else: 46 46 try: 47 47 field, source, opts, join_list, last, _ = query.setup_joins( 48 field_list, query.get_meta(), 49 query.get_initial_alias(), False) 48 node.name, opts, query.get_initial_alias(), False) 50 49 col, _, join_list = query.trim_joins(source, join_list, last, False) 51 50 52 51 self.cols[node] = (join_list[-1], col) -
django/db/models/sql/query.py
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 61fd2be..cb71e65 100644
a b class Query(object): 971 971 Adds a single aggregate expression to the Query 972 972 """ 973 973 opts = model._meta 974 field_list = aggregate.lookup.split(LOOKUP_SEP)975 if len( field_list) == 1 and aggregate.lookup in self.aggregates:974 parts, fields, _ = opts.resolve_lookup_path(aggregate.lookup) 975 if len(parts) == 1 and aggregate.lookup in self.aggregates: 976 976 # Aggregate is over an annotation 977 field_name = field_list[0] 978 col = field_name 979 source = self.aggregates[field_name] 977 col = parts[0] 978 source = self.aggregates[col] 980 979 if not is_summary: 981 980 raise FieldError("Cannot compute %s('%s'): '%s' is an aggregate" % ( 982 aggregate.name, field_name, field_name))983 elif ((len( field_list) > 1) or984 ( field_list[0] not in [i.name for i in opts.fields]) or981 aggregate.name, col, col)) 982 elif ((len(parts) > 1) or 983 (parts[0] not in [i.name for i in opts.fields]) or 985 984 self.group_by is None or 986 985 not is_summary): 987 986 # If: … … class Query(object): 992 991 # then we need to explore the joins that are required. 993 992 994 993 field, source, opts, join_list, last, _ = self.setup_joins( 995 field_list, opts, self.get_initial_alias(), False)994 aggregate.lookup, opts, self.get_initial_alias(), False) 996 995 997 996 # Process the join chain to see if it can be trimmed 998 997 col, _, join_list = self.trim_joins(source, join_list, last, False) … … class Query(object): 1007 1006 else: 1008 1007 # The simplest cases. No joins required - 1009 1008 # just reference the provided column alias. 1010 field_name = field_list[0] 1011 source = opts.get_field(field_name) 1012 col = field_name 1009 source = fields[0][0] 1010 col = source.name 1013 1011 1014 1012 # Add the aggregate to the query 1015 1013 aggregate.add_to_query(self, alias, col=col, source=source, is_summary=is_summary) 1016 1014 1017 1015 def add_filter(self, filter_expr, connector=AND, negate=False, trim=False, 1018 can_reuse=None, process_extras=True, force_having=False): 1016 can_reuse=None, process_extras=True, force_having=False, 1017 allow_explicit_fk=False): 1019 1018 """ 1020 1019 Add a single filter to the query. The 'filter_expr' is a pair: 1021 1020 (filter_string, value). E.g. ('name__contains', 'fred') … … class Query(object): 1041 1040 during the processing of extra filters to avoid infinite recursion. 1042 1041 """ 1043 1042 arg, value = filter_expr 1044 parts = arg.split(LOOKUP_SEP)1045 if not parts:1046 raise FieldError("Cannot parse keyword query %r" % arg)1047 1043 1048 # Work out the lookup type and remove it from 'parts', if necessary. 1049 if len(parts) == 1 or parts[-1] not in self.query_terms: 1050 lookup_type = 'exact' 1051 else: 1044 parts, _, last_field = self.model._meta.resolve_lookup_path( 1045 arg, allow_explicit_fk, query=self) 1046 1047 # Work out the lookup type and remove it from the end of 'parts', 1048 # if necessary. 1049 if not last_field and len(parts) and parts[-1] in self.query_terms: 1050 parts = list(parts) 1052 1051 lookup_type = parts.pop() 1052 arg = LOOKUP_SEP.join(parts) 1053 else: 1054 lookup_type = 'exact' 1053 1055 1054 1056 # By default, this is a WHERE clause. If an aggregate is referenced 1055 1057 # in the value, the filter will be promoted to a HAVING … … class Query(object): 1070 1072 having_clause = value.contains_aggregate 1071 1073 1072 1074 for alias, aggregate in self.aggregates.items(): 1073 if alias in (parts[0], LOOKUP_SEP.join(parts)):1075 if alias in (parts[0], arg): 1074 1076 entry = self.where_class() 1075 1077 entry.add((aggregate, lookup_type, value), AND) 1076 1078 if negate: … … class Query(object): 1083 1085 allow_many = trim or not negate 1084 1086 1085 1087 try: 1086 field, target, opts, join_list, last, extra_filters = self.setup_joins( 1087 parts, opts, alias, True, allow_many, allow_explicit_fk=True, 1088 field, target, opts, join_list, last, extra_filters = ( 1089 self.setup_joins( 1090 arg, opts, alias, True, allow_many, allow_explicit_fk=True, 1088 1091 can_reuse=can_reuse, negate=negate, 1089 process_extras=process_extras) 1092 process_extras=process_extras)) 1090 1093 except MultiJoin, e: 1091 1094 self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]), 1092 1095 can_reuse) … … class Query(object): 1214 1217 self.add_q(child, used_aliases, force_having=force_having) 1215 1218 else: 1216 1219 self.add_filter(child, connector, q_object.negated, 1217 can_reuse=used_aliases, force_having=force_having) 1220 can_reuse=used_aliases, force_having=force_having, 1221 allow_explicit_fk=True) 1218 1222 if force_having: 1219 1223 self.having.end_subtree() 1220 1224 else: … … class Query(object): 1234 1238 if self.filter_is_sticky: 1235 1239 self.used_aliases = used_aliases 1236 1240 1237 def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True,1241 def setup_joins(self, lookup, opts, alias, dupe_multis, allow_many=True, 1238 1242 allow_explicit_fk=False, can_reuse=None, negate=False, 1239 1243 process_extras=True): 1240 1244 """ … … class Query(object): 1260 1264 exclusions = set() 1261 1265 extra_filters = [] 1262 1266 int_alias = None 1263 for pos, name in enumerate(names): 1267 1268 names, fields, _ = opts.resolve_lookup_path(lookup, 1269 allow_explicit_fk=allow_explicit_fk, query=self) 1270 1271 num_fields = len(fields) 1272 num_names = len(names) 1273 if num_fields != num_names: 1274 if num_fields and num_fields == num_names - 1: 1275 raise FieldError("Join on field %r not permitted. Did you misspell %r for the lookup type?" % (fields[-1][0].name, names[num_fields])) 1276 else: 1277 raise FieldError("Join on field %r not permitted." % names[num_fields]) 1278 1279 for pos, field_info in enumerate(fields): 1264 1280 if int_alias is not None: 1265 1281 exclusions.add(int_alias) 1266 1282 exclusions.add(alias) 1267 1283 last.append(len(joins)) 1268 if name == 'pk': 1269 name = opts.pk.name 1270 try: 1271 field, model, direct, m2m = opts.get_field_by_name(name) 1272 except FieldDoesNotExist: 1273 for f in opts.fields: 1274 if allow_explicit_fk and name == f.attname: 1275 # XXX: A hack to allow foo_id to work in values() for 1276 # backwards compatibility purposes. If we dropped that 1277 # feature, this could be removed. 1278 field, model, direct, m2m = opts.get_field_by_name(f.name) 1279 break 1280 else: 1281 names = opts.get_all_field_names() + self.aggregate_select.keys() 1282 raise FieldError("Cannot resolve keyword %r into field. " 1283 "Choices are: %s" % (name, ", ".join(names))) 1284 1284 field, model, direct, m2m = field_info 1285 1285 if not allow_many and (m2m or not direct): 1286 1286 for alias in joins: 1287 1287 self.unref_alias(alias) … … class Query(object): 1309 1309 for (dupe_opts, dupe_col) in dupe_set: 1310 1310 self.update_dupe_avoidance(dupe_opts, dupe_col, 1311 1311 alias) 1312 cached_data = opts._join_cache.get(name )1312 cached_data = opts._join_cache.get(names[pos]) 1313 1313 orig_opts = opts 1314 1314 dupe_col = direct and field.column or field.field.column 1315 1315 dedupe = dupe_col in opts.duplicate_targets … … class Query(object): 1338 1338 to_col2 = opts.get_field_by_name( 1339 1339 field.m2m_reverse_target_field_name())[0].column 1340 1340 target = opts.pk 1341 orig_opts._join_cache[name ] = (table1, from_col1,1341 orig_opts._join_cache[names[pos]] = (table1, from_col1, 1342 1342 to_col1, table2, from_col2, to_col2, opts, 1343 1343 target) 1344 1344 … … class Query(object): 1364 1364 table = opts.db_table 1365 1365 from_col = field.column 1366 1366 to_col = target.column 1367 orig_opts._join_cache[name ] = (table, from_col, to_col,1367 orig_opts._join_cache[names[pos]] = (table, from_col, to_col, 1368 1368 opts, target) 1369 1369 1370 1370 alias = self.join((alias, table, from_col, to_col), … … class Query(object): 1393 1393 to_col2 = opts.get_field_by_name( 1394 1394 field.m2m_target_field_name())[0].column 1395 1395 target = opts.pk 1396 orig_opts._join_cache[name ] = (table1, from_col1,1396 orig_opts._join_cache[names[pos]] = (table1, from_col1, 1397 1397 to_col1, table2, from_col2, to_col2, opts, 1398 1398 target) 1399 1399 … … class Query(object): 1422 1422 field.rel.field_name)[0] 1423 1423 else: 1424 1424 target = opts.pk 1425 orig_opts._join_cache[name ] = (table, from_col, to_col,1425 orig_opts._join_cache[names[pos]] = (table, from_col, to_col, 1426 1426 opts, target) 1427 1427 1428 1428 alias = self.join((alias, table, from_col, to_col), … … class Query(object): 1437 1437 to_avoid = int_alias 1438 1438 self.update_dupe_avoidance(dupe_opts, dupe_col, to_avoid) 1439 1439 1440 if pos != len(names) - 1:1441 if pos == len(names) - 2:1442 raise FieldError("Join on field %r not permitted. Did you misspell %r for the lookup type?" % (name, names[pos + 1]))1443 else:1444 raise FieldError("Join on field %r not permitted." % name)1445 1446 1440 return field, target, opts, joins, last, extra_filters 1447 1441 1448 1442 def trim_joins(self, target, join_list, last, trim, nonnull_check=False): … … class Query(object): 1606 1600 try: 1607 1601 for name in field_names: 1608 1602 field, target, u2, joins, u3, u4 = self.setup_joins( 1609 name.split(LOOKUP_SEP), opts, alias, False, allow_m2m, 1610 True) 1603 name, opts, alias, False, allow_m2m, True) 1611 1604 final_alias = joins[-1] 1612 1605 col = target.column 1613 1606 if len(joins) > 1: … … class Query(object): 1888 1881 opts = self.model._meta 1889 1882 alias = self.get_initial_alias() 1890 1883 field, col, opts, joins, last, extra = self.setup_joins( 1891 start.split(LOOKUP_SEP), opts, alias, False)1884 start, opts, alias, False) 1892 1885 select_col = self.alias_map[joins[1]][LHS_JOIN_COL] 1893 1886 select_alias = alias 1894 1887 -
django/db/models/sql/subqueries.py
diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 1b03647..18935a7 100644
a b class DateQuery(Query): 177 177 """ 178 178 try: 179 179 result = self.setup_joins( 180 field_name .split(LOOKUP_SEP),180 field_name, 181 181 self.get_meta(), 182 182 self.get_initial_alias(), 183 183 False -
tests/modeltests/lookup/models.py
diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index 82434bb..dbd52e3 100644
a b class Tag(models.Model): 26 26 name = models.CharField(max_length=100) 27 27 class Meta: 28 28 ordering = ('name', ) 29 30 class Season(models.Model): 31 year = models.PositiveSmallIntegerField() 32 gt = models.IntegerField(null=True, blank=True) 33 34 def __unicode__(self): 35 return unicode(self.year) 36 37 class Game(models.Model): 38 season = models.ForeignKey(Season, related_name='games') 39 home = models.CharField(max_length=100) 40 away = models.CharField(max_length=100) 41 42 def __unicode__(self): 43 return u"%s at %s" % (self.away, self.home) 44 45 class Player(models.Model): 46 name = models.CharField(max_length=100) 47 games = models.ManyToManyField(Game, related_name='players') 48 49 def __unicode__(self): 50 return self.name 51 No newline at end of file -
tests/modeltests/lookup/tests.py
diff --git a/tests/modeltests/lookup/tests.py b/tests/modeltests/lookup/tests.py index 33eeae7..6e4d98e 100644
a b 1 1 from datetime import datetime 2 2 from operator import attrgetter 3 3 4 from django.core.exceptions import FieldError 4 5 from django.test import TestCase, skipUnlessDBFeature 5 from models import Author, Article, Tag 6 7 from models import Author, Article, Tag, Game, Season, Player 6 8 7 9 8 10 class LookupTests(TestCase): … … class LookupTests(TestCase): 243 245 self.assertQuerysetEqual(Article.objects.filter(id=self.a5.id).values(), 244 246 [{ 245 247 'id': self.a5.id, 246 'author_id': self.au2.id, 248 'author_id': self.au2.id, 247 249 'headline': 'Article 5', 248 250 'pub_date': datetime(2005, 8, 1, 9, 0) 249 251 }], transform=identity) … … class LookupTests(TestCase): 606 608 a16.save() 607 609 self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'b(.).*b\1'), 608 610 ['<Article: barfoobaz>', '<Article: bazbaRFOO>', '<Article: foobarbaz>']) 611 612 class LookupCollisionTests(TestCase): 613 614 def setUp(self): 615 # Here we're using 'gt' as a code number for the year, e.g. 111=>2009. 616 season_2009 = Season.objects.create(year=2009, gt=111) 617 season_2009.games.create(home="Houston Astros", away="St. Louis Cardinals") 618 season_2010 = Season.objects.create(year=2010, gt=222) 619 season_2010.games.create(home="Houston Astros", away="Chicago Cubs") 620 season_2010.games.create(home="Houston Astros", away="Milwaukee Brewers") 621 season_2010.games.create(home="Houston Astros", away="St. Louis Cardinals") 622 season_2011 = Season.objects.create(year=2011, gt=333) 623 season_2011.games.create(home="Houston Astros", away="St. Louis Cardinals") 624 season_2011.games.create(home="Houston Astros", away="Milwaukee Brewers") 625 hunter_pence = Player.objects.create(name="Hunter Pence") 626 hunter_pence.games = Game.objects.filter(season__year__in=[2009, 2010]) 627 pudge = Player.objects.create(name="Ivan Rodriquez") 628 pudge.games = Game.objects.filter(season__year=2009) 629 pedro_feliz = Player.objects.create(name="Pedro Feliz") 630 pedro_feliz.games = Game.objects.filter(season__year__in=[2011]) 631 johnson = Player.objects.create(name="Johnson") 632 johnson.games = Game.objects.filter(season__year__in=[2011]) 633 634 def test_lookup_collision(self): 635 """ 636 Ensure that genuine field names don't collide with built-in lookup 637 types ('year', 'gt', 'range', 'in' etc.). 638 Refs #11670. 639 """ 640 # Games in 2010 641 self.assertEqual(Game.objects.filter(season__year=2010).count(), 3) 642 self.assertEqual(Game.objects.filter(season__year__exact=2010).count(), 3) 643 self.assertEqual(Game.objects.filter(season__gt=222).count(), 3) 644 self.assertEqual(Game.objects.filter(season__gt__exact=222).count(), 3) 645 646 # Games in 2011 647 self.assertEqual(Game.objects.filter(season__year=2011).count(), 2) 648 self.assertEqual(Game.objects.filter(season__year__exact=2011).count(), 2) 649 self.assertEqual(Game.objects.filter(season__gt=333).count(), 2) 650 self.assertEqual(Game.objects.filter(season__gt__exact=333).count(), 2) 651 self.assertEqual(Game.objects.filter(season__year__gt=2010).count(), 2) 652 self.assertEqual(Game.objects.filter(season__gt__gt=222).count(), 2) 653 654 # Games played in 2010 and 2011 655 self.assertEqual(Game.objects.filter(season__year__in=[2010, 2011]).count(), 5) 656 self.assertEqual(Game.objects.filter(season__year__gt=2009).count(), 5) 657 self.assertEqual(Game.objects.filter(season__gt__in=[222, 333]).count(), 5) 658 self.assertEqual(Game.objects.filter(season__gt__gt=111).count(), 5) 659 660 # Players who played in 2009 661 self.assertEqual(Player.objects.filter(games__season__year=2009).distinct().count(), 2) 662 self.assertEqual(Player.objects.filter(games__season__year__exact=2009).distinct().count(), 2) 663 self.assertEqual(Player.objects.filter(games__season__gt=111).distinct().count(), 2) 664 self.assertEqual(Player.objects.filter(games__season__gt__exact=111).distinct().count(), 2) 665 666 # Players who played in 2010 667 self.assertEqual(Player.objects.filter(games__season__year=2010).distinct().count(), 1) 668 self.assertEqual(Player.objects.filter(games__season__year__exact=2010).distinct().count(), 1) 669 self.assertEqual(Player.objects.filter(games__season__gt=222).distinct().count(), 1) 670 self.assertEqual(Player.objects.filter(games__season__gt__exact=222).distinct().count(), 1) 671 672 # Players who played in 2011 673 self.assertEqual(Player.objects.filter(games__season__year=2011).distinct().count(), 2) 674 self.assertEqual(Player.objects.filter(games__season__year__exact=2011).distinct().count(), 2) 675 self.assertEqual(Player.objects.filter(games__season__gt=333).distinct().count(), 2) 676 self.assertEqual(Player.objects.filter(games__season__year__gt=2010).distinct().count(), 2) 677 self.assertEqual(Player.objects.filter(games__season__gt__gt=222).distinct().count(), 2) -
tests/regressiontests/null_queries/tests.py
diff --git a/tests/regressiontests/null_queries/tests.py b/tests/regressiontests/null_queries/tests.py index ab66ff6..35f2d2d 100644
a b class NullQueriesTests(TestCase): 39 39 # Can't use None on anything other than __exact 40 40 self.assertRaises(ValueError, Choice.objects.filter, id__gt=None) 41 41 42 # Can't use None on anything other than __exact43 self.assertRaises( ValueError, Choice.objects.filter, foo__gt=None)42 # Valid query, but fails because foo isn't a keyword 43 self.assertRaises(FieldError, Choice.objects.filter, foo__gt=None) 44 44 45 45 # Related managers use __exact=None implicitly if the object hasn't been saved. 46 46 p2 = Poll(question="How?")