Ticket #15819: non-unique-related-fields-distinct3.diff

File non-unique-related-fields-distinct3.diff, 7.8 KB (added by Ryan Kaskel, 8 years ago)
  • django/contrib/admin/views/main.py

     
    2626# Text to display within change-list table cells if the value is blank.
    2727EMPTY_CHANGELIST_VALUE = ugettext_lazy('(None)')
    2828
     29def field_needs_distinct(field):
     30    if ((hasattr(field, 'rel') and
     31         isinstance(field.rel, models.ManyToManyRel)) or
     32        (isinstance(field, models.related.RelatedObject) and
     33         not field.field.unique)):
     34         return True
     35    return False
     36
     37
    2938class ChangeList(object):
    3039    def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin):
    3140        self.model = model
     
    189198                    f = self.lookup_opts.get_field_by_name(field_name)[0]
    190199                except models.FieldDoesNotExist:
    191200                    raise IncorrectLookupParameters
    192                 if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToManyRel):
    193                     use_distinct = True
     201                use_distinct = field_needs_distinct(f)
    194202
    195203            # if key ends with __in, split parameter into separate values
    196204            if key.endswith('__in'):
     
    264272                for search_spec in orm_lookups:
    265273                    field_name = search_spec.split('__', 1)[0]
    266274                    f = self.lookup_opts.get_field_by_name(field_name)[0]
    267                     if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToManyRel):
     275                    if field_needs_distinct(f):
    268276                        use_distinct = True
    269277                        break
    270278
  • tests/regressiontests/admin_changelist/tests.py

     
    11from django.contrib import admin
    22from django.contrib.admin.options import IncorrectLookupParameters
    3 from django.contrib.admin.views.main import ChangeList
     3from django.contrib.admin.views.main import ChangeList, SEARCH_VAR
    44from django.core.paginator import Paginator
    55from django.template import Context, Template
    66from django.test import TransactionTestCase
     
    148148        band.genres.add(blues)
    149149
    150150        m = BandAdmin(Band, admin.site)
    151         cl = ChangeList(MockFilteredRequestA(blues.pk), Band, m.list_display,
     151        request = MockFilterRequest('genres', blues.pk)
     152
     153        cl = ChangeList(request, Band, m.list_display,
    152154                m.list_display_links, m.list_filter, m.date_hierarchy,
    153155                m.search_fields, m.list_select_related, m.list_per_page,
    154156                m.list_editable, m)
    155157
    156         cl.get_results(MockFilteredRequestA(blues.pk))
     158        cl.get_results(request)
    157159
    158160        # There's only one Group instance
    159161        self.assertEqual(cl.result_count, 1)
     
    169171        Membership.objects.create(group=band, music=lead, role='bass player')
    170172
    171173        m = GroupAdmin(Group, admin.site)
    172         cl = ChangeList(MockFilteredRequestB(lead.pk), Group, m.list_display,
     174        request = MockFilterRequest('members', lead.pk)
     175
     176        cl = ChangeList(request, Group, m.list_display,
    173177                m.list_display_links, m.list_filter, m.date_hierarchy,
    174178                m.search_fields, m.list_select_related, m.list_per_page,
    175179                m.list_editable, m)
    176180
    177         cl.get_results(MockFilteredRequestB(lead.pk))
     181        cl.get_results(request)
    178182
    179183        # There's only one Group instance
    180184        self.assertEqual(cl.result_count, 1)
     
    191195        Membership.objects.create(group=four, music=lead, role='guitar player')
    192196
    193197        m = QuartetAdmin(Quartet, admin.site)
    194         cl = ChangeList(MockFilteredRequestB(lead.pk), Quartet, m.list_display,
     198        request = MockFilterRequest('members', lead.pk)
     199
     200        cl = ChangeList(request, Quartet, m.list_display,
    195201                m.list_display_links, m.list_filter, m.date_hierarchy,
    196202                m.search_fields, m.list_select_related, m.list_per_page,
    197203                m.list_editable, m)
    198204
    199         cl.get_results(MockFilteredRequestB(lead.pk))
     205        cl.get_results(request)
    200206
    201207        # There's only one Quartet instance
    202208        self.assertEqual(cl.result_count, 1)
     
    213219        Invitation.objects.create(band=three, player=lead, instrument='bass')
    214220
    215221        m = ChordsBandAdmin(ChordsBand, admin.site)
    216         cl = ChangeList(MockFilteredRequestB(lead.pk), ChordsBand, m.list_display,
     222        request = MockFilterRequest('members', lead.pk)
     223
     224        cl = ChangeList(request, ChordsBand, m.list_display,
    217225                m.list_display_links, m.list_filter, m.date_hierarchy,
    218226                m.search_fields, m.list_select_related, m.list_per_page,
    219227                m.list_editable, m)
    220228
    221         cl.get_results(MockFilteredRequestB(lead.pk))
     229        cl.get_results(request)
    222230
    223231        # There's only one ChordsBand instance
    224232        self.assertEqual(cl.result_count, 1)
    225233
     234    def test_distinct_for_non_unique_related_object_in_list_filter(self):
     235        """
     236        Regressions tests for #15819: If a field listed in list_filters
     237        is a non-unique related object, distinct() must be called.
     238        """
     239        parent = Parent.objects.create(name='Mary')
     240        # Two children with the same name
     241        Child.objects.create(parent=parent, name='Daniel')
     242        Child.objects.create(parent=parent, name='Daniel')
     243
     244        m = ParentAdmin(Parent, admin.site)
     245        request = MockFilterRequest('child__name', 'Daniel')
     246
     247        cl = ChangeList(request, Parent, m.list_display, m.list_display_links,
     248                        m.list_filter, m.date_hierarchy, m.search_fields,
     249                        m.list_select_related, m.list_per_page,
     250                        m.list_editable, m)
     251
     252        # Make sure distinct() was called
     253        self.assertEqual(cl.query_set.count(), 1)
     254
     255    def test_distinct_for_non_unique_related_object_in_search_fields(self):
     256        """
     257        Regressions tests for #15819: If a field listed in search_fields
     258        is a non-unique related object, distinct() must be called.
     259        """
     260        parent = Parent.objects.create(name='Mary')
     261        Child.objects.create(parent=parent, name='Danielle')
     262        Child.objects.create(parent=parent, name='Daniel')
     263
     264        m = ParentAdmin(Parent, admin.site)
     265        request = MockSearchRequest('daniel')
     266
     267        cl = ChangeList(request, Parent, m.list_display, m.list_display_links,
     268                        m.list_filter, m.date_hierarchy, m.search_fields,
     269                        m.list_select_related, m.list_per_page,
     270                        m.list_editable, m)
     271
     272        # Make sure distinct() was called
     273        self.assertEqual(cl.query_set.count(), 1)
     274
    226275    def test_pagination(self):
    227276        """
    228277        Regression tests for #12893: Pagination in admins changelist doesn't
     
    254303        self.assertEqual(cl.paginator.page_range, [1, 2, 3])
    255304
    256305
     306class ParentAdmin(admin.ModelAdmin):
     307    list_filter = ['child__name']
     308    search_fields = ['child__name']
     309
     310
    257311class ChildAdmin(admin.ModelAdmin):
    258312    list_display = ['name', 'parent']
    259313    list_per_page = 10
     
    288342class ChordsBandAdmin(admin.ModelAdmin):
    289343    list_filter = ['members']
    290344
    291 class MockFilteredRequestA(object):
    292     def __init__(self, pk):
    293         self.GET = { 'genres' : pk }
     345class MockFilterRequest(object):
     346    def __init__(self, filter, q):
     347        self.GET = {filter: q}
    294348
    295 class MockFilteredRequestB(object):
    296     def __init__(self, pk):
    297         self.GET = { 'members': pk }
     349class MockSearchRequest(object):
     350    def __init__(self, q):
     351        self.GET = {SEARCH_VAR: q}
Back to Top