Fixed #15960 -- Extended list filer API added in r16144 slightly to pass the current model admin to the SimpleListFilter.lookups method to support finer grained control over what is filtered over. Many thanks to Carl Meyer and Julien Phalip for the suggestion and patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16152 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
f4464864c8
commit
95dc7c7486
|
@ -63,10 +63,10 @@ class SimpleListFilter(ListFilter):
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
"The list filter '%s' does not specify "
|
"The list filter '%s' does not specify "
|
||||||
"a 'parameter_name'." % self.__class__.__name__)
|
"a 'parameter_name'." % self.__class__.__name__)
|
||||||
lookup_choices = self.lookups(request)
|
lookup_choices = self.lookups(request, model_admin)
|
||||||
if lookup_choices is None:
|
if lookup_choices is None:
|
||||||
lookup_choices = ()
|
lookup_choices = ()
|
||||||
self.lookup_choices = lookup_choices
|
self.lookup_choices = list(lookup_choices)
|
||||||
|
|
||||||
def has_output(self):
|
def has_output(self):
|
||||||
return len(self.lookup_choices) > 0
|
return len(self.lookup_choices) > 0
|
||||||
|
@ -78,7 +78,7 @@ class SimpleListFilter(ListFilter):
|
||||||
"""
|
"""
|
||||||
return self.params.get(self.parameter_name, None)
|
return self.params.get(self.parameter_name, None)
|
||||||
|
|
||||||
def lookups(self, request):
|
def lookups(self, request, model_admin):
|
||||||
"""
|
"""
|
||||||
Must be overriden to return a list of tuples (value, verbose value)
|
Must be overriden to return a list of tuples (value, verbose value)
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -555,9 +555,7 @@ subclass::
|
||||||
attributes to and override the ``lookups`` and ``queryset`` methods,
|
attributes to and override the ``lookups`` and ``queryset`` methods,
|
||||||
e.g.::
|
e.g.::
|
||||||
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from django.contrib.admin import SimpleListFilter
|
from django.contrib.admin import SimpleListFilter
|
||||||
|
|
||||||
class DecadeBornListFilter(SimpleListFilter):
|
class DecadeBornListFilter(SimpleListFilter):
|
||||||
|
@ -568,7 +566,7 @@ subclass::
|
||||||
# Parameter for the filter that will be used in the URL query.
|
# Parameter for the filter that will be used in the URL query.
|
||||||
parameter_name = 'decade'
|
parameter_name = 'decade'
|
||||||
|
|
||||||
def lookups(self, request):
|
def lookups(self, request, model_admin):
|
||||||
"""
|
"""
|
||||||
Returns a list of tuples. The first element in each
|
Returns a list of tuples. The first element in each
|
||||||
tuple is the coded value for the option that will
|
tuple is the coded value for the option that will
|
||||||
|
@ -577,24 +575,24 @@ subclass::
|
||||||
in the right sidebar.
|
in the right sidebar.
|
||||||
"""
|
"""
|
||||||
return (
|
return (
|
||||||
('80s', 'in the eighties'),
|
('80s', _('in the eighties')),
|
||||||
('other', 'other'),
|
('90s', _('in the nineties')),
|
||||||
)
|
)
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
def queryset(self, request, queryset):
|
||||||
"""
|
"""
|
||||||
Returns the filtered queryset based on the value
|
Returns the filtered queryset based on the value
|
||||||
provided in the query string and retrievable via
|
provided in the query string and retrievable via
|
||||||
``value()``.
|
`self.value()`.
|
||||||
"""
|
"""
|
||||||
# Compare the requested value (either '80s' or 'other')
|
# Compare the requested value (either '80s' or 'other')
|
||||||
# to decide how to filter the queryset.
|
# to decide how to filter the queryset.
|
||||||
if self.value() == '80s':
|
if self.value() == '80s':
|
||||||
return queryset.filter(birthday__year__gte=1980,
|
return queryset.filter(birthday__year__gte=1980,
|
||||||
birthday__year__lte=1989)
|
birthday__year__lte=1989)
|
||||||
if self.value() == 'other':
|
if self.value() == '90s':
|
||||||
return queryset.filter(Q(year__lte=1979) |
|
return queryset.filter(birthday__year__gte=1990,
|
||||||
Q(year__gte=1990))
|
birthday__year__lte=1999)
|
||||||
|
|
||||||
class PersonAdmin(ModelAdmin):
|
class PersonAdmin(ModelAdmin):
|
||||||
list_filter = (DecadeBornListFilter,)
|
list_filter = (DecadeBornListFilter,)
|
||||||
|
@ -602,17 +600,38 @@ subclass::
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
As a convenience, the ``HttpRequest`` object is passed to the
|
As a convenience, the ``HttpRequest`` object is passed to the
|
||||||
filter's methods, for example::
|
``lookups`` and ``queryset`` methods, for example::
|
||||||
|
|
||||||
class AuthDecadeBornListFilter(DecadeBornListFilter):
|
class AuthDecadeBornListFilter(DecadeBornListFilter):
|
||||||
|
|
||||||
def lookups(self, request):
|
def lookups(self, request, model_admin):
|
||||||
if request.user.is_superuser:
|
if request.user.is_superuser:
|
||||||
return super(AuthDecadeBornListFilter, self).lookups(request)
|
return super(AuthDecadeBornListFilter,
|
||||||
|
self).lookups(request, model_admin)
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
def queryset(self, request, queryset):
|
||||||
if request.user.is_superuser:
|
if request.user.is_superuser:
|
||||||
return super(AuthDecadeBornListFilter, self).queryset(request, queryset)
|
return super(AuthDecadeBornListFilter,
|
||||||
|
self).queryset(request, queryset)
|
||||||
|
|
||||||
|
Also as a convenience, the ``ModelAdmin`` object is passed to
|
||||||
|
the ``lookups`` method, for example if you want to base the
|
||||||
|
lookups on the available data::
|
||||||
|
|
||||||
|
class AdvancedDecadeBornListFilter(DecadeBornListFilter):
|
||||||
|
|
||||||
|
def lookups(self, request, model_admin):
|
||||||
|
"""
|
||||||
|
Only show the lookups if there actually is
|
||||||
|
anyone born in the corresponding decades.
|
||||||
|
"""
|
||||||
|
qs = model_admin.queryset(request)
|
||||||
|
if qs.filter(birthday__year__gte=1980,
|
||||||
|
birthday__year__lte=1989).exists():
|
||||||
|
yield ('80s', _('in the eighties'))
|
||||||
|
if qs.filter(birthday__year__gte=1990,
|
||||||
|
birthday__year__lte=1999).exists():
|
||||||
|
yield ('90s', _('in the nineties'))
|
||||||
|
|
||||||
* a tuple, where the first element is a field name and the second
|
* a tuple, where the first element is a field name and the second
|
||||||
element is a class inheriting from
|
element is a class inheriting from
|
||||||
|
|
|
@ -19,8 +19,9 @@ def select_by(dictlist, key, value):
|
||||||
|
|
||||||
class DecadeListFilter(SimpleListFilter):
|
class DecadeListFilter(SimpleListFilter):
|
||||||
|
|
||||||
def lookups(self, request):
|
def lookups(self, request, model_admin):
|
||||||
return (
|
return (
|
||||||
|
('the 80s', "the 1980's"),
|
||||||
('the 90s', "the 1990's"),
|
('the 90s', "the 1990's"),
|
||||||
('the 00s', "the 2000's"),
|
('the 00s', "the 2000's"),
|
||||||
('other', "other decades"),
|
('other', "other decades"),
|
||||||
|
@ -28,6 +29,8 @@ class DecadeListFilter(SimpleListFilter):
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
def queryset(self, request, queryset):
|
||||||
decade = self.value()
|
decade = self.value()
|
||||||
|
if decade == 'the 80s':
|
||||||
|
return queryset.filter(year__gte=1980, year__lte=1989)
|
||||||
if decade == 'the 90s':
|
if decade == 'the 90s':
|
||||||
return queryset.filter(year__gte=1990, year__lte=1999)
|
return queryset.filter(year__gte=1990, year__lte=1999)
|
||||||
if decade == 'the 00s':
|
if decade == 'the 00s':
|
||||||
|
@ -45,9 +48,20 @@ class DecadeListFilterWithoutParameter(DecadeListFilter):
|
||||||
|
|
||||||
class DecadeListFilterWithNoneReturningLookups(DecadeListFilterWithTitleAndParameter):
|
class DecadeListFilterWithNoneReturningLookups(DecadeListFilterWithTitleAndParameter):
|
||||||
|
|
||||||
def lookups(self, request):
|
def lookups(self, request, model_admin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class DecadeListFilterWithQuerysetBasedLookups(DecadeListFilterWithTitleAndParameter):
|
||||||
|
|
||||||
|
def lookups(self, request, model_admin):
|
||||||
|
qs = model_admin.queryset(request)
|
||||||
|
if qs.filter(year__gte=1980, year__lte=1989).exists():
|
||||||
|
yield ('the 80s', "the 1980's")
|
||||||
|
if qs.filter(year__gte=1990, year__lte=1999).exists():
|
||||||
|
yield ('the 90s', "the 1990's")
|
||||||
|
if qs.filter(year__gte=2000, year__lte=2009).exists():
|
||||||
|
yield ('the 00s', "the 2000's")
|
||||||
|
|
||||||
class CustomUserAdmin(UserAdmin):
|
class CustomUserAdmin(UserAdmin):
|
||||||
list_filter = ('books_authored', 'books_contributed')
|
list_filter = ('books_authored', 'books_contributed')
|
||||||
|
|
||||||
|
@ -68,6 +82,9 @@ class DecadeFilterBookAdminWithoutParameter(ModelAdmin):
|
||||||
class DecadeFilterBookAdminWithNoneReturningLookups(ModelAdmin):
|
class DecadeFilterBookAdminWithNoneReturningLookups(ModelAdmin):
|
||||||
list_filter = (DecadeListFilterWithNoneReturningLookups,)
|
list_filter = (DecadeListFilterWithNoneReturningLookups,)
|
||||||
|
|
||||||
|
class DecadeFilterBookAdminWithQuerysetBasedLookups(ModelAdmin):
|
||||||
|
list_filter = (DecadeListFilterWithQuerysetBasedLookups,)
|
||||||
|
|
||||||
class ListFiltersTests(TestCase):
|
class ListFiltersTests(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -385,6 +402,23 @@ class ListFiltersTests(TestCase):
|
||||||
self.assertEqual(choices[0]['selected'], True)
|
self.assertEqual(choices[0]['selected'], True)
|
||||||
self.assertEqual(choices[0]['query_string'], '?')
|
self.assertEqual(choices[0]['query_string'], '?')
|
||||||
|
|
||||||
|
# Look for books in the 1980s ----------------------------------------
|
||||||
|
|
||||||
|
request = self.request_factory.get('/', {'publication-decade': 'the 80s'})
|
||||||
|
changelist = self.get_changelist(request, Book, modeladmin)
|
||||||
|
|
||||||
|
# Make sure the correct queryset is returned
|
||||||
|
queryset = changelist.get_query_set(request)
|
||||||
|
self.assertEqual(list(queryset), [])
|
||||||
|
|
||||||
|
# Make sure the correct choice is selected
|
||||||
|
filterspec = changelist.get_filters(request)[0][1]
|
||||||
|
self.assertEqual(force_unicode(filterspec.title), u'publication decade')
|
||||||
|
choices = list(filterspec.choices(changelist))
|
||||||
|
self.assertEqual(choices[1]['display'], u'the 1980\'s')
|
||||||
|
self.assertEqual(choices[1]['selected'], True)
|
||||||
|
self.assertEqual(choices[1]['query_string'], '?publication-decade=the+80s')
|
||||||
|
|
||||||
# Look for books in the 1990s ----------------------------------------
|
# Look for books in the 1990s ----------------------------------------
|
||||||
|
|
||||||
request = self.request_factory.get('/', {'publication-decade': 'the 90s'})
|
request = self.request_factory.get('/', {'publication-decade': 'the 90s'})
|
||||||
|
@ -398,9 +432,9 @@ class ListFiltersTests(TestCase):
|
||||||
filterspec = changelist.get_filters(request)[0][1]
|
filterspec = changelist.get_filters(request)[0][1]
|
||||||
self.assertEqual(force_unicode(filterspec.title), u'publication decade')
|
self.assertEqual(force_unicode(filterspec.title), u'publication decade')
|
||||||
choices = list(filterspec.choices(changelist))
|
choices = list(filterspec.choices(changelist))
|
||||||
self.assertEqual(choices[1]['display'], u'the 1990\'s')
|
self.assertEqual(choices[2]['display'], u'the 1990\'s')
|
||||||
self.assertEqual(choices[1]['selected'], True)
|
self.assertEqual(choices[2]['selected'], True)
|
||||||
self.assertEqual(choices[1]['query_string'], '?publication-decade=the+90s')
|
self.assertEqual(choices[2]['query_string'], '?publication-decade=the+90s')
|
||||||
|
|
||||||
# Look for books in the 2000s ----------------------------------------
|
# Look for books in the 2000s ----------------------------------------
|
||||||
|
|
||||||
|
@ -415,9 +449,9 @@ class ListFiltersTests(TestCase):
|
||||||
filterspec = changelist.get_filters(request)[0][1]
|
filterspec = changelist.get_filters(request)[0][1]
|
||||||
self.assertEqual(force_unicode(filterspec.title), u'publication decade')
|
self.assertEqual(force_unicode(filterspec.title), u'publication decade')
|
||||||
choices = list(filterspec.choices(changelist))
|
choices = list(filterspec.choices(changelist))
|
||||||
self.assertEqual(choices[2]['display'], u'the 2000\'s')
|
self.assertEqual(choices[3]['display'], u'the 2000\'s')
|
||||||
self.assertEqual(choices[2]['selected'], True)
|
self.assertEqual(choices[3]['selected'], True)
|
||||||
self.assertEqual(choices[2]['query_string'], '?publication-decade=the+00s')
|
self.assertEqual(choices[3]['query_string'], '?publication-decade=the+00s')
|
||||||
|
|
||||||
# Combine multiple filters -------------------------------------------
|
# Combine multiple filters -------------------------------------------
|
||||||
|
|
||||||
|
@ -432,9 +466,9 @@ class ListFiltersTests(TestCase):
|
||||||
filterspec = changelist.get_filters(request)[0][1]
|
filterspec = changelist.get_filters(request)[0][1]
|
||||||
self.assertEqual(force_unicode(filterspec.title), u'publication decade')
|
self.assertEqual(force_unicode(filterspec.title), u'publication decade')
|
||||||
choices = list(filterspec.choices(changelist))
|
choices = list(filterspec.choices(changelist))
|
||||||
self.assertEqual(choices[2]['display'], u'the 2000\'s')
|
self.assertEqual(choices[3]['display'], u'the 2000\'s')
|
||||||
self.assertEqual(choices[2]['selected'], True)
|
self.assertEqual(choices[3]['selected'], True)
|
||||||
self.assertEqual(choices[2]['query_string'], '?publication-decade=the+00s&author__id__exact=%s' % self.alfred.pk)
|
self.assertEqual(choices[3]['query_string'], '?publication-decade=the+00s&author__id__exact=%s' % self.alfred.pk)
|
||||||
|
|
||||||
filterspec = changelist.get_filters(request)[0][0]
|
filterspec = changelist.get_filters(request)[0][0]
|
||||||
self.assertEqual(force_unicode(filterspec.title), u'author')
|
self.assertEqual(force_unicode(filterspec.title), u'author')
|
||||||
|
@ -472,3 +506,25 @@ class ListFiltersTests(TestCase):
|
||||||
changelist = self.get_changelist(request, Book, modeladmin)
|
changelist = self.get_changelist(request, Book, modeladmin)
|
||||||
filterspec = changelist.get_filters(request)[0]
|
filterspec = changelist.get_filters(request)[0]
|
||||||
self.assertEqual(len(filterspec), 0)
|
self.assertEqual(len(filterspec), 0)
|
||||||
|
|
||||||
|
def test_simplelistfilter_with_queryset_based_lookups(self):
|
||||||
|
modeladmin = DecadeFilterBookAdminWithQuerysetBasedLookups(Book, site)
|
||||||
|
request = self.request_factory.get('/', {})
|
||||||
|
changelist = self.get_changelist(request, Book, modeladmin)
|
||||||
|
|
||||||
|
filterspec = changelist.get_filters(request)[0][0]
|
||||||
|
self.assertEqual(force_unicode(filterspec.title), u'publication decade')
|
||||||
|
choices = list(filterspec.choices(changelist))
|
||||||
|
self.assertEqual(len(choices), 3)
|
||||||
|
|
||||||
|
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'the 1990\'s')
|
||||||
|
self.assertEqual(choices[1]['selected'], False)
|
||||||
|
self.assertEqual(choices[1]['query_string'], '?publication-decade=the+90s')
|
||||||
|
|
||||||
|
self.assertEqual(choices[2]['display'], u'the 2000\'s')
|
||||||
|
self.assertEqual(choices[2]['selected'], False)
|
||||||
|
self.assertEqual(choices[2]['query_string'], '?publication-decade=the+00s')
|
||||||
|
|
Loading…
Reference in New Issue