[CMF-checkins] SVN: CMF/trunk/C - added 'email_charset' property and 'makeEmail' function

Yvo Schubbe y.2006_ at wcm-solutions.de
Sun Oct 15 10:49:23 EDT 2006


Log message for revision 70654:
  - added 'email_charset' property and 'makeEmail' function
  - fixed encoding issues in registered_email and password_email

Changed:
  U   CMF/trunk/CHANGES.txt
  U   CMF/trunk/CMFDefault/profiles/default/properties.xml
  U   CMF/trunk/CMFDefault/skins/zpt_control/portal_config_control.py
  U   CMF/trunk/CMFDefault/skins/zpt_generic/password_email.py
  U   CMF/trunk/CMFDefault/skins/zpt_generic/password_email_template.pt
  U   CMF/trunk/CMFDefault/skins/zpt_generic/reconfig_form.py
  U   CMF/trunk/CMFDefault/skins/zpt_generic/reconfig_template.pt
  U   CMF/trunk/CMFDefault/skins/zpt_generic/registered_email.py
  U   CMF/trunk/CMFDefault/skins/zpt_generic/registered_email_template.pt
  U   CMF/trunk/CMFDefault/tests/RegistrationTool.txt
  U   CMF/trunk/CMFDefault/tests/test_RegistrationTool.py
  U   CMF/trunk/CMFDefault/utils.py

-=-
Modified: CMF/trunk/CHANGES.txt
===================================================================
--- CMF/trunk/CHANGES.txt	2006-10-15 14:43:52 UTC (rev 70653)
+++ CMF/trunk/CHANGES.txt	2006-10-15 14:49:21 UTC (rev 70654)
@@ -2,12 +2,22 @@
 
   New Features
 
