From 3ad5c72a98b94f9d5c6a87313d7678b440cd29a2 Mon Sep 17 00:00:00 2001 From: richard Date: Fri, 9 May 2003 01:47:51 +0000 Subject: [PATCH] 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 git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1697 57a73879-2fb5-44c3-a270-3262357dd7e2 --- CHANGES.txt | 3 ++- roundup/backends/back_anydbm.py | 25 +++++++++++++------- roundup/backends/back_bsddb.py | 11 +++++---- roundup/backends/back_bsddb3.py | 39 +++++++++++++++++++++++++++----- roundup/backends/back_metakit.py | 17 +++++++++----- roundup/cgi/client.py | 27 ++++++++++++++++------ roundup/cgi/templating.py | 18 +++++++++------ test/test_cgi.py | 13 ++++++++++- test/test_db.py | 8 ++++--- 9 files changed, 117 insertions(+), 44 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index cb16146..fb7e8fc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -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) diff --git a/roundup/backends/back_anydbm.py b/roundup/backends/back_anydbm.py index 8299415..b15ce46 100644 --- a/roundup/backends/back_anydbm.py +++ b/roundup/backends/back_anydbm.py @@ -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 diff --git a/roundup/backends/back_bsddb.py b/roundup/backends/back_bsddb.py index b4ce868..bffebf9 100644 --- a/roundup/backends/back_bsddb.py +++ b/roundup/backends/back_bsddb.py @@ -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): diff --git a/roundup/backends/back_bsddb3.py b/roundup/backends/back_bsddb3.py index f894047..90f3379 100644 --- a/roundup/backends/back_bsddb3.py +++ b/roundup/backends/back_bsddb3.py @@ -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): diff --git a/roundup/backends/back_metakit.py b/roundup/backends/back_metakit.py index 7b94ac8..3482867 100755 --- a/roundup/backends/back_metakit.py +++ b/roundup/backends/back_metakit.py @@ -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): diff --git a/roundup/cgi/client.py b/roundup/cgi/client.py index 007d7a0..97df173 100644 --- a/roundup/cgi/client.py +++ b/roundup/cgi/client.py @@ -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) diff --git a/roundup/cgi/templating.py b/roundup/cgi/templating.py index 4eacc8c..b6cbdcc 100644 --- a/roundup/cgi/templating.py +++ b/roundup/cgi/templating.py @@ -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 diff --git a/test/test_cgi.py b/test/test_cgi.py index d87f2ec..92e0a58 100644 --- a/test/test_cgi.py +++ b/test/test_cgi.py @@ -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), diff --git a/test/test_db.py b/test/test_db.py index da9f3c7..0aa53fd 100644 --- a/test/test_db.py +++ b/test/test_db.py @@ -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() -- 2.30.2