Code

Initial implementaion (half-baked) at new Tracker instance.
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Thu, 4 Sep 2003 00:47:01 +0000 (00:47 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Thu, 4 Sep 2003 00:47:01 +0000 (00:47 +0000)
Cleaned up caching API / comments in backends.
Fixes to docs.

git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1847 57a73879-2fb5-44c3-a270-3262357dd7e2

CHANGES.txt
doc/announcement.txt
doc/customizing.txt
roundup/backends/back_anydbm.py
roundup/backends/back_metakit.py
roundup/backends/rdbms_common.py
roundup/hyperdb.py
roundup/instance.py
templates/classic/detectors/nosyreaction.py
templates/classic/detectors/statusauditor.py
test/test_db.py

index 2e227b442db5b384ed8b639431b6ebbf6391f394..48fe0142254934333426f730c3f441fe27bfd6f2 100644 (file)
@@ -1,7 +1,12 @@
 This file contains the changes to the Roundup system over time. The entries
 are given with the most recent entry first.
 
-2003-08-?? 0.6.1
+2003-09-?? 0.6.2
+Fixed:
+- cleaned up, clarified internal caching API in *dbm backends
+
+
+2003-08-31 0.6.1
 Fixed:
 - Add note about installing cgi-bin with a different interpreter
 - Importing wasn't setting None values explicitly when it should have been
index 51dc01252157ed590418fa765a40ac1308e94958..3a53e0356a00f7dc1543702a8a947996d3348154 100644 (file)
@@ -1,47 +1,38 @@
 =================================================
-SC-Track Roundup 0.6.0 - an issue tracking system
+SC-Track Roundup 0.6.1 - an issue tracking system
 =================================================
 
-I'm pleased to announce the latest feature-packed release of Roundup. See
-below for a list of some of the goodies included in this release.
+I'm pleased to announce this maintenance release of Roundup. This fix
+introduces Python2.3 compatibility. My thanks to Paul Dubois for
+contributing the csv module compatibility layer.
 
 If you're upgrading from an older version of Roundup you *must* follow
 the "Software Upgrade" guidelines given in the maintenance documentation. 
 
 Unfortunately, the Zope frontend for Roundup is currently broken. I hope to
-revive it in a future 0.6 bugfix release.
-
-The gadfly backend has now been removed, having served its purpose as a
-template for other RDBMS implementations. It is replaced by the sqlite and 
-mysql backends.
+revive it in a future 0.6 maintenance release.
 
 Roundup requires python 2.1.3 or later for correct operation.
 
-The 0.6 release has lots of new goodies including:
-
-- new instant-gratification Demo Mode ("python demo.py" :)
-- added mysql backend (see doc/mysql.txt for details)
-- web interface cleanups including nicer history display, nicer index
-  navigation and nicer popup list windows
-- searching of date ranges
-- better international support, including utf-8 email handling and ability
-  to display localized dates in web interface.
-- more documentation including revamped design document, unix manual pages
-  and some FAQ entries
-- significantly more powerful form handling allowing editing of multiple
-  items and creation of multiple items
-- tracker templates can contain subdirectories and static files (e.g.
-  images) and we may now distribute templates separately from Roundup.
-  Template HTML files now have a .html extension too.
-- user registration is now a two-step process, with confirmation from the
-  email address supplied in the registration form, and we also have a
-  password reset feature for forgotten password / login
-- Windows Service mode for roundup-server when daemonification is
-  attempted on Windows
-- lots of speed enhancements, making the web interface much more responsive
-- fixed issues with dumb email or web clients
-- email system handles more SMTP and POP features (TLS, APOP, ...)
-- lots more little tweaks and back-end work...
+This release fixes some bugs:
+
+- Add note about installing cgi-bin with a different interpreter
+- Importing wasn't setting None values explicitly when it should have been
+- Fixed import warning regarding 0xffff0000 literal, finally, really this
+  time. Checked on win2k. (sf bug 786711)
+- Fix CGI editCSV action to handle metakit's integer itemids
+- Apply fix for "remove" links from Klamer Schutte
+- Added permission check on "remove" link while I was there..
+- Applied CSV fix for python2.3 (sf bug 790363)
+- Fixed form padding in LHS menu (sf bug 790502)
+- Fixed upgrading docs for timezones (sf bug 790498)
+- Set the content type on page templates (can have XML templates now)
+- Various cosmetic fixes (thanks James Kew for being persistent :)
+- Applied patch 739314 (sorry John!)
+
+To give Roundup a try, just download (see below), unpack and run::
+
+    python demo.py
 
 Source and documentation is available at the website:
      http://roundup.sourceforge.net/
index 5df11ba0bd32b08a1591cc11bfa4f35a83a2c562..9ac41f505f589a9a5e5db1fe510357bba2a84180 100644 (file)
@@ -2,7 +2,7 @@
 Customising Roundup
 ===================
 
-:Version: $Revision: 1.95 $
+:Version: $Revision: 1.96 $
 
 .. This document borrows from the ZopeBook section on ZPT. The original is at:
    http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx
@@ -1483,7 +1483,7 @@ _value          the value of the property if any - this is the actual
 There are several methods available on these wrapper objects:
 
 =========== ================================================================
-Method    Description
+Method      Description
 =========== ================================================================
 plain       render a "plain" representation of the property. This method
             may take two arguments:
index e0f516ce1e5b5c5124d5d31c72f8a85a5e66ac32..b0a8556ea238791eae731d60a603268f4e238896 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: back_anydbm.py,v 1.123 2003-08-26 00:06:55 richard Exp $
+#$Id: back_anydbm.py,v 1.124 2003-09-04 00:47:01 richard Exp $
 '''
 This module defines a backend that saves the hyperdatabase in a database
 chosen by anydbm. It is guaranteed to always be available in python
@@ -283,17 +283,20 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
 
     def getnode(self, classname, nodeid, db=None, cache=1):
         ''' get a node from the database
+
+            Note the "cache" parameter is not used, and exists purely for
+            backward compatibility!
         '''
         if __debug__:
             print >>hyperdb.DEBUG, 'getnode', (self, classname, nodeid, db)
-        if cache:
-            # try the cache
-            cache_dict = self.cache.setdefault(classname, {})
-            if cache_dict.has_key(nodeid):
-                if __debug__:
-                    print >>hyperdb.TRACE, 'get %s %s cached'%(classname,
-                        nodeid)
-                return cache_dict[nodeid]
+
+        # try the cache
+        cache_dict = self.cache.setdefault(classname, {})
+        if cache_dict.has_key(nodeid):
+            if __debug__:
+                print >>hyperdb.TRACE, 'get %s %s cached'%(classname,
+                    nodeid)
+            return cache_dict[nodeid]
 
         if __debug__:
             print >>hyperdb.TRACE, 'get %s %s'%(classname, nodeid)
@@ -1008,10 +1011,7 @@ class Class(hyperdb.Class):
         IndexError is raised.  'propname' must be the name of a property
         of this class or a KeyError is raised.
 
-        'cache' indicates whether the transaction cache should be queried
-        for the node. If the node has been modified and you need to
-        determine what its values prior to modification are, you need to
-        set cache=0.
+        'cache' exists for backward compatibility, and is not used.
 
         Attempts to get the "creation" or "activity" properties should
         do the right thing.
@@ -1020,7 +1020,7 @@ class Class(hyperdb.Class):
             return nodeid
 
         # get the node's dict
-        d = self.db.getnode(self.classname, nodeid, cache=cache)
+        d = self.db.getnode(self.classname, nodeid)
 
         # check for one of the special props
         if propname == 'creation':
@@ -1091,12 +1091,9 @@ class Class(hyperdb.Class):
         'nodeid' must be the id of an existing node of this class or an
         IndexError is raised.
 
-        'cache' indicates whether the transaction cache should be queried
-        for the node. If the node has been modified and you need to
-        determine what its values prior to modification are, you need to
-        set cache=0.
+        'cache' exists for backwards compatibility, and is not used.
         '''
-        return Node(self, nodeid, cache=cache)
+        return Node(self, nodeid)
 
     def set(self, nodeid, **propvalues):
         '''Modify a property on an existing node of this class.
@@ -1134,14 +1131,7 @@ class Class(hyperdb.Class):
         self.fireAuditors('set', nodeid, propvalues)
         # Take a copy of the node dict so that the subsequent set
         # operation doesn't modify the oldvalues structure.
-        try:
-            # try not using the cache initially
-            oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid,
-                cache=0))
-        except IndexError:
-            # this will be needed if somone does a create() and set()
-            # with no intervening commit()
-            oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid))
+        oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid))
 
         node = self.db.getnode(self.classname, nodeid)
         if node.has_key(self.db.RETIRED_FLAG):
@@ -2040,7 +2030,9 @@ class FileClass(Class, hyperdb.FileClass):
         return newid
 
     def get(self, nodeid, propname, default=_marker, cache=1):
-        ''' trap the content propname and get it from the file
+        ''' Trap the content propname and get it from the file
+
+        'cache' exists for backwards compatibility, and is not used.
         '''
         poss_msg = 'Possibly an access right configuration problem.'
         if propname == 'content':
@@ -2051,9 +2043,9 @@ class FileClass(Class, hyperdb.FileClass):
                 return 'ERROR reading file: %s%s\n%s\n%s'%(
                         self.classname, nodeid, poss_msg, strerror)
         if default is not _marker:
-            return Class.get(self, nodeid, propname, default, cache=cache)
+            return Class.get(self, nodeid, propname, default)
         else:
-            return Class.get(self, nodeid, propname, cache=cache)
+            return Class.get(self, nodeid, propname)
 
     def getprops(self, protected=1):
         ''' In addition to the actual properties on the node, these methods
index 60997dff9aafd678766dcf672c5634263eda1227..64ab3d7841b433dba49a80a2460c352acb2bd252 100755 (executable)
@@ -1,4 +1,4 @@
-# $Id: back_metakit.py,v 1.48 2003-08-26 00:06:56 richard Exp $
+# $Id: back_metakit.py,v 1.49 2003-09-04 00:47:01 richard Exp $
 '''
    Metakit backend for Roundup, originally by Gordon McMillan.
 
@@ -373,8 +373,9 @@ class Class:
         return str(newid)
     
     def get(self, nodeid, propname, default=_marker, cache=1):
-        # default and cache aren't in the spec
-        # cache=0 means "original value"
+        '''
+            'cache' exists for backwards compatibility, and is not used.
+        '''
 
         view = self.getview()        
         id = int(nodeid)
@@ -1407,7 +1408,7 @@ class FileClass(Class, hyperdb.FileClass):
         Class.__init__(self, db, classname, **properties)
 
     def get(self, nodeid, propname, default=_marker, cache=1):
-        x = Class.get(self, nodeid, propname, default, cache)
+        x = Class.get(self, nodeid, propname, default)
         poss_msg = 'Possibly an access right configuration problem.'
         if propname == 'content':
             if x.startswith('file:'):
index 2bbe0e00479fd4bb5ac165fc178fe8e7446edf89..1bdec79c8153a4dc70929a75d420bc7ddd5040b5 100644 (file)
@@ -1,4 +1,4 @@
-# $Id: rdbms_common.py,v 1.59 2003-08-26 00:06:56 richard Exp $
+# $Id: rdbms_common.py,v 1.60 2003-09-04 00:47:01 richard Exp $
 ''' Relational database (SQL) backend common code.
 
 Basics:
@@ -1183,10 +1183,7 @@ class Class(hyperdb.Class):
         IndexError is raised.  'propname' must be the name of a property
         of this class or a KeyError is raised.
 
-        'cache' indicates whether the transaction cache should be queried
-        for the node. If the node has been modified and you need to
-        determine what its values prior to modification are, you need to
-        set cache=0.
+        'cache' exists for backwards compatibility, and is not used.
         '''
         if propname == 'id':
             return nodeid
@@ -1234,12 +1231,9 @@ class Class(hyperdb.Class):
         'nodeid' must be the id of an existing node of this class or an
         IndexError is raised.
 
-        'cache' indicates whether the transaction cache should be queried
-        for the node. If the node has been modified and you need to
-        determine what its values prior to modification are, you need to
-        set cache=0.
+        'cache' exists for backwards compatibility, and is not used.
         '''
-        return Node(self, nodeid, cache=cache)
+        return Node(self, nodeid)
 
     def set(self, nodeid, **propvalues):
         '''Modify a property on an existing node of this class.
@@ -2094,7 +2088,9 @@ class FileClass(Class, hyperdb.FileClass):
 
     _marker = []
     def get(self, nodeid, propname, default=_marker, cache=1):
-        ''' trap the content propname and get it from the file
+        ''' Trap the content propname and get it from the file
+
+        'cache' exists for backwards compatibility, and is not used.
         '''
         poss_msg = 'Possibly a access right configuration problem.'
         if propname == 'content':
@@ -2105,9 +2101,9 @@ class FileClass(Class, hyperdb.FileClass):
                 return 'ERROR reading file: %s%s\n%s\n%s'%(
                         self.classname, nodeid, poss_msg, strerror)
         if default is not self._marker:
-            return Class.get(self, nodeid, propname, default, cache=cache)
+            return Class.get(self, nodeid, propname, default)
         else:
-            return Class.get(self, nodeid, propname, cache=cache)
+            return Class.get(self, nodeid, propname)
 
     def getprops(self, protected=1):
         ''' In addition to the actual properties on the node, these methods
index 4ab22268aafdf57cd4b7b98022dad397576246ea..78ca20e1a47fd97eb11758f9388151ca5cfd3c4a 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: hyperdb.py,v 1.87 2003-03-17 22:03:03 kedder Exp $
+# $Id: hyperdb.py,v 1.88 2003-09-04 00:47:01 richard Exp $
 
 """
 Hyperdatabase implementation, especially field types.
@@ -245,6 +245,8 @@ concrete backend Class.
 
     def getnode(self, classname, nodeid, db=None, cache=1):
         '''Get a node from the database.
+
+        'cache' exists for backwards compatibility, and is not used.
         '''
         raise NotImplementedError
 
@@ -365,10 +367,7 @@ class Class:
         IndexError is raised.  'propname' must be the name of a property
         of this class or a KeyError is raised.
 
-        'cache' indicates whether the transaction cache should be queried
-        for the node. If the node has been modified and you need to
-        determine what its values prior to modification are, you need to
-        set cache=0.
+        'cache' exists for backwards compatibility, and is not used.
         """
         raise NotImplementedError
 
@@ -378,12 +377,9 @@ class Class:
         'nodeid' must be the id of an existing node of this class or an
         IndexError is raised.
 
-        'cache' indicates whether the transaction cache should be queried
-        for the node. If the node has been modified and you need to
-        determine what its values prior to modification are, you need to
-        set cache=0.
+        'cache' exists for backwards compatibility, and is not used.
         '''
-        return Node(self, nodeid, cache=cache)
+        return Node(self, nodeid)
 
     def set(self, nodeid, **propvalues):
         """Modify a property on an existing node of this class.
@@ -580,18 +576,17 @@ class Node:
     def __init__(self, cl, nodeid, cache=1):
         self.__dict__['cl'] = cl
         self.__dict__['nodeid'] = nodeid
-        self.__dict__['cache'] = cache
     def keys(self, protected=1):
         return self.cl.getprops(protected=protected).keys()
     def values(self, protected=1):
         l = []
         for name in self.cl.getprops(protected=protected).keys():
-            l.append(self.cl.get(self.nodeid, name, cache=self.cache))
+            l.append(self.cl.get(self.nodeid, name))
         return l
     def items(self, protected=1):
         l = []
         for name in self.cl.getprops(protected=protected).keys():
-            l.append((name, self.cl.get(self.nodeid, name, cache=self.cache)))
+            l.append((name, self.cl.get(self.nodeid, name)))
         return l
     def has_key(self, name):
         return self.cl.getprops().has_key(name)
@@ -604,7 +599,7 @@ class Node:
         if self.__dict__.has_key(name):
             return self.__dict__[name]
         try:
-            return self.cl.get(self.nodeid, name, cache=self.cache)
+            return self.cl.get(self.nodeid, name)
         except KeyError, value:
             # we trap this but re-raise it as AttributeError - all other
             # exceptions should pass through untrapped
@@ -612,7 +607,7 @@ class Node:
         # nope, no such attribute
         raise AttributeError, str(value)
     def __getitem__(self, name):
-        return self.cl.get(self.nodeid, name, cache=self.cache)
+        return self.cl.get(self.nodeid, name)
     def __setattr__(self, name, value):
         try:
             return self.cl.set(self.nodeid, **{name: value})
index b87334065a9589cf2a5cc1ea4235b3e752010c0c..ec87f3899616e8997da61df4c5212d6d68fd093b 100644 (file)
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: instance.py,v 1.9 2002-09-20 01:20:31 richard Exp $
+# $Id: instance.py,v 1.10 2003-09-04 00:47:01 richard Exp $
 
 __doc__ = '''
 Tracker handling (open tracker).
 
-Currently this module provides one function: open. This function opens
-a tracker. Note that trackers used to be called instances.
+Backwards compatibility for the old-style "imported" trackers.
 '''
 
-import imp, os
+import os
+
+class Vars:
+    ''' I'm just a container '''
+
+class Tracker:
+    def __init__(self, tracker_home):
+        self.tracker_home = tracker_home
+        self.select_db = self._load_python('select_db.py')
+        self.config = self._load_config('config.py')
+        raise NotImplemented, 'this is *so* not finished'
+        self.init =  XXX
+        self.Client = XXX
+        self.MailGW = XXX
+
+    def open(self):
+        return self._load_config('schema.py').db
+        self._load_config('security.py', db=db)
+
+
+    def __load_python(self, file):
+        file = os.path.join(tracker_home, file)
+        vars = Vars()
+        execfile(file, vars.__dict__)
+        return vars
+
 
 class TrackerError(Exception):
     pass
 
-class Opener:
+
+class OldStyleTrackers:
     def __init__(self):
         self.number = 0
         self.trackers = {}
@@ -39,6 +64,7 @@ class Opener:
 
             Raise ValueError if the tracker home doesn't exist.
         '''
+        import imp
         # sanity check existence of tracker home
         if not os.path.exists(tracker_home):
             raise ValueError, 'no such directory: "%s"'%tracker_home
@@ -67,11 +93,12 @@ class Opener:
 
         return tracker
 
-opener = Opener()
-open = opener.open
-
-del Opener
-del opener
+OldStyleTrackers = OldStyleTrackers()
+def open(tracker_home):
+    if os.path.exists(os.path.join(tracker_home, 'dbinit.py')):
+        # user should upgrade...
+        return OldStyleTrackers.open(tracker_home)
 
+    return Tracker(tracker_home)
 
 # vim: set filetype=python ts=4 sw=4 et si
index 2c292041105c5cb76e5622dd92ff4d23a4f24871..750621112e90d4bed5261cbfbf0158c930b04658 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: nosyreaction.py,v 1.1 2003-04-17 03:26:38 richard Exp $
+#$Id: nosyreaction.py,v 1.2 2003-09-04 00:47:01 richard Exp $
 
 from roundup import roundupdb, hyperdb
 
@@ -107,8 +107,8 @@ def updatenosy(db, cl, nodeid, newvalues):
         else:
             ok = ('yes',)
             # figure which of the messages now on the issue weren't
-            # there before - make sure we don't get a cached version!
-            oldmessages = cl.get(nodeid, 'messages', cache=0)
+            # there before
+            oldmessages = cl.get(nodeid, 'messages')
             messages = []
             for msgid in newvalues['messages']:
                 if msgid not in oldmessages:
index f1ce29b03aa91e9e62a8d907c9beee05a8ca4423..e7aa6e9b02a4dd2c242d83a55bec495af040bdac 100644 (file)
@@ -18,7 +18,7 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 #
-#$Id: statusauditor.py,v 1.2 2003-06-25 09:49:34 neaj Exp $
+#$Id: statusauditor.py,v 1.3 2003-09-04 00:47:01 richard Exp $
 
 def chatty(db, cl, nodeid, newvalues):
     ''' If the issue is currently 'unread', 'resolved' or 'done-cbb', then set
@@ -27,7 +27,7 @@ def chatty(db, cl, nodeid, newvalues):
     # don't fire if there's no new message (ie. chat)
     if not newvalues.has_key('messages'):
         return
-    if newvalues['messages'] == cl.get(nodeid, 'messages', cache=0):
+    if newvalues['messages'] == cl.get(nodeid, 'messages'):
         return
 
     # get the chatting state ID
index 29942138ea77b964fbdee547e0fd091e8af5a1cd..9b16434c0c7181dcd9e5b8781d336a59d294df98 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: test_db.py,v 1.90 2003-08-12 02:22:22 richard Exp $ 
+# $Id: test_db.py,v 1.91 2003-09-04 00:47:01 richard Exp $ 
 
 import unittest, os, shutil, time
 
@@ -296,7 +296,14 @@ class anydbmDBTestCase(MyTestCase):
         self.assertNotEqual(a, self.db.status.list())
         # try to restore retired node
         self.db.status.restore('1')
-        self.assertEqual(a, self.db.status.list())
+    def testCacheCreateSet(self):
+        self.db.issue.create(title="spam", status='1')
+        a = self.db.issue.get('1', 'title')
+        self.assertEqual(a, 'spam')
+        self.db.issue.set('1', title='ham')
+        b = self.db.issue.get('1', 'title')
+        self.assertEqual(b, 'ham')
 
     def testSerialisation(self):
         nid = self.db.issue.create(title="spam", status='1',