diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index fe86451ed6..5e64b76908 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -17,6 +17,8 @@ ACTION_CHECKBOX_NAME = '_selected_action' class ActionForm(forms.Form): action = forms.ChoiceField(label=_('Action:')) + select_across = forms.BooleanField(label='', required=False, initial=0, + widget=forms.HiddenInput({'class': 'select-across'})) checkbox = forms.CheckboxInput({'class': 'action-select'}, lambda value: False) diff --git a/django/contrib/admin/media/css/changelists.css b/django/contrib/admin/media/css/changelists.css index a9d7543829..99ff8bc0cf 100644 --- a/django/contrib/admin/media/css/changelists.css +++ b/django/contrib/admin/media/css/changelists.css @@ -228,12 +228,6 @@ border-right: 1px solid #ddd; } -.action_counter{ - font-size: 11px; - margin: 0 0.5em; - display: none; -} - #changelist table input { margin: 0; } @@ -250,6 +244,21 @@ background: white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; } +#changelist .actions.selected { + background: #fffccf; + border-top: 1px solid #fffee8; + border-bottom: 1px solid #edecd6; +} + +#changelist .actions span.all, +#changelist .actions span.action-counter, +#changelist .actions span.clear, +#changelist .actions span.question { + font-size: 11px; + margin: 0 0.5em; + display: none; +} + #changelist .actions:last-child { border-bottom: none; } diff --git a/django/contrib/admin/media/js/actions.js b/django/contrib/admin/media/js/actions.js index 658f0156be..1c78cb15e7 100644 --- a/django/contrib/admin/media/js/actions.js +++ b/django/contrib/admin/media/js/actions.js @@ -1,83 +1,111 @@ -var Actions = { - init: function() { - counterSpans = document.getElementsBySelector('span._acnt'); - counterContainer = document.getElementsBySelector('span.action_counter'); - actionCheckboxes = document.getElementsBySelector('tr input.action-select'); - selectAll = document.getElementById('action-toggle'); - lastChecked = null; - 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(e) { - if (!e) { var e = window.event; } - var target = e.target ? e.target : e.srcElement; - if (lastChecked && lastChecked != target && e.shiftKey == true) { - var inrange = false; - lastChecked.checked = target.checked; - Actions.toggleRow(lastChecked.parentNode.parentNode, target.checked); - for (var i = 0; i < actionCheckboxes.length; i++) { - if (actionCheckboxes[i] == lastChecked || actionCheckboxes[i] == target) { - inrange = (inrange) ? false : true; - } - if (inrange) { - actionCheckboxes[i].checked = target.checked; - Actions.toggleRow(actionCheckboxes[i].parentNode.parentNode, target.checked); - } - } - } - lastChecked = target; - Actions.counter(); - }); - } - var changelistTable = document.getElementsBySelector('#changelist table')[0]; - if (changelistTable) { - addEvent(changelistTable, 'click', function(e) { - if (!e) { var e = window.event; } - var target = e.target ? e.target : e.srcElement; - if (target.nodeType == 3) { target = target.parentNode; } - if (target.className == 'action-select') { - var tr = target.parentNode.parentNode; - Actions.toggleRow(tr, target.checked); - } - }); - } - }, - toggleRow: function(tr, checked) { - if (checked && tr.className.indexOf('selected') == -1) { - tr.className += ' selected'; - } else if (!checked) { - tr.className = tr.className.replace(' selected', ''); - } - }, - checked: function() { - selectAll.checked = false; - }, - checker: function(checked) { - 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; - } - selectAll.checked = (counter == actionCheckboxes.length); - } -}; - -addEvent(window, 'load', Actions.init); \ No newline at end of file +(function($) { + $.fn.actions = function(opts) { + var options = $.extend({}, $.fn.actions.defaults, opts); + var actionCheckboxes = $(this); + checker = function(checked) { + if (checked) { + showQuestion(); + } else { + reset(); + } + $(actionCheckboxes).attr("checked", checked) + .parent().parent().toggleClass(options.selectedClass, checked); + } + updateCounter = function() { + var count = $(actionCheckboxes).filter(":checked").length; + $("span._acnt").html(count); + $(options.allToggle).attr("checked", function() { + if (count == actionCheckboxes.length) { + value = true; + showQuestion(); + } else { + value = false; + clearAcross(); + } + return value + }); + } + showQuestion = function() { + $(options.acrossClears).hide(); + $(options.acrossQuestions).show(); + $(options.allContainer).hide(); + } + showClear = function() { + $(options.acrossClears).show(); + $(options.acrossQuestions).hide(); + $(options.actionContainer).toggleClass(options.selectedClass); + $(options.allContainer).show(); + $(options.counterContainer).hide(); + } + reset = function() { + $(options.acrossClears).hide(); + $(options.acrossQuestions).hide(); + $(options.allContainer).hide(); + $(options.counterContainer).show(); + } + clearAcross = function() { + reset(); + $(options.acrossInput).val(0); + $(options.actionContainer).removeClass(options.selectedClass); + } + // Show counter by default + $(options.counterContainer).show(); + // Check state of checkboxes and reinit state if needed + $(this).filter(":checked").each(function(i) { + $(this).parent().parent().toggleClass(options.selectedClass); + updateCounter(); + if ($(options.acrossInput).val() == 1) { + showClear(); + } + }); + $(options.allToggle).show().click(function() { + checker($(this).attr("checked")); + updateCounter(); + }); + $("div.actions span.question a").click(function(event) { + event.preventDefault(); + $(options.acrossInput).val(1); + showClear(); + }); + $("div.actions span.clear a").click(function(event) { + event.preventDefault(); + $(options.allToggle).attr("checked", false); + clearAcross(); + checker(0); + updateCounter(); + }); + lastChecked = null; + $(actionCheckboxes).click(function(event) { + if (!event) { var event = window.event; } + var target = event.target ? event.target : event.srcElement; + if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey == true) { + var inrange = false; + $(lastChecked).attr("checked", target.checked) + .parent().parent().toggleClass(options.selectedClass, target.checked); + $(actionCheckboxes).each(function() { + if ($.data(this) == $.data(lastChecked) || $.data(this) == $.data(target)) { + inrange = (inrange) ? false : true; + } + if (inrange) { + $(this).attr("checked", target.checked) + .parent().parent().toggleClass(options.selectedClass, target.checked); + } + }); + } + $(target).parent().parent().toggleClass(options.selectedClass, target.checked); + lastChecked = target; + updateCounter(); + }); + } + /* Setup plugin defaults */ + $.fn.actions.defaults = { + actionContainer: "div.actions", + counterContainer: "span.action-counter", + allContainer: "div.actions span.all", + acrossInput: "div.actions input.select-across", + acrossQuestions: "div.actions span.question", + acrossClears: "div.actions span.clear", + allToggle: "#action-toggle", + selectedClass: "selected" + } +})(jQuery); diff --git a/django/contrib/admin/media/js/actions.min.js b/django/contrib/admin/media/js/actions.min.js new file mode 100644 index 0000000000..aa3bbe1e74 --- /dev/null +++ b/django/contrib/admin/media/js/actions.min.js @@ -0,0 +1 @@ +(function(a){a.fn.actions=function(d){var c=a.extend({},a.fn.actions.defaults,d);var b=a(this);checker=function(e){if(e){showQuestion()}else{reset()}a(b).attr("checked",e).parent().parent().toggleClass(c.selectedClass,e)};updateCounter=function(){var e=a(b).filter(":checked").length;a("span._acnt").html(e);a(c.allToggle).attr("checked",function(){if(e==b.length){value=true;showQuestion()}else{value=false;clearAcross()}return value})};showQuestion=function(){a(c.acrossClears).hide();a(c.acrossQuestions).show();a(c.allContainer).hide()};showClear=function(){a(c.acrossClears).show();a(c.acrossQuestions).hide();a(c.actionContainer).toggleClass(c.selectedClass);a(c.allContainer).show();a(c.counterContainer).hide()};reset=function(){a(c.acrossClears).hide();a(c.acrossQuestions).hide();a(c.allContainer).hide();a(c.counterContainer).show()};clearAcross=function(){reset();a(c.acrossInput).val(0);a(c.actionContainer).removeClass(c.selectedClass)};a(c.counterContainer).show();a(this).filter(":checked").each(function(e){a(this).parent().parent().toggleClass(c.selectedClass);updateCounter();if(a(c.acrossInput).val()==1){showClear()}});a(c.allToggle).show().click(function(){checker(a(this).attr("checked"));updateCounter()});a("div.actions span.question a").click(function(e){e.preventDefault();a(c.acrossInput).val(1);showClear()});a("div.actions span.clear a").click(function(e){e.preventDefault();a(c.allToggle).attr("checked",false);clearAcross();checker(0);updateCounter()});lastChecked=null;a(b).click(function(f){if(!f){var f=window.event}var g=f.target?f.target:f.srcElement;if(lastChecked&&a.data(lastChecked)!=a.data(g)&&f.shiftKey==true){var e=false;a(lastChecked).attr("checked",g.checked).parent().parent().toggleClass(c.selectedClass,g.checked);a(b).each(function(){if(a.data(this)==a.data(lastChecked)||a.data(this)==a.data(g)){e=(e)?false:true}if(e){a(this).attr("checked",g.checked).parent().parent().toggleClass(c.selectedClass,g.checked)}})}a(g).parent().parent().toggleClass(c.selectedClass,g.checked);lastChecked=g;updateCounter()})};a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(jQuery); \ No newline at end of file diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index a0763088b5..9dfdb8bcfb 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -268,7 +268,7 @@ class ModelAdmin(BaseModelAdmin): js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] if self.actions is not None: - js.extend(['js/getElementsBySelector.js', 'js/actions.js']) + js.extend(['js/jquery.min.js', 'js/actions.min.js']) if self.prepopulated_fields: js.append('js/urlify.js') if self.opts.get_ordered_objects(): @@ -724,18 +724,24 @@ class ModelAdmin(BaseModelAdmin): # If the form's valid we can handle the action. if action_form.is_valid(): action = action_form.cleaned_data['action'] + select_across = action_form.cleaned_data['select_across'] func, name, description = self.get_actions(request)[action] # Get the list of selected PKs. If nothing's selected, we can't - # perform an action on it, so bail. + # perform an action on it, so bail. Except we want to perform + # the action explicitely on all objects. selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME) - if not selected: + if not selected and not select_across: # Reminder that something needs to be selected or nothing will happen msg = _("Items must be selected in order to perform actions on them. No items have been changed.") self.message_user(request, msg) return None - response = func(self, request, queryset.filter(pk__in=selected)) + if not select_across: + # Perform the action only on the selected objects + queryset = queryset.filter(pk__in=selected) + + response = func(self, request, queryset) # Actions may return an HttpResponse, which will be used as the # response from the POST. If not, we'll be a good little HTTP diff --git a/django/contrib/admin/templates/admin/actions.html b/django/contrib/admin/templates/admin/actions.html index 6d96616678..f4e34215fa 100644 --- a/django/contrib/admin/templates/admin/actions.html +++ b/django/contrib/admin/templates/admin/actions.html @@ -1,10 +1,20 @@ {% load i18n %}
- {% for field in action_form %}{% endfor %} + {% for field in action_form %}{% if field.label %}{% endif %}{% endfor %} {% if actions_selection_counter %} - - {% blocktrans with cl.result_count as total_count %}0 of {{ total_count }} {{ module_name }} selected{% endblocktrans %} - + + {% blocktrans with cl.result_count as total_count %}0 of {{ total_count }} {{ module_name }} selected{% endblocktrans %} + + {% if cl.result_count != cl.result_list|length %} + + {% blocktrans with cl.result_count as total_count %}All {{ total_count }} {{ module_name }} selected{% endblocktrans %} + + + {% blocktrans with cl.result_count as total_count %}Select all {{ total_count }} {{ module_name }}{% endblocktrans %} + + {% trans "Clear selection" %} + {% endif %} {% endif %}
+ diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index 3bcec9832f..fc1f392e6c 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -17,6 +17,16 @@ {% endif %} {% endblock %} +{% block extrahead %} +{{ media }} + +{% endblock %} + {% block bodyclass %}change-list{% endblock %} {% if not is_popup %}