Source code for jmb.core.admin.actions

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

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


Extra 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 ExtendibleModelAdmin pointing to that
   form. 

   .. caution:: Set any additional field as ``required = False``
      otherwise all actions that don't define that will will not let
      the form validate and you'll get "No action selected". See below
      the syntax to declare a required field just for one action

#. Configure which fields are needed for any action
   (``fields_map`` dict) and set attribute
   ``fields_map`` on your ExtendibleModelAdmin

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

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

    + javascript to toggle visibility of the fields

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


The ActionForm
--------------------------------

When ModelAdmin has actions enabled, Django creates a form and attaches it to 
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:`ExtendibleModelAdmin.get_action_form_instance` to
get an already validated form instance.

.. caution::

   if the form does not validate, you don't even get to the action and
   the error message will be: "No action selected".


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

    from jmb.core.admin import ExtendibleModelForm, JumboActionForm


    class NewsletterActionForm(JumboActionForm):
 
        fields_map = {    
           'clone': [],
           'send_newsletter': ['mailing_list'],
           'resend_newsletter': ['mailing_list:required'],
        }
        mailing_list = forms.ModelChoiceField(
            queryset=MailingList.objects.filter(status=1),
            label=_('Mailing list'),
            required=False,
            widget=forms.Select(attrs={'id': 'mailing_list'})
        )


    class NewsletterAdmin(ExtendibleModelAdmin):

       def send_newsletter(self, request, queryset):
           form = self.get_form_action_instance(request)
           ...

       action_form = NewsletterActionForm

.. _action-form-fields:


A dictionary to specify action and its extra fields to complete operation::


key is the action and value can be [] if there is not any extra fields
or a list of extra fields.  ``required`` it's used to specify that
that field is required. Example: ``send_newsletter``:
``['mailing_list:required']``.  When user select ``send newsletter``
action is required specify ``mailing_list`` to whom to send newsletter.

field_map
--------------------------------------------

This defines the mapping between action names and fields to be 
displayed. You can set a field as required using the 
``:required`` syntax as shown above. 



API
===

.. autoclass:: JumboActionForm
   :members:

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

"""

from django.contrib.admin import helpers

[docs]class JumboActionForm(helpers.ActionForm): """The default ActionForm with some logic to implement required fields according to selected action. """ #: mapping between action name and fields needed for the action field_map = {} def __init__(self, *args, **kwargs): super(JumboActionForm, self).__init__(*args, **kwargs) for field_name, field in self.fields.iteritems(): 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.parse_fields_map() def get_fields_for_action(self, action): fields = [fname.replace(':required', '') for fname in self.fields_map[action]] return fields def get_required_fields_for_action(self, action): fields = [fname.replace(':required', '') for fname in self.fields_map[action] if fname.endswith(":required")] return fields def parse_fields_map(self): pass
[docs] def clean(self): """Check that any :required """ cleaned_data = super(JumboActionForm, self).clean() if ( hasattr(self, 'fields_map') and 'action' in self.cleaned_data ): extra_action = self.cleaned_data['action'] all_extra_actions_args_dict = self.fields_map if extra_action in all_extra_actions_args_dict: selected_action_extra_fields = all_extra_actions_args_dict[extra_action] error_flag = False for selected_action_arg in selected_action_extra_fields: required_flag = False field = selected_action_arg if ":" in selected_action_arg: field, required = selected_action_arg.split(":") if required and required == "required": required_flag = True if required_flag and not self.cleaned_data[field]: self.fields[field].error_messages[field + "_error"] = \ _("No %s selected." % field.replace("_", " ")) error_flag = True if error_flag: raise forms.ValidationError(_("")) return cleaned_data