From 459a714b760cf4ed9ea46ba48531573671f5fe9d Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Thu, 3 May 2012 10:51:54 +0200 Subject: [PATCH] appy.bin: adapted job.py for Appy >0.8; appy.gen: improved mail notification mechanism. --- bin/job.py | 28 ++++++---- gen/__init__.py | 21 +++++-- gen/mail.py | 117 +++++++++++++++++++++++++++++++++++++++ gen/mixins/__init__.py | 2 + gen/model.py | 19 +++---- gen/notifier.py | 105 ----------------------------------- gen/ui/appy.css | 3 +- gen/ui/banner.jpg | Bin 4470 -> 3691 bytes gen/ui/portlet.pt | 12 ++-- gen/ui/widgets/ref.pt | 4 -- gen/wrappers/__init__.py | 4 +- 11 files changed, 168 insertions(+), 147 deletions(-) create mode 100644 gen/mail.py delete mode 100644 gen/notifier.py diff --git a/bin/job.py b/bin/job.py index e740428..07c29dc 100644 --- a/bin/job.py +++ b/bin/job.py @@ -6,12 +6,13 @@ is the userName of the Zope administrator for this instance. is the path, within Zope, to the Plone Site object (if not at the root of the Zope hierarchy, use '/' as - folder separator); + folder separator); leave blank if using appy.gen > 0.8 is the name of the Appy application. If it begins with "path=", it does not represent an Appy application, but the path, within , to any Zope object - (use '/' as folder separator) + (use '/' as folder separator); leave blank if using + appy.gen > 0.8; is the name of the method to call on the tool in this Appy application, or the method to call on the arbitrary @@ -21,7 +22,7 @@ are supported). Several arguments must be separated by '*'. Note that you can also specify several commands, separated with - semicolons (";"). This scripts performes a single commit after all commands + semicolons (";"). This scripts performs a single commit after all commands have been executed. ''' @@ -58,18 +59,21 @@ else: if not hasattr(user, 'aq_base'): user = user.__of__(app.acl_users) newSecurityManager(None, user) - # Get the Plone site - ploneSite = app # Initialised with the Zope root object. - for elem in plonePath.split('/'): - ploneSite = getattr(ploneSite, elem) + # Find the root object. + rootObject = app # Initialised with the Zope root object. + if plonePath: + for elem in plonePath.split('/'): + rootObject = getattr(rootObject, elem) # If we are in a Appy application, the object on which we will call the - # method is the tool within this application. - if not appName.startswith('path='): + # method is the config object on this root object. + if not appName: + targetObject = rootObject.data.appy() + elif not appName.startswith('path='): objectName = 'portal_%s' % appName.lower() - targetObject = getattr(ploneSite, objectName).appy() + targetObject = getattr(rootObject, objectName).appy() else: - # It can be any object within the Plone site. - targetObject = ploneSite + # It can be any object. + targetObject = rootObject for elem in appName[5:].split('/'): targetObject = getattr(targetObject, elem) # Execute the method on the target object diff --git a/gen/__init__.py b/gen/__init__.py index 401a43a..477253e 100644 --- a/gen/__init__.py +++ b/gen/__init__.py @@ -6,6 +6,7 @@ from appy import Object from appy.gen.layout import Table from appy.gen.layout import defaultFieldLayouts from appy.gen.po import PoMessage +from appy.gen.mail import sendNotification from appy.gen.utils import GroupDescr, Keywords, getClassName, SomeObjects import appy.pod from appy.pod.renderer import Renderer @@ -1770,7 +1771,7 @@ class Ref(Type): if not res: return res # We add here specific Ref rules for preventing to show the field under # some inappropriate circumstances. - if (layoutType == 'edit') and self.add: return False + if (layoutType == 'edit') and (self.add or not self.link): return False if self.isBack: if layoutType == 'edit': return False else: return getattr(obj.aq_base, self.name, None) @@ -1873,7 +1874,15 @@ class Ref(Type): # Insert p_value into it. uid = value.o.UID() if uid not in refs: - refs.append(uid) + # Where must we insert the object? At the start? At the end? + if callable(self.add): + add = self.callMethod(obj, self.add) + else: + add = self.add + if add == 'start': + refs.insert(0, uid) + else: + refs.append(uid) # Update the back reference if not back: self.back.linkObject(value, obj, back=True) @@ -2125,7 +2134,7 @@ class Pod(Type): 'contact the system administrator.' DELETE_TEMP_DOC_ERROR = 'A temporary document could not be removed. %s.' def __init__(self, validator=None, index=None, default=None, - optional=False, editDefault=False, show='view', + optional=False, editDefault=False, show=('view', 'result'), page='main', group=None, layouts=None, move=0, indexed=False, searchable=False, specificReadPermission=False, specificWritePermission=False, width=None, height=None, @@ -2634,7 +2643,7 @@ class Transition: performed before calling this method). If p_doAction is False, the action that must normally be executed after the transition has been triggered will not be executed. If p_doNotify is False, the - notifications (email,...) that must normally be launched after the + email notifications that must normally be launched after the transition has been triggered will not be launched. If p_doHistory is False, there will be no trace from this transition triggering in the workflow history. If p_doSay is False, we consider the transition is @@ -2674,8 +2683,8 @@ class Transition: msg = '' if doAction and self.action: msg = self.executeAction(obj, wf) # Send notifications if needed - if doNotify and self.notify and obj.getTool(True).enableNotifications: - notifier.sendMail(obj.appy(), self, transitionName, wf) + if doNotify and self.notify and obj.getTool(True).mailEnabled: + sendNotification(obj.appy(), self, transitionName, wf) # Return a message to the user if needed if not doSay or (transitionName == '_init_'): return if not msg: msg = 'Changes saved.' # XXX Translate diff --git a/gen/mail.py b/gen/mail.py new file mode 100644 index 0000000..fc48838 --- /dev/null +++ b/gen/mail.py @@ -0,0 +1,117 @@ +'''This package contains functions for sending email notifications.''' +import smtplib +from email.MIMEMultipart import MIMEMultipart +from email.MIMEBase import MIMEBase +from email.MIMEText import MIMEText +from email import Encoders +from email.Header import Header +from appy.shared.utils import sequenceTypes + +# ------------------------------------------------------------------------------ +def sendMail(tool, to, subject, body, attachments=None): + '''Sends a mail, via p_tool.mailHost, to p_to (a single email address or a + list of email addresses).''' + # Just log things if mail is disabled + fromAddress = tool.mailFrom + if not tool.mailEnabled: + tool.log('Mail disabled: should send mail from %s to %s.' % \ + (fromAddress, str(to))) + tool.log('Subject: %s' % subject) + tool.log('Body: %s' % body) + if attachments: + tool.log('%d attachment(s).' % len(attachments)) + return + tool.log('Sending mail from %s to %s (subject: %s).' % \ + (fromAddress, str(to), subject)) + # Create the base MIME message + body = MIMEText(body, 'plain', 'utf-8') + if attachments: + msg = MIMEMultipart() + msg.attach( body ) + else: + msg = body + # Add the header values + msg['Subject'] = Header(subject, 'utf-8') + msg['From'] = fromAddress + if isinstance(to, basestring): + msg['To'] = to + else: + if len(to) == 1: + msg['To'] = to[0] + else: + msg['To'] = fromAddress + msg['Bcc'] = ', '.join(to) + to = fromAddress + # Add attachments + if attachments: + for fileName, fileContent in attachments: + part = MIMEBase('application', 'octet-stream') + if hasattr(fileContent, 'data'): + # It is a File instance coming from the database + data = fileContent.data + if isinstance(data, basestring): + payLoad = data + else: + payLoad = '' + while data is not None: + payLoad += data.data + data = data.next + else: + payLoad = fileContent + part.set_payload(payLoad) + Encoders.encode_base64(part) + part.add_header('Content-Disposition', + 'attachment; filename="%s"' % fileName) + msg.attach(part) + # Send the email + try: + mh = smtplib.SMTP(tool.mailHost) + mh.sendmail(fromAddress, [to], msg.as_string()) + mh.quit() + except smtplib.SMTPException, e: + tool.log('Mail sending failed: %s' % str(e)) + +# ------------------------------------------------------------------------------ +def sendNotification(obj, transition, transitionName, workflow): + '''Sends mail about p_transition named p_transitionName, that has been + triggered on p_obj that is controlled by p_workflow.''' + from appy.gen.descriptors import WorkflowDescriptor + wfName = WorkflowDescriptor.getWorkflowName(workflow.__class__) + zopeObj = obj.o + tool = zopeObj.getTool() + mailInfo = transition.notify(workflow, obj) + if not mailInfo[0]: return # Send a mail to nobody. + # mailInfo may be one of the following: + # (to,) + # (to, cc) + # (to, mailSubject, mailBody) + # (to, cc, mailSubject, mailBody) + # "to" and "cc" maybe simple strings (one simple string = one email + # address or one role) or sequences of strings. + # Determine mail subject and body. + if len(mailInfo) <= 2: + # The user didn't mention mail body and subject. We will use those + # defined from i18n labels. + wfHistory = zopeObj.getHistory() + labelPrefix = '%s_%s' % (wfName, transitionName) + tName = obj.translate(labelPrefix) + keys = {'siteUrl': tool.getPath('/').absolute_url(), + 'siteTitle': tool.getAppName(), + 'objectUrl': zopeObj.absolute_url(), + 'objectTitle': zopeObj.Title(), + 'transitionName': tName, + 'transitionComment': wfHistory[0]['comments']} + mailSubject = obj.translate(labelPrefix + '_mail_subject', keys) + mailBody = obj.translate(labelPrefix + '_mail_body', keys) + else: + mailSubject = mailInfo[-1] + mailBody = mailInfo[-2] + # Determine "to" and "cc". + to = mailInfo[0] + cc = [] + if (len(mailInfo) in (2,4)) and mailInfo[1]: cc = mailInfo[1] + if type(to) not in sequenceTypes: to = [to] + if type(cc) not in sequenceTypes: cc = [cc] + # Send the mail + sendMail(tool.appy(), to, mailSubject, mailBody) +# ------------------------------------------------------------------------------ diff --git a/gen/mixins/__init__.py b/gen/mixins/__init__.py index 05e9b2d..3a1cac7 100644 --- a/gen/mixins/__init__.py +++ b/gen/mixins/__init__.py @@ -1356,6 +1356,8 @@ class BaseMixin: elif format == 'js': res = text.replace('\r\n', '').replace('\n', '') res = res.replace("'", "\\'") + elif format == 'text': + res = text.replace('
', '\n') else: res = text return res diff --git a/gen/model.py b/gen/model.py index d3d45ea..d360cf2 100644 --- a/gen/model.py +++ b/gen/model.py @@ -209,10 +209,10 @@ toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns', 'enableAdvancedSearch', 'numberOfSearchColumns', 'searchFields', 'optionalFields', 'showWorkflow', 'showAllStatesInPhase') -defaultToolFields = ('title', 'users', 'groups', 'translations', 'pages', - 'enableNotifications', 'unoEnabledPython','openOfficePort', - 'numberOfResultsPerPage', 'listBoxesMaximumWidth', - 'appyVersion') +defaultToolFields = ('title', 'unoEnabledPython','openOfficePort', + 'numberOfResultsPerPage', 'mailHost', 'mailEnabled', + 'mailFrom', 'appyVersion', 'users', 'groups', + 'translations', 'pages') class Tool(ModelClass): # In a ModelClass we need to declare attributes in the following list. @@ -222,11 +222,12 @@ class Tool(ModelClass): # Tool attributes title = gen.String(show=False, page=gen.Page('main', show=False)) def validPythonWithUno(self, value): pass # Real method in the wrapper - unoEnabledPython = gen.String(group="connectionToOpenOffice", - validator=validPythonWithUno) - openOfficePort = gen.Integer(default=2002, group="connectionToOpenOffice") + unoEnabledPython = gen.String(validator=validPythonWithUno) + openOfficePort = gen.Integer(default=2002) numberOfResultsPerPage = gen.Integer(default=30) - listBoxesMaximumWidth = gen.Integer(default=100) + mailHost = gen.String(default='localhost:25') + mailEnabled = gen.Boolean(default=False) + mailFrom = gen.String(default='info@appyframework.org') appyVersion = gen.String(show=False, layouts='f') # Ref(User) will maybe be transformed into Ref(CustomUserClass). users = gen.Ref(User, multiplicity=(0,None), add=True, link=False, @@ -246,8 +247,6 @@ class Tool(ModelClass): pages = gen.Ref(Page, multiplicity=(0,None), add=True, link=False, show='view', back=gen.Ref(attribute='toTool3', show=False), page=gen.Page('pages', show='view')) - enableNotifications = gen.Boolean(default=True, - page=gen.Page('notifications', show=False)) @classmethod def _appy_clean(klass): diff --git a/gen/notifier.py b/gen/notifier.py deleted file mode 100644 index 749725b..0000000 --- a/gen/notifier.py +++ /dev/null @@ -1,105 +0,0 @@ -'''This package contains functions for sending email notifications.''' - -# ------------------------------------------------------------------------------ -def getEmailAddress(name, email, encoding='utf-8'): - '''Creates a full email address from a p_name and p_email.''' - res = email - if name: res = name.decode(encoding) + ' <%s>' % email - return res - -def convertRolesToEmails(users, portal): - '''p_users is a list of emails and/or roles. This function returns the same - list, where all roles have been expanded to emails of users having this - role (more precisely, users belonging to the group Appy created for the - given role).''' - res = [] - for mailOrRole in users: - if mailOrRole.find('@') != -1: - # It is an email. Append it directly to the result. - res.append(mailOrRole) - else: - # It is a role. Find the corresponding group (Appy creates - # one group for every role defined in the application). - groupId = mailOrRole + '_group' - group = portal.acl_users.getGroupById(groupId) - if group: - for user in group.getAllGroupMembers(): - userMail = user.getProperty('email') - if userMail and (userMail not in res): - res.append(userMail) - return res - -# ------------------------------------------------------------------------------ -SENDMAIL_ERROR = 'Error while sending mail: %s.' -ENCODING_ERROR = 'Encoding error while sending mail: %s.' - -import socket -from appy.shared.utils import sequenceTypes -from appy.gen.descriptors import WorkflowDescriptor - -def sendMail(obj, transition, transitionName, workflow): - '''Sends mail about p_transition that has been triggered on p_obj that is - controlled by p_workflow.''' - wfName = WorkflowDescriptor.getWorkflowName(workflow.__class__) - zopeObj = obj.o - tool = zopeObj.getTool() - mailInfo = transition.notify(workflow, obj) - if not mailInfo[0]: return # Send a mail to nobody. - # mailInfo may be one of the following: - # (to,) - # (to, cc) - # (to, mailSubject, mailBody) - # (to, cc, mailSubject, mailBody) - # "to" and "cc" maybe simple strings (one simple string = one email - # address or one role) or sequences of strings. - # Determine mail subject and body. - if len(mailInfo) <= 2: - # The user didn't mention mail body and subject. We will use those - # defined from i18n labels. - wfHistory = zopeObj.getHistory() - labelPrefix = '%s_%s' % (wfName, transitionName) - tName = obj.translate(labelPrefix) - keys = {'siteUrl': tool.getPath('/').absolute_url(), - 'siteTitle': tool.getAppName(), - 'objectUrl': zopeObj.absolute_url(), - 'objectTitle': zopeObj.Title(), - 'transitionName': tName, - 'transitionComment': wfHistory[0]['comments']} - mailSubject = obj.translate(labelPrefix + '_mail_subject', keys) - mailBody = obj.translate(labelPrefix + '_mail_body', keys) - else: - mailSubject = mailInfo[-1] - mailBody = mailInfo[-2] - # Determine "to" and "cc". - to = mailInfo[0] - cc = [] - if (len(mailInfo) in (2,4)) and mailInfo[1]: cc = mailInfo[1] - if type(to) not in sequenceTypes: to = [to] - if type(cc) not in sequenceTypes: cc = [cc] - # Among "to" and "cc", convert all roles to concrete email addresses - to = convertRolesToEmails(to, portal) - cc = convertRolesToEmails(cc, portal) - # Determine "from" address - enc= portal.portal_properties.site_properties.getProperty('default_charset') - fromAddress = getEmailAddress( - portal.getProperty('email_from_name'), - portal.getProperty('email_from_address'), enc) - # Send the mail - i = 0 - for recipient in to: - i += 1 - try: - if i != 1: cc = [] - portal.MailHost.secureSend(mailBody.encode(enc), - recipient.encode(enc), fromAddress.encode(enc), - mailSubject.encode(enc), mcc=cc, charset='utf-8') - except socket.error, sg: - obj.log(SENDMAIL_ERROR % str(sg), type='warning') - break - except UnicodeDecodeError, ue: - obj.log(ENCODING_ERROR % str(ue), type='warning') - break - except Exception, e: - obj.log(SENDMAIL_ERROR % str(e), type='warning') - break -# ------------------------------------------------------------------------------ diff --git a/gen/ui/appy.css b/gen/ui/appy.css index d402dff..726ff74 100644 --- a/gen/ui/appy.css +++ b/gen/ui/appy.css @@ -60,7 +60,8 @@ img {border: 0} .portletSep { border-top: 1px solid #5F7983; margin-top: 2px;} .portletPage { font-style: italic; } .portletGroup { font-variant: small-caps; font-weight: bold; font-style: normal; - margin: 0 0 0.2em 0; } + margin-top: 0.1em } +.portletSearch { font-size: 90%; font-style: italic; padding-left: 1em} .phase { border-style: dashed; border-width: thin; padding: 4px 0.6em 5px 1em;} .phaseSelected { background-color: #F4F5F6; } .content { padding: 14px 14px 9px 15px;} diff --git a/gen/ui/banner.jpg b/gen/ui/banner.jpg index d6af136852aefae7337a79da66a469f7be76ab1b..0b5db91a04de1ef2c96bf10d17707ae535e04644 100644 GIT binary patch delta 1898 zcmY*YYc!h)8cssWwmaQH%eIOcW?ia8gi>u{XSc?7L|og@B1>AGrmb5VsfK**^qi@o zgr=B@OSUoM&Y&)z2pvO0NwZ4onkw1QxI~G>En>}?nX_l#bKXDibNlnW8tcUqIc897 z?w`9qa`$p^{lvw^%hkin)vaM(2!$3k)x*AlId0gqmZtk^b24%c+H>yzzTV6fI2VAzp-{;G1`5;DKVSei-0Ok3e+-2{^`Uw&n4!MGubf{{ zBlrR1PYsZt2N{|i364&DkbBg|HOBfg)ZdbxafbF~$M*g7{?8Mlrw@lg_aO(P{X1;m z69$L>770LMMtb_jpCUhpCt3$ZKWH#H^6aOfy$Q%4_4cVodPWd$$j+%hz)eDFsjuHz zg4^?b>!YaL7wd#W=gK0kPLxoe*4ZYuZGxdbM^dZmsGw>VL+*U5=#8w>z%wPI{*=); zRuwCa8;hQ?cr`fZVP5StEzAN&S~CXEZlD+a2`I^$NdF@LE;TOKn!>+;Qr*-F@gGk3 zBC>kdBJzrE4Kmhi@t3&eyraE$dU^z0kLM8)n@Ix+Jc(rI2XQ9mjO&2;U)0;X1%Jgp zW+oc=15ish6}ZzJKAYq`M39Kw;z$gQKr3EuEpIm-4WcgBty)?x`8JXUS7v4+f*vKcg%{WUq#Fwkz zsW*Bi1g-JfD4j#PYR9b;^*Rn=R^cE5z~Vx{ShC%C>saI?BI^%X z^GVJkaCxQt1KVVidZR;ar`M}KY%qQX>lc2SL#xbu>J!FySfLnL}E>03M92tCkSpTiO z{;dG~TAPvtcD=X;K$~+lw2tg%pQ9^L)cF>>9?8<8XZWSY56joWiYIy4ivbI-Jx>`f zl$ca_DsRFvk#zasHGG3>YvnselX z)j+6^cM(1kWmR)dsIb7f;#LSHVOzT!W#T1N2RUE7BBPnAm7Lqyd&P-q1&RV zHBm47Uh+(9!kT<3R7bWK7lW$!e%BO@SzL;%6u**@di%To<8pGjPuLaT*iJRi8V29U z22)5DMz7OouPEy52K9kAriiiI_?yv;ZXtM7LtSid=h|EuA)VX7YR9Aw%!}+YkBia6 zq>_mrk&eoA-OH|>JxJc+a?U`RY@m>tKUJ3Be@)=`l(jXTIkz&I?HqU5ui$`ZeDHYw zN};W?Y?SdRiSbWHbTszYPqTs-wY^36z1hGD3OenpJPbi37=2Y+GEm*>wccIbkxJIo zizW+nqZhYhy6(-k$Fz;k%m>P+*wxZR=F7E+IK&}l%yLaejjV?#A*2^mB}SGpbW5U8 zG_=)WSvljy(csMfzdgyJuXIxZt_6leu|;PVIIb_|vlP?G z)lJwv$U@sBO#uWc*1A<|Guimy@LQ~=_0C!If0Gj^9_({=2n*?xE46a!9P!$e%00Gd zI9qL1lSqyoMT-fu^_Rb`E2ON_5#nw;I%Ny7rfXMY%*>CD3NH7i>uRc2mAi!9SEUhospE*z#j&Rn;GxDt4=(M!ZV2oV^$}Z z7;bvwyXGW2vJ7w{5yd}?^R0Zu6LjV-K#Zz*gE6%*PgkKPL$}yJ1DcJ*_LbvOLafZo zk==ApewNF+s~fALUzs|evMEl#*U7q*rL@&t4EfFA#vIGeLy4?owBeETh)?~eUrzQu5v@zmPBT-p6>nMpg%wJ%i; h@7M+AJPG*Xh2_`QF~Km812do4B>W=#QV-Z0|1bS7*SP=y delta 2677 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 b+3)&YtY&aEIjnmbq8FF`TweRY?%O{A(xqo7 diff --git a/gen/ui/portlet.pt b/gen/ui/portlet.pt index f95fa4c..a58f76a 100644 --- a/gen/ui/portlet.pt +++ b/gen/ui/portlet.pt @@ -64,20 +64,20 @@ Group name -
- +   + onClick python:'toggleCookie(\'%s\')' % group['labelId']"/>
Group searches -
+
diff --git a/gen/ui/widgets/ref.pt b/gen/ui/widgets/ref.pt index 013a311..c9e053b 100644 --- a/gen/ui/widgets/ref.pt +++ b/gen/ui/widgets/ref.pt @@ -129,7 +129,6 @@ canWrite python: not appyType['isBack'] and contextObj.allows(appyType['writePermission']); showPlusIcon python: contextObj.mayAddReference(fieldName, folder); atMostOneRef python: (appyType['multiplicity'][1] == 1) and (len(objs)<=1); - label python: contextObj.translate('label', field=appyType); addConfirmMsg python: appyType['addConfirm'] and _('%s_addConfirm' % appyType['labelId']) or ''; navBaseCall python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, contextObj.absolute_url(), fieldName, innerRef)"> @@ -142,9 +141,6 @@ Display a simplified widget if maximum number of referenced objects is 1. - - If there is no object... diff --git a/gen/wrappers/__init__.py b/gen/wrappers/__init__.py index c318345..6e89b45 100644 --- a/gen/wrappers/__init__.py +++ b/gen/wrappers/__init__.py @@ -46,9 +46,7 @@ class AbstractWrapper(object): elif name == 'url': return self.o.absolute_url() elif name == 'state': return self.o.State() elif name == 'stateLabel': - o = self.o - appName = o.getProductConfig().PROJECTNAME - return o.translate(o.getWorkflowLabel(), domain=appName) + return self.o.translate(self.o.getWorkflowLabel()) elif name == 'history': o = self.o key = o.workflow_history.keys()[0]