diff -r a6d8d77ea99e django/contrib/admin/filterspecs.py
--- a/django/contrib/admin/filterspecs.py	Sat Mar 24 13:56:48 2012 +0000
+++ b/django/contrib/admin/filterspecs.py	Sat Mar 24 17:50:27 2012 -0700
@@ -74,9 +74,12 @@
             self.lookup_title = other_model._meta.verbose_name
         else:
             self.lookup_title = f.verbose_name # use field name
-        rel_name = other_model._meta.pk.name
+        if hasattr(f, 'rel'):
+            rel_name = f.rel.get_related_field().name
+        else:
+            rel_name = other_model._meta.pk.name
         self.lookup_kwarg = '%s__%s__exact' % (self.field_path, rel_name)
-        self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path)
+        self.lookup_kwarg_isnull = '%s__isnull' % self.field_path
         self.lookup_val = request.GET.get(self.lookup_kwarg, None)
         self.lookup_val_isnull = request.GET.get(
                                       self.lookup_kwarg_isnull, None)
@@ -176,7 +179,7 @@
 
 class DateFieldFilterSpec(FilterSpec):
     def __init__(self, f, request, params, model, model_admin,
-                 field_path=None): 
+                 field_path=None):
         super(DateFieldFilterSpec, self).__init__(f, request, params, model,
                                                   model_admin,
                                                   field_path=field_path)
diff -r a6d8d77ea99e django/contrib/admin/options.py
--- a/django/contrib/admin/options.py	Sat Mar 24 13:56:48 2012 +0000
+++ b/django/contrib/admin/options.py	Sat Mar 24 17:50:27 2012 -0700
@@ -225,18 +225,18 @@
         # if foo has been specificially included in the lookup list; so
         # drop __id if it is the last part. However, first we need to find
         # the pk attribute name.
-        pk_attr_name = None
+        rel_name = None
         for part in parts[:-1]:
             field, _, _, _ = model._meta.get_field_by_name(part)
             if hasattr(field, 'rel'):
                 model = field.rel.to
-                pk_attr_name = model._meta.pk.name
+                rel_name = field.rel.get_related_field().name
             elif isinstance(field, RelatedObject):
                 model = field.model
-                pk_attr_name = model._meta.pk.name
+                rel_name = model._meta.pk.name
             else:
-                pk_attr_name = None
-        if pk_attr_name and len(parts) > 1 and parts[-1] == pk_attr_name:
+                rel_name = None
+        if rel_name and len(parts) > 1 and parts[-1] == rel_name:
             parts.pop()
 
         try:
diff -r a6d8d77ea99e tests/regressiontests/admin_filterspecs/models.py
--- a/tests/regressiontests/admin_filterspecs/models.py	Sat Mar 24 13:56:48 2012 +0000
+++ b/tests/regressiontests/admin_filterspecs/models.py	Sat Mar 24 17:50:27 2012 -0700
@@ -21,3 +21,21 @@
         default=NO,
         choices=YES_NO_CHOICES
     )
+
+
+class Department(models.Model):
+    code = models.CharField(max_length=4, unique=True)
+    description = models.CharField(max_length=50, blank=True, null=True)
+
+    def __unicode__(self):
+        return self.description
+
+class Employee(models.Model):
+    department = models.ForeignKey(Department, to_field="code")
+    name = models.CharField(max_length=100)
+
+    def __unicode__(self):
+        return self.name
+
+    class Meta:
+        ordering = ['id']
\ No newline at end of file
diff -r a6d8d77ea99e tests/regressiontests/admin_filterspecs/tests.py
--- a/tests/regressiontests/admin_filterspecs/tests.py	Sat Mar 24 13:56:48 2012 +0000
+++ b/tests/regressiontests/admin_filterspecs/tests.py	Sat Mar 24 17:50:27 2012 -0700
@@ -6,7 +6,7 @@
 from django.contrib.admin.views.main import ChangeList
 from django.utils.encoding import force_unicode
 
-from models import Book, BoolTest
+from models import Book, BoolTest, Employee, Department
 
 def select_by(dictlist, key, value):
     return [x for x in dictlist if x[key] == value][0]
@@ -200,6 +200,67 @@
         self.assertEqual(choice['selected'], True)
         self.assertEqual(choice['query_string'], '?completed__exact=1')
 
+    def test_fk_with_to_field(self):
+        """
+        Ensure that a filter on a FK respects the FK's to_field attribute.
+        Refs #17972.
+        """
+        modeladmin = EmployeeAdmin(Employee, admin.site)
+
+        dev = Department.objects.create(code='DEV', description='Development')
+        design = Department.objects.create(code='DSN', description='Design')
+        john = Employee.objects.create(name='John Blue', department=dev)
+        jack = Employee.objects.create(name='Jack Red', department=design)
+
+        request = self.request_factory.get('/', {})
+        changelist = self.get_changelist(request, Employee, modeladmin)
+
+        # Make sure the correct queryset is returned
+        queryset = changelist.get_query_set()
+        self.assertEqual(list(queryset), [john, jack])
+
+        filterspec = changelist.get_filters(request)[0][-1]
+        self.assertEqual(force_unicode(filterspec.title()), u'department')
+        choices = list(filterspec.choices(changelist))
+
+        self.assertEqual(choices[0]['display'], u'All')
+        self.assertEqual(choices[0]['selected'], True)
+        self.assertEqual(choices[0]['query_string'], '?')
+
+        self.assertEqual(choices[1]['display'], u'Development')
+        self.assertEqual(choices[1]['selected'], False)
+        self.assertEqual(choices[1]['query_string'], '?department__code__exact=DEV')
+
+        self.assertEqual(choices[2]['display'], u'Design')
+        self.assertEqual(choices[2]['selected'], False)
+        self.assertEqual(choices[2]['query_string'], '?department__code__exact=DSN')
+
+        # Filter by Department=='Development' --------------------------------
+
+        request = self.request_factory.get('/', {'department__code__exact': 'DEV'})
+        changelist = self.get_changelist(request, Employee, modeladmin)
+
+        # Make sure the correct queryset is returned
+        queryset = changelist.get_query_set()
+        self.assertEqual(list(queryset), [john])
+
+        filterspec = changelist.get_filters(request)[0][-1]
+        self.assertEqual(force_unicode(filterspec.title()), u'department')
+        choices = list(filterspec.choices(changelist))
+
+        self.assertEqual(choices[0]['display'], u'All')
+        self.assertEqual(choices[0]['selected'], False)
+        self.assertEqual(choices[0]['query_string'], '?')
+
+        self.assertEqual(choices[1]['display'], u'Development')
+        self.assertEqual(choices[1]['selected'], True)
+        self.assertEqual(choices[1]['query_string'], '?department__code__exact=DEV')
+
+        self.assertEqual(choices[2]['display'], u'Design')
+        self.assertEqual(choices[2]['selected'], False)
+        self.assertEqual(choices[2]['query_string'], '?department__code__exact=DSN')
+
+
 class CustomUserAdmin(UserAdmin):
     list_filter = ('books_authored', 'books_contributed')
 
@@ -209,3 +270,7 @@
 
 class BoolTestAdmin(admin.ModelAdmin):
     list_filter = ('completed',)
+
+class EmployeeAdmin(admin.ModelAdmin):
+    list_display = ['name', 'department']
+    list_filter = ['department']
\ No newline at end of file
