From 1e629928e9257da5ec37a65784c0f68889d3edf4 Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Sat, 12 Nov 2016 19:09:15 +0000 Subject: [PATCH] Fixed #27313 -- Allowed overriding admin popup response template. --- django/contrib/admin/options.py | 22 +++++++-- docs/ref/contrib/admin/index.txt | 11 +++++ docs/releases/1.11.txt | 4 ++ tests/admin_views/admin.py | 1 + tests/admin_views/tests.py | 47 ++++++++++++++++++- .../custom_admin/popup_response.html | 1 + 6 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 tests/templates/custom_admin/popup_response.html diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 75c256963b..0e3c044e9b 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -509,6 +509,7 @@ class ModelAdmin(BaseModelAdmin): delete_confirmation_template = None delete_selected_confirmation_template = None object_history_template = None + popup_response_template = None # Actions actions = [] @@ -1073,7 +1074,11 @@ class ModelAdmin(BaseModelAdmin): 'value': six.text_type(value), 'obj': six.text_type(obj), }) - return SimpleTemplateResponse('admin/popup_response.html', { + return TemplateResponse(request, self.popup_response_template or [ + 'admin/%s/%s/popup_response.html' % (opts.app_label, opts.model_name), + 'admin/%s/popup_response.html' % opts.app_label, + 'admin/popup_response.html', + ], { 'popup_response_data': popup_response_data, }) @@ -1119,8 +1124,9 @@ class ModelAdmin(BaseModelAdmin): """ if IS_POPUP_VAR in request.POST: + opts = obj._meta to_field = request.POST.get(TO_FIELD_VAR) - attr = str(to_field) if to_field else obj._meta.pk.attname + attr = str(to_field) if to_field else opts.pk.attname # Retrieve the `object_id` from the resolved pattern arguments. value = request.resolver_match.args[0] new_value = obj.serializable_value(attr) @@ -1130,7 +1136,11 @@ class ModelAdmin(BaseModelAdmin): 'obj': six.text_type(obj), 'new_value': six.text_type(new_value), }) - return SimpleTemplateResponse('admin/popup_response.html', { + return TemplateResponse(request, self.popup_response_template or [ + 'admin/%s/%s/popup_response.html' % (opts.app_label, opts.model_name), + 'admin/%s/popup_response.html' % opts.app_label, + 'admin/popup_response.html', + ], { 'popup_response_data': popup_response_data, }) @@ -1299,7 +1309,11 @@ class ModelAdmin(BaseModelAdmin): 'action': 'delete', 'value': str(obj_id), }) - return SimpleTemplateResponse('admin/popup_response.html', { + return TemplateResponse(request, self.popup_response_template or [ + 'admin/%s/%s/popup_response.html' % (opts.app_label, opts.model_name), + 'admin/%s/popup_response.html' % opts.app_label, + 'admin/popup_response.html', + ], { 'popup_response_data': popup_response_data, }) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index fed034bd92..bcbdc1fb42 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1318,6 +1318,12 @@ templates used by the :class:`ModelAdmin` views: Path to a custom template, used by :meth:`history_view`. +.. attribute:: ModelAdmin.popup_response_template + + .. versionadded:: 1.11 + + Path to a custom template, used by :meth:`response_add`, + :meth:`response_change`, and :meth:`response_delete`. .. _model-admin-methods: @@ -2516,6 +2522,11 @@ app or per model. The following can: * ``change_list.html`` * ``delete_confirmation.html`` * ``object_history.html`` +* ``popup_response.html`` + +.. versionchanged:: 1.11 + + The ability to override the ``popup_response.html`` template was added. For those templates that cannot be overridden in this way, you may still override them for your entire project. Just place the new version in your diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 0dc2b27bde..e14d76ca1f 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -72,6 +72,10 @@ Minor features ` hook allows specifying the exclude fields based on the request or model instance. +* The ``popup_response.html`` template can now be overridden per app, per + model, or by setting the :attr:`.ModelAdmin.popup_response_template` + attribute. + :mod:`django.contrib.admindocs` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index cdb32a2825..bc4c7e647f 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -162,6 +162,7 @@ class CustomArticleAdmin(admin.ModelAdmin): object_history_template = 'custom_admin/object_history.html' delete_confirmation_template = 'custom_admin/delete_confirmation.html' delete_selected_confirmation_template = 'custom_admin/delete_selected_confirmation.html' + popup_response_template = 'custom_admin/popup_response.html' def changelist_view(self, request): return super(CustomArticleAdmin, self).changelist_view( diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index e8e8d1c2d4..b65439f8ac 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -969,6 +969,16 @@ class AdminCustomTemplateTests(AdminViewBasicTestCase): response = self.client.get(reverse('admin:admin_views_customarticle_history', args=(article_pk,))) self.assertTemplateUsed(response, 'custom_admin/object_history.html') + # A custom popup response template may be specified by + # ModelAdmin.popup_response_template. + response = self.client.post(reverse('admin:admin_views_customarticle_add') + '?%s=1' % IS_POPUP_VAR, { + 'content': '

