Index: django/contrib/admin/views/main.py
===================================================================
--- django/contrib/admin/views/main.py	(revision 16061)
+++ django/contrib/admin/views/main.py	(working copy)
@@ -26,6 +26,15 @@
 # Text to display within change-list table cells if the value is blank.
 EMPTY_CHANGELIST_VALUE = ugettext_lazy('(None)')
 
+def field_needs_distinct(field):
+    if ((hasattr(field, 'rel') and
+         isinstance(field.rel, models.ManyToManyRel)) or
+        (isinstance(field, models.related.RelatedObject) and
+         not field.field.unique)):
+         return True
+    return False
+
+
 class ChangeList(object):
     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):
         self.model = model
@@ -189,8 +198,7 @@
                     f = self.lookup_opts.get_field_by_name(field_name)[0]
                 except models.FieldDoesNotExist:
                     raise IncorrectLookupParameters
-                if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToManyRel):
-                    use_distinct = True
+                use_distinct = field_needs_distinct(f)
 
             # if key ends with __in, split parameter into separate values
             if key.endswith('__in'):
@@ -264,7 +272,7 @@
                 for search_spec in orm_lookups:
                     field_name = search_spec.split('__', 1)[0]
                     f = self.lookup_opts.get_field_by_name(field_name)[0]
-                    if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToManyRel):
+                    if field_needs_distinct(f):
                         use_distinct = True
                         break
 
Index: tests/regressiontests/admin_changelist/tests.py
===================================================================
--- tests/regressiontests/admin_changelist/tests.py	(revision 16061)
+++ tests/regressiontests/admin_changelist/tests.py	(working copy)
@@ -1,6 +1,6 @@
 from django.contrib import admin
 from django.contrib.admin.options import IncorrectLookupParameters
-from django.contrib.admin.views.main import ChangeList
+from django.contrib.admin.views.main import ChangeList, SEARCH_VAR
 from django.core.paginator import Paginator
 from django.template import Context, Template
 from django.test import TransactionTestCase
@@ -148,12 +148,14 @@
         band.genres.add(blues)
 
         m = BandAdmin(Band, admin.site)
