Ticket #13902: 13902_distinct_in_changelist.diff

File 13902_distinct_in_changelist.diff, 5.5 KB (added by rasca, 5 years ago)

fixes #13902

  • django/contrib/admin/views/main.py

    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): 
    164164        return order_field, order_type
    165165
    166166    def get_query_set(self):
     167        DISTINCT = False
     168
    167169        qs = self.root_query_set
    168170        lookup_params = self.params.copy() # a dictionary of the query string
    169171        for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
    170172            if i in lookup_params:
    171173                del lookup_params[i]
    172174        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
    173179            if not isinstance(key, str):
    174180                # 'key' will be used as a keyword argument later, so Python
    175181                # requires it to be a string.
    class ChangeList(object): 
    230236            else:
    231237                return "%s__icontains" % field_name
    232238
    233         if self.search_fields and self.query:
     239        if not DISTINCT and self.search_fields and self.query:
    234240            for bit in self.query.split():
    235241                or_queries = [models.Q(**{construct_search(str(field_name)): bit}) for field_name in self.search_fields]
    236242                qs = qs.filter(reduce(operator.or_, or_queries))
    237243            for field_name in self.search_fields:
    238244                if '__' in field_name:
    239                     qs = qs.distinct()
     245                    DISCTINCT = True
    240246                    break
    241247
    242         return qs
     248        if DISTINCT:
     249            return qs.distinct()
     250        else:
     251            return qs
    243252
    244253    def url_for_result(self, result):
    245254        return "%s/" % quote(getattr(result, self.pk_attname))
  • tests/regressiontests/admin_changelist/models.py

    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): 
    66
    77class Child(models.Model):
    88    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
     13class Through(models.Model):
     14    parent = models.ForeignKey(Parent)
     15    child = models.ForeignKey(Child)
  • tests/regressiontests/admin_changelist/tests.py

    diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/regressiontests/admin_changelist/tests.py
    index b70d7c5..569bfe8 100644
    a b import unittest 
    22from django.contrib import admin
    33from django.contrib.admin.views.main import ChangeList
    44from django.template import Context, Template
    5 from regressiontests.admin_changelist.models import Child, Parent
     5from regressiontests.admin_changelist.models import Child, Parent, Through
    66
    77class 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
    816    def test_select_related_preserved(self):
    917        """
    1018        Regression test for #10348: ChangeList.get_query_set() shouldn't
    class ChangeListTests(unittest.TestCase): 
    2230        table and this checks that the items are nested within the table
    2331        element tags.
    2432        """
    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
    2735        request = MockRequest()
    2836        m = ChildAdmin(Child, admin.site)
    2937        cl = ChangeList(request, Child, m.list_display, m.list_display_links,
    class ChangeListTests(unittest.TestCase): 
    5765        self.failIf(table_output.find('<td>%s</td>' % hidden_input_elem) == -1,
    5866            'Hidden input element is not enclosed in <td> element.')
    5967
     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       
    6088class ChildAdmin(admin.ModelAdmin):
    6189    list_display = ['name', 'parent']
    6290    def queryset(self, request):
    6391        return super(ChildAdmin, self).queryset(request).select_related("parent__name")
     92    list_filter = ['multiple_m2m', ]
    6493
    6594class MockRequest(object):
    6695    GET = {}
     96
     97class MockFilteredRequest(object):
     98    GET = {'multiple_m2m__id__exact': 1, }
     99
Back to Top