diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 8c09c10..2fa9c96 100644
|
a
|
b
|
class ChangeList(object):
|
| 164 | 164 | return order_field, order_type |
| 165 | 165 | |
| 166 | 166 | def get_query_set(self): |
| | 167 | DISTINCT = False |
| | 168 | |
| 167 | 169 | qs = self.root_query_set |
| 168 | 170 | lookup_params = self.params.copy() # a dictionary of the query string |
| 169 | 171 | for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): |
| 170 | 172 | if i in lookup_params: |
| 171 | 173 | del lookup_params[i] |
| 172 | 174 | for key, value in lookup_params.items(): |
| | 175 | if not DISTINCT and key.count('__') > 1: |
| | 176 | # Check if it's a relationship that might return more than one |
| | 177 | # instance |
| | 178 | DISTINCT = True |
| 173 | 179 | if not isinstance(key, str): |
| 174 | 180 | # 'key' will be used as a keyword argument later, so Python |
| 175 | 181 | # requires it to be a string. |
| … |
… |
class ChangeList(object):
|
| 230 | 236 | else: |
| 231 | 237 | return "%s__icontains" % field_name |
| 232 | 238 | |
| 233 | | if self.search_fields and self.query: |
| | 239 | if not DISTINCT and self.search_fields and self.query: |
| 234 | 240 | for bit in self.query.split(): |
| 235 | 241 | or_queries = [models.Q(**{construct_search(str(field_name)): bit}) for field_name in self.search_fields] |
| 236 | 242 | qs = qs.filter(reduce(operator.or_, or_queries)) |
| 237 | 243 | for field_name in self.search_fields: |
| 238 | 244 | if '__' in field_name: |
| 239 | | qs = qs.distinct() |
| | 245 | DISCTINCT = True |
| 240 | 246 | break |
| 241 | 247 | |
| 242 | | return qs |
| | 248 | if DISTINCT: |
| | 249 | return qs.distinct() |
| | 250 | else: |
| | 251 | return qs |
| 243 | 252 | |
| 244 | 253 | def url_for_result(self, result): |
| 245 | 254 | return "%s/" % quote(getattr(result, self.pk_attname)) |
diff --git a/tests/regressiontests/admin_changelist/models.py b/tests/regressiontests/admin_changelist/models.py
index f030a78..c043c8a 100644
|
a
|
b
|
class Parent(models.Model):
|
| 6 | 6 | |
| 7 | 7 | class Child(models.Model): |
| 8 | 8 | parent = models.ForeignKey(Parent, editable=False) |
| 9 | | name = models.CharField(max_length=30, blank=True) |
| 10 | | No newline at end of file |
| | 9 | name = models.CharField(max_length=30, blank=True) |
| | 10 | multiple_m2m = models.ManyToManyField(Parent, related_name='multiple_m2m', |
| | 11 | through='Through') |
| | 12 | |
| | 13 | class Through(models.Model): |
| | 14 | parent = models.ForeignKey(Parent) |
| | 15 | child = models.ForeignKey(Child) |
diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/regressiontests/admin_changelist/tests.py
index b70d7c5..569bfe8 100644
|
a
|
b
|
import unittest
|
| 2 | 2 | from django.contrib import admin |
| 3 | 3 | from django.contrib.admin.views.main import ChangeList |
| 4 | 4 | from django.template import Context, Template |
| 5 | | from regressiontests.admin_changelist.models import Child, Parent |
| | 5 | from regressiontests.admin_changelist.models import Child, Parent, Through |
| 6 | 6 | |
| 7 | 7 | class ChangeListTests(unittest.TestCase): |
| | 8 | def setUp(self): |
| | 9 | self.new_parent = Parent.objects.create(name='parent') |
| | 10 | self.new_child = Child.objects.create(name='name', parent=self.new_parent) |
| | 11 | |
| | 12 | def tearDown(self): |
| | 13 | self.new_parent.delete() |
| | 14 | self.new_child.delete() |
| | 15 | |
| 8 | 16 | def test_select_related_preserved(self): |
| 9 | 17 | """ |
| 10 | 18 | Regression test for #10348: ChangeList.get_query_set() shouldn't |
| … |
… |
class ChangeListTests(unittest.TestCase):
|
| 22 | 30 | table and this checks that the items are nested within the table |
| 23 | 31 | element tags. |
| 24 | 32 | """ |
| 25 | | new_parent = Parent.objects.create(name='parent') |
| 26 | | new_child = Child.objects.create(name='name', parent=new_parent) |
| | 33 | new_parent = self.new_parent |
| | 34 | new_child = self.new_child |
| 27 | 35 | request = MockRequest() |
| 28 | 36 | m = ChildAdmin(Child, admin.site) |
| 29 | 37 | cl = ChangeList(request, Child, m.list_display, m.list_display_links, |
| … |
… |
class ChangeListTests(unittest.TestCase):
|
| 57 | 65 | self.failIf(table_output.find('<td>%s</td>' % hidden_input_elem) == -1, |
| 58 | 66 | 'Hidden input element is not enclosed in <td> element.') |
| 59 | 67 | |
| | 68 | def test_distinct(self): |
| | 69 | """ |
| | 70 | Regression test for #13902: When using a ManyToMany in list_filter, |
| | 71 | results may apper more than once |
| | 72 | """ |
| | 73 | new_parent = self.new_parent |
| | 74 | new_child = self.new_child |
| | 75 | relation1 = Through.objects.create(parent=new_parent, child=new_child) |
| | 76 | relation2 = Through.objects.create(parent=new_parent, child=new_child) |
| | 77 | |
| | 78 | m = ChildAdmin(Child, admin.site) |
| | 79 | cl = ChangeList(MockFilteredRequest(), Child, m.list_display, m.list_display_links, |
| | 80 | m.list_filter, m.date_hierarchy, m.search_fields, |
| | 81 | m.list_select_related, m.list_per_page, m.list_editable, m) |
| | 82 | |
| | 83 | cl.get_results(MockFilteredRequest()) |
| | 84 | |
| | 85 | # There's only one Child instance |
| | 86 | self.assertEqual(cl.result_count, 1) |
| | 87 | |
| 60 | 88 | class ChildAdmin(admin.ModelAdmin): |
| 61 | 89 | list_display = ['name', 'parent'] |
| 62 | 90 | def queryset(self, request): |
| 63 | 91 | return super(ChildAdmin, self).queryset(request).select_related("parent__name") |
| | 92 | list_filter = ['multiple_m2m', ] |
| 64 | 93 | |
| 65 | 94 | class MockRequest(object): |
| 66 | 95 | GET = {} |
| | 96 | |
| | 97 | class MockFilteredRequest(object): |
| | 98 | GET = {'multiple_m2m__id__exact': 1, } |
| | 99 | |