From dbb79d9d8af12f5258482b526b792269d218ff39 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Iv=C3=A1n=20Raskovsky?= <raskovsky+git@gmail.com>
Date: Thu, 6 Jan 2011 12:46:33 -0300
Subject: [PATCH] fixes #13902
---
django/contrib/admin/views/main.py | 31 +++++++++++++++---
tests/regressiontests/admin_changelist/models.py | 8 ++++-
tests/regressiontests/admin_changelist/tests.py | 38 +++++++++++++++++++---
3 files changed, 66 insertions(+), 11 deletions(-)
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 886ccd9..1ae1715 100644
a
|
b
|
class ChangeList(object):
|
169 | 169 | return order_field, order_type |
170 | 170 | |
171 | 171 | def get_query_set(self): |
| 172 | is_distinct = False |
| 173 | |
172 | 174 | qs = self.root_query_set |
173 | 175 | lookup_params = self.params.copy() # a dictionary of the query string |
174 | 176 | for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): |
… |
… |
class ChangeList(object):
|
181 | 183 | del lookup_params[key] |
182 | 184 | lookup_params[smart_str(key)] = value |
183 | 185 | |
| 186 | if not is_distinct: |
| 187 | # Check if it's a relationship that might return more than one |
| 188 | # instance |
| 189 | try: |
| 190 | f = self.lookup_opts.get_field_by_name(key.split('__',1)[0]) |
| 191 | except models.FieldDoesNotExist: |
| 192 | raise IncorrectLookupParameters |
| 193 | if (isinstance(f[0], models.related.RelatedObject) or |
| 194 | isinstance(f[0].rel, (models.ManyToOneRel, |
| 195 | models.ManyToManyRel))): |
| 196 | is_distinct = True |
| 197 | |
184 | 198 | # if key ends with __in, split parameter into separate values |
185 | 199 | if key.endswith('__in'): |
186 | 200 | lookup_params[key] = value.split(',') |
… |
… |
class ChangeList(object):
|
244 | 258 | for bit in self.query.split(): |
245 | 259 | or_queries = [models.Q(**{construct_search(str(field_name)): bit}) for field_name in self.search_fields] |
246 | 260 | qs = qs.filter(reduce(operator.or_, or_queries)) |
247 | | for field_name in self.search_fields: |
248 | | if '__' in field_name: |
249 | | qs = qs.distinct() |
250 | | break |
| 261 | if not is_distinct: |
| 262 | for field_name in self.search_fields: |
| 263 | f = self.lookup_opts.get_field_by_name(field_name.split('__',1)[0]) |
| 264 | if (isinstance(f[0], models.related.RelatedObject) or |
| 265 | isinstance(f[0].rel, (models.ManyToOneRel, |
| 266 | models.ManyToManyRel))): |
| 267 | is_distinct = True |
| 268 | break |
251 | 269 | |
252 | | return qs |
| 270 | if is_distinct: |
| 271 | return qs.distinct() |
| 272 | else: |
| 273 | return qs |
253 | 274 | |
254 | 275 | def url_for_result(self, result): |
255 | 276 | 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 | |