Fixed #19182 -- Fixed ModelAdmin.lookup_allowed to work with ('fieldname', SimpleListFilter) syntax.
Thanks gauss for the report.
This commit is contained in:
parent
62dfd79f8b
commit
c4db7f075e
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue