Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #18658 -- Improved ModelAdmin.message_user API

Thanks to Lowe Thiderman for the patch and tests
  • Loading branch information...
commit edf7ad36faab8d45aafe1f96feaf46794de22fc1 1 parent 8b659e4
Preston Holmes authored November 17, 2012
1  AUTHORS
@@ -531,6 +531,7 @@ answer newbie questions, and generally made Django that much better:
531 531
     Terry Huang <terryh.tp@gmail.com>
532 532
     Travis Terry <tdterry7@gmail.com>
533 533
     thebjorn <bp@datakortet.no>
  534
+    Lowe Thiderman <lowe.thiderman@gmail.com>
534 535
     Zach Thompson <zthompson47@gmail.com>
535 536
     Michael Thornhill <michael.thornhill@gmail.com>
536 537
     Deepak Thukral <deep.thukral@gmail.com>
22  django/contrib/admin/options.py
@@ -691,12 +691,30 @@ def construct_change_message(self, request, form, formsets):
691 691
         change_message = ' '.join(change_message)
692 692
         return change_message or _('No fields changed.')
693 693
 
694  
-    def message_user(self, request, message):
  694
+    def message_user(self, request, message, level=messages.INFO, extra_tags='',
  695
+                     fail_silently=False):
695 696
         """
696 697
         Send a message to the user. The default implementation
697 698
         posts a message using the django.contrib.messages backend.
  699
+
  700
+        Exposes almost the same API as messages.add_message(), but accepts the
  701
+        positional arguments in a different order to maintain backwards
  702
+        compatibility. For convenience, it accepts the `level` argument as
  703
+        a string rather than the ususal level number.
698 704
         """
699  
-        messages.info(request, message)
  705
+
  706
+        if not isinstance(level, int):
  707
+            # attempt to get the level if passed a string
  708
+            try:
  709
+                level = getattr(messages.constants, level.upper())
  710
+            except AttributeError:
  711
+                levels = messages.constants.DEFAULT_TAGS.values()
  712
+                levels_repr = ', '.join('`%s`' % l for l in levels)
  713
+                raise ValueError('Bad message level string: `%s`. '
  714
+                        'Possible values are: %s' % (level, levels_repr))
  715
+
  716
+        messages.add_message(request, level, message, extra_tags=extra_tags,
  717
+                fail_silently=fail_silently)
700 718
 
701 719
     def save_form(self, request, form, change):
702 720
         """
9  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,
140 140
 you now know enough to get started. The rest of this document just covers more
141 141
 advanced techniques.
142 142
 
  143
+Handling errors in actions
  144
+--------------------------
  145
+
  146
+If there are foreseeable error conditions that may occur while running your
  147
+action, you should gracefully inform the user of the problem. This means
  148
+handling exceptions and and using
  149
+:meth:`django.contrib.admin.ModelAdmin.message_user` to display a user friendly
  150
+description of the problem in the response.
  151
+
143 152
 Advanced action techniques
144 153
 ==========================
145 154
 
16  docs/ref/contrib/admin/index.txt
@@ -1303,11 +1303,19 @@ templates used by the :class:`ModelAdmin` views:
1303 1303
                     return qs
1304 1304
                 return qs.filter(author=request.user)
1305 1305
 
1306  
-.. method:: ModelAdmin.message_user(request, message)
  1306
+.. method:: ModelAdmin.message_user(request, message, level=messages.INFO, extra_tags='', fail_silently=False)
1307 1307
 
1308  
-    Sends a message to the user. The default implementation creates a message
1309  
-    using the :mod:`django.contrib.messages` backend. See the
1310  
-    :ref:`custom ModelAdmin example <custom-admin-action>`.
  1308
+    Sends a message to the user using the :mod:`django.contrib.messages`
  1309
+    backend.  See the :ref:`custom ModelAdmin example <custom-admin-action>`.
  1310
+
  1311
+    .. versionadded:: 1.5
  1312
+
  1313
+    Keyword arguments allow you to change the message level, add extra CSS
  1314
+    tags, or fail silently if the ``contrib.messages`` framework is not
  1315
+    installed. These keyword arguments match those for
  1316
+    :func:`django.contrib.messages.add_message`, see that function's
  1317
+    documentation for more details. One difference is that the level may be
  1318
+    passed as a string label in addition to integer/constant.
1311 1319
 
1312 1320
 .. method:: ModelAdmin.get_paginator(queryset, per_page, orphans=0, allow_empty_first_page=True)
1313 1321
 
5  docs/releases/1.5.txt
@@ -313,6 +313,11 @@ Django 1.5 also includes several smaller improvements worth noting:
313 313
   DeprecationWarnings should be printed to the console in development
314 314
   environments the way they have been in Python versions < 2.7.
315 315
 
  316
+* The API for :meth:`django.contrib.admin.ModelAdmin.message_user` method has
  317
+  been modified to accept additional arguments adding capabilities similar to
  318
+  :func:`django.contrib.messages.add_message`. This is useful for generating
  319
+  error messages from admin actions.
  320
+
316 321
 Backwards incompatible changes in 1.5
317 322
 =====================================
318 323
 
25  tests/regressiontests/admin_views/admin.py
@@ -27,7 +27,7 @@
27 27
     Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
28 28
     AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
29 29
     AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated,
30  
-    RelatedPrepopulated, UndeletableObject, Simple)
  30
+    RelatedPrepopulated, UndeletableObject, UserMessenger, Simple)
31 31
 
32 32
 
33 33
 def callable_year(dt_value):
@@ -592,6 +592,28 @@ def callable_on_unknown(obj):
592 592
 class AttributeErrorRaisingAdmin(admin.ModelAdmin):
593 593
     list_display = [callable_on_unknown, ]
594 594
 
  595
+class MessageTestingAdmin(admin.ModelAdmin):
  596
+    actions = ["message_debug", "message_info", "message_success",
  597
+               "message_warning", "message_error", "message_extra_tags"]
  598
+
  599
+    def message_debug(self, request, selected):
  600
+        self.message_user(request, "Test debug", level="debug")
  601
+
  602
+    def message_info(self, request, selected):
  603
+        self.message_user(request, "Test info", level="info")
  604
+
  605
+    def message_success(self, request, selected):
  606
+        self.message_user(request, "Test success", level="success")
  607
+
  608
+    def message_warning(self, request, selected):
  609
+        self.message_user(request, "Test warning", level="warning")
  610
+
  611
+    def message_error(self, request, selected):
  612
+        self.message_user(request, "Test error", level="error")
  613
+
  614
+    def message_extra_tags(self, request, selected):
  615
+        self.message_user(request, "Test tags", extra_tags="extra_tag")
  616
+
595 617
 
596 618
 site = admin.AdminSite(name="admin")
597 619
 site.register(Article, ArticleAdmin)
@@ -667,6 +689,7 @@ class AttributeErrorRaisingAdmin(admin.ModelAdmin):
667 689
 site.register(AdminOrderedCallable, AdminOrderedCallableAdmin)
668 690
 site.register(Color2, CustomTemplateFilterColorAdmin)
669 691
 site.register(Simple, AttributeErrorRaisingAdmin)
  692
+site.register(UserMessenger, MessageTestingAdmin)
670 693
 
671 694
 # Register core models we need in our tests
672 695
 from django.contrib.auth.models import User, Group
4  tests/regressiontests/admin_views/models.py
@@ -651,6 +651,10 @@ class UndeletableObject(models.Model):
651 651
     """
