Fixed #17972 -- Ensured that admin filters on a foreign key respect the `to_field` attribute. This fixes a regression introduced in [14674] and Django 1.3. Thanks to graveyboat and Karen Tracey for the report.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17854 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Julien Phalip 2012-03-31 18:22:12 +00:00
parent ad355e48ce
commit c39e1cff99
4 changed files with 89 additions and 9 deletions

View File

@ -155,7 +155,10 @@ class FieldListFilter(ListFilter):
class RelatedFieldListFilter(FieldListFilter): class RelatedFieldListFilter(FieldListFilter):
def __init__(self, field, request, params, model, model_admin, field_path): def __init__(self, field, request, params, model, model_admin, field_path):
other_model = get_model_from_relation(field) 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 = '%s__%s__exact' % (field_path, rel_name)
self.lookup_kwarg_isnull = '%s__isnull' % field_path self.lookup_kwarg_isnull = '%s__isnull' % field_path
self.lookup_val = request.GET.get(self.lookup_kwarg, None) self.lookup_val = request.GET.get(self.lookup_kwarg, None)

View File

@ -245,7 +245,7 @@ class BaseModelAdmin(object):
# if foo has been specificially included in the lookup list; so # 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 # drop __id if it is the last part. However, first we need to find
# the pk attribute name. # the pk attribute name.
pk_attr_name = None rel_name = None
for part in parts[:-1]: for part in parts[:-1]:
try: try:
field, _, _, _ = model._meta.get_field_by_name(part) field, _, _, _ = model._meta.get_field_by_name(part)
@ -255,13 +255,13 @@ class BaseModelAdmin(object):
return True return True
if hasattr(field, 'rel'): if hasattr(field, 'rel'):
model = field.rel.to model = field.rel.to
pk_attr_name = model._meta.pk.name rel_name = field.rel.get_related_field().name
elif isinstance(field, RelatedObject): elif isinstance(field, RelatedObject):
model = field.model model = field.model
pk_attr_name = model._meta.pk.name rel_name = model._meta.pk.name
else: else:
pk_attr_name = None rel_name = None
if pk_attr_name and len(parts) > 1 and parts[-1] == pk_attr_name: if rel_name and len(parts) > 1 and parts[-1] == rel_name:
parts.pop() parts.pop()
if len(parts) == 1: if len(parts) == 1:

View File

@ -13,3 +13,18 @@ class Book(models.Model):
def __unicode__(self): def __unicode__(self):
return self.title 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

View File

@ -4,7 +4,6 @@ import datetime
from django.contrib.admin import (site, ModelAdmin, SimpleListFilter, from django.contrib.admin import (site, ModelAdmin, SimpleListFilter,
BooleanFieldListFilter) BooleanFieldListFilter)
from django.contrib.admin.options import IncorrectLookupParameters
from django.contrib.admin.views.main import ChangeList from django.contrib.admin.views.main import ChangeList
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User 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.test.utils import override_settings
from django.utils.encoding import force_unicode from django.utils.encoding import force_unicode
from .models import Book from .models import Book, Department, Employee
def select_by(dictlist, key, value): def select_by(dictlist, key, value):
@ -113,6 +112,9 @@ class DecadeFilterBookAdminParameterEndsWith__In(ModelAdmin):
class DecadeFilterBookAdminParameterEndsWith__Isnull(ModelAdmin): class DecadeFilterBookAdminParameterEndsWith__Isnull(ModelAdmin):
list_filter = (DecadeListFilterParameterEndsWith__Isnull,) list_filter = (DecadeListFilterParameterEndsWith__Isnull,)
class EmployeeAdmin(ModelAdmin):
list_display = ['name', 'department']
list_filter = ['department']
class ListFiltersTests(TestCase): class ListFiltersTests(TestCase):
@ -633,4 +635,64 @@ class ListFiltersTests(TestCase):
choices = list(filterspec.choices(changelist)) choices = list(filterspec.choices(changelist))
self.assertEqual(choices[2]['display'], u'the 1990\'s') self.assertEqual(choices[2]['display'], u'the 1990\'s')
self.assertEqual(choices[2]['selected'], True) self.assertEqual(choices[2]['selected'], True)
self.assertEqual(choices[2]['query_string'], '?decade__isnull=the+90s') 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')