Code

Added users' timezone support
authorkedder <kedder@57a73879-2fb5-44c3-a270-3262357dd7e2>
Mon, 27 Jan 2003 16:32:50 +0000 (16:32 +0000)
committerkedder <kedder@57a73879-2fb5-44c3-a270-3262357dd7e2>
Mon, 27 Jan 2003 16:32:50 +0000 (16:32 +0000)
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1480 57a73879-2fb5-44c3-a270-3262357dd7e2

CHANGES.txt
doc/upgrading.txt
roundup/cgi/client.py
roundup/cgi/templating.py
roundup/mailgw.py
roundup/roundupdb.py
roundup/templates/classic/dbinit.py
roundup/templates/classic/html/user.item

index d9802b62d1f2ee41b056934daabdd825e7bb0c07..1d3c15d595d2023c9636a5add0b10cdaf990b0c1 100644 (file)
@@ -23,6 +23,8 @@ are given with the most recent entry first.
   according to rfc2822 (sf bug 568873)
 - fixed cookie path to use TRACKER_WEB (sf bug 667020) (thanks Nathaniel Smith
   for helping chase it down and Luke Opperman for confirming fix)
   according to rfc2822 (sf bug 568873)
 - fixed cookie path to use TRACKER_WEB (sf bug 667020) (thanks Nathaniel Smith
   for helping chase it down and Luke Opperman for confirming fix)
+- added ability to display localized dates in web interface. User input is
+  convered to GMT (see doc/upgrading.txt).
 
 
 2003-??-?? 0.5.5
 
 
 2003-??-?? 0.5.5
index a7a6096588d8bf0e7c3edcc81fa528939e12ae5f..e4c77cfd34cfdf443f3d87821ae7221426fccf0d 100644 (file)
@@ -46,6 +46,40 @@ Migrating from 0.5 to 0.6
   is no tool for converting such data, the only solution is to close
   appropriate old issues and create new ones with the same content.
 
   is no tool for converting such data, the only solution is to close
   appropriate old issues and create new ones with the same content.
 
+0.6.0 User' timezone support
+----------------------------
+
+- From version 0.6.0 roundup supports displaying of Date data in user' local
+  timezone if he/she has provided timezone information. To make it possible
+  some modification to tracker's schema and HTML templates are required.
+  First you should add string property 'timezone' to user class in dbinit.py
+  like this:
+  
+    user = Class(db, "user", 
+                    username=String(),   password=Password(),
+                    address=String(),    realname=String(), 
+                    phone=String(),      organisation=String(),
+                    alternate_addresses=String(),
+                    queries=Multilink('query'), roles=String(),
+                    timezone=String())
+  
+  And second - html interface. Add following lines to
+  $TRACKER_HOME/html/user.item template:
+  
+        <tr>
+         <th>Timezone</th>
+         <td tal:content="structure context/timezone/field">timezone</td>
+        </tr>
+
+  After that all users should be able to provide their timezone information.
+  Timezone should be a positive or negative integer - offset from GMT.
+
+  After providing timezone, roundup will show all dates values, found in web
+  and mail interfaces in local time. It will also accept any Date info in
+  local time, convert and store it in GMT.
+
+  However you are not forced to make these modifications. By default roundup
+  will assume timezone=0 and will work as previous versions did.
 
 Migrating from 0.4.x to 0.5.0
 =============================
 
 Migrating from 0.4.x to 0.5.0
 =============================
index 49c8321815b15ac5bd95553cebed6b35458c0e90..3cb0915741cc94e4a8a8ee7a218c1bc03224ae0f 100644 (file)
@@ -1,4 +1,4 @@
-# $Id: client.py,v 1.73 2003-01-24 06:21:17 richard Exp $
+# $Id: client.py,v 1.74 2003-01-27 16:32:48 kedder Exp $
 
 __doc__ = """
 WWW request handler (also used in the stand-alone server).
 
 __doc__ = """
 WWW request handler (also used in the stand-alone server).
@@ -1209,6 +1209,8 @@ def parsePropsFromForm(db, cl, form, nodeid=0, num_re=re.compile('^\d+$')):
     props = {}
     keys = form.keys()
     properties = cl.getprops()
     props = {}
     keys = form.keys()
     properties = cl.getprops()
+    timezone = db.getUserTimezone()
+
     for key in keys:
         # see if we're performing a special multilink action
         mlaction = 'set'
     for key in keys:
         # see if we're performing a special multilink action
         mlaction = 'set'