+    - Portal: Added 'email_charset' property.
+
+    - CMFDefault utils: Added 'makeEmail' function.
+
     - CMFDefault.Image and CMFDefault.File: Overridden index_html methods
       add Cache Policy Manager-awareness and thus bring these implementations
       in line with CMFCore.FSFile and CMFCore.FSImage
       (http://www.zope.org/Collectors/CMF/454)
 
+  Bug Fixes
 
+    - CMFDefault skins: Fixed encoding issues in welcome and reminder emails.
+      'password_email' and 'registered_email' now encode their return value
+      correctly, using 'email_charset' and the new 'makeEmail' function.
+
+
 CMF 2.1.0-alpha (2006/10/09)
 
   New Features

Modified: CMF/trunk/CMFDefault/profiles/default/properties.xml
===================================================================
--- CMF/trunk/CMFDefault/profiles/default/properties.xml	2006-10-15 14:43:52 UTC (rev 70653)
+++ CMF/trunk/CMFDefault/profiles/default/properties.xml	2006-10-15 14:49:21 UTC (rev 70654)
@@ -8,5 +8,6 @@
     type="string">Portal Administrator</property>
  <property name="validate_email" type="boolean">False</property>
  <property name="default_charset" type="string">utf-8</property>
+ <property name="email_charset" type="string">iso-8859-1</property>
  <property name="enable_permalink" type="boolean">False</property>
 </site>

Modified: CMF/trunk/CMFDefault/skins/zpt_control/portal_config_control.py
===================================================================
--- CMF/trunk/CMFDefault/skins/zpt_control/portal_config_control.py	2006-10-15 14:43:52 UTC (rev 70653)
+++ CMF/trunk/CMFDefault/skins/zpt_control/portal_config_control.py	2006-10-15 14:49:21 UTC (rev 70654)
@@ -7,6 +7,8 @@
 
 if not ptool.hasProperty('default_charset'):
     ptool.manage_addProperty('default_charset', '', 'string')
+if not ptool.hasProperty('email_charset'):
+    ptool.manage_addProperty('email_charset', '', 'string')
 ptool.editProperties(kw)
 
 return context.setStatus(True, _(u'CMF Settings changed.'))

Modified: CMF/trunk/CMFDefault/skins/zpt_generic/password_email.py
===================================================================
--- CMF/trunk/CMFDefault/skins/zpt_generic/password_email.py	2006-10-15 14:43:52 UTC (rev 70653)
+++ CMF/trunk/CMFDefault/skins/zpt_generic/password_email.py	2006-10-15 14:49:21 UTC (rev 70654)
@@ -1,29 +1,25 @@
-##parameters=member=None, password='baz'
+##parameters=member=None, password='secret'
 ##
 from Products.CMFCore.utils import getToolByName
 from Products.CMFDefault.utils import decode
+from Products.CMFDefault.utils import makeEmail
+from Products.CMFDefault.utils import Message as _
 
 atool = getToolByName(script, 'portal_actions')
 ptool = getToolByName(script, 'portal_properties')
 utool = getToolByName(script, 'portal_url')
-default_charset = ptool.getProperty('default_charset')
 portal_url = utool()
 
 
 options = {}
-
-email_from_name = ptool.getProperty('email_from_name')
-email_from_address = ptool.getProperty('email_from_address')
-options['portal_address'] = '%s <%s>' % (email_from_name, email_from_address)
-member_address = member and member.email or 'foo at example.org'
-options['member_address'] = '<%s>' % member_address
-options['content_type'] = 'text/plain; charset=%s' % default_charset
-
-options['portal_title'] = ptool.title()
 options['password'] = password
 
-rendered = context.password_email_template(**decode(options, script))
-if isinstance(rendered, unicode):
-    return rendered.encode(default_charset)
-else:
-    return rendered
+headers = {}
+headers['Subject'] = _(u'${portal_title}: Membership reminder',
+                      mapping={'portal_title': decode(ptool.title(), script)})
+headers['From'] = '%s <%s>' % (ptool.getProperty('email_from_name'),
+                               ptool.getProperty('email_from_address'))
+headers['To'] = '<%s>' % (member and member.email or 'foo at example.org')
+
+mtext = context.password_email_template(**decode(options, script))
+return makeEmail(mtext, script, headers)

Modified: CMF/trunk/CMFDefault/skins/zpt_generic/password_email_template.pt
===================================================================
--- CMF/trunk/CMFDefault/skins/zpt_generic/password_email_template.pt	2006-10-15 14:43:52 UTC (rev 70653)
+++ CMF/trunk/CMFDefault/skins/zpt_generic/password_email_template.pt	2006-10-15 14:49:21 UTC (rev 70654)
@@ -1,11 +1,5 @@
 <tal:page i18n:domain="cmf_default"
->Subject: <tal:span i18n:translate=""><tal:span i18n:name="portal_title"
-    tal:content="options/portal_title" />: Membership reminder</tal:span>
-From: <tal:span tal:replace="structure options/portal_address" />
-To: <tal:span tal:replace="structure options/member_address" />
-Content-Type: <tal:span tal:replace="structure options/content_type" />
-
-<tal:span i18n:translate="">Your password: <tal:span i18n:name="password"
+><tal:span i18n:translate="">Your password: <tal:span i18n:name="password"
     tal:content="options/password | default">baz</tal:span></tal:span>
 
 <tal:span i18n:translate="">Request made by IP <tal:span i18n:name="ip"

Modified: CMF/trunk/CMFDefault/skins/zpt_generic/reconfig_form.py
===================================================================
--- CMF/trunk/CMFDefault/skins/zpt_generic/reconfig_form.py	2006-10-15 14:43:52 UTC (rev 70653)
+++ CMF/trunk/CMFDefault/skins/zpt_generic/reconfig_form.py	2006-10-15 14:49:21 UTC (rev 70654)
@@ -30,6 +30,7 @@
                     'validate_email': ptool.getProperty('validate_email'),
                     'default_charset':
                                     ptool.getProperty('default_charset', ''),
+                    'email_charset': ptool.getProperty('email_charset', ''),
                     'listButtonInfos': tuple(buttons) }
 
 return context.reconfig_template(**decode(options, script))

