Code

A couple of form value handling changes:
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Fri, 9 May 2003 01:47:51 +0000 (01:47 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Fri, 9 May 2003 01:47:51 +0000 (01:47 +0000)
 - multilink properties may hhave multiple form values "1", "2,4", "5", ...
 - string search properties are split on whitespace and match any of the
   values

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

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

index cb161461277ecf1f6aa1d722f82f626a9f4dc329..fb7e8fcda7e76361f78acb887ea6de0dafec34dd 100644 (file)
@@ -65,6 +65,7 @@ Feature:
   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 @@ Fixed:
   (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)
index 8299415e12658d3cb1b88178b958bbfc710b443a..b15ce46f78b2a55be665b13f7dc1416c7e7cd2f6 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.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 @@ class Class(hyperdb.Class):
                 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 @@ class Class(hyperdb.Class):
                             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
index b4ce8688ca5ece62f1e68131c3a7fe0005d01633..bffebf9116d20d6b3ec0b30e3c2626b9cd6d29fa 100644 (file)
@@ -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 @@ class Database(Database):
     #
     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 @@ class Database(Database):
         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):
index f894047ea6c2ea9710e19a6d1711495e33f4c6c1..90f337903254a46cbfe4d4126bfbab3b31abe2e5 100644 (file)
@@ -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 @@ class Database(Database):
     #
     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 @@ class Database(Database):
             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):
index 7b94ac847e8b384601485db7e672e448f7f448ea..34828679aaa7a389b68305a2dc2a36f06e039a3e 100755 (executable)
@@ -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 @@ class Class:
                 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):
index 007d7a04315d9eb40ad2b6cbeae9787309b912e3..97df1733b750fdfca9aa726b56ea4f137002d686 100644 (file)
@@ -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 @@ You should then receive another email with the new password.
             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 @@ You should then receive another email with the new password.
             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 @@ def extractFormList(value):
     ''' 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)
index 4eacc8cc258846563f534b4a455cc4345ff074fe..b6cbdcc569242ee3207110aa6c15d3c3c61a2159 100644 (file)
@@ -1457,13 +1457,17 @@ class HTMLRequest:
         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
 
index d87f2ec6dc2888b16b618deb98cbe68829c73d91..92e0a58793a637c232ad1410e764be14aefde5bc 100644 (file)
@@ -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 @@ class FormTestCase(unittest.TestCase):
         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), 
index da9f3c7fdbbdb9c907125deec9e5282bf7bf84dc..0aa53fd406a7be1dbd569e59a037a27e9a128e86 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.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 @@ class anydbmDBTestCase(MyTestCase):
 
     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()