Ticket #19235: 19235.admin-actions-template.diff

File 19235.admin-actions-template.diff, 21.4 KB (added by Julien Phalip, 11 years ago)
  • django/contrib/admin/options.py

    diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
    index 19c212d..9288073 100644
    a b class ModelAdmin(BaseModelAdmin):  
    339339    actions_on_top = True
    340340    actions_on_bottom = False
    341341    actions_selection_counter = True
     342    actions_template = None
    342343
    343344    def __init__(self, model, admin_site):
    344345        self.model = model
    class ModelAdmin(BaseModelAdmin):  
    925926        # There can be multiple action forms on the page (at the top
    926927        # and bottom of the change list, for example). Get the action
    927928        # whose button was pushed.
     929        # The request will not contain 'index' field if button layout is used.
    928930        try:
    929931            action_index = int(request.POST.get('index', 0))
    930932        except ValueError:
    class ModelAdmin(BaseModelAdmin):  
    12021204            return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
    12031205
    12041206        # If the request was POSTed, this might be a bulk action or a bulk
    1205         # edit. Try to look up an action or confirmation first, but if this
    1206         # isn't an action the POST will fall through to the bulk edit check,
    1207         # below.
    1208         action_failed = False
    1209         selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
    1210 
    1211         # Actions with no confirmation
    1212         if (actions and request.method == 'POST' and
    1213                 'index' in request.POST and '_save' not in request.POST):
    1214             if selected:
    1215                 response = self.response_action(request, queryset=cl.get_query_set(request))
    1216                 if response:
    1217                     return response
    1218                 else:
    1219                     action_failed = True
    1220             else:
    1221                 msg = _("Items must be selected in order to perform "
    1222                         "actions on them. No items have been changed.")
    1223                 self.message_user(request, msg)
    1224                 action_failed = True
    1225 
    1226         # Actions with confirmation
    1227         if (actions and request.method == 'POST' and
    1228                 helpers.ACTION_CHECKBOX_NAME in request.POST and
    1229                 'index' not in request.POST and '_save' not in request.POST):
    1230             if selected:
    1231                 response = self.response_action(request, queryset=cl.get_query_set(request))
    1232                 if response:
    1233                     return response
    1234                 else:
    1235                     action_failed = True
     1207        # edit. It is a bulk edit if '_save' is in request.POST and it is a
     1208        # bulk action if '_save' is not in request.POST.
     1209
     1210        if actions and request.method == 'POST' and '_save' not in request.POST:
     1211            response = self.response_action(request, queryset=cl.get_query_set(request))
     1212            if response:
     1213                return response
    12361214
    12371215        # If we're allowing changelist editing, we need to construct a formset
    12381216        # for the changelist given all the fields to be edited. Then we'll
    class ModelAdmin(BaseModelAdmin):  
    12401218        formset = cl.formset = None
    12411219
    12421220        # Handle POSTed bulk-edit data.
    1243         if (request.method == "POST" and cl.list_editable and
    1244                 '_save' in request.POST and not action_failed):
     1221        if request.method == 'POST' and cl.list_editable and '_save' in request.POST:
    12451222            FormSet = self.get_changelist_formset(request)
    12461223            formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
    12471224            if formset.is_valid():
  • django/contrib/admin/static/admin/js/actions.js

    diff --git a/django/contrib/admin/static/admin/js/actions.js b/django/contrib/admin/static/admin/js/actions.js
    index 8494970..c5ad2d7 100644
    a b  
    104104                $('form#changelist-form table#result_list tr').find('td:gt(0) :input').change(function() {
    105105                        list_editable_changed = true;
    106106                });
    107                 $('form#changelist-form button[name="index"]').click(function(event) {
     107                /* button[name="index"] is for select layout and button[name="action"] is for button layout */
     108                $('form#changelist-form button[name="index"], form#changelist-form button[name="action"]').click(function(event) {
    108109                        if (list_editable_changed) {
    109110                                return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
    110111                        }
     
    117118                                }
    118119                        });
    119120                        if (action_changed) {
     121                                /* action_changed will always be false in button layout. Confirmations will only be shown in select layout. */
    120122                                if (list_editable_changed) {
    121123                                        return confirm(gettext("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."));
    122124                                } else {
  • django/contrib/admin/static/admin/js/actions.min.js

    diff --git a/django/contrib/admin/static/admin/js/actions.min.js b/django/contrib/admin/static/admin/js/actions.min.js
    index 6b1947c..4caaa65 100644
    a b  
    1 (function(a){a.fn.actions=function(n){var b=a.extend({},a.fn.actions.defaults,n),e=a(this),g=false,k=function(c){c?i():j();a(e).attr("checked",c).parent().parent().toggleClass(b.selectedClass,c)},f=function(){var c=a(e).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},true));a(b.allToggle).attr("checked",function(){if(c==e.length){value=true;i()}else{value=false;l()}return value})},i=
    2 function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();a(b.allContainer).hide()},m=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},j=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},l=function(){j();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)};a(b.counterContainer).show();
    3 a(this).filter(":checked").each(function(){a(this).parent().parent().toggleClass(b.selectedClass);f();a(b.acrossInput).val()==1&&m()});a(b.allToggle).show().click(function(){k(a(this).attr("checked"));f()});a("div.actions span.question a").click(function(c){c.preventDefault();a(b.acrossInput).val(1);m()});a("div.actions span.clear a").click(function(c){c.preventDefault();a(b.allToggle).attr("checked",false);l();k(0);f()});lastChecked=null;a(e).click(function(c){if(!c)c=window.event;var d=c.target?
    4 c.target:c.srcElement;if(lastChecked&&a.data(lastChecked)!=a.data(d)&&c.shiftKey===true){var h=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))h=h?false:true;h&&a(this).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);lastChecked=d;f()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){g=
    5 true});a('form#changelist-form button[name="index"]').click(function(){if(g)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))});a('form#changelist-form input[name="_save"]').click(function(){var c=false;a("div.actions select option:selected").each(function(){if(a(this).val())c=true});if(c)return g?confirm(gettext("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.")):
    6 confirm(gettext("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."))})};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"}})(django.jQuery);
     1(function(a){a.fn.actions=function(m){var b=a.extend({},a.fn.actions.defaults,m),f=a(this),e=!1,j=function(c){c?h():i();a(f).attr("checked",c).parent().parent().toggleClass(b.selectedClass,c)},g=function(){var c=a(f).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},!0));a(b.allToggle).attr("checked",function(){c==f.length?(value=!0,h()):(value=!1,k());return value})},h=function(){a(b.acrossClears).hide();
     2a(b.acrossQuestions).show();a(b.allContainer).hide()},l=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},i=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},k=function(){i();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);
     3g();1==a(b.acrossInput).val()&&l()});a(b.allToggle).show().click(function(){j(a(this).attr("checked"));g()});a("div.actions span.question a").click(function(c){c.preventDefault();a(b.acrossInput).val(1);l()});a("div.actions span.clear a").click(function(c){c.preventDefault();a(b.allToggle).attr("checked",!1);k();j(0);g()});lastChecked=null;a(f).click(function(c){c||(c=window.event);var d=c.target?c.target:c.srcElement;if(lastChecked&&a.data(lastChecked)!=a.data(d)&&!0===c.shiftKey){var e=!1;a(lastChecked).attr("checked",
     4d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(f).each(function(){if(a.data(this)==a.data(lastChecked)||a.data(this)==a.data(d))e=e?!1:!0;e&&a(this).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);lastChecked=d;g()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){e=!0});a('form#changelist-form button[name="index"], form#changelist-form button[name="action"]').click(function(){if(e)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))});
     5a('form#changelist-form input[name="_save"]').click(function(){var b=!1;a("div.actions select option:selected").each(function(){a(this).val()&&(b=!0)});if(b)return e?confirm(gettext("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.")):confirm(gettext("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."))})};
     6a.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"}})(django.jQuery);
  • deleted file django/contrib/admin/templates/admin/actions.html

    diff --git a/django/contrib/admin/templates/admin/actions.html b/django/contrib/admin/templates/admin/actions.html
    deleted file mode 100644
    index aaaa245..0000000
    + -  
    1 {% load i18n %}
    2 <div class="actions">
    3     {% for field in action_form %}{% if field.label %}<label>{{ field.label }} {% endif %}{{ field }}{% if field.label %}</label>{% endif %}{% endfor %}
    4     <button type="submit" class="button" title="{% trans "Run the selected action" %}" name="index" value="{{ action_index|default:0 }}">{% trans "Go" %}</button>
    5     {% if actions_selection_counter %}
    6         <script type="text/javascript">var _actions_icnt="{{ cl.result_list|length|default:"0" }}";</script>
    7         <span class="action-counter">{{ selection_note }}</span>
    8         {% if cl.result_count != cl.result_list|length %}
    9         <span class="all">{{ selection_note_all }}</span>
    10         <span class="question">
    11             <a href="javascript:;" title="{% trans "Click here to select the objects across all pages" %}">{% blocktrans with cl.result_count as total_count %}Select all {{ total_count }} {{ module_name }}{% endblocktrans %}</a>
    12         </span>
    13         <span class="clear"><a href="javascript:;">{% trans "Clear selection" %}</a></span>
    14         {% endif %}
    15     {% endif %}
    16 </div>
  • new file django/contrib/admin/templates/admin/actions_as_buttons.html

    diff --git a/django/contrib/admin/templates/admin/actions_as_buttons.html b/django/contrib/admin/templates/admin/actions_as_buttons.html
    new file mode 100644
    index 0000000..604b988
    - +  
     1<div class="actions">
     2    {{ action_form.select_across }}
     3    {% for choice in action_form.fields.action.choices %}
     4        {% if choice.0 %}
     5        <button type="submit" class="button" name="action" value="{{ choice.0 }}">{{ choice.1 }}</button>
     6        {% endif %}
     7    {% endfor %}
     8    {% include "admin/actions_selection_counter.html" %}
     9</div>
  • new file django/contrib/admin/templates/admin/actions_as_select.html

    diff --git a/django/contrib/admin/templates/admin/actions_as_select.html b/django/contrib/admin/templates/admin/actions_as_select.html
    new file mode 100644
    index 0000000..d3b32e7
    - +  
     1{% load i18n %}
     2<div class="actions">
     3    {% for field in action_form %}{% if field.label %}<label>{{ field.label }} {% endif %}{{ field }}{% if field.label %}</label>{% endif %}{% endfor %}
     4    <button type="submit" class="button" title="{% trans "Run the selected action" %}" name="index" value="{{ action_index|default:0 }}">{% trans "Go" %}</button>
     5    {% include "admin/actions_selection_counter.html" %}
     6</div>
  • new file django/contrib/admin/templates/admin/actions_selection_counter.html

    diff --git a/django/contrib/admin/templates/admin/actions_selection_counter.html b/django/contrib/admin/templates/admin/actions_selection_counter.html
    new file mode 100644
    index 0000000..743bbd7
    - +  
     1{% load i18n %}
     2{% if actions_selection_counter %}
     3<span class="nowrap">
     4  <script type="text/javascript">var _actions_icnt="{{ cl.result_list|length|default:"0" }}";</script>
     5  <span class="action-counter">{{ selection_note }}</span>
     6  {% if cl.result_count != cl.result_list|length %}
     7  <span class="all">{{ selection_note_all }}</span>
     8  <span class="question">
     9    <a href="javascript:;" title="{% trans "Click here to select the objects across all pages" %}">{% blocktrans with cl.result_count as total_count %}Select all {{ total_count }} {{ module_name }}{% endblocktrans %}</a>
     10  </span>
     11  <span class="clear"><a href="javascript:;">{% trans "Clear selection" %}</a></span>
     12  {% endif %}
     13</span>
     14{% endif %}
     15 No newline at end of file
  • django/contrib/admin/templates/admin/change_list.html

    diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html
    index c72b663..b310381 100644
    a b  
    8787      {% endif %}
    8888
    8989      {% block result_list %}
    90           {% if action_form and actions_on_top and cl.full_result_count %}{% admin_actions %}{% endif %}
     90          {% if action_form and actions_on_top and cl.full_result_count %}{% admin_actions cl.model_admin %}{% endif %}
    9191          {% result_list cl %}
    92           {% if action_form and actions_on_bottom and cl.full_result_count %}{% admin_actions %}{% endif %}
     92          {% if action_form and actions_on_bottom and cl.full_result_count %}{% admin_actions cl.model_admin %}{% endif %}
    9393      {% endblock %}
    9494      {% block pagination %}{% pagination cl %}{% endblock %}
    9595      </form>
  • django/contrib/admin/templatetags/admin_list.py

    diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
    index ce435de..95a61bb 100644
    a b def admin_list_filter(cl, spec):  
    378378        'spec': spec,
    379379    }))
    380380
    381 @register.inclusion_tag('admin/actions.html', takes_context=True)
    382 def admin_actions(context):
     381@register.simple_tag(takes_context=True)
     382def admin_actions(context, model_admin):
    383383    """
    384384    Track the number of times the action field has been rendered on the page,
    385385    so we know which value to use.
    386386    """
    387387    context['action_index'] = context.get('action_index', -1) + 1
    388     return context
     388    actions_template = get_template(
     389        model_admin.actions_template or 'admin/actions_as_buttons.html')
     390    return actions_template.render(context)
  • docs/ref/contrib/admin/index.txt

    diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
    index 29ee66b..b2a5983 100644
    a b The `Overriding Admin Templates`_ section describes how to override or extend  
    940940the default admin templates.  Use the following options to override the default
    941941templates used by the :class:`ModelAdmin` views:
    942942
     943.. attribute:: ModelAdmin.actions_template
     944
     945    .. versionadded:: 1.5
     946
     947    Path to a template used for rendering the changelist's action bar. Django
     948    provides two templates: ``admin/actions_as_buttons.html`` (default)
     949    and ``admin/actions_as_select.html`` (used to be the default in version 1.4
     950    and below).
     951
    943952.. attribute:: ModelAdmin.add_form_template
    944953
    945954    Path to a custom template, used by :meth:`add_view`.
  • docs/releases/1.5.txt

    diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
    index 8b7af2c..ac8ff28 100644
    a b Minor features  
    224224
    225225Django 1.5 also includes several smaller improvements worth noting:
    226226
     227* :doc:`Admin actions</ref/contrib/admin/actions>` are now displayed as buttons
     228  by default. The rendering of actions can also be controlled via
     229  ``ModelAdmin``'s new :attr:`~django.contrib.admin.ModelAdmin.actions_template`
     230  attribute.
     231
    227232* The template engine now interprets ``True``, ``False`` and ``None`` as the
    228233  corresponding Python objects.
    229234
    Miscellaneous  
    567572  HTML validation against pre-HTML5 Strict DTDs, you should add a div around it
    568573  in your pages.
    569574
     575* The admin template ``admin/actions.html`` was removed. If you have overridden
     576  that template in your project, you may use ``ModelAdmin``'s new
     577  :attr:`~django.contrib.admin.ModelAdmin.actions_template` attribute to
     578  achieve the same type of customization.
     579
    570580Features deprecated in 1.5
    571581==========================
    572582
  • tests/regressiontests/admin_views/admin.py

    diff --git a/tests/regressiontests/admin_views/admin.py b/tests/regressiontests/admin_views/admin.py
    index a5476e9..c77d0d9 100644
    a b class PersonAdmin(admin.ModelAdmin):  
    161161    list_filter = ('gender',)
    162162    search_fields = ('^name',)
    163163    save_as = True
     164    actions_template = 'admin/actions_as_select.html'
    164165
    165166    def get_changelist_formset(self, request, **kwargs):
    166167        return super(PersonAdmin, self).get_changelist_formset(request,
    redirect_to.short_description = 'Redirect to (Awesome action)'  
    229230
    230231class ExternalSubscriberAdmin(admin.ModelAdmin):
    231232    actions = [redirect_to, external_mail]
     233    actions_template = 'admin/actions_as_select.html'
    232234
    233235
    234236class Podcast(Media):
  • tests/regressiontests/admin_views/tests.py

    diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
    index 72dc6a3..7f6143e 100644
    a b class AdminViewListEditable(TestCase):  
    17181718        # 2 inputs per object(the field and the hidden id field) = 6
    17191719        # 3 management hidden fields = 3
    17201720        # 4 action inputs (3 regular checkboxes, 1 checkbox to select all)
    1721         # main form submit button = 1
     1721        # main form submit button = 1 (assuming that select layout is used for actions)
    17221722        # search field and search submit button = 2
    17231723        # CSRF field = 1
    17241724        # field to track 'select all' across paginated views = 1
    class AdminActionsTest(TestCase):  
    23242324
    23252325    def test_actions_ordering(self):
    23262326        """
    2327         Ensure that actions are ordered as expected.
     2327        Ensure that actions are ordered as expected in select layout.
    23282328        Refs #15964.
    23292329        """
    23302330        response = self.client.get('/test_admin/admin/admin_views/externalsubscriber/')
    class AdminActionsTest(TestCase):  
    23352335<option value="external_mail">External mail (Another awesome action)</option>
    23362336</select>''', html=True)
    23372337
     2338    def test_actions_as_buttons(self):
     2339        """
     2340        Test that actions are displayed as buttons by default.
     2341        Refs #19235.
     2342        """
     2343        response = self.client.get('/test_admin/admin/admin_views/subscriber/')
     2344        # Must check separately because HTML checking does not work with more than one top-level element.
     2345        self.assertContains(response, '''
     2346<button type="submit" class="button" name="action" value="delete_selected">Delete selected subscribers</button>''', html=True)
     2347        self.assertContains(response, '''
     2348<button type="submit" class="button" name="action" value="mail_admin">Mail admin</button>''', html=True)
     2349
    23382350    def test_model_without_action(self):
    23392351        "Tests a ModelAdmin without any action"
    23402352        response = self.client.get('/test_admin/admin/admin_views/oldsubscriber/')
Back to Top