-        cl = ChangeList(MockFilteredRequestA(blues.pk), Band, m.list_display,
+        request = MockFilterRequest('genres', blues.pk)
+
+        cl = ChangeList(request, Band, m.list_display,
                 m.list_display_links, m.list_filter, m.date_hierarchy,
                 m.search_fields, m.list_select_related, m.list_per_page,
                 m.list_editable, m)
 
-        cl.get_results(MockFilteredRequestA(blues.pk))
+        cl.get_results(request)
 
         # There's only one Group instance
         self.assertEqual(cl.result_count, 1)
@@ -169,12 +171,14 @@
         Membership.objects.create(group=band, music=lead, role='bass player')
 
         m = GroupAdmin(Group, admin.site)
-        cl = ChangeList(MockFilteredRequestB(lead.pk), Group, m.list_display,
+        request = MockFilterRequest('members', lead.pk)
+
+        cl = ChangeList(request, Group, m.list_display,
                 m.list_display_links, m.list_filter, m.date_hierarchy,
                 m.search_fields, m.list_select_related, m.list_per_page,
                 m.list_editable, m)
 
-        cl.get_results(MockFilteredRequestB(lead.pk))
+        cl.get_results(request)
 
         # There's only one Group instance
         self.assertEqual(cl.result_count, 1)
@@ -191,12 +195,14 @@
         Membership.objects.create(group=four, music=lead, role='guitar player')
 
         m = QuartetAdmin(Quartet, admin.site)
-        cl = ChangeList(MockFilteredRequestB(lead.pk), Quartet, m.list_display,
+        request = MockFilterRequest('members', lead.pk)
+
+        cl = ChangeList(request, Quartet, m.list_display,
                 m.list_display_links, m.list_filter, m.date_hierarchy,
                 m.search_fields, m.list_select_related, m.list_per_page,
                 m.list_editable, m)
 
-        cl.get_results(MockFilteredRequestB(lead.pk))
+        cl.get_results(request)
 
         # There's only one Quartet instance
         self.assertEqual(cl.result_count, 1)
@@ -213,16 +219,59 @@
         Invitation.objects.create(band=three, player=lead, instrument='bass')
 
         m = ChordsBandAdmin(ChordsBand, admin.site)
-        cl = ChangeList(MockFilteredRequestB(lead.pk), ChordsBand, m.list_display,
+        request = MockFilterRequest('members', lead.pk)
+
+        cl = ChangeList(request, ChordsBand, m.list_display,
                 m.list_display_links, m.list_filter, m.date_hierarchy,
                 m.search_fields, m.list_select_related, m.list_per_page,
                 m.list_editable, m)
 
-        cl.get_results(MockFilteredRequestB(lead.pk))
+        cl.get_results(request)
 
         # There's only one ChordsBand instance
         self.assertEqual(cl.result_count, 1)
 
+    def test_distinct_for_non_unique_related_object_in_list_filter(self):
+        """
+        Regressions tests for #15819: If a field listed in list_filters
+        is a non-unique related object, distinct() must be called.
+        """
+        parent = Parent.objects.create(name='Mary')
+        # Two children with the same name
+        Child.objects.create(parent=parent, name='Daniel')
+        Child.objects.create(parent=parent, name='Daniel')
+
+        m = ParentAdmin(Parent, admin.site)
+        request = MockFilterRequest('child__name', 'Daniel')
+
+        cl = ChangeList(request, Parent, m.list_display, m.list_display_links,
+                        m.list_filter, m.date_hierarchy, m.search_fields,
+                        m.list_select_related, m.list_per_page,
+                        m.list_editable, m)
+
+        # Make sure distinct() was called
+        self.assertEqual(cl.query_set.count(), 1)
+
+    def test_distinct_for_non_unique_related_object_in_search_fields(self):
+        """
+        Regressions tests for #15819: If a field listed in search_fields
+        is a non-unique related object, distinct() must be called.
+        """
+        parent = Parent.objects.create(name='Mary')
+        Child.objects.create(parent=parent, name='Danielle')
+        Child.objects.create(parent=parent, name='Daniel')
+
+        m = ParentAdmin(Parent, admin.site)
+        request = MockSearchRequest('daniel')
+
+        cl = ChangeList(request, Parent, m.list_display, m.list_display_links,
+                        m.list_filter, m.date_hierarchy, m.search_fields,
+                        m.list_select_related, m.list_per_page,
+                        m.list_editable, m)
+
+        # Make sure distinct() was called
+        self.assertEqual(cl.query_set.count(), 1)
+
     def test_pagination(self):
         """
         Regression tests for #12893: Pagination in admins changelist doesn't
@@ -254,6 +303,11 @@
         self.assertEqual(cl.paginator.page_range, [1, 2, 3])
 
 
+class ParentAdmin(admin.ModelAdmin):
+    list_filter = ['child__name']
+    search_fields = ['child__name']
+
+
 class ChildAdmin(admin.ModelAdmin):
     list_display = ['name', 'parent']
     list_per_page = 10
@@ -288,10 +342,10 @@
 class ChordsBandAdmin(admin.ModelAdmin):
     list_filter = ['members']
 
-class MockFilteredRequestA(object):
-    def __init__(self, pk):
-        self.GET = { 'genres' : pk }
+class MockFilterRequest(object):
+    def __init__(self, filter, q):
+        self.GET = {filter: q}
 
-class MockFilteredRequestB(object):
-    def __init__(self, pk):
-        self.GET = { 'members': pk }
+class MockSearchRequest(object):
+    def __init__(self, q):
+        self.GET = {SEARCH_VAR: q}
