Codice sorgente per jmb.jadmin.actions

#  coding: utf-8
""".. _action-forms:

.. image:: ../images/actions.png


Action Form
==================

Admin Actions_ in daily usage often require one or more
arguments. This extension provides an easy way to display a form to
fill in what needs to be passed to the actions. Namely:

#. Create a form that holds all the fields needed for any action and
   set ``action_form`` on your JumboModelAdmin pointing to that
   form.

   .. caution:: Set any additional field as ``required = False``
      otherwise all actions that don't define that will not let
      the form validate and you'll get a ConfigurationError

   Fields  defined in this action may be used in different actions.
   A field required in one action may not be needed in another.
   See below how to obtain this

#. Configure which fields are needed for any action
   in ``action_form.fields_map`` dict. See example below

#. Modify ``change_list.html`` template to add

    + templatetag to render ``admin/actions.html`` (inluded in
      ``{jmb.jadmin}result_list``)

    + javascript to toggle visibility of the fields

   this step is already done in *jmb.jadmin* provided template


JumboModelAdmin.action_form
--------------------------------

When ModelAdmin has actions enabled, Django creates a form and places it
as attribute ``action_form``, thought is not officially documented.
This action will hold

* the selected action

* the selected ids or ``select_across`` boolean to say all records
  where selected.

Entending and modifying that form we make available any fields defined
in it.  The selected row will be available as a queryset, as usual.

You can use :meth:`JumboModelAdmin.get_action_form_instance` to
get an already validated form instance

This is used to specify a different form to add extra action fields.
To use this you need override class JumboActionForm. It's a good idea
define these forms in ``admin_forms.py`` or in ``admin/forms.py``:

.. code-block:: python

    from jmb.jadmin import JumboActionForm

    class NewsletterActionForm(JumboActionForm):
        customization = forms.CharField(widget=forms.Textarea, required=False)
        mailing_list = forms.ModelChoiceField(
            queryset=MailingList.objects.filter(status=1),
            label=_('Mailing list'),
            required=False,  # IMPORTANT
            # This is automatically added if not specified. Used from JS to toggle extra action fields
            widget=forms.Select(attrs={'id': 'mailing_list', 'class': 'extra_action_field'})
        )
       fields_map = {
           'export_action': ['output_type'],
           'clone': [],  ## not needed for actions that do not require a  field
           'send_newsletter': ['mailing_list', 'customization'],
           'resend_newsletter': ['mailing_list:required'],
        }

While your ModelAdmin can be sort of:

.. code-block:: python

    from jmb.jadmin import JumboModelAdmin
    from .admin_form import NewsletterActionForm

    class NewsletterAdmin(JumboModelAdmin):

        def send_newsletter(self, request, queryset):
            form = self.get_action_form_instance(request)
            if form.cleaned_data['field_name']:
            ...

        action_form = NewsletterActionForm

.. _fields-map:

JumboActionForm.fields_map
---------------------------------------

A dictionary to specify action and its extra fields to complete operation.
As in the example above you can have::

    fields_map = {
       'clone': [],
       'export_action': ['output_type'],
       'resend_newsletter': ['mailing_list:required', 'start_time'],
    }

* no extra fields

* optional fields: ``output_type`` and ``start_time`` have a default
  and are not required

* required fields. Example: ``send_newsletter``:
  ``['mailing_list:required']``.  When user select ``send newsletter``
  action is required to specify ``mailing_list`` to whom to send newsletter.

.. versionadded:: 0.9
   This field was previously named ``action_form_fields`` and was set on ``ModelAdmin``


Default Action Export
........................

If you use export action to export data as it is currently the deafult
in ``jmb.jadmin``, we use a ``fields_map`` as follows::

    fields_map = {
        'export_action': ['output_type'],
    }

that you must retain if you want to use that action.

JumboModelAdmin.get_action_form_instance
--------------------------------------------

It's a handy function that creates the form in the same way django
creates it, and returns a validated form.

Display optional fields in popup
================================

It's possible to have fields to show in a modal. This is particularly usefull when
the fields to be compiled are a lot.

You need to setup the template ``change_list.html`` to host the form and the needed
javascript. follows a possible implementation that is working
correctly. Be sure to understand it and change according to your needs::

  {% extends "jadmin:admin/change_list.html" %}
  {% load result_list admin_static admin_list i18n admin_urls %}


  {% block extrahead %}
    {{ block.super }}
    <script>
      $(document).ready(function ($) {
          $('select[name=action]').change(function(e){
              var value = $('select[name=action]').val();
              if (value == 'send_newsletter') {
                  $("#actionModal").modal('show');
                  $(".actions").appendTo(".modal-body");
                  $('.double').hide();
                  $('.field_action').hide();
                  $('#customization').css('width', '500px'); // a text field named customization
                  $('#actionModal').on('hide.bs.modal', function (e) {
                      $(".actions").insertAfter($('input[name=csrfmiddlewaretoken]'));
                      $('.double').show();
                      $('.field_action').show();
                      $('select[name=action]').val('').prop('selected', true).change();
                  });
              }
          });
      });
    </script>
  {% endblock %}
  {% block result_list %}
    {{ block.super }}
      <div class="modal hide" id="actionModal">
          <div class="modal-header">
              <button class="close" data-dismiss="modal">×</button>
              <h3>My super modal action</h3>

          </div>
          <div class="modal-body"></div>
      </div>
  {% endblock %}



API
===

.. autoclass:: JumboActionForm
   :members:

.. _Actions: https://docs.djangoproject.com/en/dev/ref/contrib/admin/actions/#admin-actions

"""