@@ -1351,7 +1353,7 @@ def parsePropsFromForm(db, cl, form, nodeid=0, num_re=re.compile('^\d+$')):
                 # fix the CRLF/CR -> LF stuff
                 value = fixNewlines(value)
             elif isinstance(proptype, hyperdb.Date):
                 # fix the CRLF/CR -> LF stuff
                 value = fixNewlines(value)
             elif isinstance(proptype, hyperdb.Date):
-                value = date.Date(value)
+                value = date.Date(value, offset=timezone)
             elif isinstance(proptype, hyperdb.Interval):
                 value = date.Interval(value)
             elif isinstance(proptype, hyperdb.Boolean):
             elif isinstance(proptype, hyperdb.Interval):
                 value = date.Interval(value)
             elif isinstance(proptype, hyperdb.Boolean):
index 3b846337b242561574aa1b944ed9edabe3ef78b9..948ab30c38d15c22ec0b6a6adc24a9eca87128e9 100644 (file)
@@ -515,6 +515,7 @@ class HTMLItem(HTMLPermissions):
         comments = {}
         history = self._klass.history(self._nodeid)
         history.sort()
         comments = {}
         history = self._klass.history(self._nodeid)
         history.sort()
+        timezone = self._db.getUserTimezone()
         if direction == 'descending':
             history.reverse()
             for prop_n in self._props.keys():
         if direction == 'descending':
             history.reverse()
             for prop_n in self._props.keys():
@@ -530,7 +531,7 @@ class HTMLItem(HTMLPermissions):
                                 self._klass.get(self._nodeid, prop_n, None), current[prop_n])
  
         for id, evt_date, user, action, args in history:
                                 self._klass.get(self._nodeid, prop_n, None), current[prop_n])
  
         for id, evt_date, user, action, args in history:
-            date_s = str(evt_date).replace("."," ")
+            date_s = str(evt_date.local(timezone)).replace("."," ")
             arg_s = ''
             if action == 'link' and type(args) == type(()):
                 if len(args) == 3:
             arg_s = ''
             if action == 'link' and type(args) == type(()):
                 if len(args) == 3:
@@ -632,10 +633,10 @@ class HTMLItem(HTMLPermissions):
                                     current[k] = old
 
                         elif isinstance(prop, hyperdb.Date) and args[k]:
                                     current[k] = old
 
                         elif isinstance(prop, hyperdb.Date) and args[k]:
-                            d = date.Date(args[k])
+                            d = date.Date(args[k]).local(timezone)
                             cell.append('%s: %s'%(k, str(d)))
                             if current.has_key(k):
                             cell.append('%s: %s'%(k, str(d)))
                             if current.has_key(k):
-                                cell[-1] += ' -> %s'%current[k]
+                                cell[-1] += ' -> %s' % date.Date(current[k]).local(timezone)
                                 current[k] = str(d)
 
                         elif isinstance(prop, hyperdb.Interval) and args[k]:
                                 current[k] = str(d)
 
                         elif isinstance(prop, hyperdb.Interval) and args[k]:
@@ -918,7 +919,7 @@ class DateHTMLProperty(HTMLProperty):
         '''
         if self._value is None:
             return ''
         '''
         if self._value is None:
             return ''
-        return str(self._value)
+        return str(self._value.local(self._db.getUserTimezone()))
 
     def field(self, size = 30):
         ''' Render a form edit field for the property
 
     def field(self, size = 30):
         ''' Render a form edit field for the property
@@ -926,7 +927,7 @@ class DateHTMLProperty(HTMLProperty):
         if self._value is None:
             value = ''
         else:
         if self._value is None:
             value = ''
         else:
-            value = cgi.escape(str(self._value))
+            value = cgi.escape(str(self._value.local(self._db.getUserTimezone())))
             value = '&quot;'.join(value.split('"'))
         return '<input name="%s" value="%s" size="%s">'%(self._name, value, size)
 
             value = '&quot;'.join(value.split('"'))
         return '<input name="%s" value="%s" size="%s">'%(self._name, value, size)
 
index a12f909c49c20c4b24bf82a2045b89caea7a1a86..962d9b865e379772c49f1c53a9dc84d4512e8a0c 100644 (file)
@@ -73,7 +73,7 @@ are calling the create() method to create a new node). If an auditor raises
 an exception, the original message is bounced back to the sender with the
 explanatory message given in the exception. 
 
 an exception, the original message is bounced back to the sender with the
 explanatory message given in the exception. 
 
