changeset 1631:8a908bbad1ef

A couple of form value handling changes: - multilink properties may hhave multiple form values "1", "2,4", "5", ... - string search properties are split on whitespace and match any of the values
author Richard Jones <richard@users.sourceforge.net>
date Fri, 09 May 2003 01:47:51 +0000
parents 27768800be5c
children eb4d04a9d647
files CHANGES.txt roundup/backends/back_anydbm.py roundup/backends/back_bsddb.py roundup/backends/back_bsddb3.py roundup/backends/back_metakit.py roundup/cgi/client.py roundup/cgi/templating.py test/test_cgi.py test/test_db.py
diffstat 9 files changed, 117 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES.txt	Thu May 08 07:07:42 2003 +0000
+++ b/CHANGES.txt	Fri May 09 01:47:51 2003 +0000
@@ -65,6 +65,7 @@
   3rd-party templates.
 - extended date syntax to make range searches even more useful
 - SMTP login and TLS support added (sf bug 710853 with extras ;)
+  Note: requires python 2.2+
 
 Fixed:
 - applied unicode patch. All data is stored in utf-8. Incoming messages
@@ -92,7 +93,7 @@
   (sf "bug" 621226 for the users of the "standards compliant" browser IE)
 
 
-2003-??-?? 0.5.7
+2003-05-08 0.5.7
 - fixed Interval maths (sf bug 665357)
 - fixed sqlite rollback/caching bug (sf bug 689383)
 - fixed rdbms table update detection logic (sf bug 703297)
--- a/roundup/backends/back_anydbm.py	Thu May 08 07:07:42 2003 +0000
+++ b/roundup/backends/back_anydbm.py	Fri May 09 01:47:51 2003 +0000
@@ -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.120 2003-04-22 20:53:54 kedder Exp $
+#$Id: back_anydbm.py,v 1.121 2003-05-09 01:47:50 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
@@ -1685,11 +1685,17 @@
                 u.sort()
                 l.append((MULTILINK, k, u))
             elif isinstance(propclass, String) and k != 'id':
-                # simple glob searching
-                v = re.sub(r'([\|\{\}\\\.\+\[\]\(\)])', r'\\\1', v)
-                v = v.replace('?', '.')
-                v = v.replace('*', '.*?')
-                l.append((STRING, k, re.compile(v, re.I)))
+                if type(v) is not type([]):
+                    v = [v]
+                m = []
+                for v in v:
+                    # simple glob searching
+                    v = re.sub(r'([\|\{\}\\\.\+\[\]\(\)])', r'\\\1', v)
+                    v = v.replace('?', '.')
+                    v = v.replace('*', '.*?')
+                    m.append(v)
+                m = re.compile('(%s)'%('|'.join(m)), re.I)
+                l.append((STRING, k, m))
             elif isinstance(propclass, Date):
                 try:
                     date_rng = Range(v, date.Date, offset=timezone)
@@ -1761,11 +1767,14 @@
                             continue
                         break
                     elif t == STRING:
+                        if node[k] is None:
+                            break
                         # RE search
-                        if node[k] is None or not v.search(node[k]):
+                        if not v.search(node[k]):
                             break
                     elif t == DATE or t == INTERVAL:
-                        if node[k] is None: break
+                        if node[k] is None:
+                            break
                         if v.to_value:
                             if not (v.from_value <= node[k] and v.to_value >= node[k]):
                                 break
--- a/roundup/backends/back_bsddb.py	Thu May 08 07:07:42 2003 +0000
+++ b/roundup/backends/back_bsddb.py	Fri May 09 01:47:51 2003 +0000
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: back_bsddb.py,v 1.25 2003-03-26 11:19:28 richard Exp $
+#$Id: back_bsddb.py,v 1.26 2003-05-09 01:47:50 richard Exp $
 '''
 This module defines a backend that saves the hyperdatabase in BSDDB.
 '''
@@ -74,6 +74,9 @@
     #
     def getjournal(self, classname, nodeid):
         ''' get the journal for id
+
+            Raise IndexError if the node doesn't exist (as per history()'s
+            API)
         '''
         if __debug__:
             print >>hyperdb.DEBUG, 'getjournal', (self, classname, nodeid)
@@ -114,10 +117,8 @@
         db.close()
 
         # add all the saved journal entries for this node
-        for entry in journal:
-            (nodeid, date_stamp, user, action, params) = entry
-            date_obj = date.Date(date_stamp)
-            res.append((nodeid, date_obj, user, action, params))
+        for nodeid, date_stamp, user, action, params in journal:
+            res.append((nodeid, date.Date(date_stamp), user, action, params))
         return res
 
     def getCachedJournalDB(self, classname):
--- a/roundup/backends/back_bsddb3.py	Thu May 08 07:07:42 2003 +0000
+++ b/roundup/backends/back_bsddb3.py	Fri May 09 01:47:51 2003 +0000
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: back_bsddb3.py,v 1.18 2002-10-03 06:56:29 richard Exp $
+#$Id: back_bsddb3.py,v 1.19 2003-05-09 01:47:50 richard Exp $
 '''
 This module defines a backend that saves the hyperdatabase in BSDDB3.
 '''
