From 4dbeb4bca4638ff851a2f4844d262dbe1652f7b5 Mon Sep 17 00:00:00 2001
From: Jannis Leidel <jannis@leidel.info>
Date: Thu, 9 Feb 2012 18:59:05 +0000
Subject: [PATCH] Fixed #17515 -- Added ability to override the template of
 custom admin FilterSpec classes. Thanks, saxix and Julien Phalip.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17483 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
 django/contrib/admin/filters.py                    |  1 +
 django/contrib/admin/templatetags/admin_list.py    | 12 +++++++++---
 docs/ref/contrib/admin/index.txt                   | 14 ++++++++++++--
 tests/regressiontests/admin_views/admin.py         | 10 +++++++++-
 tests/regressiontests/admin_views/models.py        |  4 ++++
 .../templates/custom_filter_template.html          |  7 +++++++
 tests/regressiontests/admin_views/tests.py         | 13 +++++++++++++
 7 files changed, 55 insertions(+), 6 deletions(-)
 create mode 100644 tests/regressiontests/admin_views/templates/custom_filter_template.html

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 @@
+<h3>By {{ filter_title }} (custom)</h3>
+<ul>
+{% for choice in choices %}
+    <li{% if choice.selected %} class="selected"{% endif %}>
+    <a href="{{ choice.query_string|iriencode }}">{{ choice.display }}</a></li>
+{% endfor %}
+</ul>
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"