Modified: CMF/trunk/CMFDefault/skins/zpt_generic/reconfig_template.pt
===================================================================
--- CMF/trunk/CMFDefault/skins/zpt_generic/reconfig_template.pt	2006-10-15 14:43:52 UTC (rev 70653)
+++ CMF/trunk/CMFDefault/skins/zpt_generic/reconfig_template.pt	2006-10-15 14:49:21 UTC (rev 70654)
@@ -94,6 +94,17 @@
   </td>
  </tr>
  <tr>
+  <th i18n:translate="">Portal email encoding</th>
+  <td>
+   <input name="email_charset" value=""
+      tal:attributes="value form/email_charset" />
+   <dl class="FieldHelp">
+    <dd i18n:translate="">Charset used to encode emails send by the portal.
+     If empty, 'utf-8' is used if necessary.</dd>
+   </dl>
+  </td>
+ </tr>
+ <tr>
   <td>&nbsp;</td>
   <td>
    <metal:macro metal:use-macro="context/form_widgets/macros/buttons" />

Modified: CMF/trunk/CMFDefault/skins/zpt_generic/registered_email.py
===================================================================
--- CMF/trunk/CMFDefault/skins/zpt_generic/registered_email.py	2006-10-15 14:43:52 UTC (rev 70653)
+++ CMF/trunk/CMFDefault/skins/zpt_generic/registered_email.py	2006-10-15 14:49:21 UTC (rev 70654)
@@ -1,23 +1,18 @@
-##parameters=member=None, password='baz', email='foo at example.org'
+##parameters=member=None, password='secret', email='foo at example.org'
 ##
 from Products.CMFCore.utils import getToolByName
 from Products.CMFDefault.utils import decode
+from Products.CMFDefault.utils import makeEmail
+from Products.CMFDefault.utils import Message as _
 
 atool = getToolByName(script, 'portal_actions')
 ptool = getToolByName(script, 'portal_properties')
 utool = getToolByName(script, 'portal_url')
-default_charset = ptool.getProperty('default_charset')
 portal_url = utool()
 
 
 options = {}
 
-email_from_name = ptool.getProperty('email_from_name')
-email_from_address = ptool.getProperty('email_from_address')
-options['portal_address'] = '%s <%s>' % (email_from_name, email_from_address)
-options['member_address'] = '<%s>' % email
-options['content_type'] = 'text/plain; charset=%s' % default_charset
-
 options['portal_title'] = ptool.title()
 options['portal_description'] = ptool.getProperty('description')
 options['portal_url'] = portal_url
@@ -28,10 +23,16 @@
 
 target = atool.getActionInfo('user/login')['url']
 options['login_url'] = '%s' % target
+
+email_from_name = ptool.getProperty('email_from_name')
 options['signature'] = email_from_name
 
-rendered = context.registered_email_template(**decode(options, script))
-if isinstance(rendered, unicode):
-    return rendered.encode(default_charset)
-else:
-    return rendered
+headers = {}
+headers['Subject'] = _(u'${portal_title}: Your Membership Information',
+                      mapping={'portal_title': decode(ptool.title(), script)})
+headers['From'] = '%s <%s>' % (email_from_name,
+                               ptool.getProperty('email_from_address'))
+headers['To'] = '<%s>' % email
+
+mtext = context.registered_email_template(**decode(options, script))
+return makeEmail(mtext, script, headers)

Modified: CMF/trunk/CMFDefault/skins/zpt_generic/registered_email_template.pt
===================================================================
--- CMF/trunk/CMFDefault/skins/zpt_generic/registered_email_template.pt	2006-10-15 14:43:52 UTC (rev 70653)
+++ CMF/trunk/CMFDefault/skins/zpt_generic/registered_email_template.pt	2006-10-15 14:49:21 UTC (rev 70654)
@@ -1,12 +1,5 @@
 <tal:page i18n:domain="cmf_default"
