Fixed #19182 -- Fixed ModelAdmin.lookup_allowed to work with ('fieldname', SimpleListFilter) syntax.

Thanks gauss for the report.
This commit is contained in:
Anentropic 2013-09-26 19:25:30 +01:00 committed by Tim Graham
parent 62dfd79f8b
commit c4db7f075e
2 changed files with 82 additions and 2 deletions

View File

@ -319,6 +319,8 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
return qs return qs
def lookup_allowed(self, lookup, value): def lookup_allowed(self, lookup, value):
from django.contrib.admin.filters import SimpleListFilter
model = self.model model = self.model
# Check FKey lookups that are allowed, so that popups produced by # Check FKey lookups that are allowed, so that popups produced by
# ForeignKeyRawIdWidget, on the basis of ForeignKey.limit_choices_to, # ForeignKeyRawIdWidget, on the basis of ForeignKey.limit_choices_to,
@ -365,7 +367,15 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
if len(parts) == 1: if len(parts) == 1:
return True return True
clean_lookup = LOOKUP_SEP.join(parts) 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): def has_add_permission(self, request):
""" """

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
import datetime import datetime
from django.contrib.admin import (site, ModelAdmin, SimpleListFilter, from django.contrib.admin import (site, ModelAdmin, SimpleListFilter,
BooleanFieldListFilter) BooleanFieldListFilter, AllValuesFieldListFilter)
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
@ -38,26 +38,32 @@ class DecadeListFilter(SimpleListFilter):
if decade == 'the 00s': if decade == 'the 00s':
return queryset.filter(year__gte=2000, year__lte=2009) return queryset.filter(year__gte=2000, year__lte=2009)
class DecadeListFilterWithTitleAndParameter(DecadeListFilter): class DecadeListFilterWithTitleAndParameter(DecadeListFilter):
title = 'publication decade' title = 'publication decade'
parameter_name = 'publication-decade' parameter_name = 'publication-decade'
class DecadeListFilterWithoutTitle(DecadeListFilter): class DecadeListFilterWithoutTitle(DecadeListFilter):
parameter_name = 'publication-decade' parameter_name = 'publication-decade'
class DecadeListFilterWithoutParameter(DecadeListFilter): class DecadeListFilterWithoutParameter(DecadeListFilter):
title = 'publication decade' title = 'publication decade'
class DecadeListFilterWithNoneReturningLookups(DecadeListFilterWithTitleAndParameter): class DecadeListFilterWithNoneReturningLookups(DecadeListFilterWithTitleAndParameter):
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
pass pass
class DecadeListFilterWithFailingQueryset(DecadeListFilterWithTitleAndParameter): class DecadeListFilterWithFailingQueryset(DecadeListFilterWithTitleAndParameter):
def queryset(self, request, queryset): def queryset(self, request, queryset):
raise 1/0 raise 1/0
class DecadeListFilterWithQuerysetBasedLookups(DecadeListFilterWithTitleAndParameter): class DecadeListFilterWithQuerysetBasedLookups(DecadeListFilterWithTitleAndParameter):
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
@ -69,10 +75,12 @@ class DecadeListFilterWithQuerysetBasedLookups(DecadeListFilterWithTitleAndParam
if qs.filter(year__gte=2000, year__lte=2009).exists(): if qs.filter(year__gte=2000, year__lte=2009).exists():
yield ('the 00s', "the 2000's") yield ('the 00s', "the 2000's")
class DecadeListFilterParameterEndsWith__In(DecadeListFilter): class DecadeListFilterParameterEndsWith__In(DecadeListFilter):
title = 'publication decade' title = 'publication decade'
parameter_name = 'decade__in' # Ends with '__in" parameter_name = 'decade__in' # Ends with '__in"
class DecadeListFilterParameterEndsWith__Isnull(DecadeListFilter): class DecadeListFilterParameterEndsWith__Isnull(DecadeListFilter):
title = 'publication decade' title = 'publication decade'
parameter_name = 'decade__isnull' # Ends with '__isnull" parameter_name = 'decade__isnull' # Ends with '__isnull"
@ -93,41 +101,61 @@ class DepartmentListFilterLookupWithNonStringValue(SimpleListFilter):
if self.value(): if self.value():
return queryset.filter(department__id=self.value()) return queryset.filter(department__id=self.value())
class DepartmentListFilterLookupWithUnderscoredParameter(DepartmentListFilterLookupWithNonStringValue):
parameter_name = 'department__whatever'
class CustomUserAdmin(UserAdmin): class CustomUserAdmin(UserAdmin):
list_filter = ('books_authored', 'books_contributed') list_filter = ('books_authored', 'books_contributed')
class BookAdmin(ModelAdmin): class BookAdmin(ModelAdmin):
list_filter = ('year', 'author', 'contributors', 'is_best_seller', 'date_registered', 'no') list_filter = ('year', 'author', 'contributors', 'is_best_seller', 'date_registered', 'no')
ordering = ('-id',) ordering = ('-id',)
class BookAdminWithTupleBooleanFilter(BookAdmin): class BookAdminWithTupleBooleanFilter(BookAdmin):
list_filter = ('year', 'author', 'contributors', ('is_best_seller', BooleanFieldListFilter), 'date_registered', 'no') 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): class DecadeFilterBookAdmin(ModelAdmin):
list_filter = ('author', DecadeListFilterWithTitleAndParameter) list_filter = ('author', DecadeListFilterWithTitleAndParameter)
ordering = ('-id',) ordering = ('-id',)
class DecadeFilterBookAdminWithoutTitle(ModelAdmin): class DecadeFilterBookAdminWithoutTitle(ModelAdmin):
list_filter = (DecadeListFilterWithoutTitle,) list_filter = (DecadeListFilterWithoutTitle,)
class DecadeFilterBookAdminWithoutParameter(ModelAdmin): class DecadeFilterBookAdminWithoutParameter(ModelAdmin):
list_filter = (DecadeListFilterWithoutParameter,) list_filter = (DecadeListFilterWithoutParameter,)
class DecadeFilterBookAdminWithNoneReturningLookups(ModelAdmin): class DecadeFilterBookAdminWithNoneReturningLookups(ModelAdmin):
list_filter = (DecadeListFilterWithNoneReturningLookups,) list_filter = (DecadeListFilterWithNoneReturningLookups,)
class DecadeFilterBookAdminWithFailingQueryset(ModelAdmin): class DecadeFilterBookAdminWithFailingQueryset(ModelAdmin):
list_filter = (DecadeListFilterWithFailingQueryset,) list_filter = (DecadeListFilterWithFailingQueryset,)
class DecadeFilterBookAdminWithQuerysetBasedLookups(ModelAdmin): class DecadeFilterBookAdminWithQuerysetBasedLookups(ModelAdmin):
list_filter = (DecadeListFilterWithQuerysetBasedLookups,) list_filter = (DecadeListFilterWithQuerysetBasedLookups,)
class DecadeFilterBookAdminParameterEndsWith__In(ModelAdmin): class DecadeFilterBookAdminParameterEndsWith__In(ModelAdmin):
list_filter = (DecadeListFilterParameterEndsWith__In,) list_filter = (DecadeListFilterParameterEndsWith__In,)
class DecadeFilterBookAdminParameterEndsWith__Isnull(ModelAdmin): class DecadeFilterBookAdminParameterEndsWith__Isnull(ModelAdmin):
list_filter = (DecadeListFilterParameterEndsWith__Isnull,) list_filter = (DecadeListFilterParameterEndsWith__Isnull,)
class EmployeeAdmin(ModelAdmin): class EmployeeAdmin(ModelAdmin):
list_display = ['name', 'department'] list_display = ['name', 'department']
list_filter = ['department'] list_filter = ['department']
@ -137,6 +165,10 @@ class DepartmentFilterEmployeeAdmin(EmployeeAdmin):
list_filter = [DepartmentListFilterLookupWithNonStringValue, ] list_filter = [DepartmentListFilterLookupWithNonStringValue, ]
class DepartmentFilterUnderscoredEmployeeAdmin(EmployeeAdmin):
list_filter = [DepartmentListFilterLookupWithUnderscoredParameter, ]
class ListFiltersTests(TestCase): class ListFiltersTests(TestCase):
def setUp(self): def setUp(self):
@ -453,6 +485,23 @@ class ListFiltersTests(TestCase):
self.assertEqual(choice['selected'], True) self.assertEqual(choice['selected'], True)
self.assertEqual(choice['query_string'], '?is_best_seller__isnull=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): def test_simplelistfilter(self):
modeladmin = DecadeFilterBookAdmin(Book, site) modeladmin = DecadeFilterBookAdmin(Book, site)
@ -692,6 +741,27 @@ class ListFiltersTests(TestCase):
self.assertEqual(choices[1]['selected'], True) self.assertEqual(choices[1]['selected'], True)
self.assertEqual(choices[1]['query_string'], '?department=%s' % self.john.pk) 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): def test_fk_with_to_field(self):
""" """
Ensure that a filter on a FK respects the FK's to_field attribute. Ensure that a filter on a FK respects the FK's to_field attribute.