[CalendarServer-changes] [3229] CalendarServer/branches/users/sagen/localization-3218/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Fri Oct 24 15:30:36 PDT 2008


Revision: 3229
          http://trac.macosforge.org/projects/calendarserver/changeset/3229
Author:   sagen at apple.com
Date:     2008-10-24 15:30:35 -0700 (Fri, 24 Oct 2008)
Log Message:
-----------
Fixing the _ rebinding code, and using localizable rendering for emails

Modified Paths:
--------------
    CalendarServer/branches/users/sagen/localization-3218/twistedcaldav/config.py
    CalendarServer/branches/users/sagen/localization-3218/twistedcaldav/localization.py
    CalendarServer/branches/users/sagen/localization-3218/twistedcaldav/mail.py

Modified: CalendarServer/branches/users/sagen/localization-3218/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/sagen/localization-3218/twistedcaldav/config.py	2008-10-24 22:24:16 UTC (rev 3228)
+++ CalendarServer/branches/users/sagen/localization-3218/twistedcaldav/config.py	2008-10-24 22:30:35 UTC (rev 3229)
@@ -284,7 +284,7 @@
 
     "ListenBacklog": 50,
     "IdleConnectionTimeOut": 15,
-    "UIDReservationTimeOut": 30 * 60
+    "UIDReservationTimeOut": 30 * 60,
 
     #
     # Implementation details

Modified: CalendarServer/branches/users/sagen/localization-3218/twistedcaldav/localization.py
===================================================================
--- CalendarServer/branches/users/sagen/localization-3218/twistedcaldav/localization.py	2008-10-24 22:24:16 UTC (rev 3228)
+++ CalendarServer/branches/users/sagen/localization-3218/twistedcaldav/localization.py	2008-10-24 22:30:35 UTC (rev 3229)
@@ -103,15 +103,15 @@
             self.translations[key] = self.translation
 
     def __enter__(self):
-        # Get the caller's locals so we can rebind their '_' to our translator
-        caller_locals = inspect.stack()[-1][0].f_locals
+        # Get the caller's globals so we can rebind their '_' to our translator
+        caller_globals = inspect.stack()[1][0].f_globals
 
         # Store whatever '_' is already bound to so we can restore it later
-        if caller_locals.has_key('_'):
-            self.prev = caller_locals['_']
+        if caller_globals.has_key('_'):
+            self.prev = caller_globals['_']
 
         # Rebind '_' to our translator
-        caller_locals['_'] = self.translation.gettext
+        caller_globals['_'] = self.translation.gettext
 
         # What we return here is accessible to the caller via the 'as' clause
         return self
@@ -119,7 +119,7 @@
     def __exit__(self, type, value, traceback):
         # Restore '_' if it previously had a value
         if hasattr(self, 'prev'):
-            inspect.stack()[-1][0].f_locals['_'] = self.prev
+            inspect.stack()[1][0].f_globals['_'] = self.prev
 
         # Don't swallow exceptions
         return False

Modified: CalendarServer/branches/users/sagen/localization-3218/twistedcaldav/mail.py
===================================================================
--- CalendarServer/branches/users/sagen/localization-3218/twistedcaldav/mail.py	2008-10-24 22:24:16 UTC (rev 3228)
+++ CalendarServer/branches/users/sagen/localization-3218/twistedcaldav/mail.py	2008-10-24 22:30:35 UTC (rev 3229)
@@ -18,6 +18,7 @@
 Mail Gateway for Calendar Server
 
 """
+from __future__ import with_statement
 
 from email.mime.image import MIMEImage
 from email.mime.multipart import MIMEMultipart
@@ -42,6 +43,7 @@
 from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.scheduling.scheduler import IMIPScheduler
 from twistedcaldav.sql import AbstractSQLDatabase
+from twistedcaldav.localization import translationTo
 
 from zope.interface import implements
 
@@ -481,7 +483,7 @@
             calendar = ical.Component.fromString(request.content.read())
             headers = request.getAllHeaders()
             self.mailer.outbound(headers['originator'], headers['recipient'],
-                calendar)
+                calendar, language='en')
 
             # TODO: what to return?
             return """
@@ -646,7 +648,7 @@
 
 
 
-    def outbound(self, organizer, attendee, calendar):
+    def outbound(self, organizer, attendee, calendar, language='en'):
         # create token, send email
         token = self.db.getToken(organizer, attendee)
         if token is None:
@@ -667,7 +669,6 @@
         if organizerAttendee is not None:
             organizerAttendee.setValue("mailto:%s" % (addressWithToken,))
 
-        msgId, message = self._generateTemplateMessage(calendar, organizer)
 
         # The email's From will include the organizer's real name email
         # address if available.  Otherwise it will be the server's email
@@ -679,17 +680,17 @@
         cn = calendar.getOrganizerProperty().params().get('CN',
             ['Calendar Server'])[0]
         formattedFrom = "%s <%s>" % (cn, fromAddr)
-        message = message.replace("${fromaddress}", formattedFrom)
 
         # Reply-to address will be the server+token address