great article

', + 'date_0': '2008-03-18', + 'date_1': '10:54:39', + IS_POPUP_VAR: '1' + }) + self.assertEqual(response.template_name, 'custom_admin/popup_response.html') + def test_extended_bodyclass_template_change_form(self): """ The admin/change_form.html template uses block.super in the @@ -3433,7 +3443,7 @@ action) reverse('admin:admin_views_subscriber_changelist') + '?%s' % IS_POPUP_VAR) self.assertIsNone(response.context["action_form"]) - def test_popup_template_response(self): + def test_popup_template_response_on_add(self): """ Success on popups shall be rendered from template in order to allow easy customization. @@ -3442,7 +3452,40 @@ action) reverse('admin:admin_views_actor_add') + '?%s=1' % IS_POPUP_VAR, {'name': 'Troy McClure', 'age': '55', IS_POPUP_VAR: '1'}) self.assertEqual(response.status_code, 200) - self.assertEqual(response.template_name, 'admin/popup_response.html') + self.assertListEqual(response.template_name, [ + 'admin/admin_views/actor/popup_response.html', + 'admin/admin_views/popup_response.html', + 'admin/popup_response.html', + ]) + self.assertTemplateUsed(response, 'admin/popup_response.html') + + def test_popup_template_response_on_change(self): + instance = Actor.objects.create(name='David Tennant', age=45) + response = self.client.post( + reverse('admin:admin_views_actor_change', args=(instance.pk,)) + '?%s=1' % IS_POPUP_VAR, + {'name': 'David Tennant', 'age': '46', IS_POPUP_VAR: '1'} + ) + self.assertEqual(response.status_code, 200) + self.assertListEqual(response.template_name, [ + 'admin/admin_views/actor/popup_response.html', + 'admin/admin_views/popup_response.html', + 'admin/popup_response.html', + ]) + self.assertTemplateUsed(response, 'admin/popup_response.html') + + def test_popup_template_response_on_delete(self): + instance = Actor.objects.create(name='David Tennant', age=45) + response = self.client.post( + reverse('admin:admin_views_actor_delete', args=(instance.pk,)) + '?%s=1' % IS_POPUP_VAR, + {IS_POPUP_VAR: '1'} + ) + self.assertEqual(response.status_code, 200) + self.assertListEqual(response.template_name, [ + 'admin/admin_views/actor/popup_response.html', + 'admin/admin_views/popup_response.html', + 'admin/popup_response.html', + ]) + self.assertTemplateUsed(response, 'admin/popup_response.html') def test_popup_template_escaping(self): popup_response_data = json.dumps({ diff --git a/tests/templates/custom_admin/popup_response.html b/tests/templates/custom_admin/popup_response.html new file mode 100644 index 0000000000..fd21d13d14 --- /dev/null +++ b/tests/templates/custom_admin/popup_response.html @@ -0,0 +1 @@ +{% extends "admin/popup_response.html" %}