[gen] Pod field: more work on mailing lists.

This commit is contained in:
Gaetan Delannay 2014-09-17 14:22:36 +02:00
parent f4ea1a5570
commit e6f2b5213e
10 changed files with 154 additions and 26 deletions

View file

@ -26,13 +26,30 @@ from appy.pod import PodError
from appy.pod.renderer import Renderer from appy.pod.renderer import Renderer
from appy.shared import utils as sutils from appy.shared import utils as sutils
# ------------------------------------------------------------------------------
class Mailing:
'''Represents a mailing list as can be used by a pod field (see below).'''
def __init__(self, name=None, logins=None, subject=None, body=None):
# The mailing list name, as shown in the user interface
self.name = name
# The list of logins that will be used as recipients for sending
# emails.
self.logins = logins
# The mail subject
self.subject = subject
# The mail body
self.body = body
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class Pod(Field): class Pod(Field):
'''A pod is a field allowing to produce a (PDF, ODT, Word, RTF...) document '''A pod is a field allowing to produce a (PDF, ODT, Word, RTF...) document
from data contained in Appy class and linked objects or anything you from data contained in Appy class and linked objects or anything you
want to put in it. It is the way gen uses pod.''' want to put in it. It is the way gen uses pod.'''
# Layout for rendering a POD field for exporting query results. # Some right-aligned layouts, convenient for pod fields exporting query
rLayouts = {'view': 'fl!'} # results or multi-template pod fields.
rLayouts = {'view': Table('fl!', css_class='podTable')} # "r"ight
# "r"ight "m"ulti-template (where the global field label is not used
rmLayouts = {'view': Table('f!', css_class='podTable')}
allFormats = {'.odt': ('pdf', 'doc', 'odt'), '.ods': ('xls', 'ods')} allFormats = {'.odt': ('pdf', 'doc', 'odt'), '.ods': ('xls', 'ods')}
POD_ERROR = 'An error occurred while generating the document. Please ' \ POD_ERROR = 'An error occurred while generating the document. Please ' \
@ -55,58 +72,72 @@ class Pod(Field):
pxView = pxCell = Px(''' pxView = pxCell = Px('''
<x var="uid=obj.uid" <x var="uid=obj.uid"
for="info in field.getVisibleTemplates(obj)"> for="info in field.getVisibleTemplates(obj)"
var2="mailings=field.getVisibleMailings(obj, info.template);
lineBreak=((loop.info.nb + 1) % field.maxPerRow) == 0">
<x for="fmt in info.formats" <x for="fmt in info.formats"
var2="freezeAllowed=(fmt in info.freezeFormats) and \ var2="freezeAllowed=(fmt in info.freezeFormats) and \
(field.show != 'result'); (field.show != 'result');
hasMailings=mailings and (fmt in mailings);
dropdownEnabled=freezeAllowed or hasMailings;
frozen=field.isFrozen(obj, info.template, fmt)"> frozen=field.isFrozen(obj, info.template, fmt)">
<!-- A clickable icon if no freeze action is allowed --> <!-- A clickable icon if no freeze action is allowed and no mailing is
<x if="not freezeAllowed">:field.pxIcon</x> available for this format -->
<x if="not dropdownEnabled">:field.pxIcon</x>
<!-- A clickable icon and a dropdown menu else. --> <!-- A clickable icon and a dropdown menu else. -->
<span if="freezeAllowed" class="dropdownMenu" <span if="dropdownEnabled" class="dropdownMenu"
var2="dropdownId='%s_%s' % (uid, \ var2="dropdownId='%s_%s' % (uid, \
field.getFreezeName(info.template, fmt, sep='_'))" field.getFreezeName(info.template, fmt, sep='_'))"
onmouseover=":'toggleDropdown(%s)' % q(dropdownId)" onmouseover=":'toggleDropdown(%s)' % q(dropdownId)"
onmouseout=":'toggleDropdown(%s,%s)' % (q(dropdownId), q('none'))"> onmouseout=":'toggleDropdown(%s,%s)' % (q(dropdownId), q('none'))">
<x>:field.pxIcon</x> <x>:field.pxIcon</x>
<!-- The dropdown menu containing freeze actions --> <!-- The dropdown menu containing freeze actions -->
<table id=":dropdownId" class="dropdown" width="75px"> <table id=":dropdownId" class="dropdown" width="100px">
<!-- Unfreeze --> <!-- Unfreeze -->
<tr if="frozen" valign="top"> <tr if="freezeAllowed and frozen" valign="top">
<td> <td width="85px">
<a onclick=":'freezePod(%s,%s,%s,%s,%s)' % (q(uid), q(name), \ <a onclick=":'freezePod(%s,%s,%s,%s,%s)' % (q(uid), q(name), \
q(info.template), q(fmt), q('unfreeze'))" q(info.template), q(fmt), q('unfreeze'))"
class="smaller">:_('unfreezeField')</a> class="smaller">:_('unfreezeField')</a>
</td> </td>
<td align="center"><img src=":url('unfreeze')"/></td> <td width="15px"><img src=":url('unfreeze')"/></td>
</tr> </tr>
<!-- (Re-)freeze --> <!-- (Re-)freeze -->
<tr valign="top"> <tr if="freezeAllowed" valign="top">
<td> <td width="85px">
<a onclick=":'freezePod(%s,%s,%s,%s,%s)' % (q(uid), q(name), \ <a onclick=":'freezePod(%s,%s,%s,%s,%s)' % (q(uid), q(name), \
q(info.template), q(fmt), q('freeze'))" q(info.template), q(fmt), q('freeze'))"
class="smaller">:_('freezeField')</a> class="smaller">:_('freezeField')</a>
</td> </td>
<td align="center"><img src=":url('freeze')"/></td> <td width="15px"><img src=":url('freeze')"/></td>
</tr> </tr>
<!-- (Re-)upload --> <!-- (Re-)upload -->
<tr valign="top"> <tr if="freezeAllowed" valign="top">
<td> <td width="85px">
<a onclick=":'uploadPod(%s,%s,%s,%s)' % (q(uid), q(name), \ <a onclick=":'uploadPod(%s,%s,%s,%s)' % (q(uid), q(name), \
q(info.template), q(fmt))" q(info.template), q(fmt))"
class="smaller">:_('uploadField')</a> class="smaller">:_('uploadField')</a>
</td> </td>
<td align="center"><img src=":url('upload')"/></td> <td width="15px"><img src=":url('upload')"/></td>
</tr> </tr>
<!-- Mailing lists -->
<x if="hasMailings" var2="sendLabel=_('email_send')">
<tr for="mailing in mailings[fmt]" valign="top"
var2="mailingName=field.getMailingName(obj, mailing)">
<td width="85px"><span title=":sendLabel">:mailingName</span></td>
<td width="15px"><img src=":url('email')"/></td>
</tr>
</x>
</table> </table>
</span> </span>
</x> </x>
<!-- Show the specific template name only if there is more than one <!-- Show the specific template name only if there is more than one
template. For a single template, the field label already does the template. For a single template, the field label already does the
job. --> job. -->
<span if="len(field.template) &gt; 1" <span if="len(field.template) &gt; 1"
class=":not loop.info.last and 'pod smaller' or \ class=":(not loop.info.last and not lineBreak) and 'pod smaller' \
'smaller'">:field.getTemplateName(obj, info.template)</span> or 'smaller'">:field.getTemplateName(obj, info.template)</span>
<br if="lineBreak"/>
</x>''') </x>''')
pxEdit = pxSearch = '' pxEdit = pxSearch = ''
@ -118,8 +149,9 @@ class Pod(Field):
maxChars=None, colspan=1, master=None, masterValue=None, maxChars=None, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None, focus=False, historized=False, mapping=None, label=None,
template=None, templateName=None, showTemplate=None, template=None, templateName=None, showTemplate=None,
freezeTemplate=None, context=None, stylesMapping={}, freezeTemplate=None, maxPerRow=5, context=None,
formats=None, getChecked=None): stylesMapping={}, formats=None, getChecked=None, mailing=None,
mailingName=None, showMailing=None, mailingInfo=None):
# Param "template" stores the path to the pod template(s). If there is # Param "template" stores the path to the pod template(s). If there is
# a single template, a string is expected. Else, a list or tuple of # a single template, a string is expected. Else, a list or tuple of
# strings is expected. Every such path must be relative to your # strings is expected. Every such path must be relative to your
@ -186,6 +218,9 @@ class Pod(Field):
# - upload a document: the frozen or uploaded document will be replaced # - upload a document: the frozen or uploaded document will be replaced
# by a new document uploaded by the current user. # by a new document uploaded by the current user.
self.freezeTemplate = freezeTemplate self.freezeTemplate = freezeTemplate
# If p_template contains more than 1 template, "maxPerRow" tells how
# much templates must appear side by side.
self.maxPerRow = maxPerRow
# The context is a dict containing a specific pod context, or a method # The context is a dict containing a specific pod context, or a method
# that returns such a dict. # that returns such a dict.
self.context = context self.context = context
@ -203,6 +238,33 @@ class Pod(Field):
# objects linked via the Ref field that are currently selected in the # objects linked via the Ref field that are currently selected in the
# user interface. # user interface.
self.getChecked = getChecked self.getChecked = getChecked
# Mailing lists can be defined for this pod field. For every visible
# mailing list, a menu item will be available in the user interface and
# will allow to send the pod result as attachment to the mailing list
# recipients. Attribute p_mailing stores a mailing list's id
# (as a string) or a list of ids.
self.mailing = mailing
if isinstance(mailing, basestring):
self.mailing = [mailing]
elif isinstance(mailing, tuple):
self.mailing = list(mailing)
# "mailingName" returns the name of the mailing as will be shown in the
# user interface. It must be a method accepting the mailing list id
# (from self.mailing) as single arg and returning the mailing list's
# name.
self.mailingName = mailingName
# "showMailing" below determines when the mailing list(s) must be shown.
# It may store a method accepting a mailing list's id (among
# self.mailing) and a template (among self.template) and returning the
# list or tuple of formats for which the pod result can be sent to the
# mailing list. If no such method is defined, the mailing list will be
# available for all visible templates and formats.
self.showMailing = showMailing
# When it it time to send an email, "mailingInfo" gives all the
# necessary information for this email: recipients, subject, body. It
# must be a method whose single arg is the mailing id (from
# self.mailing) and that returns an instance of class Mailing (above).
self.mailingInfo = mailingInfo
Field.__init__(self, None, (0,1), default, show, page, group, layouts, Field.__init__(self, None, (0,1), default, show, page, group, layouts,
move, indexed, searchable, specificReadPermission, move, indexed, searchable, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
@ -257,9 +319,9 @@ class Pod(Field):
'''Gets the name of a template given its p_fileName.''' '''Gets the name of a template given its p_fileName.'''
res = None res = None
if self.templateName: if self.templateName:
# Use the method specified in self.templateName. # Use the method specified in self.templateName
res = self.templateName(obj, fileName) res = self.templateName(obj, fileName)
# Else, deduce a nice name from p_fileName. # Else, deduce a nice name from p_fileName
if not res: if not res:
name = os.path.splitext(os.path.basename(fileName))[0] name = os.path.splitext(os.path.basename(fileName))[0]
res = gutils.produceNiceMessage(name) res = gutils.produceNiceMessage(name)
@ -311,6 +373,38 @@ class Pod(Field):
freezeFormats=self.getFreezeFormats(obj, template))) freezeFormats=self.getFreezeFormats(obj, template)))
return res return res
def getVisibleMailings(self, obj, template):
'''Gets, among self.mailing, the mailing(s) that can be shown for
p_template, as a dict ~{s_format:[s_id]}~.'''
if not self.mailing: return
res = {}
for mailing in self.mailing:
# Is this mailing visible ? In which format(s) ?
if not self.showMailing:
# By default, the mailing is available in any format
formats = True
else:
formats = self.showMailing(obj, mailing, template)
if not formats: continue
if isinstance(formats, bool): formats = self.formats
elif isinstance(formats, basestring): formats = (formats,)
# Add this mailing to the result
for fmt in formats:
if fmt in res: res[fmt].append(mailing)
else: res[fmt] = [mailing]
return res
def getMailingName(self, obj, mailing):
'''Gets the name of a particular p_mailing.'''
res = None
if self.mailingName:
# Use the method specified in self.mailingName
res = self.mailingName(obj, mailing)
if not res:
# Deduce a nice name from p_mailing
res = gutils.produceNiceMessage(mailing)
return res
def getValue(self, obj, template=None, format=None, result=None, def getValue(self, obj, template=None, format=None, result=None,
queryData=None, customContext=None, noSecurity=False): queryData=None, customContext=None, noSecurity=False):
'''For a pod field, getting its value means computing a pod document or '''For a pod field, getting its value means computing a pod document or

