Skip to content

Commit

Permalink
Fixed #15960 -- Extended list filer API added in r16144 slightly to p…
Browse files Browse the repository at this point in the history
…ass 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
  • Loading branch information
jezdez committed May 4, 2011
1 parent f446486 commit 95dc7c7
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 27 deletions.
6 changes: 3 additions & 3 deletions django/contrib/admin/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ def __init__(self, request, params, model, model_admin):
raise ImproperlyConfigured(
"The list filter '%s' does not specify "
"a 'parameter_name'." % self.__class__.__name__)
lookup_choices = self.lookups(request)
lookup_choices = self.lookups(request, model_admin)
if lookup_choices is None:
lookup_choices = ()
self.lookup_choices = lookup_choices
self.lookup_choices = list(lookup_choices)

def has_output(self):
return len(self.lookup_choices) > 0
Expand All @@ -78,7 +78,7 @@ def value(self):
"""
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)
"""
Expand Down
45 changes: 32 additions & 13 deletions docs/ref/contrib/admin/index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -555,9 +555,7 @@ subclass::
attributes to and override the ``lookups`` and ``queryset`` methods,
e.g.::

from django.db.models import Q
from django.utils.translation import ugettext_lazy as _

from django.contrib.admin import SimpleListFilter

class DecadeBornListFilter(SimpleListFilter):
Expand All @@ -568,7 +566,7 @@ subclass::
# Parameter for the filter that will be used in the URL query.
parameter_name = 'decade'

def lookups(self, request):
def lookups(self, request, model_admin):
"""
Returns a list of tuples. The first element in each
tuple is the coded value for the option that will
Expand All @@ -577,42 +575,63 @@ subclass::
in the right sidebar.
"""
return (
('80s', 'in the eighties'),
('other', 'other'),
('80s', _('in the eighties')),
('90s', _('in the nineties')),
)

def queryset(self, request, queryset):
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
``value()``.
`self.value()`.
"""
# Compare the requested value (either '80s' or 'other')
# to decide how to filter the queryset.
if self.value() == '80s':
return queryset.filter(birthday__year__gte=1980,
birthday__year__lte=1989)
if self.value() == 'other':
return queryset.filter(Q(year__lte=1979) |
Q(year__gte=1990))
if self.value() == '90s':
return queryset.filter(birthday__year__gte=1990,
birthday__year__lte=1999)

class PersonAdmin(ModelAdmin):
list_filter = (DecadeBornListFilter,)

.. note::

As a convenience, the ``HttpRequest`` object is passed to the
filter's methods, for example::
``lookups`` and ``queryset`` methods, for example::

class AuthDecadeBornListFilter(DecadeBornListFilter):

def lookups(self, request):
def lookups(self, request, model_admin):
if request.user.is_superuser:
return super(AuthDecadeBornListFilter, self).lookups(request)
return super(AuthDecadeBornListFilter,
self).lookups(request, model_admin)

def queryset(self, request, queryset):
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
element is a class inheriting from
Expand Down
78 changes: 67 additions & 11 deletions tests/regressiontests/admin_filters/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ def select_by(dictlist, key, value):

class DecadeListFilter(SimpleListFilter):

def lookups(self, request):
def lookups(self, request, model_admin):
return (
('the 80s', "the 1980's"),
('the 90s', "the 1990's"),
('the 00s', "the 2000's"),
('other', "other decades"),
)

def queryset(self, request, queryset):
decade = self.value()
if decade == 'the 80s':
return queryset.filter(year__gte=1980, year__lte=1989)
if decade == 'the 90s':
return queryset.filter(year__gte=1990, year__lte=1999)
if decade == 'the 00s':
Expand All @@ -45,9 +48,20 @@ class DecadeListFilterWithoutParameter(DecadeListFilter):

class DecadeListFilterWithNoneReturningLookups(DecadeListFilterWithTitleAndParameter):

def lookups(self, request):
def lookups(self, request, model_admin):
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):
list_filter = ('books_authored', 'books_contributed')

Expand All @@ -68,6 +82,9 @@ class DecadeFilterBookAdminWithoutParameter(ModelAdmin):
class DecadeFilterBookAdminWithNoneReturningLookups(ModelAdmin):
list_filter = (DecadeListFilterWithNoneReturningLookups,)

class DecadeFilterBookAdminWithQuerysetBasedLookups(ModelAdmin):
list_filter = (DecadeListFilterWithQuerysetBasedLookups,)

class ListFiltersTests(TestCase):

def setUp(self):
Expand Down Expand Up @@ -385,6 +402,23 @@ def test_simplelistfilter(self):
self.assertEqual(choices[0]['selected'], True)
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 ----------------------------------------

request = self.request_factory.get('/', {'publication-decade': 'the 90s'})
Expand All @@ -398,9 +432,9 @@ def test_simplelistfilter(self):
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 1990\'s')
self.assertEqual(choices[1]['selected'], True)
self.assertEqual(choices[1]['query_string'], '?publication-decade=the+90s')
self.assertEqual(choices[2]['display'], u'the 1990\'s')
self.assertEqual(choices[2]['selected'], True)
self.assertEqual(choices[2]['query_string'], '?publication-decade=the+90s')

# Look for books in the 2000s ----------------------------------------

Expand All @@ -415,9 +449,9 @@ def test_simplelistfilter(self):
filterspec = changelist.get_filters(request)[0][1]
self.assertEqual(force_unicode(filterspec.title), u'publication decade')
choices = list(filterspec.choices(changelist))
self.assertEqual(choices[2]['display'], u'the 2000\'s')
self.assertEqual(choices[2]['selected'], True)
self.assertEqual(choices[2]['query_string'], '?publication-decade=the+00s')
self.assertEqual(choices[3]['display'], u'the 2000\'s')
self.assertEqual(choices[3]['selected'], True)
self.assertEqual(choices[3]['query_string'], '?publication-decade=the+00s')

# Combine multiple filters -------------------------------------------

Expand All @@ -432,9 +466,9 @@ def test_simplelistfilter(self):
filterspec = changelist.get_filters(request)[0][1]
self.assertEqual(force_unicode(filterspec.title), u'publication decade')
choices = list(filterspec.choices(changelist))
self.assertEqual(choices[2]['display'], u'the 2000\'s')
self.assertEqual(choices[2]['selected'], True)
self.assertEqual(choices[2]['query_string'], '?publication-decade=the+00s&author__id__exact=%s' % self.alfred.pk)
self.assertEqual(choices[3]['display'], u'the 2000\'s')
self.assertEqual(choices[3]['selected'], True)
self.assertEqual(choices[3]['query_string'], '?publication-decade=the+00s&author__id__exact=%s' % self.alfred.pk)

filterspec = changelist.get_filters(request)[0][0]
self.assertEqual(force_unicode(filterspec.title), u'author')
Expand Down Expand Up @@ -472,3 +506,25 @@ def test_simplelistfilter_with_none_returning_lookups(self):
changelist = self.get_changelist(request, Book, modeladmin)
filterspec = changelist.get_filters(request)[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')

0 comments on commit 95dc7c7

Please sign in to comment.