Ticket #13902: 13902_distinct_in_changelist2.diff

File 13902_distinct_in_changelist2.diff, 6.3 KB (added by rasca, 5 years ago)

new patch, checking fields.rel

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

    diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
    index 8c09c10..0c5886d 100644
    a b class ChangeList(object): 
    164164        return order_field, order_type
    165165
    166166    def get_query_set(self):
     167        is_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):
    class ChangeList(object): 
    176178                del lookup_params[key]
    177179                lookup_params[smart_str(key)] = value
    178180
     181            if not is_distinct:
     182                # Check if it's a relationship that might return more than one
     183                # instance
     184                try:
     185                    f = self.lookup_opts.get_field(key.split('__',1)[0])
     186                except models.FieldDoesNotExist:
     187                    raise IncorrectLookupParameters
     188                if isinstance(f.rel, (models.ManyToOneRel, models.ManyToManyRel)):
     189                    is_distinct = True
     190
    179191            # if key ends with __in, split parameter into separate values
    180192            if key.endswith('__in'):
    181193                lookup_params[key] = value.split(',')
    class ChangeList(object): 
    234246            for bit in self.query.split():
    235247                or_queries = [models.Q(**{construct_search(str(field_name)): bit}) for field_name in self.search_fields]
    236248                qs = qs.filter(reduce(operator.or_, or_queries))
    237             for field_name in self.search_fields:
    238                 if '__' in field_name:
    239                     qs = qs.distinct()
    240                     break
     249            if not is_distinct:
     250                for field_name in self.search_fields:
     251                    f = self.lookup_opts.get_field(field_name.split('__',1)[0])
     252                    if isinstance(f.rel, (models.ManyToOneRel, models.ManyToManyRel)):
     253                        is_distinct = True
     254                        break
    241255
    242         return qs
     256        if is_distinct:
     257            return qs.distinct()
     258        else:
     259            return qs
    243260
    244261    def url_for_result(self, result):
    245262        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 c8ad1ce..89a3424 100644
    a b from django.contrib import admin 
    22from django.contrib.admin.views.main import ChangeList
    33from django.template import Context, Template
    44from django.test import TransactionTestCase
    5 from regressiontests.admin_changelist.models import Child, Parent
     5from regressiontests.admin_changelist.models import Child, Parent, Through
    66
    77class ChangeListTests(TransactionTestCase):
     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
    812    def test_select_related_preserved(self):
    913        """
    1014        Regression test for #10348: ChangeList.get_query_set() shouldn't
    class ChangeListTests(TransactionTestCase): 
    2125        Verifies that inclusion tag result_list generates a table when with
    2226        default ModelAdmin settings.
    2327        """
    24         new_parent = Parent.objects.create(name='parent')
    25         new_child = Child.objects.create(name='name', parent=new_parent)
     28        new_parent = self.new_parent
     29        new_child = self.new_child
    2630        request = MockRequest()
    2731        m = ChildAdmin(Child, admin.site)
    2832        cl = ChangeList(request, Child, m.list_display, m.list_display_links,
    class ChangeListTests(TransactionTestCase): 
    4549        when list_editable is enabled are rendered in a div outside the
    4650        table.
    4751        """
    48         new_parent = Parent.objects.create(name='parent')
    49         new_child = Child.objects.create(name='name', parent=new_parent)
     52        new_parent = self.new_parent
     53        new_child = self.new_child
    5054        request = MockRequest()
    5155        m = ChildAdmin(Child, admin.site)
    5256
    class ChangeListTests(TransactionTestCase): 
    7175        self.failIf('<td>%s</td>' % editable_name_field == -1,
    7276            'Failed to find "name" list_editable field in: %s' % table_output)
    7377
     78    def test_distinct(self):
     79        """
     80        Regression test for #13902: When using a ManyToMany in list_filter,
     81        results may apper more than once
     82        """
     83        new_parent = self.new_parent
     84        new_child = self.new_child
     85        relation1 = Through.objects.create(parent=new_parent, child=new_child)
     86        relation2 = Through.objects.create(parent=new_parent, child=new_child)
     87
     88        m = ChildAdmin(Child, admin.site)
     89        cl = ChangeList(MockFilteredRequest(), Child, m.list_display, m.list_display_links,
     90                m.list_filter, m.date_hierarchy, m.search_fields,
     91                m.list_select_related, m.list_per_page, m.list_editable, m)
     92
     93        cl.get_results(MockFilteredRequest())
     94
     95        # There's only one Child instance
     96        self.assertEqual(cl.result_count, 1)
     97       
    7498class ChildAdmin(admin.ModelAdmin):
    7599    list_display = ['name', 'parent']
    76100    def queryset(self, request):
    77101        return super(ChildAdmin, self).queryset(request).select_related("parent__name")
     102    list_filter = ['multiple_m2m', ]
    78103
    79104class MockRequest(object):
    80105    GET = {}
     106
     107class MockFilteredRequest(object):
     108    GET = {'multiple_m2m__id__exact': 1, }
     109
Back to Top