Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit f109f36a06c362f3a2f698648a9cc3917abbfccd 1 parent c14937c
Jannis Leidel authored
2  django/contrib/admin/helpers.py
@@ -17,6 +17,8 @@
17 17
 
18 18
 class ActionForm(forms.Form):
19 19
     action = forms.ChoiceField(label=_('Action:'))
  20
+    select_across = forms.BooleanField(label='', required=False, initial=0,
  21
+        widget=forms.HiddenInput({'class': 'select-across'}))
20 22
 
21 23
 checkbox = forms.CheckboxInput({'class': 'action-select'}, lambda value: False)
22 24
 
21  django/contrib/admin/media/css/changelists.css
@@ -228,12 +228,6 @@
228 228
     border-right: 1px solid #ddd;
229 229
 }
230 230
 
231  
-.action_counter{
232  
-    font-size: 11px;
233  
-    margin: 0 0.5em;
234  
-    display: none;
235  
-}
236  
-
237 231
 #changelist table input {
238 232
     margin: 0;
239 233
 }
@@ -250,6 +244,21 @@
250 244
     background: white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x;
251 245
 }
252 246
 
  247
+#changelist .actions.selected {
  248
+    background: #fffccf;
  249
+    border-top: 1px solid #fffee8;
  250
+    border-bottom: 1px solid #edecd6;
  251
+}
  252
+
  253
+#changelist .actions span.all,
  254
+#changelist .actions span.action-counter,
  255
+#changelist .actions span.clear,
  256
+#changelist .actions span.question {
  257
+    font-size: 11px;
  258
+    margin: 0 0.5em;
  259
+    display: none;
  260
+}
  261
+
253 262
 #changelist .actions:last-child {
254 263
     border-bottom: none;
255 264
 }
194  django/contrib/admin/media/js/actions.js
... ...
@@ -1,83 +1,111 @@
1  
-var Actions = {
2  
-    init: function() {
3  
-        counterSpans = document.getElementsBySelector('span._acnt');
4  
-        counterContainer = document.getElementsBySelector('span.action_counter');
5  
-        actionCheckboxes = document.getElementsBySelector('tr input.action-select');
6  
-        selectAll = document.getElementById('action-toggle');
7  
-        lastChecked = null;
8  
-        for(var i = 0; i < counterContainer.length; i++) {
9  
-            counterContainer[i].style.display = 'inline';
10  
-        }
11  
-        if (selectAll) {
12  
-            selectAll.style.display = 'inline';
13  
-            addEvent(selectAll, 'click', function() {
14  
-                Actions.checker(selectAll.checked);
15  
-                Actions.counter();
16  
-            });
17  
-        }
18  
-        for(var i = 0; i < actionCheckboxes.length; i++) {
19  
-            addEvent(actionCheckboxes[i], 'click', function(e) {
20  
-                if (!e) { var e = window.event; }
21  
-                var target = e.target ? e.target : e.srcElement;
22  
-                if (lastChecked && lastChecked != target && e.shiftKey == true) {
23  
-                    var inrange = false;
24  
-                    lastChecked.checked = target.checked;
25  
-                    Actions.toggleRow(lastChecked.parentNode.parentNode, target.checked);
26  
-                    for (var i = 0; i < actionCheckboxes.length; i++) {
27  
-                        if (actionCheckboxes[i] == lastChecked || actionCheckboxes[i] == target) {
28  
-                            inrange = (inrange) ? false : true;
29  
-                        }
30  
-                        if (inrange) {
31  
-                            actionCheckboxes[i].checked = target.checked;
32  
-                            Actions.toggleRow(actionCheckboxes[i].parentNode.parentNode, target.checked);
33  
-                        }
34  
-                    }
35  
-                }
36  
-                lastChecked = target;
37  
-                Actions.counter();
38  
-            });
39  
-        }
40  
-        var changelistTable = document.getElementsBySelector('#changelist table')[0];
41  
-        if (changelistTable) {
42  
-            addEvent(changelistTable, 'click', function(e) {
43  
-                if (!e) { var e = window.event; }
44  
-                var target = e.target ? e.target : e.srcElement;
45  
-                if (target.nodeType == 3) { target = target.parentNode; }
46  
-                if (target.className == 'action-select') {
47  
-                    var tr = target.parentNode.parentNode;
48  
-                    Actions.toggleRow(tr, target.checked);
49  
-                }
50  
-            });
51  
-        }
52  
-    },
53  
-    toggleRow: function(tr, checked) {
54  
-        if (checked && tr.className.indexOf('selected') == -1) {
55  
-            tr.className += ' selected';
56  
-        } else if (!checked) {
57  
-            tr.className = tr.className.replace(' selected', '');
58  
-        }  
59  
-    },
60  
-    checked: function() {
61  
-        selectAll.checked = false;
62  
-    },
63  
-    checker: function(checked) {
64  
-        for(var i = 0; i < actionCheckboxes.length; i++) {
65  
-            actionCheckboxes[i].checked = checked;
66  
-            Actions.toggleRow(actionCheckboxes[i].parentNode.parentNode, checked);
67  
-        }
68  
-    },
69  
-    counter: function() {
70  
-        counter = 0;
71  
-        for(var i = 0; i < actionCheckboxes.length; i++) {
72  
-            if(actionCheckboxes[i].checked){
73  
-                counter++;
74  
-            }
75  
-        }
76  
-        for(var i = 0; i < counterSpans.length; i++) {
77  
-            counterSpans[i].innerHTML = counter;
78  
-        }
79  
-        selectAll.checked = (counter == actionCheckboxes.length);
80  
-    }
81  
-};
82  
-
83  
-addEvent(window, 'load', Actions.init);
  1
