diff --git a/django/contrib/admin/media/css/changelists.css b/django/contrib/admin/media/css/changelists.css index 43033780ac..a9d7543829 100644 --- a/django/contrib/admin/media/css/changelists.css +++ b/django/contrib/admin/media/css/changelists.css @@ -228,6 +228,12 @@ border-right: 1px solid #ddd; } +.action_counter{ + font-size: 11px; + margin: 0 0.5em; + display: none; +} + #changelist table input { margin: 0; } diff --git a/django/contrib/admin/media/js/actions.js b/django/contrib/admin/media/js/actions.js index d34488ab9d..87b2e1b84e 100644 --- a/django/contrib/admin/media/js/actions.js +++ b/django/contrib/admin/media/js/actions.js @@ -1,10 +1,22 @@ var Actions = { init: function() { - var selectAll = document.getElementById('action-toggle'); + counterSpans = document.getElementsBySelector('span._acnt'); + counterContainer = document.getElementsBySelector('span.action_counter'); + actionCheckboxes = document.getElementsBySelector('tr input.action-select'); + selectAll = document.getElementById('action-toggle'); + for(var i = 0; i < counterContainer.length; i++) { + counterContainer[i].style.display = 'inline'; + } if (selectAll) { selectAll.style.display = 'inline'; addEvent(selectAll, 'click', function() { Actions.checker(selectAll.checked); + Actions.counter(); + }); + } + for(var i = 0; i < actionCheckboxes.length; i++) { + addEvent(actionCheckboxes[i], 'click', function() { + Actions.counter(); }); } var changelistTable = document.getElementsBySelector('#changelist table')[0]; @@ -16,6 +28,7 @@ var Actions = { if (target.className == 'action-select') { var tr = target.parentNode.parentNode; Actions.toggleRow(tr, target.checked); + Actions.checked(); } }); } @@ -27,13 +40,26 @@ var Actions = { tr.className = tr.className.replace(' selected', ''); } }, + checked: function() { + selectAll.checked = false; + }, checker: function(checked) { - var actionCheckboxes = document.getElementsBySelector('tr input.action-select'); for(var i = 0; i < actionCheckboxes.length; i++) { actionCheckboxes[i].checked = checked; Actions.toggleRow(actionCheckboxes[i].parentNode.parentNode, checked); } + }, + counter: function() { + counter = 0; + for(var i = 0; i < actionCheckboxes.length; i++) { + if(actionCheckboxes[i].checked){ + counter++; + } + } + for(var i = 0; i < counterSpans.length; i++) { + counterSpans[i].innerHTML = counter; + } } }; -addEvent(window, 'load', Actions.init); +addEvent(window, 'load', Actions.init); \ No newline at end of file diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index c18af6260a..6dc707eefb 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -210,6 +210,7 @@ class ModelAdmin(BaseModelAdmin): action_form = helpers.ActionForm actions_on_top = True actions_on_bottom = False + actions_selection_counter = True def __init__(self, model, admin_site): self.model = model @@ -1024,7 +1025,13 @@ class ModelAdmin(BaseModelAdmin): else: action_form = None + if cl.result_count == 1: + module_name = force_unicode(opts.verbose_name) + else: + module_name = force_unicode(opts.verbose_name_plural) + context = { + 'module_name': module_name, 'title': cl.title, 'is_popup': cl.is_popup, 'cl': cl, @@ -1035,6 +1042,7 @@ class ModelAdmin(BaseModelAdmin): 'action_form': action_form, 'actions_on_top': self.actions_on_top, 'actions_on_bottom': self.actions_on_bottom, + 'actions_selection_counter': self.actions_selection_counter, } context.update(extra_context or {}) context_instance = template.RequestContext(request, current_app=self.admin_site.name) diff --git a/django/contrib/admin/templates/admin/actions.html b/django/contrib/admin/templates/admin/actions.html index bf4b975dfb..6d96616678 100644 --- a/django/contrib/admin/templates/admin/actions.html +++ b/django/contrib/admin/templates/admin/actions.html @@ -2,4 +2,9 @@
{% for field in action_form %}{% endfor %} + {% if actions_selection_counter %} + + {% blocktrans with cl.result_count as total_count %}0 of {{ total_count }} {{ module_name }} selected{% endblocktrans %} + + {% endif %}
diff --git a/docs/ref/contrib/admin/_images/article_actions.png b/docs/ref/contrib/admin/_images/article_actions.png index df4ab8f1ec..78a78ae494 100644 Binary files a/docs/ref/contrib/admin/_images/article_actions.png and b/docs/ref/contrib/admin/_images/article_actions.png differ diff --git a/docs/ref/contrib/admin/_images/article_actions_message.png b/docs/ref/contrib/admin/_images/article_actions_message.png index 31c5637469..6ea9439b8e 100644 Binary files a/docs/ref/contrib/admin/_images/article_actions_message.png and b/docs/ref/contrib/admin/_images/article_actions_message.png differ diff --git a/docs/ref/contrib/admin/_images/user_actions.png b/docs/ref/contrib/admin/_images/user_actions.png index 9a62f70685..fdbe2ad897 100644 Binary files a/docs/ref/contrib/admin/_images/user_actions.png and b/docs/ref/contrib/admin/_images/user_actions.png differ diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 97e9c8bcc9..d4851330c1 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -697,6 +697,12 @@ Controls where on the page the actions bar appears. By default, the admin changelist displays actions at the top of the page (``actions_on_top = True; actions_on_bottom = False``). +.. attribute:: ModelAdmin.actions_selection_counter + +Controls whether a selection counter is display next to the action dropdown. +By default, the admin changelist will display it +(``actions_selection_counter = True``). + .. attribute:: ModelAdmin.change_list_template Path to a custom template that will be used by the model objects "change list" diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index df43701586..899fff80bc 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -1209,6 +1209,13 @@ class AdminActionsTest(TestCase): self.assertContains(response, msg) self.failUnlessEqual(Subscriber.objects.count(), 2) + def test_selection_counter(self): + """ + Check if the selection counter is there. + """ + response = self.client.get('/test_admin/admin/admin_views/subscriber/') + self.assertContains(response, '0 of 2 subscribers selected') + class TestCustomChangeList(TestCase): fixtures = ['admin-views-users.xml']