From d917a66cb79604f1224286dbc22f121c915a68f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Iv=C3=A1n=20Raskovsky?= <raskovsky+git@gmail.com>
Date: Thu, 30 Dec 2010 16:42:37 -0300
Subject: [PATCH] fixes #13902
---
django/contrib/admin/views/main.py | 27 ++++++++++++---
tests/regressiontests/admin_changelist/models.py | 8 ++++-
tests/regressiontests/admin_changelist/tests.py | 38 +++++++++++++++++++---
3 files changed, 62 insertions(+), 11 deletions(-)
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 0b98f11..680a690 100644
a
|
b
|
class ChangeList(object):
|
166 | 166 | return order_field, order_type |
167 | 167 | |
168 | 168 | def get_query_set(self): |
| 169 | is_distinct = False |
| 170 | |
169 | 171 | qs = self.root_query_set |
170 | 172 | lookup_params = self.params.copy() # a dictionary of the query string |
171 | 173 | for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): |
… |
… |
class ChangeList(object):
|
178 | 180 | del lookup_params[key] |
179 | 181 | lookup_params[smart_str(key)] = value |
180 | 182 | |
| 183 | if not is_distinct: |
| 184 | # Check if it's a relationship that might return more than one |
| 185 | # instance |
| 186 | try: |
| 187 | f = self.lookup_opts.get_field(key.split('__',1)[0]) |
| 188 | except models.FieldDoesNotExist: |
| 189 | raise IncorrectLookupParameters |
| 190 | if isinstance(f.rel, (models.ManyToOneRel, models.ManyToManyRel)): |
| 191 | is_distinct = True |
| 192 | |
181 | 193 | # if key ends with __in, split parameter into separate values |
182 | 194 | if key.endswith('__in'): |
183 | 195 | lookup_params[key] = value.split(',') |
… |
… |
class ChangeList(object):
|
241 | 253 | for bit in self.query.split(): |
242 | 254 | or_queries = [models.Q(**{construct_search(str(field_name)): bit}) for field_name in self.search_fields] |
243 | 255 | qs = qs.filter(reduce(operator.or_, or_queries)) |
244 | | for field_name in self.search_fields: |
245 | | if '__' in field_name: |
246 | | qs = qs.distinct() |
247 | | break |
| 256 | if not is_distinct: |
| 257 | for field_name in self.search_fields: |
| 258 | f = self.lookup_opts.get_field(field_name.split('__',1)[0]) |
| 259 | if isinstance(f.rel, (models.ManyToOneRel, models.ManyToManyRel)): |
| 260 | is_distinct = True |
| 261 | break |
248 | 262 | |
249 | | return qs |
| 263 | if is_distinct: |
| 264 | return qs.distinct() |
| 265 | else: |
| 266 | return qs |
250 | 267 | |
251 | 268 | def url_for_result(self, result): |
252 | 269 | 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 fca1b9e..b268e4f 100644
a
|
b
|
from django.core.paginator import Paginator
|
5 | 5 | from django.template import Context, Template |
6 | 6 | from django.test import TransactionTestCase |
7 | 7 | |
8 | | from regressiontests.admin_changelist.models import Child, Parent |
| 8 | from regressiontests.admin_changelist.models import Child, Parent, Through |
9 | 9 | |
10 | 10 | |
11 | 11 | class ChangeListTests(TransactionTestCase): |
| 12 | def setUp(self): |
| 13 | self.new_parent = Parent.objects.create(name='parent') |
| 14 | self.new_child = Child.objects.create(name='name', parent=self.new_parent) |
| 15 | |
12 | 16 | def test_select_related_preserved(self): |
13 | 17 | """ |
14 | 18 | Regression test for #10348: ChangeList.get_query_set() shouldn't |
… |
… |
class ChangeListTests(TransactionTestCase):
|
25 | 29 | Verifies that inclusion tag result_list generates a table when with |
26 | 30 | default ModelAdmin settings. |
27 | 31 | """ |
28 | | new_parent = Parent.objects.create(name='parent') |
29 | | new_child = Child.objects.create(name='name', parent=new_parent) |
| 32 | new_parent = self.new_parent |
| 33 | new_child = self.new_child |
30 | 34 | request = MockRequest() |
31 | 35 | m = ChildAdmin(Child, admin.site) |
32 | 36 | cl = ChangeList(request, Child, m.list_display, m.list_display_links, |
… |
… |
class ChangeListTests(TransactionTestCase):
|
49 | 53 | when list_editable is enabled are rendered in a div outside the |
50 | 54 | table. |
51 | 55 | """ |
52 | | new_parent = Parent.objects.create(name='parent') |
53 | | new_child = Child.objects.create(name='name', parent=new_parent) |
| 56 | new_parent = self.new_parent |
| 57 | new_child = self.new_child |
54 | 58 | request = MockRequest() |
55 | 59 | m = ChildAdmin(Child, admin.site) |
56 | 60 | |
… |
… |
class ChangeListTests(TransactionTestCase):
|
116 | 120 | self.assertIsInstance(cl.paginator, CustomPaginator) |
117 | 121 | |
118 | 122 | |
| 123 | def test_distinct(self): |
| 124 | """ |
| 125 | Regression test for #13902: When using a ManyToMany in list_filter, |
| 126 | results may apper more than once |
| 127 | """ |
| 128 | new_parent = self.new_parent |
| 129 | new_child = self.new_child |
| 130 | relation1 = Through.objects.create(parent=new_parent, child=new_child) |
| 131 | relation2 = Through.objects.create(parent=new_parent, child=new_child) |
| 132 | |
| 133 | m = ChildAdmin(Child, admin.site) |
| 134 | cl = ChangeList(MockFilteredRequest(), Child, m.list_display, m.list_display_links, |
| 135 | m.list_filter, m.date_hierarchy, m.search_fields, |
| 136 | m.list_select_related, m.list_per_page, m.list_editable, m) |
| 137 | |
| 138 | cl.get_results(MockFilteredRequest()) |
| 139 | |
| 140 | # There's only one Child instance |
| 141 | self.assertEqual(cl.result_count, 1) |
| 142 | |
119 | 143 | class ChildAdmin(admin.ModelAdmin): |
120 | 144 | list_display = ['name', 'parent'] |
121 | 145 | def queryset(self, request): |
122 | 146 | return super(ChildAdmin, self).queryset(request).select_related("parent__name") |
| 147 | list_filter = ['multiple_m2m', ] |
123 | 148 | |
124 | 149 | |
125 | 150 | class MockRequest(object): |
126 | 151 | GET = {} |
127 | 152 | |
| 153 | class MockFilteredRequest(object): |
| 154 | GET = {'multiple_m2m__id__exact': 1, } |
128 | 155 | |
129 | 156 | class CustomPaginator(Paginator): |
130 | 157 | def __init__(self, queryset, page_size, orphans=0, allow_empty_first_page=True): |
131 | 158 | super(CustomPaginator, self).__init__(queryset, 5, orphans=2, |
132 | 159 | allow_empty_first_page=allow_empty_first_page) |
| 160 | |