652 652
     name = models.CharField(max_length=255)
653 653
 
  654
+class UserMessenger(models.Model):
  655
+    """
  656
+    Dummy class for testing message_user functions on ModelAdmin
  657
+    """
654 658
 
655 659
 class Simple(models.Model):
656 660
     """
58  tests/regressiontests/admin_views/tests.py
@@ -3697,3 +3697,61 @@ def test_client_logout_url_can_be_used_to_login(self):
3697 3697
         self.assertEqual(response.template_name, 'admin/login.html')
3698 3698
         self.assertEqual(response.request['PATH_INFO'], '/test_admin/admin/')
3699 3699
         self.assertContains(response, '<input type="hidden" name="next" value="/test_admin/admin/" />')
  3700
+
  3701
+
  3702
+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
  3703
+class AdminUserMessageTest(TestCase):
  3704
+    urls = "regressiontests.admin_views.urls"
  3705
+    fixtures = ['admin-views-users.xml']
  3706
+
  3707
+    def setUp(self):
  3708
+        self.client.login(username='super', password='secret')
  3709
+
  3710
+    def tearDown(self):
  3711
+        self.client.logout()
  3712
+
  3713
+    def send_message(self, level):
  3714
+        """
  3715
+        Helper that sends a post to the dummy test methods and asserts that a
  3716
+        message with the level has appeared in the response.
  3717
+        """
  3718
+        action_data = {
  3719
+            ACTION_CHECKBOX_NAME: [1],
  3720
+            'action': 'message_%s' % level,
  3721
+            'index': 0,
  3722
+        }
  3723
+
  3724
+        response = self.client.post('/test_admin/admin/admin_views/usermessenger/',
  3725
+                                    action_data, follow=True)
  3726
+        self.assertContains(response,
  3727
+                            '<li class="%s">Test %s</li>' % (level, level),
  3728
+                            html=True)
  3729
+
  3730
+    @override_settings(MESSAGE_LEVEL=10)  # Set to DEBUG for this request
  3731
+    def test_message_debug(self):
  3732
+        self.send_message('debug')
  3733
+
  3734
+    def test_message_info(self):
  3735
+        self.send_message('info')
  3736
+
  3737
+    def test_message_success(self):
  3738
+        self.send_message('success')
  3739
+
  3740
+    def test_message_warning(self):
  3741
+        self.send_message('warning')
  3742
+
  3743
+    def test_message_error(self):
  3744
+        self.send_message('error')
  3745
+
  3746
+    def test_message_extra_tags(self):
  3747
+        action_data = {
  3748
+            ACTION_CHECKBOX_NAME: [1],
  3749
+            'action': 'message_extra_tags',
  3750
+            'index': 0,
  3751
+        }
  3752
+
  3753
+        response = self.client.post('/test_admin/admin/admin_views/usermessenger/',
  3754
+                                    action_data, follow=True)
  3755
+        self.assertContains(response,
  3756
+                            '<li class="extra_tag info">Test tags</li>',
  3757
+                            html=True)

0 notes on commit edf7ad3

Please sign in to comment.
Something went wrong with that request. Please try again.