Ticket #13902: 13902_distinct_in_changelist3.patch

File 13902_distinct_in_changelist3.patch, 6.9 KB (added by rasca, 5 years ago)

Updating patch for trunk after modifications due to security issues.

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

    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): 
    166166        return order_field, order_type
    167167
    168168    def get_query_set(self):
     169        is_distinct = False
     170
    169171        qs = self.root_query_set
    170172        lookup_params = self.params.copy() # a dictionary of the query string
    171173        for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
    class ChangeList(object): 
    178180                del lookup_params[key]
    179181                lookup_params[smart_str(key)] = value
    180182
     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
    181193            # if key ends with __in, split parameter into separate values
    182194            if key.endswith('__in'):
    183195                lookup_params[key] = value.split(',')
    class ChangeList(object): 
    241253            for bit in self.query.split():
    242254                or_queries = [models.Q(**{construct_search(str(field_name)): bit}) for field_name in self.search_fields]
    243255                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
    248262
    249         return qs
     263        if is_distinct:
     264            return qs.distinct()
     265        else:
     266            return qs
    250267
    251268    def url_for_result(self, result):
    252269        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 fca1b9e..b268e4f 100644
    a b from django.core.paginator import Paginator 
    55from django.template import Context, Template
    66from django.test import TransactionTestCase
    77
    8 from regressiontests.admin_changelist.models import Child, Parent
     8from regressiontests.admin_changelist.models import Child, Parent, Through
    99
    1010
    1111class 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
    1216    def test_select_related_preserved(self):
    1317        """
    1418        Regression test for #10348: ChangeList.get_query_set() shouldn't
    class ChangeListTests(TransactionTestCase): 
    2529        Verifies that inclusion tag result_list generates a table when with
    2630        default ModelAdmin settings.
    2731        """
    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
    3034        request = MockRequest()
    3135        m = ChildAdmin(Child, admin.site)
    3236        cl = ChangeList(request, Child, m.list_display, m.list_display_links,
    class ChangeListTests(TransactionTestCase): 
    4953        when list_editable is enabled are rendered in a div outside the
    5054        table.
    5155        """
    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
    5458        request = MockRequest()
    5559        m = ChildAdmin(Child, admin.site)
    5660
    class ChangeListTests(TransactionTestCase): 
    116120        self.assertIsInstance(cl.paginator, CustomPaginator)
    117121
    118122
     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       
    119143class ChildAdmin(admin.ModelAdmin):
    120144    list_display = ['name', 'parent']
    121145    def queryset(self, request):
    122146        return super(ChildAdmin, self).queryset(request).select_related("parent__name")
     147    list_filter = ['multiple_m2m', ]
    123148
    124149
    125150class MockRequest(object):
    126151    GET = {}
    127152
     153class MockFilteredRequest(object):
     154    GET = {'multiple_m2m__id__exact': 1, }
    128155
    129156class CustomPaginator(Paginator):
    130157    def __init__(self, queryset, page_size, orphans=0, allow_empty_first_page=True):
    131158        super(CustomPaginator, self).__init__(queryset, 5, orphans=2,
    132159            allow_empty_first_page=allow_empty_first_page)
     160
Back to Top