from __future__ import unicode_literals

from django import forms
from django.contrib.admin import helpers
from django.utils.translation import ugettext_lazy as _

from . import utils


class ConfigurationError(Exception):
    pass


def get_output_types():
    yield ('csv', 'csv')
    try:
        import xlwt
        yield ('xls', 'xls')
    except ImportError:
        pass
    try:
        import openpyxl
        yield ('xlsx', 'xlsx')
    except ImportError:
        pass

OUTPUT_TYPES = tuple(get_output_types())


def export_action(modeladmin, request, queryset, output_type=None):
    """
    action to export selected items of a changelist_view in

    :arg modeladmin: the ModelAdmin
    :arg request: A request object
    :arg queryset: a queryset
    :arg output_type: csv / xls / xlsx
    """
    if not output_type:
        form = modeladmin.get_action_form_instance(request)
        output_type = form.cleaned_data.get('output_type', 'csv')

    export_data = utils.ExportData(modeladmin, queryset)
    model_plural_name = export_data.plural_name
    filename = '{model_plural_name}.{output_type}'.format(**locals())

    if output_type in ('csv', 'xls', 'xlsx'):
        return utils.attach_response(
            export_data.export(output_type=output_type), filename, output_type)

export_action.short_description = _("Export selected %(verbose_name_plural)s")


[documenti]class JumboActionForm(helpers.ActionForm): """The default ActionForm with some logic to implement required fields according to selected action. """ output_type = forms.ChoiceField(choices=OUTPUT_TYPES, required=False) #: mapping between action name and fields needed for the action fields_map = {} def __init__(self, *args, **kwargs): super(JumboActionForm, self).__init__(*args, **kwargs) for field_name, field in self.fields.items(): if field_name in ['action', 'select_across']: continue field.widget.attrs['id'] = field_name try: field.widget.attrs['class'] = field.widget.attrs['class'] + ' extra_action_field' except KeyError: field.widget.attrs['class'] = 'extra_action_field' self.check_no_required_field()
[documenti] def check_no_required_field(self): """Check that no field is ``required`` other than ``action``. That would make the validation fail. If you need a required field for an action add ``:required`` in the declaration in ``fields_map`` as documented :ref:`above <fields-map>` """ for field_name, field in self.fields.items(): if field_name == 'action': continue if field.required: msg = _("No field in JumboActionForm can be required: change '{}'") raise ConfigurationError(msg.format(field_name))
[documenti] def get_fields_for_action(self, action=None): """Return a list of field_names for action ``action`` Parses ``fields_map`` attribute :arg action: action for which field_names are required. If action is None it looks for selected action in ``self.cleaned_data``. If no action is selected it returns an empty list """ if not action: action = self.cleaned_data['action'] fields = [fname.replace(':required', '') for fname in self.fields_map[action]] return fields
[documenti] def get_required_fields_for_action(self, action=None): """Return a list of required field_names for action ``action`` Parses ``fields_map`` attribute :arg action: action for which field_names are required. If action is None it looks for selected action in ``self.cleaned_data``. If no action is selected it returns an empty list """ if not action: action = self.cleaned_data['action'] if not not action: return [] fields = [fname.replace(':required', '') for fname in self.fields_map.get('action', []) if fname.endswith(":required")] return fields
[documenti] def clean(self): """Check that any required field is defined """ cleaned_data = super(JumboActionForm, self).clean() if (hasattr(self, 'fields_map') and 'action' in self.cleaned_data): selected_action = self.cleaned_data['action'] required_fields = self.get_required_fields_for_action(selected_action) errors = [] for field_name in required_fields: if not self.cleaned_data[field_name]: msg = _("'%(field_name)s' is required") verr = forms.ValidationError( msg, code='required', params={'field_name': self.fields[field_name].label}) self.add_error(field_name, verr) errors += [verr] return cleaned_data