Ticket #11670: 11670.field-lookup-collisions.6.diff

File 11670.field-lookup-collisions.6.diff, 37.1 KB (added by Julien Phalip, 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  
    1313from django.core.paginator import Paginator
    1414from django.core.urlresolvers import reverse
    1515from 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
     16from django.db.models.fields import BLANK_CHOICE_DASH
    1817from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS
    1918from django.http import Http404, HttpResponse, HttpResponseRedirect
    2019from django.shortcuts import get_object_or_404
    class BaseModelAdmin(object):  
    233232                if k == lookup and v == value:
    234233                    return True
    235234
    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)
    237239
    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:
    241246            parts.pop()
     247            num_parts += -1
    242248
    243249        # Special case -- foo__id__exact and foo__id queries are implied
    244250        # if foo has been specificially included in the lookup list; so
    245251        # drop __id if it is the last part. However, first we need to find
    246252        # 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):
    259255            parts.pop()
     256            num_parts += -1
    260257
    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:
    266259            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
    272263
    273264    def has_add_permission(self, request):
    274265        """
  • 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  
    11from django.db import models
    22from django.db.models.sql.constants import LOOKUP_SEP
    33from django.db.models.deletion import Collector
    4 from django.db.models.related import RelatedObject
     4from django.db.models.related import RelatedObject, get_model_from_relation, NotRelationField
    55from django.forms.forms import pretty_name
    66from django.utils import formats
    77from django.utils.html import escape
    def display_for_field(value, field):  
    302302    else:
    303303        return smart_unicode(value)
    304304
    305 
    306 class NotRelationField(Exception):
    307     pass
    308 
    309 
    310 def get_model_from_relation(field):
    311     if isinstance(field, models.related.RelatedObject):
    312         return field.model
    313     elif getattr(field, 'rel'): # or isinstance?
    314         return field.rel.to
    315     else:
    316         raise NotRelationField
    317 
    318 
    319305def reverse_field_path(model, path):
    320306    """ Create a reversed field path.
    321307
    def reverse_field_path(model, path):  
    343329            related_name = field.field.name
    344330            parent = field.model
    345331        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)
    367333
    368334
    369335def remove_trailing_data_field(fields):
    def get_limit_choices_to_from_path(model, path):  
    381347    If final model in path is linked via a ForeignKey or ManyToManyField which
    382348    has a `limit_choices_to` attribute, return it as a Q object.
    383349    """
    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]
    386352    fields = remove_trailing_data_field(fields)
    387353    limit_choices_to = (
    388354        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  
    44from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model,
    55    _get_foreign_key)
    66from django.contrib.admin import ListFilter, FieldListFilter
    7 from django.contrib.admin.util import get_fields_from_path, NotRelationField
    87from django.contrib.admin.options import (flatten_fieldsets, BaseModelAdmin,
    98    HORIZONTAL, VERTICAL)
    109
    def validate(cls, model):  
    8483                    # item is option #1
    8584                    field = item
    8685                # 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:
    9088                    raise ImproperlyConfigured("'%s.list_filter[%d]' refers to '%s'"
    9189                            " which does not refer to a Field."
    9290                            % (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  
    1010
    1111from django.contrib.admin import FieldListFilter
    1212from django.contrib.admin.options import IncorrectLookupParameters
    13 from django.contrib.admin.util import quote, get_fields_from_path
     13from django.contrib.admin.util import quote
    1414
    1515# Changelist settings
    1616ALL_VAR = 'all'
    class ChangeList(object):  
    105105                        field, field_list_filter_class = list_filter, FieldListFilter.create
    106106                    if not isinstance(field, models.Field):
    107107                        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,
    110110                        self.model, self.model_admin, field_path=field_path)
    111111                if spec and spec.has_output():
    112112                    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 FieldDoesNotExist
    2 from django.db.models.sql.constants import LOOKUP_SEP
    31from django.db.models.sql.expressions import SQLEvaluator
    42from django.db.models.sql.where import Constraint, WhereNode
    53from django.contrib.gis.db.models.fields import GeometryField
    class GeoWhereNode(WhereNode):  
    5856        'address__point'.
    5957
    6058        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.
    6260        """
    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]
    8864        else:
    8965            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  
     1from copy import copy
    12import re
    23from bisect import bisect
    34
     5from django.core.exceptions import FieldError
     6from django.db.models.sql.constants import LOOKUP_SEP
    47from django.conf import settings
    5 from django.db.models.related import RelatedObject
     8from django.db.models.related import RelatedObject, get_model_from_relation, NotRelationField
    69from django.db.models.fields.related import ManyToManyRel
    710from django.db.models.fields import AutoField, FieldDoesNotExist
    811from django.db.models.fields.proxy import OrderWrt
    class Options(object):  
    5457        # from *other* models. Needed for some admin checks. Internal use only.
    5558        self.related_fkey_lookups = []
    5659
     60        self._resolve_lookup_path_cache = {}
     61
    5762    def contribute_to_class(self, cls, name):
    5863        from django.db import connection
    5964        from django.db.backends.util import truncate_name
    class Options(object):  
    495500        Returns the index of the primary key field in the self.fields list.
    496501        """
    497502        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):  
    3636                {'%s__isnull' % self.parent_model._meta.module_name: False})
    3737        lst = [(x._get_pk_val(), smart_unicode(x)) for x in queryset]
    3838        return first_choice + lst
    39        
     39
    4040    def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
    4141        # Defer to the actual field definition for db prep
    4242        return self.field.get_db_prep_lookup(lookup_type, value,
    class RelatedObject(object):  
    6767
    6868    def get_cache_name(self):
    6969        return "_%s_cache" % self.get_accessor_name()
     70
     71class NotRelationField(Exception):
     72    pass
     73
     74def 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):  
    383383        The 'name' is of the form 'field1__field2__...__fieldN'.
    384384        """
    385385        name, order = get_order_dir(name, default_order)
    386         pieces = name.split(LOOKUP_SEP)
    387386        if not alias:
    388387            alias = self.query.get_initial_alias()
    389         field, target, opts, joins, last, extra = self.query.setup_joins(pieces,
    390                 opts, alias, False)
     388        field, target, opts, joins, last, extra = self.query.setup_joins(
     389            name, opts, alias, False)
    391390        alias = joins[-1]
    392391        col = target.column
    393392        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):  
    3636    def prepare_leaf(self, node, query, allow_joins):
    3737        if not allow_joins and LOOKUP_SEP in node.name:
    3838            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 and
     39        opts = query.get_meta()
     40        parts, _, _ = opts.resolve_lookup_path(node.name)
     41        if (len(parts) == 1 and
    4242            node.name in query.aggregate_select.keys()):
    4343            self.contains_aggregate = True
    4444            self.cols[node] = query.aggregate_select[node.name]
    4545        else:
    4646            try:
    4747                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)
    5049                col, _, join_list = query.trim_joins(source, join_list, last, False)
    5150
    5251                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):  
    971971        Adds a single aggregate expression to the Query
    972972        """
    973973        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:
    976976            # 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]
    980979            if not is_summary:
    981980                raise FieldError("Cannot compute %s('%s'): '%s' is an aggregate" % (
    982                     aggregate.name, field_name, field_name))
    983         elif ((len(field_list) > 1) or
    984             (field_list[0] not in [i.name for i in opts.fields]) or
     981                    aggregate.name, col, col))
     982        elif ((len(parts) > 1) or
     983            (parts[0] not in [i.name for i in opts.fields]) or
    985984            self.group_by is None or
    986985            not is_summary):
    987986            # If:
    class Query(object):  
    992991            # then we need to explore the joins that are required.
    993992
    994993            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)
    996995
    997996            # Process the join chain to see if it can be trimmed
    998997            col, _, join_list = self.trim_joins(source, join_list, last, False)
    class Query(object):  
    10071006        else:
    10081007            # The simplest cases. No joins required -
    10091008            # 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
    10131011
    10141012        # Add the aggregate to the query
    10151013        aggregate.add_to_query(self, alias, col=col, source=source, is_summary=is_summary)
    10161014
    10171015    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):
    10191018        """
    10201019        Add a single filter to the query. The 'filter_expr' is a pair:
    10211020        (filter_string, value). E.g. ('name__contains', 'fred')
    class Query(object):  
    10411040        during the processing of extra filters to avoid infinite recursion.
    10421041        """
    10431042        arg, value = filter_expr
    1044         parts = arg.split(LOOKUP_SEP)
    1045         if not parts:
    1046             raise FieldError("Cannot parse keyword query %r" % arg)
    10471043
    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)
    10521051            lookup_type = parts.pop()
     1052            arg = LOOKUP_SEP.join(parts)
     1053        else:
     1054            lookup_type = 'exact'
    10531055
    10541056        # By default, this is a WHERE clause. If an aggregate is referenced
    10551057        # in the value, the filter will be promoted to a HAVING
    class Query(object):  
    10701072            having_clause = value.contains_aggregate
    10711073
    10721074        for alias, aggregate in self.aggregates.items():
    1073             if alias in (parts[0], LOOKUP_SEP.join(parts)):
     1075            if alias in (parts[0], arg):
    10741076                entry = self.where_class()
    10751077                entry.add((aggregate, lookup_type, value), AND)
    10761078                if negate:
    class Query(object):  
    10831085        allow_many = trim or not negate
    10841086
    10851087        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,
    10881091                    can_reuse=can_reuse, negate=negate,
    1089                     process_extras=process_extras)
     1092                    process_extras=process_extras))
    10901093        except MultiJoin, e:
    10911094            self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]),
    10921095                    can_reuse)
    class Query(object):  
    12141217                    self.add_q(child, used_aliases, force_having=force_having)
    12151218                else:
    12161219                    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)
    12181222                if force_having:
    12191223                    self.having.end_subtree()
    12201224                else:
    class Query(object):  
    12341238        if self.filter_is_sticky:
    12351239            self.used_aliases = used_aliases
    12361240
    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,
    12381242            allow_explicit_fk=False, can_reuse=None, negate=False,
    12391243            process_extras=True):
    12401244        """
    class Query(object):  
    12601264        exclusions = set()
    12611265        extra_filters = []
    12621266        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):
    12641280            if int_alias is not None:
    12651281                exclusions.add(int_alias)
    12661282            exclusions.add(alias)
    12671283            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
    12851285            if not allow_many and (m2m or not direct):
    12861286                for alias in joins:
    12871287                    self.unref_alias(alias)
    class Query(object):  
    13091309                        for (dupe_opts, dupe_col) in dupe_set:
    13101310                            self.update_dupe_avoidance(dupe_opts, dupe_col,
    13111311                                    alias)
    1312             cached_data = opts._join_cache.get(name)
     1312            cached_data = opts._join_cache.get(names[pos])
    13131313            orig_opts = opts
    13141314            dupe_col = direct and field.column or field.field.column
    13151315            dedupe = dupe_col in opts.duplicate_targets
    class Query(object):  
    13381338                        to_col2 = opts.get_field_by_name(
    13391339                            field.m2m_reverse_target_field_name())[0].column
    13401340                        target = opts.pk
    1341                         orig_opts._join_cache[name] = (table1, from_col1,
     1341                        orig_opts._join_cache[names[pos]] = (table1, from_col1,
    13421342                                to_col1, table2, from_col2, to_col2, opts,
    13431343                                target)
    13441344
    class Query(object):  
    13641364                        table = opts.db_table
    13651365                        from_col = field.column
    13661366                        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,
    13681368                                opts, target)
    13691369
    13701370                    alias = self.join((alias, table, from_col, to_col),
    class Query(object):  
    13931393                        to_col2 = opts.get_field_by_name(
    13941394                            field.m2m_target_field_name())[0].column
    13951395                        target = opts.pk
    1396                         orig_opts._join_cache[name] = (table1, from_col1,
     1396                        orig_opts._join_cache[names[pos]] = (table1, from_col1,
    13971397                                to_col1, table2, from_col2, to_col2, opts,
    13981398                                target)
    13991399
    class Query(object):  
    14221422                                field.rel.field_name)[0]
    14231423                        else:
    14241424                            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,
    14261426                                opts, target)
    14271427
    14281428                    alias = self.join((alias, table, from_col, to_col),
    class Query(object):  
    14371437                    to_avoid = int_alias
    14381438                self.update_dupe_avoidance(dupe_opts, dupe_col, to_avoid)
    14391439
    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 
    14461440        return field, target, opts, joins, last, extra_filters
    14471441
    14481442    def trim_joins(self, target, join_list, last, trim, nonnull_check=False):
    class Query(object):  
    16061600        try:
    16071601            for name in field_names:
    16081602                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)
    16111604                final_alias = joins[-1]
    16121605                col = target.column
    16131606                if len(joins) > 1:
    class Query(object):  
    18881881        opts = self.model._meta
    18891882        alias = self.get_initial_alias()
    18901883        field, col, opts, joins, last, extra = self.setup_joins(
    1891                 start.split(LOOKUP_SEP), opts, alias, False)
     1884            start, opts, alias, False)
    18921885        select_col = self.alias_map[joins[1]][LHS_JOIN_COL]
    18931886        select_alias = alias
    18941887
  • 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):  
    177177        """
    178178        try:
    179179            result = self.setup_joins(
    180                 field_name.split(LOOKUP_SEP),
     180                field_name,
    181181                self.get_meta(),
    182182                self.get_initial_alias(),
    183183                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):  
    2626    name = models.CharField(max_length=100)
    2727    class Meta:
    2828        ordering = ('name', )
     29
     30class 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
     37class 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
     45class 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  
    11from datetime import datetime
    22from operator import attrgetter
     3
    34from django.core.exceptions import FieldError
    45from django.test import TestCase, skipUnlessDBFeature
    5 from models import Author, Article, Tag
     6
     7from models import Author, Article, Tag, Game, Season, Player
    68
    79
    810class LookupTests(TestCase):
    class LookupTests(TestCase):  
    243245        self.assertQuerysetEqual(Article.objects.filter(id=self.a5.id).values(),
    244246            [{
    245247                'id': self.a5.id,
    246                 'author_id': self.au2.id, 
     248                'author_id': self.au2.id,
    247249                'headline': 'Article 5',
    248250                'pub_date': datetime(2005, 8, 1, 9, 0)
    249251            }], transform=identity)
    class LookupTests(TestCase):  
    606608        a16.save()
    607609        self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'b(.).*b\1'),
    608610            ['<Article: barfoobaz>', '<Article: bazbaRFOO>', '<Article: foobarbaz>'])
     611
     612class 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):  
    3939        # Can't use None on anything other than __exact
    4040        self.assertRaises(ValueError, Choice.objects.filter, id__gt=None)
    4141
    42         # Can't use None on anything other than __exact
    43         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)
    4444
    4545        # Related managers use __exact=None implicitly if the object hasn't been saved.
    4646        p2 = Poll(question="How?")
Back to Top