Ticket #15819: query_contains_multijoin.diff

File query_contains_multijoin.diff, 6.4 KB (added by Anssi Kääriäinen, 12 years ago)
  • django/contrib/admin/util.py

    diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
    index 61182a6..a05c352 100644
    a b from django.utils.encoding import force_unicode, smart_unicode, smart_str  
    1212from django.utils.translation import ungettext
    1313from django.core.urlresolvers import reverse
    1414
    15 def lookup_needs_distinct(opts, lookup_path):
    16     """
    17     Returns True if 'distinct()' should be used to query the given lookup path.
    18     """
    19     field_name = lookup_path.split('__', 1)[0]
    20     field = opts.get_field_by_name(field_name)[0]
    21     if ((hasattr(field, 'rel') and
    22          isinstance(field.rel, models.ManyToManyRel)) or
    23         (isinstance(field, models.related.RelatedObject) and
    24          not field.field.unique)):
    25          return True
    26     return False
    27 
    2815def prepare_lookup_value(key, value):
    2916    """
    3017    Returns a lookup value prepared to be used in queryset filtering.
  • django/contrib/admin/views/main.py

    diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
    index b11f9d5..ef8db01 100644
    a b from django.utils.http import urlencode  
    1212from django.contrib.admin import FieldListFilter
    1313from django.contrib.admin.options import IncorrectLookupParameters
    1414from django.contrib.admin.util import (quote, get_fields_from_path,
    15     lookup_needs_distinct, prepare_lookup_value)
     15    prepare_lookup_value)
    1616
    1717# Changelist settings
    1818ALL_VAR = 'all'
    class ChangeList(object):  
    7979
    8080    def get_filters(self, request):
    8181        lookup_params = self.params.copy() # a dictionary of the query string
    82         use_distinct = False
    8382
    8483        # Remove all the parameters that are globally and systematically
    8584        # ignored.
    class ChangeList(object):  
    120119                        field = get_fields_from_path(self.model, field_path)[-1]
    121120                    spec = field_list_filter_class(field, request, lookup_params,
    122121                        self.model, self.model_admin, field_path=field_path)
    123                     # Check if we need to use distinct()
    124                     use_distinct = (use_distinct or
    125                                     lookup_needs_distinct(self.lookup_opts,
    126                                                           field_path))
     122
    127123                if spec and spec.has_output():
    128124                    filter_specs.append(spec)
    129125
    130126        # At this point, all the parameters used by the various ListFilters
    131127        # have been removed from lookup_params, which now only contains other
    132128        # parameters passed via the query string. We now loop through the
    133         # remaining parameters both to ensure that all the parameters are valid
    134         # fields and to determine if at least one of them needs distinct(). If
    135         # the lookup parameters aren't real fields, then bail out.
     129        # remaining parameters to ensure that all the parameters are valid
     130        # fields. If the lookup parameters aren't real fields, then bail out.
    136131        try:
    137132            for key, value in lookup_params.items():
    138133                lookup_params[key] = prepare_lookup_value(key, value)
    139                 use_distinct = (use_distinct or
    140                                 lookup_needs_distinct(self.lookup_opts, key))
    141             return filter_specs, bool(filter_specs), lookup_params, use_distinct
     134
     135            return filter_specs, bool(filter_specs), lookup_params
    142136        except FieldDoesNotExist, e:
    143137            raise IncorrectLookupParameters(e)
    144138
    class ChangeList(object):  
    298292    def get_query_set(self, request):
    299293        # First, we collect all the declared list filters.
    300294        (self.filter_specs, self.has_filters, remaining_lookup_params,
    301          use_distinct) = self.get_filters(request)
     295         ) = self.get_filters(request)
    302296
    303297        # Then, we let every list filter modify the queryset to its liking.
    304298        qs = self.root_query_set
    class ChangeList(object):  
    363357                or_queries = [models.Q(**{orm_lookup: bit})
    364358                              for orm_lookup in orm_lookups]
    365359                qs = qs.filter(reduce(operator.or_, or_queries))
    366             if not use_distinct:
    367                 for search_spec in orm_lookups:
    368                     if lookup_needs_distinct(self.lookup_opts, search_spec):
    369                         use_distinct = True
    370                         break
    371360
    372         if use_distinct:
     361        if qs.query.contains_multijoin:
    373362            return qs.distinct()
    374363        else:
    375364            return qs
  • django/db/models/sql/query.py

    diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
    index ce11716..992b105 100644
    a b class Query(object):  
    155155        # are the fields to defer, or False if these are the only fields to
    156156        # load.
    157157        self.deferred_loading = (set(), True)
     158        # We keep track if the query contains multijoins (reverse FK joins or
     159        # m2m joins) so that users of the queryset can easily decide if they
     160        # need to use distinct or not
     161        self.contains_multijoin = False
    158162
    159163    def __str__(self):
    160164        """
    class Query(object):  
    300304        else:
    301305            obj.used_aliases = set()
    302306        obj.filter_is_sticky = False
    303 
     307        obj.contains_multijoin = self.contains_multijoin
    304308        obj.__dict__.update(kwargs)
    305309        if hasattr(obj, '_setup_query'):
    306310            obj._setup_query()
    class Query(object):  
    13151319                    raise FieldError("Cannot resolve keyword %r into field. "
    13161320                            "Choices are: %s" % (name, ", ".join(names)))
    13171321
    1318             if not allow_many and (m2m or not direct):
    1319                 for alias in joins:
    1320                     self.unref_alias(alias)
    1321                 raise MultiJoin(pos + 1)
     1322            if m2m or not direct:
     1323                if not allow_many:
     1324                    for alias in joins:
     1325                        self.unref_alias(alias)
     1326                    raise MultiJoin(pos + 1)
     1327                # This is a heuristic only - it is possible the query does not
     1328                # actually contain a multijoin due to filtering reducing the
     1329                # join to singlevalued join.
     1330                self.contains_multijoin = True
    13221331            if model:
    13231332                # The field lives on a base class of the current model.
    13241333                # Skip the chain of proxy to the concrete proxied model
Back to Top