Ticket #13166: modeladmin-13166-js-warnings.diff

File modeladmin-13166-js-warnings.diff, 17.6 KB (added by Paul Smith, 14 years ago)

New patch: includes additions to actions.js that warn users when they're submitting ambiguous values.

  • django/contrib/admin/media/js/actions.min.js

     
    1 (function(a){a.fn.actions=function(g){var b=a.extend({},a.fn.actions.defaults,g),e=a(this);checker=function(c){c?showQuestion():reset();a(e).attr("checked",c).parent().parent().toggleClass(b.selectedClass,c)};updateCounter=function(){var c=a(e).filter(":checked").length;a("span._acnt").html(c);a(b.allToggle).attr("checked",function(){if(c==e.length){value=true;showQuestion()}else{value=false;clearAcross()}return value})};showQuestion=function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();
    2 a(b.allContainer).hide()};showClear=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()};reset=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()};clearAcross=function(){reset();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)};a(b.counterContainer).show();a(this).filter(":checked").each(function(){a(this).parent().parent().toggleClass(b.selectedClass);
    3 updateCounter();a(b.acrossInput).val()==1&&showClear()});a(b.allToggle).show().click(function(){checker(a(this).attr("checked"));updateCounter()});a("div.actions span.question a").click(function(c){c.preventDefault();a(b.acrossInput).val(1);showClear()});a("div.actions span.clear a").click(function(c){c.preventDefault();a(b.allToggle).attr("checked",false);clearAcross();checker(0);updateCounter()});lastChecked=null;a(e).click(function(c){if(!c)c=window.event;var d=c.target?c.target:c.srcElement;if(lastChecked&&
    4 a.data(lastChecked)!=a.data(d)&&c.shiftKey==true){var f=false;a(lastChecked).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(e).each(function(){if(a.data(this)==a.data(lastChecked)||a.data(this)==a.data(d))f=f?false:true;f&&a(this).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);lastChecked=d;updateCounter()})};a.fn.actions.defaults={actionContainer:"div.actions",
    5 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);
     1(function($){$.fn.actions=function(opts){var options=$.extend({},$.fn.actions.defaults,opts);var actionCheckboxes=$(this);var list_editable_changed=false;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);};$(options.counterContainer).show();$(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();});$("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){list_editable_changed=true;});$('form#changelist-form button[name="index"]').click(function(event){if(list_editable_changed){answer=confirm("You have unsaved changes on individual editable fields. If you run this action, your unsaved changes will be lost.");if(answer){return true;}else{return false;}}});$('form#changelist-form input[name="_save"]').click(function(event){var action_changed=false;$("div.actions select option:selected").each(function(){if($(this).val()!=""){action_changed=true;}});if(action_changed&&!list_editable_changed){answer=confirm("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button.");if(answer){return true;}else{return false;}}if(action_changed&&list_editable_changed){answer=confirm("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.");if(answer){return true;}else{return false;}}});};$.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);
  • django/contrib/admin/media/js/actions.js

     
    22        $.fn.actions = function(opts) {
    33                var options = $.extend({}, $.fn.actions.defaults, opts);
    44                var actionCheckboxes = $(this);
     5                var list_editable_changed = false;
    56                checker = function(checked) {
    67                        if (checked) {
    78                                showQuestion();
     
    9697                        lastChecked = target;
    9798                        updateCounter();
    9899                });
     100                $('form#changelist-form table#result_list tr').find('td:gt(0) :input').change(function() {
     101                    list_editable_changed = true;
     102                });
     103                $('form#changelist-form button[name="index"]').click(function(event) {
     104                    if (list_editable_changed) {
     105                        answer = confirm('You have unsaved changes on individual editable fields. If you run this action, your unsaved changes will be lost.');
     106                        if (answer) { return true }
     107                        else { return false };
     108                    }
     109                });
     110                $('form#changelist-form input[name="_save"]').click(function(event) {
     111                    var action_changed = false;
     112                    $('div.actions select option:selected').each(function() {
     113                        if ($(this).val() != "") {
     114                            action_changed = true;
     115                        }
     116                    });
     117                    if (action_changed && !list_editable_changed) {
     118                        answer = confirm("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button.");
     119                        if (answer) { return true }
     120                        else { return false };
     121                    }
     122                    if (action_changed && list_editable_changed) {
     123                        answer = confirm("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.");
     124                        if (answer) { return true }
     125                        else { return false };
     126                    }
     127                });
    99128        }
    100129        /* Setup plugin defaults */
    101130        $.fn.actions.defaults = {
  • django/contrib/admin/options.py

     
    732732
    733733            # Get the list of selected PKs. If nothing's selected, we can't
    734734            # perform an action on it, so bail. Except we want to perform
    735             # the action explicitely on all objects.
     735            # the action explicitly on all objects.
    736736            selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
    737737            if not selected and not select_across:
    738738                # Reminder that something needs to be selected or nothing will happen
     
    756756        else:
    757757            msg = _("No action selected.")
    758758            self.message_user(request, msg)
     759            return None
    759760
    760761    @csrf_protect_m
    761762    @transaction.commit_on_success
     
    974975            return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
    975976
    976977        # If the request was POSTed, this might be a bulk action or a bulk edit.
    977         # Try to look up an action or confirmation first, but if this isn't an
    978         # action the POST will fall through to the bulk edit check, below.
    979         if actions and request.method == 'POST' and (helpers.ACTION_CHECKBOX_NAME in request.POST or 'index' in request.POST):
    980             response = self.response_action(request, queryset=cl.get_query_set())
    981             if response:
    982                 return response
     978        # Try to look up an action or confirmation first, but if this isn't an action the POST
     979        # will fall through to the bulk edit check, below.
    983980
     981        action_failed = False
     982
     983        # Actions with no confirmation
     984        if actions and request.method == 'POST' and 'index' in request.POST and '_save' not in request.POST:
     985            if len(request.POST.get(helpers.ACTION_CHECKBOX_NAME, [])):
     986                response = self.response_action(request, queryset=cl.get_query_set())
     987                if response:
     988                    return response
     989                else:
     990                    action_failed = True
     991            else:
     992                msg = _("Items must be selected in order to perform actions on them. No items have been changed.")
     993                self.message_user(request, msg)
     994                action_failed = True
     995
     996        # Actions with confirmation
     997        if actions and request.method == 'POST' and helpers.ACTION_CHECKBOX_NAME in request.POST and 'index' not in request.POST and '_save' not in request.POST:
     998            if len(request.POST.get(helpers.ACTION_CHECKBOX_NAME, [])):
     999                response = self.response_action(request, queryset=cl.get_query_set())
     1000                if response:
     1001                    return response
     1002                else:
     1003                    action_failed = True
     1004
     1005
    9841006        # If we're allowing changelist editing, we need to construct a formset
    9851007        # for the changelist given all the fields to be edited. Then we'll
    9861008        # use the formset to validate/process POSTed data.
    9871009        formset = cl.formset = None
    9881010
    9891011        # Handle POSTed bulk-edit data.
    990         if request.method == "POST" and self.list_editable:
     1012        if request.method == "POST" and self.list_editable and '_save' in request.POST and not action_failed:
    9911013            FormSet = self.get_changelist_formset(request)
    9921014            formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
    9931015            if formset.is_valid():
  • django/contrib/admin/templates/admin/change_list.html

     
    8282        {% endif %}
    8383      {% endblock %}
    8484     
    85       <form action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>{% csrf_token %}
     85      <form id="changelist-form" action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>{% csrf_token %}
    8686      {% if cl.formset %}
    8787        {{ cl.formset.management_form }}
    8888      {% endif %}
  • django/contrib/admin/templates/admin/change_list_results.html

     
    11{% if results %}
    2 <table cellspacing="0">
     2<table cellspacing="0" id="result_list">
    33<thead>
    44<tr>
    55{% for header in result_headers %}<th{{ header.class_attrib }}>
  • tests/regressiontests/admin_views/tests.py

     
    10851085            "form-2-alive": "checked",
    10861086            "form-2-gender": "1",
    10871087            "form-2-id": "3",
     1088
     1089            "_save": "Save",
    10881090        }
    10891091        response = self.client.post('/test_admin/admin/admin_views/person/',
    10901092                                    data, follow=True)
     
    11051107            "form-2-alive": "checked",
    11061108            "form-2-gender": "1",
    11071109            "form-2-id": "3",
     1110
     1111            "_save": "Save",
    11081112        }
    11091113        self.client.post('/test_admin/admin/admin_views/person/', data)
    11101114
     
    11241128            "form-1-id": "3",
    11251129            "form-1-gender": "1",
    11261130            "form-1-alive": "checked",
     1131
     1132            "_save": "Save",
    11271133        }
    11281134        self.client.post('/test_admin/admin/admin_views/person/?gender__exact=1', data)
    11291135
     
    11361142            "form-MAX_NUM_FORMS": "0",
    11371143
    11381144            "form-0-id": "1",
    1139             "form-0-gender": "1"
     1145            "form-0-gender": "1",
     1146
     1147            "_save": "Save",
    11401148        }
    11411149        self.client.post('/test_admin/admin/admin_views/person/?q=mauchly', data)
    11421150
     
    11521160            "form-0-id": "2",
    11531161            "form-0-alive": "1",
    11541162            "form-0-gender": "2",
     1163
     1164            # Ensure that the form processing understands this as a list_editable "Save"
     1165            # and not an action "Go".
     1166            "_save": "Save",
    11551167        }
    11561168        response = self.client.post('/test_admin/admin/admin_views/person/', data)
    11571169        self.assertContains(response, "Grace is not a Zombie")
     
    11661178            "form-0-id": "2",
    11671179            "form-0-alive": "1",
    11681180            "form-0-gender": "2",
     1181
     1182            "_save": "Save",
    11691183        }
    11701184        response = self.client.post('/test_admin/admin/admin_views/person/', data)
    11711185        non_form_errors = response.context['cl'].formset.non_form_errors()
     
    12011215            "form-3-order": "0",
    12021216            "form-3-id": "4",
    12031217            "form-3-collector": "1",
     1218
     1219            # Ensure that the form processing understands this as a list_editable "Save"
     1220            # and not an action "Go".
     1221            "_save": "Save",
    12041222        }
    12051223        response = self.client.post('/test_admin/admin/admin_views/category/', data)
    12061224        # Successful post will redirect
     
    12121230        self.failUnlessEqual(Category.objects.get(id=3).order, 1)
    12131231        self.failUnlessEqual(Category.objects.get(id=4).order, 0)
    12141232
     1233    def test_list_editable_action_submit(self):
     1234        # List editable changes should not be executed if the action "Go" button is
     1235        # used to submit the form.
     1236        data = {
     1237            "form-TOTAL_FORMS": "3",
     1238            "form-INITIAL_FORMS": "3",
     1239            "form-MAX_NUM_FORMS": "0",
     1240
     1241            "form-0-gender": "1",
     1242            "form-0-id": "1",
     1243
     1244            "form-1-gender": "2",
     1245            "form-1-id": "2",
     1246
     1247            "form-2-alive": "checked",
     1248            "form-2-gender": "1",
     1249            "form-2-id": "3",
     1250
     1251            "index": "0",
     1252            "_selected_action": [u'3'],
     1253            "action": [u'', u'delete_selected'],
     1254        }
     1255        self.client.post('/test_admin/admin/admin_views/person/', data)
     1256
     1257        self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, True)
     1258        self.failUnlessEqual(Person.objects.get(name="Grace Hopper").gender, 1)
     1259
     1260    def test_list_editable_action_choices(self):
     1261        # List editable changes should be executed if the "Save" button is
     1262        # used to submit the form - any action choices should be ignored.
     1263        data = {
     1264            "form-TOTAL_FORMS": "3",
     1265            "form-INITIAL_FORMS": "3",
     1266            "form-MAX_NUM_FORMS": "0",
     1267
     1268            "form-0-gender": "1",
     1269            "form-0-id": "1",
     1270
     1271            "form-1-gender": "2",
     1272            "form-1-id": "2",
     1273
     1274            "form-2-alive": "checked",
     1275            "form-2-gender": "1",
     1276            "form-2-id": "3",
     1277
     1278            "_save": "Save",
     1279            "_selected_action": [u'1'],
     1280            "action": [u'', u'delete_selected'],
     1281        }
     1282        self.client.post('/test_admin/admin/admin_views/person/', data)
     1283
     1284        self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, False)
     1285        self.failUnlessEqual(Person.objects.get(name="Grace Hopper").gender, 2)
     1286
     1287
     1288
     1289
    12151290class AdminSearchTest(TestCase):
    12161291    fixtures = ['admin-views-users','multiple-child-classes']
    12171292
Back to Top