diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index b64445ebfde..76b8d30c0d3 100644 --- a/django/contrib/admin/filters.py +++ b/django/contrib/admin/filters.py @@ -155,7 +155,10 @@ class FieldListFilter(ListFilter): class RelatedFieldListFilter(FieldListFilter): def __init__(self, field, request, params, model, model_admin, field_path): other_model = get_model_from_relation(field) - rel_name = other_model._meta.pk.name + if hasattr(field, 'rel'): + rel_name = field.rel.get_related_field().name + else: + rel_name = other_model._meta.pk.name self.lookup_kwarg = '%s__%s__exact' % (field_path, rel_name) self.lookup_kwarg_isnull = '%s__isnull' % field_path self.lookup_val = request.GET.get(self.lookup_kwarg, None) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 73c2958103f..2071792bdbf 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -245,7 +245,7 @@ class BaseModelAdmin(object): # 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]: try: field, _, _, _ = model._meta.get_field_by_name(part) @@ -255,13 +255,13 @@ class BaseModelAdmin(object): return True 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() if len(parts) == 1: diff --git a/tests/regressiontests/admin_filters/models.py b/tests/regressiontests/admin_filters/models.py index 2fa6e6631dc..deb8ee88c35 100644 --- a/tests/regressiontests/admin_filters/models.py +++ b/tests/regressiontests/admin_filters/models.py @@ -13,3 +13,18 @@ class Book(models.Model): def __unicode__(self): return self.title + + +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 \ No newline at end of file diff --git a/tests/regressiontests/admin_filters/tests.py b/tests/regressiontests/admin_filters/tests.py index b42dfd700d7..d87c447e302 100644 --- a/tests/regressiontests/admin_filters/tests.py +++ b/tests/regressiontests/admin_filters/tests.py @@ -4,7 +4,6 @@ import datetime from django.contrib.admin import (site, ModelAdmin, SimpleListFilter, BooleanFieldListFilter) -from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.admin.views.main import ChangeList from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User @@ -13,7 +12,7 @@ from django.test import TestCase, RequestFactory from django.test.utils import override_settings from django.utils.encoding import force_unicode -from .models import Book +from .models import Book, Department, Employee def select_by(dictlist, key, value): @@ -113,6 +112,9 @@ class DecadeFilterBookAdminParameterEndsWith__In(ModelAdmin): class DecadeFilterBookAdminParameterEndsWith__Isnull(ModelAdmin): list_filter = (DecadeListFilterParameterEndsWith__Isnull,) +class EmployeeAdmin(ModelAdmin): + list_display = ['name', 'department'] + list_filter = ['department'] class ListFiltersTests(TestCase): @@ -633,4 +635,64 @@ class ListFiltersTests(TestCase): choices = list(filterspec.choices(changelist)) self.assertEqual(choices[2]['display'], u'the 1990\'s') self.assertEqual(choices[2]['selected'], True) - self.assertEqual(choices[2]['query_string'], '?decade__isnull=the+90s') \ No newline at end of file + self.assertEqual(choices[2]['query_string'], '?decade__isnull=the+90s') + + 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, 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(request) + 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(request) + 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')