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> |