+(function($) {
  2
+	$.fn.actions = function(opts) {
  3
+		var options = $.extend({}, $.fn.actions.defaults, opts);
  4
+		var actionCheckboxes = $(this);
  5
+		checker = function(checked) {
  6
+			if (checked) {
  7
+				showQuestion();
  8
+			} else {
  9
+				reset();
  10
+			}
  11
+			$(actionCheckboxes).attr("checked", checked)
  12
+				.parent().parent().toggleClass(options.selectedClass, checked);
  13
+		}
  14
+		updateCounter = function() {
  15
+			var count = $(actionCheckboxes).filter(":checked").length;
  16
+			$("span._acnt").html(count);
  17
+			$(options.allToggle).attr("checked", function() {
  18
+				if (count == actionCheckboxes.length) {
  19
+					value = true;
  20
+					showQuestion();
  21
+				} else {
  22
+					value = false;
  23
+					clearAcross();
  24
+				}
  25
+				return value
  26
+			});
  27
+		}
  28
+		showQuestion = function() {
  29
+			$(options.acrossClears).hide();
  30
+			$(options.acrossQuestions).show();
  31
+			$(options.allContainer).hide();
  32
+		}
  33
+		showClear = function() {
  34
+			$(options.acrossClears).show();
  35
+			$(options.acrossQuestions).hide();
  36
+			$(options.actionContainer).toggleClass(options.selectedClass);
  37
+			$(options.allContainer).show();
  38
+			$(options.counterContainer).hide();
  39
+		}
  40
+		reset = function() {
  41
+			$(options.acrossClears).hide();
  42
+			$(options.acrossQuestions).hide();
  43
+			$(options.allContainer).hide();
  44
+			$(options.counterContainer).show();
  45
+		}
  46
+		clearAcross = function() {
  47
+			reset();
  48
+			$(options.acrossInput).val(0);
  49
+			$(options.actionContainer).removeClass(options.selectedClass);
  50
+		}
  51
+		// Show counter by default
  52
+		$(options.counterContainer).show();
  53
+		// Check state of checkboxes and reinit state if needed
  54
+		$(this).filter(":checked").each(function(i) {
  55
+			$(this).parent().parent().toggleClass(options.selectedClass);
  56
+			updateCounter();
  57
+			if ($(options.acrossInput).val() == 1) {
  58
+				showClear();
  59
+			}
  60
+		});
  61
+		$(options.allToggle).show().click(function() {
  62
+			checker($(this).attr("checked"));
  63
+			updateCounter();
  64
+		});
  65
+		$("div.actions span.question a").click(function(event) {
  66
+			event.preventDefault();
  67
+			$(options.acrossInput).val(1);
  68
+			showClear();
  69
+		});
  70
+		$("div.actions span.clear a").click(function(event) {
  71
+			event.preventDefault();
  72
+			$(options.allToggle).attr("checked", false);
  73
+			clearAcross();
  74
+			checker(0);
  75
+			updateCounter();
  76
+		});
  77
+		lastChecked = null;
  78
+		$(actionCheckboxes).click(function(event) {
  79
+			if (!event) { var event = window.event; }
  80
+			var target = event.target ? event.target : event.srcElement;
  81
+			if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey == true) {
  82
+				var inrange = false;
  83
+				$(lastChecked).attr("checked", target.checked)
  84
+					.parent().parent().toggleClass(options.selectedClass, target.checked);
  85
+				$(actionCheckboxes).each(function() {
  86
+					if ($.data(this) == $.data(lastChecked) || $.data(this) == $.data(target)) {
  87
+						inrange = (inrange) ? false : true;
  88
+					}
  89
+					if (inrange) {
  90
+						$(this).attr("checked", target.checked)
  91
+							.parent().parent().toggleClass(options.selectedClass, target.checked);
  92
+					}
  93
+				});
  94
+			}
  95
+			$(target).parent().parent().toggleClass(options.selectedClass, target.checked);
  96
+			lastChecked = target;
  97
+			updateCounter();
  98
+		});
  99
+	}
  100
+	/* Setup plugin defaults */
  101
+	$.fn.actions.defaults = {
  102
+		actionContainer: "div.actions",
  103
+		counterContainer: "span.action-counter",
  104
+		allContainer: "div.actions span.all",
  105
+		acrossInput: "div.actions input.select-across",
  106
+		acrossQuestions: "div.actions span.question",
  107
+		acrossClears: "div.actions span.clear",
  108
+		allToggle: "#action-toggle",
  109
+		selectedClass: "selected"
  110
+	}
  111
+})(jQuery);
1  django/contrib/admin/media/js/actions.min.js
... ...
@@ -0,0 +1 @@
  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);