-        message = message.replace("${replytoaddress}", addressWithToken)
 
         toAddr = attendee
         if not attendee.startswith("mailto:"):
             raise ValueError("ATTENDEE address '%s' must be mailto: for iMIP operation." % (attendee,))
         attendee = attendee[7:]
-        message = message.replace("${toaddress}", attendee)
 
+        msgId, message = self.generateEmail(calendar, organizer, formattedFrom,
+            addressWithToken, attendee, language=language)
+
         self.log_debug("Sending: %s" % (message,))
         def _success(result, msgId, fromAddr, toAddr):
             self.log_info("Mail gateway sent message %s from %s to %s" %
@@ -706,41 +707,93 @@
         deferred.addErrback(_failure, msgId, fromAddr, toAddr)
 
 
-    def _generateTemplateMessage(self, calendar, organizer):
+    def generateEmail(self, calendar, organizer, fromAddress, replyToAddress,
+        toAddress, language='en'):
 
-        title, summary = self._generateCalendarSummary(calendar, organizer)
+        details = self.getEventDetails(calendar, organizer, language=language)
 
-        msg = MIMEMultipart()
-        msg["From"] = "${fromaddress}"
-        msg["Reply-To"] = "${replytoaddress}"
-        msg["To"] = "${toaddress}"
-        msg["Date"] = rfc822date()
-        msgId = messageid()
-        msg["Message-ID"] = msgId
+        with translationTo(language):
+            msg = MIMEMultipart()
+            msg["From"] = fromAddress
+            msg["Reply-To"] = replyToAddress
+            msg["To"] = toAddress
+            msg["Date"] = rfc822date()
+            msgId = messageid()
+            msg["Message-ID"] = msgId
 
-        msgAlt = MIMEMultipart("alternative")
-        msg.attach(msgAlt)
+            cancelled = (calendar.propertyValue("METHOD") == "CANCEL")
+            msg["Subject"] = (
+                _("Event cancelled") if cancelled else
+                _("Event invitation: %(summary)s") % {
+                    'summary' : details['summary']
+                }
+            )
 
-        # plain text version
-        if calendar.propertyValue("METHOD") == "CANCEL":
-            msg["Subject"] = "Event cancelled"
-            plainText = u"An event has been cancelled.  Click the link below.\n"
-        else:
-            msg["Subject"] = "Event invitation: %s" % (title,)
-            plainText = u"You've been invited to the following event:  %s To accept or decline this invitation, click the link below.\n" % (summary,)
+            msgAlt = MIMEMultipart("alternative")
+            msg.attach(msgAlt)
 
-        msgPlain = MIMEText(plainText.encode("UTF-8"), "plain", "UTF-8")
-        msgAlt.attach(msgPlain)
+            # Get localized labels
+            details['inviteLabel'] = _("Event Invitation")
+            details['dateLabel'] = _("Date")
+            details['timeLabel'] = _("Time")
+            details['descLabel'] = _("Description")
+            details['orgLabel'] = _("Organizer")
+            details['attLabel'] = _("Attendees")
+            details['locLabel'] = _("Location")
 
-        # html version
-        msgHtmlRelated = MIMEMultipart("related", type="text/html")
-        msgAlt.attach(msgHtmlRelated)
-        htmlText = u"""
-<html><body><div>
-<img src="cid:ical.jpg">
-%s
-</div></body></html>
-""" % plainText
+            # plain text version
+            plainTemplate = u"""%(inviteLabel)s: %(summary)s
+
+%(orgLabel)s: %(organizerName)s %(organizerAddr)s
+%(locLabel)s: %(location)s
+%(dateLabel)s: %(dateInfo)s
+%(timeLabel)s: %(timeInfo)s
+%(descLabel)s: %(description)s
+"""
+
+            if cancelled:
+                plainText = _("Event cancelled")
+            else:
+                print plainTemplate, details
+                plainText = plainTemplate % details
+
+            msgPlain = MIMEText(plainText.encode("UTF-8"), "plain", "UTF-8")
+            msgAlt.attach(msgPlain)
+
+            # html version
+            msgHtmlRelated = MIMEMultipart("related", type="text/html")
+            msgAlt.attach(msgHtmlRelated)
+
+            htmlTemplate = u"""<html>
+    <body><div>
+    <img src="cid:ical.jpg"/>
+
+    <p>%(inviteLabel)s</p>
+
+    <h1>%(summary)s</h1>
+    <p>
+    <h3>%(orgLabel)s:</h3> %(organizerName)s %(organizerAddr)s
+    </p>
+    <p>
+    <h3>%(locLabel)s:</h3> %(location)s
+    </p>
+    <p>
+    <h3>%(dateLabel)s:</h3> %(dateInfo)s
+    </p>
+    <p>
+    <h3>%(timeLabel)s:</h3> %(timeInfo)s
+    </p>
+    <p>
+    <h3>%(descLabel)s:</h3> %(description)s
+    </p>
+
+    """
+            if cancelled:
+                htmlText = _("Event cancelled")
+            else:
+                htmlText = htmlTemplate % details
+
+        self.log_info(htmlText)
         msgHtml = MIMEText(htmlText.encode("UTF-8"), "html", "UTF-8")
         msgHtmlRelated.attach(msgHtml)
 
@@ -769,135 +822,53 @@
         return msgId, msg.as_string()
 
 
-    def _generateCalendarSummary(self, calendar, organizer):
+    def getEventDetails(self, calendar, organizer, language='en'):
 
         # Get the most appropriate component
         component = calendar.masterComponent()
         if component is None:
             component = calendar.mainComponent(True)
 
+        results = { }
+
+        # The organizer in calendar has already been replaced with
+        # special token address
+        results['organizerAddr'] = organizer
+
         organizerProp = component.getOrganizerProperty()
         if "CN" in organizerProp.params():
-            organizer = "%s <%s>" % (organizerProp.params()["CN"][0],
-                organizer,)
-
-        if calendar.propertyValue("METHOD") == "CANCEL":
-            dtinfo = ""
+            results['organizerName'] = organizerProp.params()["CN"][0]
         else:
-            dtinfo = self._getDateTimeInfo(component)
+            results['organizerName'] = ''
 
+
         summary = component.propertyValue("SUMMARY")
         if summary is None:
             summary = ""
+        results['summary'] = summary
 
         description = component.propertyValue("DESCRIPTION")
         if description is None:
             description = ""
+        results['description'] = description
 
-        return summary, """
+        location = component.propertyValue("LOCATION")
+        if location is None:
+            location = ""
+        results['location'] = location
 
-Summary: %s
-Organizer: %s
-%sDescription: %s
+        with translationTo(language) as trans:
+            results['dateInfo'] = trans.date(component)
+            results['timeInfo'] = trans.time(component)
 
-""" % (summary, organizer, dtinfo, description,)
+        return results
 
-    def _getDateTimeInfo(self, component):
 
-        dtstart = component.propertyNativeValue("DTSTART")
-        tzid_start = component.getProperty("DTSTART").params().get("TZID", "UTC")
 
-        dtend = component.propertyNativeValue("DTEND")
-        if dtend:
-            tzid_end = component.getProperty("DTEND").params().get("TZID", "UTC")
-            duration = dtend - dtstart
-        else:
-            duration = component.propertyNativeValue("DURATION")
-            if duration:
-                dtend = dtstart + duration
-                tzid_end = tzid_start
-            else:
-                if isinstance(dtstart, datetime.date):
-                    dtend = None
-                    duration = datetime.timedelta(days=1)
-                else:
-                    dtend = dtstart + datetime.timedelta(days=1)
-                    dtend.hour = dtend.minute = dtend.second = 0
-                    duration = dtend - dtstart
-        result = "Starts:      %s\n" % (self._getDateTimeText(dtstart, tzid_start),)
-        if dtend is not None:
-            result += "Ends:        %s\n" % (self._getDateTimeText(dtend, tzid_end),)
-        result += "Duration:    %s\n" % (self._getDurationText(duration),)
 
-        if not isinstance(dtstart, datetime.datetime):
-            result += "All Day\n"
 
-        for property_name in ("RRULE", "RDATE", "EXRULE", "EXDATE", "RECURRENCE-ID",):
-            if component.hasProperty(property_name):
-                result += "Recurring\n"
-                break
 
-        return result
 
-    def _getDateTimeText(self, dtvalue, tzid):
-
-        if isinstance(dtvalue, datetime.datetime):
-            timeformat = "%A, %B %e, %Y %I:%M %p"
-        elif isinstance(dtvalue, datetime.date):
-            timeformat = "%A, %B %e, %Y"
-            tzid = ""
-        if tzid:
-            tzid = " (%s)" % (tzid,)
-
-        return "%s%s" % (dtvalue.strftime(timeformat), tzid,)
-
-    def _getDurationText(self, duration):
-
-        result = ""
-        if duration.days > 0:
-            result += "%d %s" % (
-                duration.days,
-                self._pluralize(duration.days, "day", "days")
-            )
-
-        hours = duration.seconds / 3600
-        minutes = divmod(duration.seconds / 60, 60)[1]
-        seconds = divmod(duration.seconds, 60)[1]
-
-        if hours > 0:
-            if result:
-                result += ", "
-            result += "%d %s" % (
-                hours,
-                self._pluralize(hours, "hour", "hours")
-            )
-
-        if minutes > 0:
-            if result:
-                result += ", "
-            result += "%d %s" % (
-                minutes,
-                self._pluralize(minutes, "minute", "minutes")
-            )
-
-        if seconds > 0:
-            if result:
-                result += ", "
-            result += "%d %s" % (
-                seconds,
-                self._pluralize(seconds, "second", "seconds")
-            )
-
-        return result
-
-    def _pluralize(self, number, unit1, unitS):
-        return unit1 if number == 1 else unitS
-
-
-
-
-
-
 #
 # POP3
 #
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20081024/2eac18f6/attachment-0001.html>


More information about the calendarserver-changes mailing list