-$Id: mailgw.py,v 1.107 2003-01-15 22:17:19 kedder Exp $
+$Id: mailgw.py,v 1.108 2003-01-27 16:32:46 kedder Exp $
 '''
 
 import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri
 '''
 
 import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri
@@ -869,7 +869,7 @@ def setPropArrayFromString(self, cl, propString, nodeid = None):
             props[propname] = password.Password(value.strip())
         elif isinstance(proptype, hyperdb.Date):
             try:
             props[propname] = password.Password(value.strip())
         elif isinstance(proptype, hyperdb.Date):
             try:
-                props[propname] = date.Date(value.strip())
+                props[propname] = date.Date(value.strip()).local(self.db.getUserTimezone())
             except ValueError, message:
                 errors.append('contains an invalid date for %s.'%propname)
         elif isinstance(proptype, hyperdb.Interval):
             except ValueError, message:
                 errors.append('contains an invalid date for %s.'%propname)
         elif isinstance(proptype, hyperdb.Interval):
index 4b3761aebd9772f0f18bf56717e1f5205a047237..162452a901cf13d7ebbdcf29b517404fec5287bf 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: roundupdb.py,v 1.78 2003-01-15 22:17:19 kedder Exp $
+# $Id: roundupdb.py,v 1.79 2003-01-27 16:32:48 kedder Exp $
 
 __doc__ = """
 Extending hyperdb with types specific to issue-tracking.
 
 __doc__ = """
 Extending hyperdb with types specific to issue-tracking.
@@ -55,6 +55,20 @@ class Database:
         that owns this connection to the hyperdatabase."""
         return self.user.lookup(self.journaltag)
 
         that owns this connection to the hyperdatabase."""
         return self.user.lookup(self.journaltag)
 
+    def getUserTimezone(self):
+        """Return user timezone defined in 'timezone' property of user class.
+        If no such property exists return 0
+        """
+        userid = self.getuid()
+        try:
+            timezone = int(self.user.get(userid, 'timezone'))
+        except (KeyError, ValueError):
+            # If there is no class 'user' or current user doesn't have timezone 
+            # property or that property is not numeric assume he/she lives in 
+            # Greenwich :)
+            timezone = 0
+        return timezone
+
 class MessageSendError(RuntimeError):
     pass
 
 class MessageSendError(RuntimeError):
     pass
 
index cebff2961b284cdc06f6b2fb0a11f3a2370957ec..21b3ca8655d8b88dfcd0c898df1583640d1c14c4 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: dbinit.py,v 1.31 2002-10-10 07:17:39 richard Exp $
+# $Id: dbinit.py,v 1.32 2003-01-27 16:32:50 kedder Exp $
 
 import os
 
 
 import os
 
@@ -65,7 +65,9 @@ def open(name=None):
                     address=String(),    realname=String(), 
                     phone=String(),      organisation=String(),
                     alternate_addresses=String(),
                     address=String(),    realname=String(), 
                     phone=String(),      organisation=String(),
                     alternate_addresses=String(),
-                    queries=Multilink('query'), roles=String())
+                    queries=Multilink('query'), roles=String(),
+                    timezone=String())
+)
     user.setkey("username")
 
     # FileClass automatically gets these properties:
     user.setkey("username")
 
     # FileClass automatically gets these properties:
index 9e47594c6a66bcd5564b98c13e166b1f8c471cc7..5f6374cc885cd3bc279ce3ea215b180aa23ec933 100644 (file)
@@ -48,6 +48,10 @@ You are not allowed to view this page.
   <th>Organisation</th>
   <td tal:content="structure context/organisation/field">organisation</td>
  </tr>
   <th>Organisation</th>
   <td tal:content="structure context/organisation/field">organisation</td>
  </tr>
+ <tr>
+  <th>Timezone</th>
+  <td tal:content="structure context/timezone/field">timezone</td>
+ </tr>
  <tr>
   <th>E-mail address</th>
   <td tal:content="structure context/address/field">address</td>
  <tr>
   <th>E-mail address</th>
   <td tal:content="structure context/address/field">address</td>
@@ -95,6 +99,10 @@ You are not allowed to view this page.
   <th>Organisation</th>
   <td tal:content="context/organisation">organisation</td>
  </tr>
   <th>Organisation</th>
   <td tal:content="context/organisation">organisation</td>
  </tr>
+ <tr>
+  <th>Timezone</th>
+  <td tal:content="context/timezone">timezone</td>
+ </tr>
  <tr>
   <th>E-mail address</th>
   <td tal:content="context/address/email">address</td>
  <tr>
   <th>E-mail address</th>
   <td tal:content="context/address/email">address</td>