14  django/contrib/admin/options.py
@@ -268,7 +268,7 @@ def _media(self):
268 268
 
269 269
         js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
270 270
         if self.actions is not None:
271  
-            js.extend(['js/getElementsBySelector.js', 'js/actions.js'])
  271
+            js.extend(['js/jquery.min.js', 'js/actions.min.js'])
272 272
         if self.prepopulated_fields:
273 273
             js.append('js/urlify.js')
274 274
         if self.opts.get_ordered_objects():
@@ -724,18 +724,24 @@ def response_action(self, request, queryset):
724 724
         # If the form's valid we can handle the action.
725 725
         if action_form.is_valid():
726 726
             action = action_form.cleaned_data['action']
  727
+            select_across = action_form.cleaned_data['select_across']
727 728
             func, name, description = self.get_actions(request)[action]
728 729
 
729 730
             # Get the list of selected PKs. If nothing's selected, we can't
730  
-            # perform an action on it, so bail.
  731
+            # perform an action on it, so bail. Except we want to perform
  732
+            # the action explicitely on all objects.
731 733
             selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
732  
-            if not selected:
  734
+            if not selected and not select_across:
733 735
                 # Reminder that something needs to be selected or nothing will happen
734 736
                 msg = _("Items must be selected in order to perform actions on them. No items have been changed.")
735 737
                 self.message_user(request, msg)
736 738
                 return None
737 739
 
738  
-            response = func(self, request, queryset.filter(pk__in=selected))
  740
+            if not select_across:
  741
+                # Perform the action only on the selected objects
  742
+                queryset = queryset.filter(pk__in=selected)
  743
+
  744
+            response = func(self, request, queryset)
739 745
 
740 746
             # Actions may return an HttpResponse, which will be used as the
741 747
             # response from the POST. If not, we'll be a good little HTTP
18  django/contrib/admin/templates/admin/actions.html
... ...
@@ -1,10 +1,20 @@
1 1
 {% load i18n %}
2 2
 <div class="actions">
3  
-    {% for field in action_form %}<label>{{ field.label }} {{ field }}</label>{% endfor %}
  3
+    {% for field in action_form %}{% if field.label %}<label>{{ field.label }} {% endif %}{{ field }}{% if field.label %}</label>{% endif %}{% endfor %}
4 4
     <button type="submit" class="button" title="{% trans "Run the selected action" %}" name="index" value="{{ action_index|default:0 }}">{% trans "Go" %}</button>
5 5
     {% if actions_selection_counter %}
6  
-    <span class="action_counter">
7  
-      {% blocktrans with cl.result_count as total_count %}<span class="_acnt">0</span> of {{ total_count }} {{ module_name }} selected{% endblocktrans %}
8  
-    </span>
  6
+        <span class="action-counter">
  7
+            {% blocktrans with cl.result_count as total_count %}<span class="_acnt">0</span> of {{ total_count }} {{ module_name }} selected{% endblocktrans %}
  8
+        </span>
  9
+        {% if cl.result_count != cl.result_list|length %}
  10
+        <span class="all">
  11
+            {% blocktrans with cl.result_count as total_count %}All {{ total_count }} {{ module_name }} selected{% endblocktrans %}
  12
+        </span>
  13
+        <span class="question">
  14
+            <a href="javascript:;" title="{% trans "Click here to select all objects across all pages" %}">{% blocktrans with cl.result_count as total_count %}Select all {{ total_count }} {{ module_name }}{% endblocktrans %}</a>
  15
+        </span>
  16
+        <span class="clear"><a href="javascript:;">{% trans "Clear selection" %}</a></span>
  17
+        {% endif %}
9 18
     {% endif %}
10 19
 </div>
  20
+
10  django/contrib/admin/templates/admin/change_list.html
@@ -17,6 +17,16 @@
17 17
   {% endif %}
18 18
 {% endblock %}
19 19
 
  20
+{% block extrahead %}
  21
+{{ media }}
  22
+<script type="text/javascript">
  23
+jQuery.noConflict();
  24
+jQuery(document).ready(function($) {
  25
+    $("tr input.action-select").actions();
  26
+});
  27
+</script>
  28
+{% endblock %}
  29
+
20 30
 {% block bodyclass %}change-list{% endblock %}
21 31
 
22 32
 {% if not is_popup %}

0 notes on commit f109f36

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