From c4db7f075e269411287ff14b5518412250ba455f Mon Sep 17 00:00:00 2001 From: Anentropic Date: Thu, 26 Sep 2013 19:25:30 +0100 Subject: [PATCH] Fixed #19182 -- Fixed ModelAdmin.lookup_allowed to work with ('fieldname', SimpleListFilter) syntax. Thanks gauss for the report. --- django/contrib/admin/options.py | 12 +++++- tests/admin_filters/tests.py | 72 ++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 3b570c1a5f..3b02ac020c 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -319,6 +319,8 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)): return qs def lookup_allowed(self, lookup, value): + from django.contrib.admin.filters import SimpleListFilter + model = self.model # Check FKey lookups that are allowed, so that popups produced by # ForeignKeyRawIdWidget, on the basis of ForeignKey.limit_choices_to, @@ -365,7 +367,15 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)): if len(parts) == 1: return True clean_lookup = LOOKUP_SEP.join(parts) - return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy + valid_lookups = [self.date_hierarchy] + for filter_item in self.list_filter: + if isinstance(filter_item, type) and issubclass(filter_item, SimpleListFilter): + valid_lookups.append(filter_item.parameter_name) + elif isinstance(filter_item, (list, tuple)): + valid_lookups.append(filter_item[0]) + else: + valid_lookups.append(filter_item) + return clean_lookup in valid_lookups def has_add_permission(self, request): """ diff --git a/tests/admin_filters/tests.py b/tests/admin_filters/tests.py index 99b21bd266..bdfacdc206 100644 --- a/tests/admin_filters/tests.py +++ b/tests/admin_filters/tests.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import datetime from django.contrib.admin import (site, ModelAdmin, SimpleListFilter, - BooleanFieldListFilter) + BooleanFieldListFilter, AllValuesFieldListFilter) from django.contrib.admin.views.main import ChangeList from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User @@ -38,26 +38,32 @@ class DecadeListFilter(SimpleListFilter): if decade == 'the 00s': return queryset.filter(year__gte=2000, year__lte=2009) + class DecadeListFilterWithTitleAndParameter(DecadeListFilter): title = 'publication decade' parameter_name = 'publication-decade' + class DecadeListFilterWithoutTitle(DecadeListFilter): parameter_name = 'publication-decade' + class DecadeListFilterWithoutParameter(DecadeListFilter): title = 'publication decade' + class DecadeListFilterWithNoneReturningLookups(DecadeListFilterWithTitleAndParameter): def lookups(self, request, model_admin): pass + class DecadeListFilterWithFailingQueryset(DecadeListFilterWithTitleAndParameter): def queryset(self, request, queryset): raise 1/0 + class DecadeListFilterWithQuerysetBasedLookups(DecadeListFilterWithTitleAndParameter): def lookups(self, request, model_admin): @@ -69,10 +75,12 @@ class DecadeListFilterWithQuerysetBasedLookups(DecadeListFilterWithTitleAndParam if qs.filter(year__gte=2000, year__lte=2009).exists(): yield ('the 00s', "the 2000's") + class DecadeListFilterParameterEndsWith__In(DecadeListFilter): title = 'publication decade' parameter_name = 'decade__in' # Ends with '__in" + class DecadeListFilterParameterEndsWith__Isnull(DecadeListFilter): title = 'publication decade' parameter_name = 'decade__isnull' # Ends with '__isnull" @@ -93,41 +101,61 @@ class DepartmentListFilterLookupWithNonStringValue(SimpleListFilter): if self.value(): return queryset.filter(department__id=self.value()) + +class DepartmentListFilterLookupWithUnderscoredParameter(DepartmentListFilterLookupWithNonStringValue): + parameter_name = 'department__whatever' + + class CustomUserAdmin(UserAdmin): list_filter = ('books_authored', 'books_contributed') + class BookAdmin(ModelAdmin): list_filter = ('year', 'author', 'contributors', 'is_best_seller', 'date_registered', 'no') ordering = ('-id',) + class BookAdminWithTupleBooleanFilter(BookAdmin): list_filter = ('year', 'author', 'contributors', ('is_best_seller', BooleanFieldListFilter), 'date_registered', 'no') + +class BookAdminWithUnderscoreLookupAndTuple(BookAdmin): + list_filter = ('year', ('author__email', AllValuesFieldListFilter), 'contributors', 'is_best_seller', 'date_registered', 'no') + + class DecadeFilterBookAdmin(ModelAdmin): list_filter = ('author', DecadeListFilterWithTitleAndParameter) ordering = ('-id',) + class DecadeFilterBookAdminWithoutTitle(ModelAdmin): list_filter = (DecadeListFilterWithoutTitle,) + class DecadeFilterBookAdminWithoutParameter(ModelAdmin): list_filter = (DecadeListFilterWithoutParameter,) + class DecadeFilterBookAdminWithNoneReturningLookups(ModelAdmin): list_filter = (DecadeListFilterWithNoneReturningLookups,) + class DecadeFilterBookAdminWithFailingQueryset(ModelAdmin): list_filter = (DecadeListFilterWithFailingQueryset,) + class DecadeFilterBookAdminWithQuerysetBasedLookups(ModelAdmin): list_filter = (DecadeListFilterWithQuerysetBasedLookups,) + class DecadeFilterBookAdminParameterEndsWith__In(ModelAdmin): list_filter = (DecadeListFilterParameterEndsWith__In,) + class DecadeFilterBookAdminParameterEndsWith__Isnull(ModelAdmin): list_filter = (DecadeListFilterParameterEndsWith__Isnull,) + class EmployeeAdmin(ModelAdmin): list_display = ['name', 'department'] list_filter = ['department'] @@ -137,6 +165,10 @@ class DepartmentFilterEmployeeAdmin(EmployeeAdmin): list_filter = [DepartmentListFilterLookupWithNonStringValue, ] +class DepartmentFilterUnderscoredEmployeeAdmin(EmployeeAdmin): + list_filter = [DepartmentListFilterLookupWithUnderscoredParameter, ] + + class ListFiltersTests(TestCase): def setUp(self): @@ -453,6 +485,23 @@ class ListFiltersTests(TestCase): self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?is_best_seller__isnull=True') + def test_fieldlistfilter_underscorelookup_tuple(self): + """ + Ensure ('fieldpath', ClassName ) lookups pass lookup_allowed checks + when fieldpath contains double underscore in value. + Refs #19182 + """ + modeladmin = BookAdminWithUnderscoreLookupAndTuple(Book, site) + request = self.request_factory.get('/') + changelist = self.get_changelist(request, Book, modeladmin) + + request = self.request_factory.get('/', {'author__email': 'alfred@example.com'}) + changelist = self.get_changelist(request, Book, modeladmin) + + # Make sure the correct queryset is returned + queryset = changelist.get_queryset(request) + self.assertEqual(list(queryset), [self.bio_book, self.djangonaut_book]) + def test_simplelistfilter(self): modeladmin = DecadeFilterBookAdmin(Book, site) @@ -692,6 +741,27 @@ class ListFiltersTests(TestCase): self.assertEqual(choices[1]['selected'], True) self.assertEqual(choices[1]['query_string'], '?department=%s' % self.john.pk) + def test_lookup_with_non_string_value_underscored(self): + """ + Ensure SimpleListFilter lookups pass lookup_allowed checks when + parameter_name attribute contains double-underscore value. + Refs #19182 + """ + modeladmin = DepartmentFilterUnderscoredEmployeeAdmin(Employee, site) + request = self.request_factory.get('/', {'department__whatever': self.john.pk}) + changelist = self.get_changelist(request, Employee, modeladmin) + + queryset = changelist.get_queryset(request) + + self.assertEqual(list(queryset), [self.john]) + + filterspec = changelist.get_filters(request)[0][-1] + self.assertEqual(force_text(filterspec.title), 'department') + choices = list(filterspec.choices(changelist)) + self.assertEqual(choices[1]['display'], 'DEV') + self.assertEqual(choices[1]['selected'], True) + self.assertEqual(choices[1]['query_string'], '?department__whatever=%s' % self.john.pk) + def test_fk_with_to_field(self): """ Ensure that a filter on a FK respects the FK's to_field attribute.