summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPreston Holmes <preston@ptone.com>2012-11-17 22:53:31 +0100
committerPreston Holmes <preston@ptone.com>2012-11-19 16:03:09 -0800
commitedf7ad36faab8d45aafe1f96feaf46794de22fc1 (patch)
treecbafab6d93982161f9d1520e4781a7452c0e9987
parent8b659e439b51953064a7a79924b4066e08d7127d (diff)
Fixed #18658 -- Improved ModelAdmin.message_user API
Thanks to Lowe Thiderman for the patch and tests
-rw-r--r--AUTHORS1
-rw-r--r--django/contrib/admin/options.py22
-rw-r--r--docs/ref/contrib/admin/actions.txt9
-rw-r--r--docs/ref/contrib/admin/index.txt16
-rw-r--r--docs/releases/1.5.txt5
-rw-r--r--tests/regressiontests/admin_views/admin.py25
-rw-r--r--tests/regressiontests/admin_views/models.py4
-rw-r--r--tests/regressiontests/admin_views/tests.py58
8 files changed, 133 insertions, 7 deletions
diff --git a/AUTHORS b/AUTHORS
index 7a04f1f243..f14ca69f5e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -531,6 +531,7 @@ answer newbie questions, and generally made Django that much better:
Terry Huang <terryh.tp@gmail.com>
Travis Terry <tdterry7@gmail.com>
thebjorn <bp@datakortet.no>
+ Lowe Thiderman <lowe.thiderman@gmail.com>
Zach Thompson <zthompson47@gmail.com>
Michael Thornhill <michael.thornhill@gmail.com>
Deepak Thukral <deep.thukral@gmail.com>
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 19c212db9a..64d71fe1af 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -691,12 +691,30 @@ class ModelAdmin(BaseModelAdmin):
change_message = ' '.join(change_message)
return change_message or _('No fields changed.')
- def message_user(self, request, message):
+ def message_user(self, request, message, level=messages.INFO, extra_tags='',
+ fail_silently=False):
"""
Send a message to the user. The default implementation
posts a message using the django.contrib.messages backend.
+
+ Exposes almost the same API as messages.add_message(), but accepts the
+ positional arguments in a different order to maintain backwards
+ compatibility. For convenience, it accepts the `level` argument as
+ a string rather than the ususal level number.
"""
- messages.info(request, message)
+
+ if not isinstance(level, int):
+ # attempt to get the level if passed a string
+ try:
+ level = getattr(messages.constants, level.upper())
+ except AttributeError:
+ levels = messages.constants.DEFAULT_TAGS.values()
+ levels_repr = ', '.join('`%s`' % l for l in levels)
+ raise ValueError('Bad message level string: `%s`. '
+ 'Possible values are: %s' % (level, levels_repr))
+
+ messages.add_message(request, level, message, extra_tags=extra_tags,
+ fail_silently=fail_silently)
def save_form(self, request, form, change):
"""
diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt
index 3f3b52983b..d7eef623d5 100644
--- a/docs/ref/contrib/admin/actions.txt
+++ b/docs/ref/contrib/admin/actions.txt
@@ -140,6 +140,15 @@ That's really all there is to it! If you're itching to write your own actions,
you now know enough to get started. The rest of this document just covers more
advanced techniques.
+Handling errors in actions
+--------------------------
+
+If there are foreseeable error conditions that may occur while running your
+action, you should gracefully inform the user of the problem. This means
+handling exceptions and and using
+:meth:`django.contrib.admin.ModelAdmin.message_user` to display a user friendly
+description of the problem in the response.
+
Advanced action techniques
==========================
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index 9336ce174e..38668a0a2c 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -1303,11 +1303,19 @@ templates used by the :class:`ModelAdmin` views:
return qs
return qs.filter(author=request.user)
-.. method:: ModelAdmin.message_user(request, message)
+.. method:: ModelAdmin.message_user(request, message, level=messages.INFO, extra_tags='', fail_silently=False)
- Sends a message to the user. The default implementation creates a message
- using the :mod:`django.contrib.messages` backend. See the
- :ref:`custom ModelAdmin example <custom-admin-action>`.
+ Sends a message to the user using the :mod:`django.contrib.messages`
+ backend. See the :ref:`custom ModelAdmin example <custom-admin-action>`.
+
+ .. versionadded:: 1.5
+
+ Keyword arguments allow you to change the message level, add extra CSS
+ tags, or fail silently if the ``contrib.messages`` framework is not
+ installed. These keyword arguments match those for
+ :func:`django.contrib.messages.add_message`, see that function's
+ documentation for more details. One difference is that the level may be
+ passed as a string label in addition to integer/constant.
.. method:: ModelAdmin.get_paginator(queryset, per_page, orphans=0, allow_empty_first_page=True)
diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
index 5d5902d45d..b73bb041e9 100644
--- a/docs/releases/1.5.txt
+++ b/docs/releases/1.5.txt
@@ -313,6 +313,11 @@ Django 1.5 also includes several smaller improvements worth noting:
DeprecationWarnings should be printed to the console in development
environments the way they have been in Python versions < 2.7.
+* The API for :meth:`django.contrib.admin.ModelAdmin.message_user` method has
+ been modified to accept additional arguments adding capabilities similar to
+ :func:`django.contrib.messages.add_message`. This is useful for generating
+ error messages from admin actions.
+
Backwards incompatible changes in 1.5
=====================================
diff --git a/tests/regressiontests/admin_views/admin.py b/tests/regressiontests/admin_views/admin.py
index 6bb6ba59b0..40e800419e 100644
--- a/tests/regressiontests/admin_views/admin.py
+++ b/tests/regressiontests/admin_views/admin.py
@@ -27,7 +27,7 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated,
- RelatedPrepopulated, UndeletableObject, Simple)
+ RelatedPrepopulated, UndeletableObject, UserMessenger, Simple)
def callable_year(dt_value):
@@ -592,6 +592,28 @@ def callable_on_unknown(obj):
class AttributeErrorRaisingAdmin(admin.ModelAdmin):
list_display = [callable_on_unknown, ]
+class MessageTestingAdmin(admin.ModelAdmin):
+ actions = ["message_debug", "message_info", "message_success",
+ "message_warning", "message_error", "message_extra_tags"]
+
+ def message_debug(self, request, selected):
+ self.message_user(request, "Test debug", level="debug")
+
+ def message_info(self, request, selected):
+ self.message_user(request, "Test info", level="info")
+
+ def message_success(self, request, selected):
+ self.message_user(request, "Test success", level="success")
+
+ def message_warning(self, request, selected):
+ self.message_user(request, "Test warning", level="warning")
+
+ def message_error(self, request, selected):
+ self.message_user(request, "Test error", level="error")
+
+ def message_extra_tags(self, request, selected):
+ self.message_user(request, "Test tags", extra_tags="extra_tag")
+
site = admin.AdminSite(name="admin")
site.register(Article, ArticleAdmin)
@@ -667,6 +689,7 @@ site.register(AdminOrderedAdminMethod, AdminOrderedAdminMethodAdmin)
site.register(AdminOrderedCallable, AdminOrderedCallableAdmin)
site.register(Color2, CustomTemplateFilterColorAdmin)
site.register(Simple, AttributeErrorRaisingAdmin)
+site.register(UserMessenger, MessageTestingAdmin)
# 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 2b143004d9..6f2ddc23a2 100644
--- a/tests/regressiontests/admin_views/models.py
+++ b/tests/regressiontests/admin_views/models.py
@@ -651,6 +651,10 @@ class UndeletableObject(models.Model):
"""
name = models.CharField(max_length=255)
+class UserMessenger(models.Model):
+ """
+ Dummy class for testing message_user functions on ModelAdmin
+ """
class Simple(models.Model):
"""
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index b5e0f407e5..313809ec1c 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -3697,3 +3697,61 @@ class AdminViewLogoutTest(TestCase):
self.assertEqual(response.template_name, 'admin/login.html')
self.assertEqual(response.request['PATH_INFO'], '/test_admin/admin/')
self.assertContains(response, '<input type="hidden" name="next" value="/test_admin/admin/" />')
+
+
+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
+class AdminUserMessageTest(TestCase):
+ urls = "regressiontests.admin_views.urls"
+ fixtures = ['admin-views-users.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def send_message(self, level):
+ """
+ Helper that sends a post to the dummy test methods and asserts that a
+ message with the level has appeared in the response.
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1],
+ 'action': 'message_%s' % level,
+ 'index': 0,
+ }
+
+ response = self.client.post('/test_admin/admin/admin_views/usermessenger/',
+ action_data, follow=True)
+ self.assertContains(response,
+ '<li class="%s">Test %s</li>' % (level, level),
+ html=True)
+
+ @override_settings(MESSAGE_LEVEL=10) # Set to DEBUG for this request
+ def test_message_debug(self):
+ self.send_message('debug')
+
+ def test_message_info(self):
+ self.send_message('info')
+
+ def test_message_success(self):
+ self.send_message('success')
+
+ def test_message_warning(self):
+ self.send_message('warning')
+
+ def test_message_error(self):
+ self.send_message('error')
+
+ def test_message_extra_tags(self):
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1],
+ 'action': 'message_extra_tags',
+ 'index': 0,
+ }
+
+ response = self.client.post('/test_admin/admin/admin_views/usermessenger/',
+ action_data, follow=True)
+ self.assertContains(response,
+ '<li class="extra_tag info">Test tags</li>',
+ html=True)