Commit f109f36a authored by Jannis Leidel's avatar Jannis Leidel
Browse files

Fixed #12282 - When paginated allow selecting all items in the admin changlist.

Thanks to Martin Mahner, Rob Hudson and Zain Memon for providing inital patches and guidance.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12298 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent c14937cf
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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)

+15 −6
Original line number Diff line number Diff line
@@ -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;
}
+111 −83
Original line number Diff line number Diff line
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();
            });
(function($) {
	$.fn.actions = function(opts) {
		var options = $.extend({}, $.fn.actions.defaults, opts);
		var actionCheckboxes = $(this);
		checker = function(checked) {
			if (checked) {
				showQuestion();
			} else {
				reset();
			}
        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;
			$(actionCheckboxes).attr("checked", checked)
				.parent().parent().toggleClass(options.selectedClass, checked);
		}
                        if (inrange) {
                            actionCheckboxes[i].checked = target.checked;
                            Actions.toggleRow(actionCheckboxes[i].parentNode.parentNode, target.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();
		}
                lastChecked = target;
                Actions.counter();
            });
		showClear = function() {
			$(options.acrossClears).show();
			$(options.acrossQuestions).hide();
			$(options.actionContainer).toggleClass(options.selectedClass);
			$(options.allContainer).show();
			$(options.counterContainer).hide();
		}
        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);
		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);
		}
    },
    toggleRow: function(tr, checked) {
        if (checked && tr.className.indexOf('selected') == -1) {
            tr.className += ' selected';
        } else if (!checked) {
            tr.className = tr.className.replace(' selected', '');
		// 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();
			}
    },
    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);
		});
		$(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;
					}
    },
    counter: function() {
        counter = 0;
        for(var i = 0; i < actionCheckboxes.length; i++) {
            if(actionCheckboxes[i].checked){
                counter++;
					if (inrange) {
						$(this).attr("checked", target.checked)
							.parent().parent().toggleClass(options.selectedClass, target.checked);
					}
				});
			}
        for(var i = 0; i < counterSpans.length; i++) {
            counterSpans[i].innerHTML = counter;
			$(target).parent().parent().toggleClass(options.selectedClass, target.checked);
			lastChecked = target;
			updateCounter();
		});
	}
        selectAll.checked = (counter == actionCheckboxes.length);
	/* 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"
	}
};

addEvent(window, 'load', Actions.init);
 No newline at end of file
})(jQuery);
+1 −0
Original line number Diff line number Diff line
(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
+10 −4
Original line number Diff line number Diff line
@@ -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
Loading