View file

@ -718,3 +718,7 @@ msgstr ""
#. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above." #. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above."
msgid "wrong_browser" msgid "wrong_browser"
msgstr "" msgstr ""
#. Default: "Send by email"
msgid "email_send"
msgstr ""

View file

@ -718,3 +718,7 @@ msgstr ""
#. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above." #. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above."
msgid "wrong_browser" msgid "wrong_browser"
msgstr "" msgstr ""
#. Default: "Send by email"
msgid "email_send"
msgstr ""

View file

@ -718,3 +718,7 @@ msgstr ""
#. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above." #. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above."
msgid "wrong_browser" msgid "wrong_browser"
msgstr "" msgstr ""
#. Default: "Send by email"
msgid "email_send"
msgstr ""

View file

@ -719,3 +719,7 @@ msgstr "You are not allowed to consult this."
#. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above." #. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above."
msgid "wrong_browser" msgid "wrong_browser"
msgstr "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above." msgstr "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above."
#. Default: "Send by email"
msgid "email_send"
msgstr "Send by email"

View file

@ -718,3 +718,7 @@ msgstr ""
#. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above." #. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above."
msgid "wrong_browser" msgid "wrong_browser"
msgstr "" msgstr ""
#. Default: "Send by email"
msgid "email_send"
msgstr ""

