From 2888dd42ebafd09fc856dfbcc084bc909466936e Mon Sep 17 00:00:00 2001
From: Tay Ray Chuan <rctay89@gmail.com>
Date: Sat, 13 Mar 2010 15:15:42 +0800
Subject: [PATCH] contrib.admin: allow overriding of actions.html template at app level
- make changelist_view() set the context variable actions_template,
checking the usual template path
- make {% admin_actions %} get and render the actions template manually
using the path set in the context variable actions_template
- update documentation to say that actions.html can be overriden at the
app level via the ModelAdmin attribute, actions_template
- add regression tests in tests/regressiontests/admin_actions
---
django/contrib/admin/options.py | 21 ++++++++++
django/contrib/admin/templatetags/admin_list.py | 27 +++++++++----
docs/ref/contrib/admin/index.txt | 11 +++++
.../admin_actions/fixtures/admin-views-users.xml | 17 ++++++++
tests/regressiontests/admin_actions/models.py | 19 +++++++++
tests/regressiontests/admin_actions/tests.py | 40 ++++++++++++++++++++
tests/templates/admin/admin_actions/actions.html | 3 +
.../templates/admin/admin_actions/cat/actions.html | 3 +
8 files changed, 132 insertions(+), 9 deletions(-)
create mode 100644 tests/regressiontests/admin_actions/__init__.py
create mode 100644 tests/regressiontests/admin_actions/fixtures/admin-views-users.xml
create mode 100644 tests/regressiontests/admin_actions/models.py
create mode 100644 tests/regressiontests/admin_actions/tests.py
create mode 100644 tests/templates/admin/admin_actions/actions.html
create mode 100644 tests/templates/admin/admin_actions/cat/actions.html
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 1f8ff6d..6a17b46 100644
a
|
b
|
from django.db import models, transaction
|
13 | 13 | from django.db.models.fields import BLANK_CHOICE_DASH |
14 | 14 | from django.http import Http404, HttpResponse, HttpResponseRedirect |
15 | 15 | from django.shortcuts import get_object_or_404, render_to_response |
| 16 | from django.template.loader import find_template |
16 | 17 | from django.utils.decorators import method_decorator |
17 | 18 | from django.utils.datastructures import SortedDict |
18 | 19 | from django.utils.functional import update_wrapper |
… |
… |
class ModelAdmin(BaseModelAdmin):
|
210 | 211 | # Actions |
211 | 212 | actions = [] |
212 | 213 | action_form = helpers.ActionForm |
| 214 | actions_template = None |
213 | 215 | actions_on_top = True |
214 | 216 | actions_on_bottom = False |
215 | 217 | actions_selection_counter = True |
… |
… |
class ModelAdmin(BaseModelAdmin):
|
1060 | 1062 | if actions: |
1061 | 1063 | action_form = self.action_form(auto_id=None) |
1062 | 1064 | action_form.fields['action'].choices = self.get_action_choices(request) |
| 1065 | |
| 1066 | if self.actions_template: |
| 1067 | actions_template = self.actions_template |
| 1068 | else: |
| 1069 | # Search for the appropriate template path for inclusion by {% admin_actions %} |
| 1070 | for t in ( |
| 1071 | 'admin/%s/%s/actions.html' % (app_label, opts.object_name.lower()), |
| 1072 | 'admin/%s/actions.html' % app_label, |
| 1073 | 'admin/actions.html', |
| 1074 | ): |
| 1075 | try: |
| 1076 | find_template(t) |
| 1077 | except template.TemplateDoesNotExist: |
| 1078 | continue |
| 1079 | else: |
| 1080 | actions_template = t |
| 1081 | break |
1063 | 1082 | else: |
1064 | 1083 | action_form = None |
| 1084 | actions_template = None |
1065 | 1085 | |
1066 | 1086 | selection_note_all = ungettext('%(total_count)s selected', |
1067 | 1087 | 'All %(total_count)s selected', cl.result_count) |
… |
… |
class ModelAdmin(BaseModelAdmin):
|
1078 | 1098 | 'root_path': self.admin_site.root_path, |
1079 | 1099 | 'app_label': app_label, |
1080 | 1100 | 'action_form': action_form, |
| 1101 | 'actions_template': actions_template, |
1081 | 1102 | 'actions_on_top': self.actions_on_top, |
1082 | 1103 | 'actions_on_bottom': self.actions_on_bottom, |
1083 | 1104 | 'actions_selection_counter': self.actions_selection_counter, |
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index 565db32..6f65044 100644
a
|
b
|
from django.utils.safestring import mark_safe
|
13 | 13 | from django.utils.text import capfirst |
14 | 14 | from django.utils.translation import ugettext as _ |
15 | 15 | from django.utils.encoding import smart_unicode, force_unicode |
16 | | from django.template import Library |
| 16 | from django.template import Library, Node |
| 17 | from django.template.loader import get_template |
17 | 18 | |
18 | 19 | |
19 | 20 | register = Library() |
… |
… |
def admin_list_filter(cl, spec):
|
287 | 288 | return {'title': spec.title(), 'choices' : list(spec.choices(cl))} |
288 | 289 | admin_list_filter = register.inclusion_tag('admin/filter.html')(admin_list_filter) |
289 | 290 | |
290 | | def admin_actions(context): |
291 | | """ |
292 | | Track the number of times the action field has been rendered on the page, |
293 | | so we know which value to use. |
294 | | """ |
295 | | context['action_index'] = context.get('action_index', -1) + 1 |
296 | | return context |
297 | | admin_actions = register.inclusion_tag("admin/actions.html", takes_context=True)(admin_actions) |
| 291 | #@register.tag |
| 292 | def admin_actions(parser, token): |
| 293 | class IncludeActionsTemplateNode(Node): |
| 294 | def render(self, context): |
| 295 | # Track the number of times the action field has been rendered on |
| 296 | # the page, so we know which value to use. |
| 297 | context['action_index'] = context.get('action_index', -1) + 1 |
| 298 | |
| 299 | try: |
| 300 | t = get_template(context.get('actions_template', 'admin/actions.html')) |
| 301 | return t.render(context) |
| 302 | except: |
| 303 | return '' |
| 304 | |
| 305 | return IncludeActionsTemplateNode() |
| 306 | admin_actions = register.tag(admin_actions) |
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index f7aefa4..f3c9e50 100644
a
|
b
|
The `Overriding Admin Templates`_ section describes how to override or extend
|
718 | 718 | the default admin templates. Use the following options to override the default |
719 | 719 | templates used by the :class:`ModelAdmin` views: |
720 | 720 | |
| 721 | .. attribute:: ModelAdmin.actions_template |
| 722 | |
| 723 | .. versionadded:: 1.2 |
| 724 | |
| 725 | Path to a custom template that will be used to display actions in the model |
| 726 | objects "change list" view. |
| 727 | |
| 728 | If you don't specify this attribute, a default template shipped with Django |
| 729 | that provides the standard appearance is used. |
| 730 | |
721 | 731 | .. attribute:: ModelAdmin.add_form_template |
722 | 732 | |
723 | 733 | .. versionadded:: 1.2 |
… |
… |
app or per model. The following can:
|
1363 | 1373 | * ``app_index.html`` |
1364 | 1374 | * ``change_form.html`` |
1365 | 1375 | * ``change_list.html`` |
| 1376 | * ``actions.html`` |
1366 | 1377 | * ``delete_confirmation.html`` |
1367 | 1378 | * ``object_history.html`` |
1368 | 1379 | |
diff --git a/tests/regressiontests/admin_actions/__init__.py b/tests/regressiontests/admin_actions/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/regressiontests/admin_actions/fixtures/admin-views-users.xml b/tests/regressiontests/admin_actions/fixtures/admin-views-users.xml
new file mode 100644
index 0000000..aba8f4a
-
|
+
|
|
| 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <django-objects version="1.0"> |
| 3 | <object pk="100" model="auth.user"> |
| 4 | <field type="CharField" name="username">super</field> |
| 5 | <field type="CharField" name="first_name">Super</field> |
| 6 | <field type="CharField" name="last_name">User</field> |
| 7 | <field type="CharField" name="email">super@example.com</field> |
| 8 | <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field> |
| 9 | <field type="BooleanField" name="is_staff">True</field> |
| 10 | <field type="BooleanField" name="is_active">True</field> |
| 11 | <field type="BooleanField" name="is_superuser">True</field> |
| 12 | <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field> |
| 13 | <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field> |
| 14 | <field to="auth.group" name="groups" rel="ManyToManyRel"></field> |
| 15 | <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field> |
| 16 | </object> |
| 17 | </django-objects> |
diff --git a/tests/regressiontests/admin_actions/models.py b/tests/regressiontests/admin_actions/models.py
new file mode 100644
index 0000000..47afed4
-
|
+
|
|
| 1 | from django.contrib import admin |
| 2 | from django.db import models |
| 3 | |
| 4 | class Animal(models.Model): |
| 5 | name = models.CharField(max_length=128) |
| 6 | |
| 7 | class Meta: |
| 8 | abstract = True |
| 9 | |
| 10 | class Cat(Animal): |
| 11 | """Used for model overrides.""" |
| 12 | pass |
| 13 | |
| 14 | class Dog(Animal): |
| 15 | """Used for app overrides.""" |
| 16 | pass |
| 17 | |
| 18 | admin.site.register(Cat) |
| 19 | admin.site.register(Dog) |
diff --git a/tests/regressiontests/admin_actions/tests.py b/tests/regressiontests/admin_actions/tests.py
new file mode 100644
index 0000000..6731e74
-
|
+
|
|
| 1 | from django.test import TestCase |
| 2 | |
| 3 | from models import Cat, Dog |
| 4 | |
| 5 | class TestInline(TestCase): |
| 6 | fixtures = ['admin-views-users.xml'] |
| 7 | |
| 8 | def setUp(self): |
| 9 | self.base_url = '/test_admin/admin/admin_actions' |
| 10 | |
| 11 | # this is needed for the actions form to show up (with the 'delete' action) |
| 12 | Dog(name='Jacky').save() |
| 13 | Cat(name='Felix').save() |
| 14 | |
| 15 | self.app_element = '<p class="app-element"></p>' |
| 16 | self.model_element = '<p class="model-element"></p>' |
| 17 | |
| 18 | result = self.client.login(username='super', password='secret') |
| 19 | self.failUnlessEqual(result, True) |
| 20 | |
| 21 | def tearDown(self): |
| 22 | self.client.logout() |
| 23 | |
| 24 | def test_app_can_override(self): |
| 25 | """ |
| 26 | Test that the actions.html template can be overriden by an app. |
| 27 | """ |
| 28 | changelist_url = '%s/dog/' % self.base_url |
| 29 | response = self.client.get(changelist_url) |
| 30 | |
| 31 | self.assertContains(response, self.app_element) |
| 32 | |
| 33 | def test_model_can_override(self): |
| 34 | """ |
| 35 | Test that the actions.html template can be overriden by a model. |
| 36 | """ |
| 37 | changelist_url = '%s/cat/' % self.base_url |
| 38 | response = self.client.get(changelist_url) |
| 39 | |
| 40 | self.assertContains(response, self.model_element) |
diff --git a/tests/templates/admin/admin_actions/actions.html b/tests/templates/admin/admin_actions/actions.html
new file mode 100644
index 0000000..9c5d2e4
-
|
+
|
|
| 1 | {% include "admin/actions.html" %} |
| 2 | |
| 3 | <p class="app-element"></p> |
diff --git a/tests/templates/admin/admin_actions/cat/actions.html b/tests/templates/admin/admin_actions/cat/actions.html
new file mode 100644
index 0000000..3563c8a
-
|
+
|
|
| 1 | {% include "admin/actions.html" %} |
| 2 | |
| 3 | <p class="model-element"></p> |