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
|
12 | 12 | from django.utils.translation import ungettext |
13 | 13 | from django.core.urlresolvers import reverse |
14 | 14 | |
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 | | |
28 | 15 | def prepare_lookup_value(key, value): |
29 | 16 | """ |
30 | 17 | Returns a lookup value prepared to be used in queryset filtering. |
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
|
12 | 12 | from django.contrib.admin import FieldListFilter |
13 | 13 | from django.contrib.admin.options import IncorrectLookupParameters |
14 | 14 | from django.contrib.admin.util import (quote, get_fields_from_path, |
15 | | lookup_needs_distinct, prepare_lookup_value) |
| 15 | prepare_lookup_value) |
16 | 16 | |
17 | 17 | # Changelist settings |
18 | 18 | ALL_VAR = 'all' |
… |
… |
class ChangeList(object):
|
79 | 79 | |
80 | 80 | def get_filters(self, request): |
81 | 81 | lookup_params = self.params.copy() # a dictionary of the query string |
82 | | use_distinct = False |
83 | 82 | |
84 | 83 | # Remove all the parameters that are globally and systematically |
85 | 84 | # ignored. |
… |
… |
class ChangeList(object):
|
120 | 119 | field = get_fields_from_path(self.model, field_path)[-1] |
121 | 120 | spec = field_list_filter_class(field, request, lookup_params, |
122 | 121 | 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 | |
127 | 123 | if spec and spec.has_output(): |
128 | 124 | filter_specs.append(spec) |
129 | 125 | |
130 | 126 | # At this point, all the parameters used by the various ListFilters |
131 | 127 | # have been removed from lookup_params, which now only contains other |
132 | 128 | # 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. |
136 | 131 | try: |
137 | 132 | for key, value in lookup_params.items(): |
138 | 133 | 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 |
142 | 136 | except FieldDoesNotExist, e: |
143 | 137 | raise IncorrectLookupParameters(e) |
144 | 138 | |
… |
… |
class ChangeList(object):
|
298 | 292 | def get_query_set(self, request): |
299 | 293 | # First, we collect all the declared list filters. |
300 | 294 | (self.filter_specs, self.has_filters, remaining_lookup_params, |
301 | | use_distinct) = self.get_filters(request) |
| 295 | ) = self.get_filters(request) |
302 | 296 | |
303 | 297 | # Then, we let every list filter modify the queryset to its liking. |
304 | 298 | qs = self.root_query_set |
… |
… |
class ChangeList(object):
|
363 | 357 | or_queries = [models.Q(**{orm_lookup: bit}) |
364 | 358 | for orm_lookup in orm_lookups] |
365 | 359 | 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 |
371 | 360 | |
372 | | if use_distinct: |
| 361 | if qs.query.contains_multijoin: |
373 | 362 | return qs.distinct() |
374 | 363 | else: |
375 | 364 | return qs |
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):
|
155 | 155 | # are the fields to defer, or False if these are the only fields to |
156 | 156 | # load. |
157 | 157 | 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 |
158 | 162 | |
159 | 163 | def __str__(self): |
160 | 164 | """ |
… |
… |
class Query(object):
|
300 | 304 | else: |
301 | 305 | obj.used_aliases = set() |
302 | 306 | obj.filter_is_sticky = False |
303 | | |
| 307 | obj.contains_multijoin = self.contains_multijoin |
304 | 308 | obj.__dict__.update(kwargs) |
305 | 309 | if hasattr(obj, '_setup_query'): |
306 | 310 | obj._setup_query() |
… |
… |
class Query(object):
|
1315 | 1319 | raise FieldError("Cannot resolve keyword %r into field. " |
1316 | 1320 | "Choices are: %s" % (name, ", ".join(names))) |
1317 | 1321 | |
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 |
1322 | 1331 | if model: |
1323 | 1332 | # The field lives on a base class of the current model. |
1324 | 1333 | # Skip the chain of proxy to the concrete proxied model |