diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index 36dcea1ea7..c1fc9790c0 100644 --- a/django/contrib/admin/filters.py +++ b/django/contrib/admin/filters.py @@ -63,10 +63,10 @@ class SimpleListFilter(ListFilter): 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 @@ -78,7 +78,7 @@ class SimpleListFilter(ListFilter): """ 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) """ diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index e8bc59724d..fb48ba8119 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -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): @@ -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 @@ -577,24 +575,24 @@ 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,) @@ -602,17 +600,38 @@ subclass:: .. 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 diff --git a/tests/regressiontests/admin_filters/tests.py b/tests/regressiontests/admin_filters/tests.py index 6b32475a64..7717984014 100644 --- a/tests/regressiontests/admin_filters/tests.py +++ b/tests/regressiontests/admin_filters/tests.py @@ -19,8 +19,9 @@ 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"), @@ -28,6 +29,8 @@ class DecadeListFilter(SimpleListFilter): 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': @@ -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') @@ -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): @@ -385,6 +402,23 @@ class ListFiltersTests(TestCase): 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'}) @@ -398,9 +432,9 @@ class ListFiltersTests(TestCase): 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 ---------------------------------------- @@ -415,9 +449,9 @@ class ListFiltersTests(TestCase): 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 ------------------------------------------- @@ -432,9 +466,9 @@ class ListFiltersTests(TestCase): 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') @@ -472,3 +506,25 @@ class ListFiltersTests(TestCase): 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')