diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index b2d74ce671..550683bdd9 100644 --- a/django/contrib/admin/filters.py +++ b/django/contrib/admin/filters.py @@ -17,6 +17,7 @@ from django.contrib.admin.util import (get_model_from_relation, class ListFilter(object): title = None # Human-readable title to appear in the right sidebar. + template = 'admin/filter.html' def __init__(self, request, params, model, model_admin): # This dictionary will eventually contain the request's query string diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index ba3d1329ad..e778429e90 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -13,7 +13,8 @@ from django.utils.text import capfirst from django.utils.translation import ugettext as _ from django.utils.encoding import smart_unicode, force_unicode from django.template import Library - +from django.template.loader import get_template +from django.template.context import Context register = Library() @@ -360,9 +361,14 @@ def search_form(cl): 'search_var': SEARCH_VAR } -@register.inclusion_tag('admin/filter.html') +@register.simple_tag def admin_list_filter(cl, spec): - return {'title': spec.title, 'choices' : list(spec.choices(cl))} + tpl = get_template(spec.template) + return tpl.render(Context({ + 'title': spec.title, + 'choices' : list(spec.choices(cl)), + 'spec': spec, + })) @register.inclusion_tag('admin/actions.html', takes_context=True) def admin_actions(context): diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 76ea7804ab..c40ce89204 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -697,8 +697,18 @@ subclass:: .. note:: - The ``FieldListFilter`` API is currently considered internal - and prone to refactoring. + The ``FieldListFilter`` API is currently considered internal and prone + to refactoring. + + .. versionadded:: 1.4 + + It is possible to specify a custom template for rendering a list filter:: + + class FilterWithCustomTemplate(SimpleListFilter): + template = "custom_template.html" + + See the default template provided by django (``admin/filter.html``) for + a concrete example. .. attribute:: ModelAdmin.list_max_show_all diff --git a/tests/regressiontests/admin_views/admin.py b/tests/regressiontests/admin_views/admin.py index e541a4265e..62228663fe 100644 --- a/tests/regressiontests/admin_views/admin.py +++ b/tests/regressiontests/admin_views/admin.py @@ -13,6 +13,7 @@ from django.conf.urls import patterns, url from django.db import models from django.forms.models import BaseModelFormSet from django.http import HttpResponse +from django.contrib.admin import BooleanFieldListFilter from .models import (Article, Chapter, Account, Media, Child, Parent, Picture, Widget, DooHickey, Grommet, Whatsit, FancyDoodad, Category, Link, @@ -25,7 +26,7 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture, CoverLetter, Story, OtherStory, Book, Promo, ChapterXtra1, Pizza, Topping, Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug, AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, - AdminOrderedCallable, Report) + AdminOrderedCallable, Report, Color2) def callable_year(dt_value): @@ -525,6 +526,12 @@ class ReportAdmin(admin.ModelAdmin): ) +class CustomTemplateBooleanFieldListFilter(BooleanFieldListFilter): + template = 'custom_filter_template.html' + +class CustomTemplateFilterColorAdmin(admin.ModelAdmin): + list_filter = (('warm', CustomTemplateBooleanFieldListFilter),) + site = admin.AdminSite(name="admin") site.register(Article, ArticleAdmin) site.register(CustomArticle, CustomArticleAdmin) @@ -594,6 +601,7 @@ site.register(AdminOrderedField, AdminOrderedFieldAdmin) site.register(AdminOrderedModelMethod, AdminOrderedModelMethodAdmin) site.register(AdminOrderedAdminMethod, AdminOrderedAdminMethodAdmin) site.register(AdminOrderedCallable, AdminOrderedCallableAdmin) +site.register(Color2, CustomTemplateFilterColorAdmin) # Register core models we need in our tests from django.contrib.auth.models import User, Group diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index b3426b4ec3..7d58952b87 100644 --- a/tests/regressiontests/admin_views/models.py +++ b/tests/regressiontests/admin_views/models.py @@ -105,6 +105,10 @@ class Color(models.Model): def __unicode__(self): return self.value +# we replicate Color to register with another ModelAdmin +class Color2(Color): + class Meta: + proxy = True class Thing(models.Model): title = models.CharField(max_length=20) diff --git a/tests/regressiontests/admin_views/templates/custom_filter_template.html b/tests/regressiontests/admin_views/templates/custom_filter_template.html new file mode 100644 index 0000000000..e5c9a8e7d8 --- /dev/null +++ b/tests/regressiontests/admin_views/templates/custom_filter_template.html @@ -0,0 +1,7 @@ +

By {{ filter_title }} (custom)

+ diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index fc46f7e8ae..fa189203a2 100755 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -1,6 +1,7 @@ # coding: utf-8 from __future__ import with_statement, absolute_import +import os import re import datetime import urlparse @@ -593,6 +594,18 @@ class AdminViewFormUrlTest(TestCase): self.assertTrue('form_url' in response.context, msg='form_url not present in response.context') self.assertEqual(response.context['form_url'], 'pony') + def test_filter_with_custom_template(self): + """ + Ensure that one can use a custom template to render an admin filter. + Refs #17515. + """ + template_dirs = settings.TEMPLATE_DIRS + ( + os.path.join(os.path.dirname(__file__), 'templates'),) + with self.settings(TEMPLATE_DIRS=template_dirs): + response = self.client.get("/test_admin/admin/admin_views/color2/") + self.assertTrue('custom_filter_template.html' in [t.name for t in response.templates]) + + class AdminJavaScriptTest(AdminViewBasicTest): urls = "regressiontests.admin_views.urls"