->Subject: <tal:span i18n:translate=""><tal:span i18n:name="portal_title"
-    tal:content="options/portal_title"
-    />: Your Membership Information</tal:span>
-From: <tal:span tal:replace="structure options/portal_address" />
-To: <tal:span tal:replace="structure options/member_address" />
-Content-Type: <tal:span tal:replace="structure options/content_type" />
-
-<tal:span i18n:translate=""
+><tal:span i18n:translate=""
 >You have been registered as a member of "<tal:span i18n:name="portal_title"
    tal:content="options/portal_title" />", which
 allows you to personalize your view of the website and participate in the

Modified: CMF/trunk/CMFDefault/tests/RegistrationTool.txt
===================================================================
--- CMF/trunk/CMFDefault/tests/RegistrationTool.txt	2006-10-15 14:43:52 UTC (rev 70653)
+++ CMF/trunk/CMFDefault/tests/RegistrationTool.txt	2006-10-15 14:49:21 UTC (rev 70654)
@@ -1,4 +1,5 @@
 RegistrationTool
+----------------
 
   First we need some dummy code::
 
@@ -46,3 +47,164 @@
 
     >>> rtool.MailHost.lastMessage
     'Welcome: foo, secret, foo at example.org'
+
+password_email and registered_email
+-----------------------------------
+
+  First we need some dummy code::
+
+    >>> from os.path import join
+    >>> from OFS.Folder import Folder
+    >>> from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
+    >>> from Products.PythonScripts.PythonScript import PythonScript
+    >>> class DummySite(Folder):
+    ...     def getPhysicalRoot(self): return self
+    ...     def getPhysicalPath(self): return ('root',)
+    ...     def addResource(self, dir, f_name):
+    ...         r_name, f_type = f_name.split('.')
+    ...         if f_type == 'pt': r = ZopePageTemplate(r_name)
+    ...         if f_type == 'py': r = PythonScript(r_name)
+    ...         f = file(join(dir, f_name), 'r')
+    ...         r.write(f.read())
+    ...         f.close()
+    ...         setattr(self, r_name, r)
+
+    >>> class DummyTool(Folder):
+    ...     def getActionInfo(self, action_chain): return self.login_action
+    ...     def getProperty(self, id, d=None): return getattr(self, id, d)
+    ...     def title(self): return self.Title
+    ...     def __call__(self): return self.url
+
+    >>> from zope import interface
+    >>> from zope.i18n.interfaces import INegotiator
+    >>> class Negotiator:
+    ...     interface.implements(INegotiator)
+    ...     def getLanguage(*ignored): return 'test'
+
+    >>> from zope.i18n.testmessagecatalog import TestMessageFallbackDomain
+    >>> class DummyFallbackTranslationService:
+    ...     def translate(self, domain, msgid, mapping, context,
+    ...                   target_language, default):
+    ...         util = TestMessageFallbackDomain(domain)
+    ...         return util.translate(msgid, mapping, context,
+    ...                               target_language, default)
+
+  And have to set up security and dummy translations::
+
+    >>> from AccessControl.SecurityManagement import newSecurityManager
+    >>> from AccessControl.User import UnrestrictedUser
+    >>> newSecurityManager(None, UnrestrictedUser('god', '', ['Manager'], ''))
+
+    >>> from zope import component
+    >>> component.provideUtility(Negotiator())
+    >>> from Products.Five import i18n
+    >>> old_fallback_translation_service = i18n._fallback_translation_service
+    >>> i18n._fallback_translation_service = DummyFallbackTranslationService()
+
+  Now we can set up password_email and registered_email with dummy context::
+
+    >>> from Testing.makerequest import makerequest
+    >>> s = makerequest(DummySite())
+    >>> s.REQUEST.environ['HTTP_X_FORWARDED_FOR'] = 'NNN.NNN.NNN.NNN'
+    >>> s.portal_actions = s.portal_properties = s.portal_url = DummyTool()
+    >>> s.ZopeTime = 'NNNN/NN/NN'
+    >>> s.description = 'THE SITE DESCRIPTION.'
+    >>> s.default_charset = 'utf-8'
+    >>> s.email_from_name = u'WEBMASTER \xc4\xd6\xdc'.encode('utf-8')
+    >>> s.email_from_address = 'WEBMASTER at EXAMPLE.ORG'
+    >>> s.Title = 'WWW.EXAMPLE.ORG'
+    >>> s.url = 'PORTAL_URL'
+    >>> s.login_action = {'url': 'LOGIN_URL'}
+
+    >>> from os.path import dirname
+    >>> from Products import CMFDefault
+    >>> dir = join(dirname(CMFDefault.__file__), 'skins', 'zpt_generic')
+    >>> s.addResource(dir, 'password_email.py')
+    >>> s.addResource(dir, 'password_email_template.pt')
+    >>> s.addResource(dir, 'registered_email.py')
+    >>> s.addResource(dir, 'registered_email_template.pt')
+
+  password_email creates a complete reminder email::
+
+    >>> s.email_charset = 'iso-8859-1'
+    >>> print s.password_email()
+    Content-Type: text/plain; charset="us-ascii"
+    MIME-Version: 1.0
+    Content-Transfer-Encoding: 7bit
+    To: <foo at example.org>
+    From: WEBMASTER =?iso-8859-1?q?=C4=D6=DC?= <WEBMASTER at EXAMPLE.ORG>
+    Subject: [[cmf_default][WWW.EXAMPLE.ORG: Membership reminder]]
+    <BLANKLINE>
+    [[cmf_default][Your password: secret]]
+    <BLANKLINE>
+    [[cmf_default][Request made by IP NNN.NNN.NNN.NNN at NNNN/NN/NN]]
+    <BLANKLINE>
+    <BLANKLINE>
+
+    >>> s.email_charset = ''
+    >>> print s.password_email()
+    Content-Type: text/plain; charset="us-ascii"
+    MIME-Version: 1.0
+    Content-Transfer-Encoding: 7bit
+    To: <foo at example.org>
+    From: WEBMASTER =?utf-8?b?w4TDlsOc?= <WEBMASTER at EXAMPLE.ORG>
+    Subject: [[cmf_default][WWW.EXAMPLE.ORG: Membership reminder]]
+    <BLANKLINE>
+    [[cmf_default][Your password: secret]]
+    <BLANKLINE>
+    [[cmf_default][Request made by IP NNN.NNN.NNN.NNN at NNNN/NN/NN]]
+    <BLANKLINE>
+    <BLANKLINE>
+
+  registered_email creates a complete welcome email::
+
+    >>> s.email_charset = 'iso-8859-1'
+    >>> print s.registered_email()
+    Content-Type: text/plain; charset="iso-8859-1"
+    MIME-Version: 1.0
+    Content-Transfer-Encoding: quoted-printable
+    To: <foo at example.org>
+    From: WEBMASTER =?iso-8859-1?q?=C4=D6=DC?= <WEBMASTER at EXAMPLE.ORG>
+    Subject: [[cmf_default][WWW.EXAMPLE.ORG: Your Membership Information]]
+    <BLANKLINE>
+    [[cmf_default][You have been ... (You have been ...)]]
+    <BLANKLINE>
+    [[cmf_default][This describes the purpose of the website:]]
+    <BLANKLINE>
+    THE SITE DESCRIPTION.
+    <BLANKLINE>
+    [[cmf_default][Visit us at PORTAL_URL]]
+    <BLANKLINE>
+    [[cmf_default][Here is your login data (mind upper and lower case):]]
+    <BLANKLINE>
+    [[cmf_default][Member ID]]: foo
+    [[cmf_default][Password]]: secret
+    <BLANKLINE>
+    [[cmf_default][You can use this URL to log in:]]
+    <BLANKLINE>
+    LOGIN_URL
+    <BLANKLINE>
+    <BLANKLINE>
+    WEBMASTER =C4=D6=DC
+    <BLANKLINE>
+    <BLANKLINE>
+
+    >>> s.email_charset = ''
+    >>> print s.registered_email()
+    Content-Type: text/plain; charset="utf-8"
+    MIME-Version: 1.0
+    Content-Transfer-Encoding: base64
+    To: <foo at example.org>
+    From: WEBMASTER =?utf-8?b?w4TDlsOc?= <WEBMASTER at EXAMPLE.ORG>
+    Subject: [[cmf_default][WWW.EXAMPLE.ORG: Your Membership Information]]
+    <BLANKLINE>
+    W1tj...
+    <BLANKLINE>
+
+  Finally we have to clean up::
+
+    >>> i18n._fallback_translation_service = old_fallback_translation_service
+    >>> from AccessControl.SecurityManagement import noSecurityManager
+    >>> noSecurityManager()
+    >>> from zope.testing.cleanup import cleanUp
+    >>> cleanUp()

