X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fbackends%2Fback_anydbm.py;h=91f8a76a4221d7ee7be304f19af475493bdf89e1;hb=f33816f46bf6fec4906e05ad93a8fbc493fdda98;hp=fee6d95dd6d9f967886ba3938a5cbb919cb2e7a8;hpb=8c7c3ac5b665acdbfdf0e6643c4666140934d725;p=roundup.git diff --git a/roundup/backends/back_anydbm.py b/roundup/backends/back_anydbm.py index fee6d95..91f8a76 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.65 2002-08-30 08:35:45 richard Exp $ +#$Id: back_anydbm.py,v 1.79 2002-09-15 23:06:20 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 @@ -121,7 +121,10 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): ''' if __debug__: print >>hyperdb.DEBUG, 'getclass', (self, classname) - return self.classes[classname] + try: + return self.classes[classname] + except KeyError: + raise KeyError, 'There is no class called "%s"'%classname # # Class DBs @@ -154,7 +157,7 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): if os.path.exists(path): db_type = whichdb.whichdb(path) if not db_type: - raise hyperdb.DatabaseError, "Couldn't identify database type" + raise DatabaseError, "Couldn't identify database type" elif os.path.exists(path+'.db'): # if the path ends in '.db', it's a dbm database, whether # anydbm says it's dbhash or not! @@ -175,14 +178,14 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): # new database? let anydbm pick the best dbm if not db_type: if __debug__: - print >>hyperdb.DEBUG, "opendb anydbm.open(%r, 'n')"%path - return anydbm.open(path, 'n') + print >>hyperdb.DEBUG, "opendb anydbm.open(%r, 'c')"%path + return anydbm.open(path, 'c') # open the database with the correct module try: dbm = __import__(db_type) except ImportError: - raise hyperdb.DatabaseError, \ + raise DatabaseError, \ "Couldn't open database - the required module '%s'"\ " is not available"%db_type if __debug__: @@ -447,7 +450,8 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): # # Journal # - def addjournal(self, classname, nodeid, action, params): + def addjournal(self, classname, nodeid, action, params, creator=None, + creation=None): ''' Journal the Action 'action' may be: @@ -457,9 +461,9 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): ''' if __debug__: print >>hyperdb.DEBUG, 'addjournal', (self, classname, nodeid, - action, params) + action, params, creator, creation) self.transactions.append((self.doSaveJournal, (classname, nodeid, - action, params))) + action, params, creator, creation))) def getjournal(self, classname, nodeid): ''' get the journal for id @@ -603,28 +607,22 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): self.databases[db_name] = self.opendb(db_name, 'c') return self.databases[db_name] - def doSaveJournal(self, classname, nodeid, action, params): - # handle supply of the special journalling parameters (usually - # supplied on importing an existing database) + def doSaveJournal(self, classname, nodeid, action, params, creator, + creation): + # serialise the parameters now if necessary if isinstance(params, type({})): - if params.has_key('creator'): - journaltag = self.user.get(params['creator'], 'username') - del params['creator'] - else: - journaltag = self.journaltag - if params.has_key('created'): - journaldate = params['created'].serialise() - del params['created'] - else: - journaldate = date.Date().serialise() - if params.has_key('activity'): - del params['activity'] - - # serialise the parameters now if action in ('set', 'create'): params = self.serialise(classname, params) + + # handle supply of the special journalling parameters (usually + # supplied on importing an existing database) + if creator: + journaltag = creator else: journaltag = self.journaltag + if creation: + journaldate = creation.serialise() + else: journaldate = date.Date().serialise() # create the journal entry @@ -678,6 +676,11 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): self.destroyednodes = {} self.transactions = [] + def close(self): + ''' Nothing to do + ''' + pass + _marker = [] class Class(hyperdb.Class): '''The handle to a particular class of nodes in a hyperdatabase.''' @@ -803,8 +806,8 @@ class Class(hyperdb.Class): l = [] for entry in value: if type(entry) != type(''): - raise ValueError, '"%s" link value (%s) must be '\ - 'String'%(key, value) + raise ValueError, '"%s" multilink value (%r) '\ + 'must contain Strings'%(key, value) # if it isn't a number, it's a key if not num_re.match(entry): try: @@ -884,7 +887,9 @@ class Class(hyperdb.Class): proptype = properties[prop] value = self.get(nodeid, prop) # "marshal" data where needed - if isinstance(proptype, hyperdb.Date): + if value is None: + pass + elif isinstance(proptype, hyperdb.Date): value = value.get_tuple() elif isinstance(proptype, hyperdb.Interval): value = value.get_tuple() @@ -919,6 +924,9 @@ class Class(hyperdb.Class): if propname == 'id': newid = value continue + elif value is None: + # don't set Nones + continue elif isinstance(prop, hyperdb.Date): value = date.Date(value) elif isinstance(prop, hyperdb.Interval): @@ -927,12 +935,26 @@ class Class(hyperdb.Class): pwd = password.Password() pwd.unpack(value) value = pwd - if value is not None: - d[propname] = value + d[propname] = value + + # extract the extraneous journalling gumpf and nuke it + if d.has_key('creator'): + creator = d['creator'] + del d['creator'] + else: + creator = None + if d.has_key('creation'): + creation = d['creation'] + del d['creation'] + else: + creation = None + if d.has_key('activity'): + del d['activity'] - # add + # add the node and journal self.db.addnode(self.classname, newid, d) - self.db.addjournal(self.classname, newid, 'create', d) + self.db.addjournal(self.classname, newid, 'create', d, creator, + creation) return newid def get(self, nodeid, propname, default=_marker, cache=1): @@ -976,10 +998,9 @@ class Class(hyperdb.Class): raise ValueError, 'Journalling is disabled for this class' journal = self.db.getjournal(self.classname, nodeid) if journal: - name = self.db.getjournal(self.classname, nodeid)[0][2] + return self.db.getjournal(self.classname, nodeid)[0][2] else: - return None - return self.db.user.lookup(name) + return self.db.journaltag # get the property (raises KeyErorr if invalid) prop = self.properties[propname] @@ -996,9 +1017,13 @@ class Class(hyperdb.Class): else: return default + # return a dupe of the list so code doesn't get confused + if isinstance(prop, Multilink): + return d[propname][:] + return d[propname] - # XXX not in spec + # not in spec def getnode(self, nodeid, cache=1): ''' Return a convenience wrapper for the node. @@ -1351,7 +1376,7 @@ class Class(hyperdb.Class): otherwise a KeyError is raised. ''' if not self.key: - raise TypeError, 'No key property set' + raise TypeError, 'No key property set for class %s'%self.classname cldb = self.db.getclassdb(self.classname) try: for nodeid in self.db.getnodeids(self.classname, cldb): @@ -1365,7 +1390,7 @@ class Class(hyperdb.Class): cldb.close() raise KeyError, keyvalue - # XXX: change from spec - allows multiple props to match + # change from spec - allows multiple props to match def find(self, **propspec): '''Get the ids of nodes in this class which link to the given nodes. @@ -1471,8 +1496,8 @@ class Class(hyperdb.Class): sort spec. "filterspec" is {propname: value(s)} - "sort" is ['+propname', '-propname', 'propname', ...] - "group is ['+propname', '-propname', 'propname', ...] + "sort" and "group" are (dir, prop) where dir is '+', '-' or None + and prop is a prop name or None "search_matches" is {nodeid: marker} ''' cn = self.classname @@ -1591,126 +1616,109 @@ class Class(hyperdb.Class): k.append(v) l = k - # optimise sort - m = [] - for entry in sort: - if entry[0] != '-': - m.append(('+', entry)) - else: - m.append((entry[0], entry[1:])) - sort = m - - # optimise group - m = [] - for entry in group: - if entry[0] != '-': - m.append(('+', entry)) - else: - m.append((entry[0], entry[1:])) - group = m # now, sort the result def sortfun(a, b, sort=sort, group=group, properties=self.getprops(), db = self.db, cl=self): a_id, an = a b_id, bn = b # sort by group and then sort - for list in group, sort: - for dir, prop in list: - # sorting is class-specific - propclass = properties[prop] + for dir, prop in group, sort: + if dir is None or prop is None: continue - # handle the properties that might be "faked" - # also, handle possible missing properties - try: - if not an.has_key(prop): - an[prop] = cl.get(a_id, prop) - av = an[prop] - except KeyError: - # the node doesn't have a value for this property - if isinstance(propclass, Multilink): av = [] - else: av = '' + # sorting is class-specific + propclass = properties[prop] + + # handle the properties that might be "faked" + # also, handle possible missing properties + try: + if not an.has_key(prop): + an[prop] = cl.get(a_id, prop) + av = an[prop] + except KeyError: + # the node doesn't have a value for this property + if isinstance(propclass, Multilink): av = [] + else: av = '' + try: + if not bn.has_key(prop): + bn[prop] = cl.get(b_id, prop) + bv = bn[prop] + except KeyError: + # the node doesn't have a value for this property + if isinstance(propclass, Multilink): bv = [] + else: bv = '' + + # String and Date values are sorted in the natural way + if isinstance(propclass, String): + # clean up the strings + if av and av[0] in string.uppercase: + av = an[prop] = av.lower() + if bv and bv[0] in string.uppercase: + bv = bn[prop] = bv.lower() + if (isinstance(propclass, String) or + isinstance(propclass, Date)): + # it might be a string that's really an integer try: - if not bn.has_key(prop): - bn[prop] = cl.get(b_id, prop) - bv = bn[prop] - except KeyError: - # the node doesn't have a value for this property - if isinstance(propclass, Multilink): bv = [] - else: bv = '' - - # String and Date values are sorted in the natural way - if isinstance(propclass, String): - # clean up the strings - if av and av[0] in string.uppercase: - av = an[prop] = av.lower() - if bv and bv[0] in string.uppercase: - bv = bn[prop] = bv.lower() - if (isinstance(propclass, String) or - isinstance(propclass, Date)): - # it might be a string that's really an integer - try: - av = int(av) - bv = int(bv) - except: - pass + av = int(av) + bv = int(bv) + except: + pass + if dir == '+': + r = cmp(av, bv) + if r != 0: return r + elif dir == '-': + r = cmp(bv, av) + if r != 0: return r + + # Link properties are sorted according to the value of + # the "order" property on the linked nodes if it is + # present; or otherwise on the key string of the linked + # nodes; or finally on the node ids. + elif isinstance(propclass, Link): + link = db.classes[propclass.classname] + if av is None and bv is not None: return -1 + if av is not None and bv is None: return 1 + if av is None and bv is None: continue + if link.getprops().has_key('order'): if dir == '+': - r = cmp(av, bv) + r = cmp(link.get(av, 'order'), + link.get(bv, 'order')) if r != 0: return r elif dir == '-': - r = cmp(bv, av) + r = cmp(link.get(bv, 'order'), + link.get(av, 'order')) if r != 0: return r - - # Link properties are sorted according to the value of - # the "order" property on the linked nodes if it is - # present; or otherwise on the key string of the linked - # nodes; or finally on the node ids. - elif isinstance(propclass, Link): - link = db.classes[propclass.classname] - if av is None and bv is not None: return -1 - if av is not None and bv is None: return 1 - if av is None and bv is None: continue - if link.getprops().has_key('order'): - if dir == '+': - r = cmp(link.get(av, 'order'), - link.get(bv, 'order')) - if r != 0: return r - elif dir == '-': - r = cmp(link.get(bv, 'order'), - link.get(av, 'order')) - if r != 0: return r - elif link.getkey(): - key = link.getkey() - if dir == '+': - r = cmp(link.get(av, key), link.get(bv, key)) - if r != 0: return r - elif dir == '-': - r = cmp(link.get(bv, key), link.get(av, key)) - if r != 0: return r - else: - if dir == '+': - r = cmp(av, bv) - if r != 0: return r - elif dir == '-': - r = cmp(bv, av) - if r != 0: return r - - # Multilink properties are sorted according to how many - # links are present. - elif isinstance(propclass, Multilink): + elif link.getkey(): + key = link.getkey() if dir == '+': - r = cmp(len(av), len(bv)) + r = cmp(link.get(av, key), link.get(bv, key)) if r != 0: return r elif dir == '-': - r = cmp(len(bv), len(av)) + r = cmp(link.get(bv, key), link.get(av, key)) if r != 0: return r - elif isinstance(propclass, Number) or isinstance(propclass, Boolean): + else: if dir == '+': r = cmp(av, bv) + if r != 0: return r elif dir == '-': r = cmp(bv, av) - - # end for dir, prop in list: - # end for list in sort, group: + if r != 0: return r + + # Multilink properties are sorted according to how many + # links are present. + elif isinstance(propclass, Multilink): + if dir == '+': + r = cmp(len(av), len(bv)) + if r != 0: return r + elif dir == '-': + r = cmp(len(bv), len(av)) + if r != 0: return r + elif isinstance(propclass, Number) or isinstance(propclass, Boolean): + if dir == '+': + r = cmp(av, bv) + elif dir == '-': + r = cmp(bv, av) + + # end for dir, prop in sort, group: # if all else fails, compare the ids return cmp(a[0], b[0]) @@ -1743,7 +1751,9 @@ class Class(hyperdb.Class): d['id'] = String() d['creation'] = hyperdb.Date() d['activity'] = hyperdb.Date() - d['creator'] = hyperdb.Link("user") + # can't be a link to user because the user might have been + # retired since the journal entry was created + d['creator'] = hyperdb.String() return d def addprop(self, **properties): @@ -1832,7 +1842,7 @@ class FileClass(Class): # extract the "content" property from the proplist i = propnames.index('content') - content = proplist[i] + content = eval(proplist[i]) del propnames[i] del proplist[i] @@ -1893,7 +1903,7 @@ class FileClass(Class): self.db.indexer.add_text((self.classname, nodeid, 'content'), content, mime_type) -# XXX deviation from spec - was called ItemClass +# deviation from spec - was called ItemClass class IssueClass(Class, roundupdb.IssueClass): # Overridden methods: def __init__(self, db, classname, **properties): @@ -1909,344 +1919,11 @@ class IssueClass(Class, roundupdb.IssueClass): if not properties.has_key('files'): properties['files'] = hyperdb.Multilink("file") if not properties.has_key('nosy'): - properties['nosy'] = hyperdb.Multilink("user") + # note: journalling is turned off as it really just wastes + # space. this behaviour may be overridden in an instance + properties['nosy'] = hyperdb.Multilink("user", do_journal="no") if not properties.has_key('superseder'): properties['superseder'] = hyperdb.Multilink(classname) Class.__init__(self, db, classname, **properties) # -#$Log: not supported by cvs2svn $ -#Revision 1.64 2002/08/22 07:57:11 richard -#Consistent quoting -# -#Revision 1.63 2002/08/22 04:42:28 richard -#use more robust date stamp comparisons in pack(), make journal smaller too -# -#Revision 1.62 2002/08/21 07:07:27 richard -#In preparing to turn back on link/unlink journal events (by default these -#are turned off) I've: -#- fixed back_anydbm so it can journal those events again (had broken it -# with recent changes) -#- changed the serialisation format for dates and intervals to use a -# numbers-only (and sign for Intervals) string instead of tuple-of-ints. -# Much smaller. -# -#Revision 1.61 2002/08/19 02:53:27 richard -#full database export and import is done -# -#Revision 1.60 2002/08/19 00:23:19 richard -#handle "unset" initial Link values (!) -# -#Revision 1.59 2002/08/16 04:28:13 richard -#added is_retired query to Class -# -#Revision 1.58 2002/08/01 15:06:24 gmcm -#Use same regex to split search terms as used to index text. -#Fix to back_metakit for not changing journaltag on reopen. -#Fix htmltemplate's do_link so [No ] strings are href'd. -#Fix bogus "nosy edited ok" msg - the **d syntax does NOT share d between caller and callee. -# -#Revision 1.57 2002/07/31 23:57:36 richard -# . web forms may now unset Link values (like assignedto) -# -#Revision 1.56 2002/07/31 22:04:33 richard -#cleanup -# -#Revision 1.55 2002/07/30 08:22:38 richard -#Session storage in the hyperdb was horribly, horribly inefficient. We use -#a simple anydbm wrapper now - which could be overridden by the metakit -#backend or RDB backend if necessary. -#Much, much better. -# -#Revision 1.54 2002/07/26 08:26:59 richard -#Very close now. The cgi and mailgw now use the new security API. The two -#templates have been migrated to that setup. Lots of unit tests. Still some -#issue in the web form for editing Roles assigned to users. -# -#Revision 1.53 2002/07/25 07:14:06 richard -#Bugger it. Here's the current shape of the new security implementation. -#Still to do: -# . call the security funcs from cgi and mailgw -# . change shipped templates to include correct initialisation and remove -# the old config vars -#... that seems like a lot. The bulk of the work has been done though. Honest :) -# -#Revision 1.52 2002/07/19 03:36:34 richard -#Implemented the destroy() method needed by the session database (and possibly -#others). At the same time, I removed the leading underscores from the hyperdb -#methods that Really Didn't Need Them. -#The journal also raises IndexError now for all situations where there is a -#request for the journal of a node that doesn't have one. It used to return -#[] in _some_ situations, but not all. This _may_ break code, but the tests -#pass... -# -#Revision 1.51 2002/07/18 23:07:08 richard -#Unit tests and a few fixes. -# -#Revision 1.50 2002/07/18 11:50:58 richard -#added tests for number type too -# -#Revision 1.49 2002/07/18 11:41:10 richard -#added tests for boolean type, and fixes to anydbm backend -# -#Revision 1.48 2002/07/18 11:17:31 gmcm -#Add Number and Boolean types to hyperdb. -#Add conversion cases to web, mail & admin interfaces. -#Add storage/serialization cases to back_anydbm & back_metakit. -# -#Revision 1.47 2002/07/14 23:18:20 richard -#. fixed the journal bloat from multilink changes - we just log the add or -# remove operations, not the whole list -# -#Revision 1.46 2002/07/14 06:06:34 richard -#Did some old TODOs -# -#Revision 1.45 2002/07/14 04:03:14 richard -#Implemented a switch to disable journalling for a Class. CGI session -#database now uses it. -# -#Revision 1.44 2002/07/14 02:05:53 richard -#. all storage-specific code (ie. backend) is now implemented by the backends -# -#Revision 1.43 2002/07/10 06:30:30 richard -#...except of course it's nice to use valid Python syntax -# -#Revision 1.42 2002/07/10 06:21:38 richard -#Be extra safe -# -#Revision 1.41 2002/07/10 00:21:45 richard -#explicit database closing -# -#Revision 1.40 2002/07/09 04:19:09 richard -#Added reindex command to roundup-admin. -#Fixed reindex on first access. -#Also fixed reindexing of entries that change. -# -#Revision 1.39 2002/07/09 03:02:52 richard -#More indexer work: -#- all String properties may now be indexed too. Currently there's a bit of -# "issue" specific code in the actual searching which needs to be -# addressed. In a nutshell: -# + pass 'indexme="yes"' as a String() property initialisation arg, eg: -# file = FileClass(db, "file", name=String(), type=String(), -# comment=String(indexme="yes")) -# + the comment will then be indexed and be searchable, with the results -# related back to the issue that the file is linked to -#- as a result of this work, the FileClass has a default MIME type that may -# be overridden in a subclass, or by the use of a "type" property as is -# done in the default templates. -#- the regeneration of the indexes (if necessary) is done once the schema is -# set up in the dbinit. -# -#Revision 1.38 2002/07/08 06:58:15 richard -#cleaned up the indexer code: -# - it splits more words out (much simpler, faster splitter) -# - removed code we'll never use (roundup.roundup_indexer has the full -# implementation, and replaces roundup.indexer) -# - only index text/plain and rfc822/message (ideas for other text formats to -# index are welcome) -# - added simple unit test for indexer. Needs more tests for regression. -# -#Revision 1.37 2002/06/20 23:52:35 richard -#More informative error message -# -#Revision 1.36 2002/06/19 03:07:19 richard -#Moved the file storage commit into blobfiles where it belongs. -# -#Revision 1.35 2002/05/25 07:16:24 rochecompaan -#Merged search_indexing-branch with HEAD -# -#Revision 1.34 2002/05/15 06:21:21 richard -# . node caching now works, and gives a small boost in performance -# -#As a part of this, I cleaned up the DEBUG output and implemented TRACE -#output (HYPERDBTRACE='file to trace to') with checkpoints at the start of -#CGI requests. Run roundup with python -O to skip all the DEBUG/TRACE stuff -#(using if __debug__ which is compiled out with -O) -# -#Revision 1.33 2002/04/24 10:38:26 rochecompaan -#All database files are now created group readable and writable. -# -#Revision 1.32 2002/04/15 23:25:15 richard -#. node ids are now generated from a lockable store - no more race conditions -# -#We're using the portalocker code by Jonathan Feinberg that was contributed -#to the ASPN Python cookbook. This gives us locking across Unix and Windows. -# -#Revision 1.31 2002/04/03 05:54:31 richard -#Fixed serialisation problem by moving the serialisation step out of the -#hyperdb.Class (get, set) into the hyperdb.Database. -# -#Also fixed htmltemplate after the showid changes I made yesterday. -# -#Unit tests for all of the above written. -# -#Revision 1.30.2.1 2002/04/03 11:55:57 rochecompaan -# . Added feature #526730 - search for messages capability -# -#Revision 1.30 2002/02/27 03:40:59 richard -#Ran it through pychecker, made fixes -# -#Revision 1.29 2002/02/25 14:34:31 grubert -# . use blobfiles in back_anydbm which is used in back_bsddb. -# change test_db as dirlist does not work for subdirectories. -# ATTENTION: blobfiles now creates subdirectories for files. -# -#Revision 1.28 2002/02/16 09:14:17 richard -# . #514854 ] History: "User" is always ticket creator -# -#Revision 1.27 2002/01/22 07:21:13 richard -#. fixed back_bsddb so it passed the journal tests -# -#... it didn't seem happy using the back_anydbm _open method, which is odd. -#Yet another occurrance of whichdb not being able to recognise older bsddb -#databases. Yadda yadda. Made the HYPERDBDEBUG stuff more sane in the -#process. -# -#Revision 1.26 2002/01/22 05:18:38 rochecompaan -#last_set_entry was referenced before assignment -# -#Revision 1.25 2002/01/22 05:06:08 rochecompaan -#We need to keep the last 'set' entry in the journal to preserve -#information on 'activity' for nodes. -# -#Revision 1.24 2002/01/21 16:33:20 rochecompaan -#You can now use the roundup-admin tool to pack the database -# -#Revision 1.23 2002/01/18 04:32:04 richard -#Rollback was breaking because a message hadn't actually been written to the file. Needs -#more investigation. -# -#Revision 1.22 2002/01/14 02:20:15 richard -# . changed all config accesses so they access either the instance or the -# config attriubute on the db. This means that all config is obtained from -# instance_config instead of the mish-mash of classes. This will make -# switching to a ConfigParser setup easier too, I hope. -# -#At a minimum, this makes migration a _little_ easier (a lot easier in the -#0.5.0 switch, I hope!) -# -#Revision 1.21 2002/01/02 02:31:38 richard -#Sorry for the huge checkin message - I was only intending to implement #496356 -#but I found a number of places where things had been broken by transactions: -# . modified ROUNDUPDBSENDMAILDEBUG to be SENDMAILDEBUG and hold a filename -# for _all_ roundup-generated smtp messages to be sent to. -# . the transaction cache had broken the roundupdb.Class set() reactors -# . newly-created author users in the mailgw weren't being committed to the db -# -#Stuff that made it into CHANGES.txt (ie. the stuff I was actually working -#on when I found that stuff :): -# . #496356 ] Use threading in messages -# . detectors were being registered multiple times -# . added tests for mailgw -# . much better attaching of erroneous messages in the mail gateway -# -#Revision 1.20 2001/12/18 15:30:34 rochecompaan -#Fixed bugs: -# . Fixed file creation and retrieval in same transaction in anydbm -# backend -# . Cgi interface now renders new issue after issue creation -# . Could not set issue status to resolved through cgi interface -# . Mail gateway was changing status back to 'chatting' if status was -# omitted as an argument -# -#Revision 1.19 2001/12/17 03:52:48 richard -#Implemented file store rollback. As a bonus, the hyperdb is now capable of -#storing more than one file per node - if a property name is supplied, -#the file is called designator.property. -#I decided not to migrate the existing files stored over to the new naming -#scheme - the FileClass just doesn't specify the property name. -# -#Revision 1.18 2001/12/16 10:53:38 richard -#take a copy of the node dict so that the subsequent set -#operation doesn't modify the oldvalues structure -# -#Revision 1.17 2001/12/14 23:42:57 richard -#yuck, a gdbm instance tests false :( -#I've left the debugging code in - it should be removed one day if we're ever -#_really_ anal about performace :) -# -#Revision 1.16 2001/12/12 03:23:14 richard -#Cor blimey this anydbm/whichdb stuff is yecchy. Turns out that whichdb -#incorrectly identifies a dbm file as a dbhash file on my system. This has -#been submitted to the python bug tracker as issue #491888: -#https://sourceforge.net/tracker/index.php?func=detail&aid=491888&group_id=5470&atid=105470 -# -#Revision 1.15 2001/12/12 02:30:51 richard -#I fixed the problems with people whose anydbm was using the dbm module at the -#backend. It turns out the dbm module modifies the file name to append ".db" -#and my check to determine if we're opening an existing or new db just -#tested os.path.exists() on the filename. Well, no longer! We now perform a -#much better check _and_ cope with the anydbm implementation module changing -#too! -#I also fixed the backends __init__ so only ImportError is squashed. -# -#Revision 1.14 2001/12/10 22:20:01 richard -#Enabled transaction support in the bsddb backend. It uses the anydbm code -#where possible, only replacing methods where the db is opened (it uses the -#btree opener specifically.) -#Also cleaned up some change note generation. -#Made the backends package work with pydoc too. -# -#Revision 1.13 2001/12/02 05:06:16 richard -#. We now use weakrefs in the Classes to keep the database reference, so -# the close() method on the database is no longer needed. -# I bumped the minimum python requirement up to 2.1 accordingly. -#. #487480 ] roundup-server -#. #487476 ] INSTALL.txt -# -#I also cleaned up the change message / post-edit stuff in the cgi client. -#There's now a clearly marked "TODO: append the change note" where I believe -#the change note should be added there. The "changes" list will obviously -#have to be modified to be a dict of the changes, or somesuch. -# -#More testing needed. -# -#Revision 1.12 2001/12/01 07:17:50 richard -#. We now have basic transaction support! Information is only written to -# the database when the commit() method is called. Only the anydbm -# backend is modified in this way - neither of the bsddb backends have been. -# The mail, admin and cgi interfaces all use commit (except the admin tool -# doesn't have a commit command, so interactive users can't commit...) -#. Fixed login/registration forwarding the user to the right page (or not, -# on a failure) -# -#Revision 1.11 2001/11/21 02:34:18 richard -#Added a target version field to the extended issue schema -# -#Revision 1.10 2001/10/09 23:58:10 richard -#Moved the data stringification up into the hyperdb.Class class' get, set -#and create methods. This means that the data is also stringified for the -#journal call, and removes duplication of code from the backends. The -#backend code now only sees strings. -# -#Revision 1.9 2001/10/09 07:25:59 richard -#Added the Password property type. See "pydoc roundup.password" for -#implementation details. Have updated some of the documentation too. -# -#Revision 1.8 2001/09/29 13:27:00 richard -#CGI interfaces now spit up a top-level index of all the instances they can -#serve. -# -#Revision 1.7 2001/08/12 06:32:36 richard -#using isinstance(blah, Foo) now instead of isFooType -# -#Revision 1.6 2001/08/07 00:24:42 richard -#stupid typo -# -#Revision 1.5 2001/08/07 00:15:51 richard -#Added the copyright/license notice to (nearly) all files at request of -#Bizar Software. -# -#Revision 1.4 2001/07/30 01:41:36 richard -#Makes schema changes mucho easier. -# -#Revision 1.3 2001/07/25 01:23:07 richard -#Added the Roundup spec to the new documentation directory. -# -#Revision 1.2 2001/07/23 08:20:44 richard -#Moved over to using marshal in the bsddb and anydbm backends. -#roundup-admin now has a "freshen" command that'll load/save all nodes (not -# retired - mod hyperdb.Class.list() so it lists retired nodes) -# -#