Ticket #13902: 13902_distinct_in_changelist4.patch

File 13902_distinct_in_changelist4.patch, 7.2 KB (added by rasca, 14 years ago)

new patch, this time checking for RelatedObject also

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

    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):  
    169169        return order_field, order_type
    170170
    171171    def get_query_set(self):
     172        is_distinct = False
     173
    172174        qs = self.root_query_set
    173175        lookup_params = self.params.copy() # a dictionary of the query string
    174176        for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
    class ChangeList(object):  
    181183                del lookup_params[key]
    182184                lookup_params[smart_str(key)] = value
    183185
     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
    184198            # if key ends with __in, split parameter into separate values
    185199            if key.endswith('__in'):
    186200                lookup_params[key] = value.split(',')
    class ChangeList(object):  
    244258            for bit in self.query.split():
    245259                or_queries = [models.Q(**{construct_search(str(field_name)): bit}) for field_name in self.search_fields]
    246260                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
    251269
    252         return qs
     270        if is_distinct:
     271            return qs.distinct()
     272        else:
     273            return qs
    253274
    254275    def url_for_result(self, result):
    255276        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