Modified: CMF/trunk/CMFDefault/tests/test_RegistrationTool.py
===================================================================
--- CMF/trunk/CMFDefault/tests/test_RegistrationTool.py	2006-10-15 14:43:52 UTC (rev 70653)
+++ CMF/trunk/CMFDefault/tests/test_RegistrationTool.py	2006-10-15 14:49:21 UTC (rev 70654)
@@ -140,7 +140,7 @@
     return unittest.TestSuite((
         unittest.makeSuite(RegistrationToolTests),
         doctest.DocFileSuite('RegistrationTool.txt',
-                             optionflags=doctest.NORMALIZE_WHITESPACE),
+               optionflags=(doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE)),
         ))
 
 if __name__ == '__main__':

Modified: CMF/trunk/CMFDefault/utils.py
===================================================================
--- CMF/trunk/CMFDefault/utils.py	2006-10-15 14:43:52 UTC (rev 70653)
+++ CMF/trunk/CMFDefault/utils.py	2006-10-15 14:49:21 UTC (rev 70654)
@@ -19,6 +19,8 @@
 import re
 import StringIO
 import rfc822
+from email.Header import make_header
+from email.MIMEText import MIMEText
 from sgmllib import SGMLParser
 
 from AccessControl import ModuleSecurityInfo