@@ -74,7 +74,31 @@
     #
     def getjournal(self, classname, nodeid):
         ''' get the journal for id
+
+            Raise IndexError if the node doesn't exist (as per history()'s
+            API)
         '''
+        if __debug__:
+            print >>hyperdb.DEBUG, 'getjournal', (self, classname, nodeid)
+
+        # our journal result
+        res = []
+
+        # add any journal entries for transactions not committed to the
+        # database
+        for method, args in self.transactions:
+            if method != self.doSaveJournal:
+                continue
+            (cache_classname, cache_nodeid, cache_action, cache_params,
+                cache_creator, cache_creation) = args
+            if cache_classname == classname and cache_nodeid == nodeid:
+                if not cache_creator:
+                    cache_creator = self.curuserid
+                if not cache_creation:
+                    cache_creation = date.Date()
+                res.append((cache_nodeid, cache_creation, cache_creator,
+                    cache_action, cache_params))
+
         # attempt to open the journal - in some rare cases, the journal may
         # not exist
         try:
@@ -84,14 +108,17 @@
             raise IndexError, 'no such %s %s'%(classname, nodeid)
         # more handling of bad journals
         if not db.has_key(nodeid):
+            db.close()
+            if res:
+                # we have some unsaved journal entries, be happy!
+                return res
             raise IndexError, 'no such %s %s'%(classname, nodeid)
         journal = marshal.loads(db[nodeid])
-        res = []
-        for entry in journal:
-            (nodeid, date_stamp, user, action, params) = entry
-            date_obj = date.Date(date_stamp)
-            res.append((nodeid, date_obj, user, action, params))
         db.close()
+
+        # add all the saved journal entries for this node
+        for nodeid, date_stamp, user, action, params in journal:
+            res.append((nodeid, date.Date(date_stamp), user, action, params))
         return res
 
     def getCachedJournalDB(self, classname):
--- a/roundup/backends/back_metakit.py	Thu May 08 07:07:42 2003 +0000
+++ b/roundup/backends/back_metakit.py	Fri May 09 01:47:51 2003 +0000
@@ -1,4 +1,4 @@
-# $Id: back_metakit.py,v 1.46 2003-04-20 11:58:45 kedder Exp $
+# $Id: back_metakit.py,v 1.47 2003-05-09 01:47:50 richard Exp $
 '''
    Metakit backend for Roundup, originally by Gordon McMillan.
 
@@ -944,11 +944,16 @@
                 else:
                     orcriteria[propname] = u
             elif isinstance(prop, hyperdb.String):
-                # simple glob searching
-                v = re.sub(r'([\|\{\}\\\.\+\[\]\(\)])', r'\\\1', value)
-                v = v.replace('?', '.')
-                v = v.replace('*', '.*?')
-                regexes[propname] = re.compile(v, re.I)
+                if type(value) is not type([]):
+                    value = [value]
+                m = []
+                for v in value:
+                    # simple glob searching
+                    v = re.sub(r'([\|\{\}\\\.\+\[\]\(\)])', r'\\\1', v)
+                    v = v.replace('?', '.')
+                    v = v.replace('*', '.*?')
+                    m.append(v)
+                regexes[propname] = re.compile('(%s)'%('|'.join(m)), re.I)
             elif propname == 'id':
                 where[propname] = int(value)
             elif isinstance(prop, hyperdb.Boolean):
--- a/roundup/cgi/client.py	Thu May 08 07:07:42 2003 +0000
+++ b/roundup/cgi/client.py	Fri May 09 01:47:51 2003 +0000
@@ -1,4 +1,4 @@
-# $Id: client.py,v 1.114 2003-04-24 07:19:59 richard Exp $
+# $Id: client.py,v 1.115 2003-05-09 01:47:50 richard Exp $
 
 __doc__ = """
 WWW request handler (also used in the stand-alone server).
@@ -1283,15 +1283,17 @@
             return 0
         return 1
 
-    def searchAction(self):
+    def searchAction(self, wcre=re.compile(r'[\s,]+')):
         ''' Mangle some of the form variables.
 
             Set the form ":filter" variable based on the values of the
             filter variables - if they're set to anything other than
             "dontcare" then add them to :filter.
 
-            Also handle the ":queryname" variable and save off the query to
+            Handle the ":queryname" variable and save off the query to
             the user's query list.
+
+            Split any String query values on whitespace and comma.
         '''
         # generic edit is per-class only
         if not self.searchPermission():
@@ -1319,6 +1321,15 @@
             else:
                 if not self.form[key].value:
                     continue
+                if isinstance(props[key], hyperdb.String):
+                    v = self.form[key].value
+                    l = wcre.split(v)
+                    if len(l) > 1:
+                        self.form.value.remove(self.form[key])
+                        # replace the single value with the split list
+                        for v in l:
+                            self.form.value.append(cgi.MiniFieldStorage(key, v))
+
             self.form.value.append(cgi.MiniFieldStorage('@filter', key))
 
         # handle saving the query params
@@ -1857,18 +1868,20 @@
     ''' Extract a list of values from the form value.
 
         It may be one of:
-         [MiniFieldStorage, MiniFieldStorage, ...]
+         [MiniFieldStorage('value'), MiniFieldStorage('value','value',...), ...]
          MiniFieldStorage('value,value,...')
          MiniFieldStorage('value')
     '''
     # multiple values are OK
     if isinstance(value, type([])):
-        # it's a list of MiniFieldStorages
-        value = [i.value.strip() for i in value]
+        # it's a list of MiniFieldStorages - join then into
+        values = ','.join([i.value.strip() for i in value])
     else:
         # it's a MiniFieldStorage, but may be a comma-separated list
         # of values
-        value = [i.strip() for i in value.value.split(',')]
+        values = value.value
+
+    value = [i.strip() for i in values.split(',')]
 
     # filter out the empty bits
     return filter(None, value)
--- a/roundup/cgi/templating.py	Thu May 08 07:07:42 2003 +0000
+++ b/roundup/cgi/templating.py	Fri May 09 01:47:51 2003 +0000
@@ -1457,13 +1457,17 @@
         if self.classname is not None:
             props = db.getclass(self.classname).getprops()
             for name in self.filter:
-                if self.form.has_key(name):
-                    prop = props[name]
-                    fv = self.form[name]
-                    if (isinstance(prop, hyperdb.Link) or
-                            isinstance(prop, hyperdb.Multilink)):
-                        self.filterspec[name] = lookupIds(db, prop,
-                            handleListCGIValue(fv))
+                if not self.form.has_key(name):
+                    continue
+                prop = props[name]
+                fv = self.form[name]
+                if (isinstance(prop, hyperdb.Link) or
+                        isinstance(prop, hyperdb.Multilink)):
+                    self.filterspec[name] = lookupIds(db, prop,
+                        handleListCGIValue(fv))
+                else:
+                    if isinstance(fv, type([])):
+                        self.filterspec[name] = [v.value for v in fv]
                     else:
                         self.filterspec[name] = fv.value
 
--- a/test/test_cgi.py	Thu May 08 07:07:42 2003 +0000
+++ b/test/test_cgi.py	Fri May 09 01:47:51 2003 +0000
@@ -8,7 +8,7 @@
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 #
-# $Id: test_cgi.py,v 1.15 2003-04-17 06:51:44 richard Exp $
+# $Id: test_cgi.py,v 1.16 2003-05-09 01:47:50 richard Exp $
 
 import unittest, os, shutil, errno, sys, difflib, cgi, re
 
@@ -233,6 +233,17 @@
         self.assertEqual(self.parseForm({'nosy': 'admin,2'}, 'issue'),
             ({('issue', None): {'nosy': ['1','2']}}, []))
 
+    def testMixedMultilink(self):
+        form = cgi.FieldStorage()
+        form.list.append(cgi.MiniFieldStorage('nosy', '1,2'))
+        form.list.append(cgi.MiniFieldStorage('nosy', '3'))
+        cl = client.Client(self.instance, None, {'PATH_INFO':'/'}, form)
+        cl.classname = 'issue'
+        cl.nodeid = None
+        cl.db = self.db
+        self.assertEqual(cl.parsePropsFromForm(), 
+            ({('issue', None): {'nosy': ['1','2', '3']}}, []))
+
     def testEmptyMultilinkSet(self):
         nodeid = self.db.issue.create(nosy=['1','2'])
         self.assertEqual(self.parseForm({'nosy': ''}, 'issue', nodeid), 
--- a/test/test_db.py	Thu May 08 07:07:42 2003 +0000
+++ b/test/test_db.py	Fri May 09 01:47:51 2003 +0000
@@ -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.88 2003-04-22 20:53:55 kedder Exp $ 
+# $Id: test_db.py,v 1.89 2003-05-09 01:47:51 richard Exp $ 
 
 import unittest, os, shutil, time
 
@@ -674,9 +674,11 @@
 
     def testFilteringString(self):
         ae, filt = self.filteringSetup()
-        ae(filt(None, {'title': 'issue one'}, ('+','id'), (None,None)), ['1'])
-        ae(filt(None, {'title': 'issue'}, ('+','id'), (None,None)),
+        ae(filt(None, {'title': ['one']}, ('+','id'), (None,None)), ['1'])
+        ae(filt(None, {'title': ['issue']}, ('+','id'), (None,None)),
             ['1','2','3'])
+        ae(filt(None, {'title': ['one', 'two']}, ('+','id'), (None,None)),
+            ['1', '2'])
 
     def testFilteringLink(self):
         ae, filt = self.filteringSetup()

Roundup Issue Tracker: http://roundup-tracker.org/