View file

@ -719,3 +719,7 @@ msgstr "Vous n'êtes pas autorisé à consulter ceci."
#. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above." #. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above."
msgid "wrong_browser" msgid "wrong_browser"
msgstr "Microsoft Internet Explorer ${version} n'est pas supporté. Veuillez mettre à jour votre navigateur à la version ${min} ou supérieure." msgstr "Microsoft Internet Explorer ${version} n'est pas supporté. Veuillez mettre à jour votre navigateur à la version ${min} ou supérieure."
#. Default: "Send by email"
msgid "email_send"
msgstr "Envoyer par email"

View file

@ -718,3 +718,7 @@ msgstr ""
#. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above." #. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above."
msgid "wrong_browser" msgid "wrong_browser"
msgstr "" msgstr ""
#. Default: "Send by email"
msgid "email_send"
msgstr ""

View file

@ -718,3 +718,7 @@ msgstr ""
#. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above." #. Default: "Microsoft Internet Explorer ${version} is not supported. Please upgrade your browser to version ${min} or above."
msgid "wrong_browser" msgid "wrong_browser"
msgstr "" msgstr ""
#. Default: "Send by email"
msgid "email_send"
msgstr ""

View file

@ -100,9 +100,10 @@ td.search { padding-top: 8px }
.popup { display: none; position: absolute; top: 30%; left: 35%; .popup { display: none; position: absolute; top: 30%; left: 35%;
width: 350px; z-index : 100; background: white; padding: 8px; width: 350px; z-index : 100; background: white; padding: 8px;
border: 1px solid grey; box-shadow: 2px 2px 2px #888888} border: 1px solid grey; box-shadow: 2px 2px 2px #888888}
.dropdown { display:none; position: absolute; border: 1px solid #cccccc; .dropdown { display:none; position: absolute; top: 15px; left: 1px;
background-color: white; padding: 3px 4px 0; font-size: 8pt; border: 1px solid #cccccc; background-color: white;
font-weight: normal; text-align: left; z-index: 2 } padding: 3px 4px 0; font-size: 8pt; font-weight: normal;
text-align: left; z-index: 2; line-height: normal }
.dropdownMenu { cursor: pointer; font-size: 93%; position: relative } .dropdownMenu { cursor: pointer; font-size: 93%; position: relative }
.dropdown a:hover { text-decoration: underline } .dropdown a:hover { text-decoration: underline }
.addForm { display: inline } .addForm { display: inline }
@ -164,6 +165,7 @@ td.search { padding-top: 8px }
.error { margin: 5px } .error { margin: 5px }
.smaller { font-size: 95% } .smaller { font-size: 95% }
.pod { padding-right: 15px } .pod { padding-right: 15px }
.podTable { line-height: 20px }
.cbCell { width: 10px; text-align: center} .cbCell { width: 10px; text-align: center}
.tabs { position:relative; bottom:-2px } .tabs { position:relative; bottom:-2px }
.tab { padding: 0 10px 0 10px; text-align: center; font-size: 90%; .tab { padding: 0 10px 0 10px; text-align: center; font-size: 90%;