@@ -26,6 +28,7 @@
 from Products.PageTemplates.GlobalTranslationService \
         import getGlobalTranslationService
 from ZTUtils.Zope import complex_marshal
+from zope import i18n
 from zope.i18n.interfaces import IUserPreferredCharsets
 from zope.i18nmessageid import MessageFactory
 
@@ -466,5 +469,24 @@
     charsets = envadapter.getPreferredCharsets() or ['utf-8']
     return charsets[0]
 
+security.declarePublic('makeEmail')
+def makeEmail(mtext, context, headers={}):
+    """ Make email message.
+    """
+    ptool = getToolByName(context, 'portal_properties')
+    email_charset = ptool.getProperty('email_charset', None) or 'utf-8'
+    try:
+        msg = MIMEText(mtext.encode(), 'plain')
+    except UnicodeEncodeError:
+        msg = MIMEText(mtext.encode(email_charset), 'plain', email_charset)
+    for k, val in headers.items():
+        if isinstance(val, str):
+            val = decode(val, context)
+        if isinstance(val, i18n.Message):
+            val = translate(val, context)
+        header = make_header([ (w, email_charset) for w in val.split(' ') ])
+        msg[k] = str(header)
+    return msg.as_string()
+
 security.declarePublic('Message')
 Message = _ = MessageFactory('cmf_default')



More information about the CMF-checkins mailing list