# 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")