X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fhyperdb.py;h=4bac87fc63039f2f258e2a26737990063e101f3b;hb=c9e46f5700bb319acc389498e7ec904a4ac7ed11;hp=a301d7cd3fa04ac7bdc0fae34681d709171e47b2;hpb=d752b3cfe2b038a68bcda411c18bb5acb1735c00;p=roundup.git diff --git a/roundup/hyperdb.py b/roundup/hyperdb.py index a301d7c..4bac87f 100644 --- a/roundup/hyperdb.py +++ b/roundup/hyperdb.py @@ -15,11 +15,11 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: hyperdb.py,v 1.81 2002-08-30 08:37:16 richard Exp $ +# $Id: hyperdb.py,v 1.96 2004-02-11 23:55:08 richard Exp $ -__doc__ = """ -Hyperdatabase implementation, especially field types. +"""Hyperdatabase implementation, especially field types. """ +__docformat__ = 'restructuredtext' # standard python modules import sys, os, time, re @@ -163,8 +163,7 @@ transaction. Implementation -------------- -All methods except __repr__ and getnode must be implemented by a -concrete backend Class. +All methods except __repr__ must be implemented by a concrete backend Database. ''' @@ -188,7 +187,15 @@ concrete backend Class. raise NotImplementedError def post_init(self): - """Called once the schema initialisation has finished.""" + """Called once the schema initialisation has finished. + If 'refresh' is true, we want to rebuild the backend + structures. + """ + raise NotImplementedError + + def refresh_database(self): + """Called to indicate that the backend should rebuild all tables + and structures. Not called in normal usage.""" raise NotImplementedError def __getattr__(self, classname): @@ -223,8 +230,8 @@ concrete backend Class. raise NotImplementedError def addnode(self, classname, nodeid, node): - '''Add the specified node to its class's db. - ''' + """Add the specified node to its class's db. + """ raise NotImplementedError def serialise(self, classname, node): @@ -243,26 +250,23 @@ concrete backend Class. ''' return node - def getnode(self, classname, nodeid, db=None, cache=1): + def getnode(self, classname, nodeid): '''Get a node from the database. + + 'cache' exists for backwards compatibility, and is not used. ''' raise NotImplementedError - def hasnode(self, classname, nodeid, db=None): + def hasnode(self, classname, nodeid): '''Determine if the database has a given node. ''' raise NotImplementedError - def countnodes(self, classname, db=None): + def countnodes(self, classname): '''Count the number of nodes that exist for a particular Class. ''' raise NotImplementedError - def getnodeids(self, classname, db=None): - '''Retrieve all the ids of the nodes for a particular Class. - ''' - raise NotImplementedError - def storefile(self, classname, nodeid, property, content): '''Store the content of the file in the database. @@ -365,25 +369,25 @@ 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 - def getnode(self, nodeid, cache=1): + # not in spec + def getnode(self, nodeid): ''' Return a convenience wrapper for the node. '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 getnodeids(self, retired=None): + '''Retrieve all the ids of the nodes for a particular Class. + ''' + raise NotImplementedError def set(self, nodeid, **propvalues): """Modify a property on an existing node of this class. @@ -416,6 +420,13 @@ class Class: """ raise NotImplementedError + def restore(self, nodeid): + '''Restpre a retired node. + + Make node available for all operations like it was before retirement. + ''' + raise NotImplementedError + def is_retired(self, nodeid): '''Return true if the node is rerired ''' @@ -427,8 +438,11 @@ class Class: WARNING: this method should never be used except in extremely rare situations where there could never be links to the node being deleted + WARNING: use retire() instead + WARNING: the properties of this node will not be available ever again + WARNING: really, use retire() instead Well, I think that's enough warnings. This method exists mostly to @@ -474,15 +488,16 @@ class Class: raise NotImplementedError def labelprop(self, default_to_id=0): - ''' Return the property name for a label for the given node. + """Return the property name for a label for the given node. This method attempts to generate a consistent label for the node. It tries the following in order: - 1. key property - 2. "name" property - 3. "title" property - 4. first property from the sorted property name list - ''' + + 1. key property + 2. "name" property + 3. "title" property + 4. first property from the sorted property name list + """ raise NotImplementedError def lookup(self, keyvalue): @@ -512,12 +527,23 @@ class Class: """ raise NotImplementedError - def filter(self, search_matches, filterspec, sort, group, - num_re = re.compile('^\d+$')): - ''' Return a list of the ids of the active nodes in this class that - match the 'filter' spec, sorted by the group spec and then the - sort spec - ''' + def filter(self, search_matches, filterspec, sort=(None,None), + group=(None,None)): + """Return a list of the ids of the active nodes in this class that + match the 'filter' spec, sorted by the group spec and then the + sort spec. + + "filterspec" is {propname: value(s)} + + "sort" and "group" are (dir, prop) where dir is '+', '-' or None + and prop is a prop name or None + + "search_matches" is {nodeid: marker} + + The filter must match all properties specificed - but if the + property value to match is a list, any one of the values in the + list may match for that property to match. + """ raise NotImplementedError def count(self): @@ -552,32 +578,217 @@ class Class: ''' raise NotImplementedError + def safeget(self, nodeid, propname, default=None): + """Safely get the value of a property on an existing node of this class. + + Return 'default' if the node doesn't exist. + """ + try: + return self.get(nodeid, propname) + except IndexError: + return default + +class HyperdbValueError(ValueError): + ''' Error converting a raw value into a Hyperdb value ''' + pass + +def convertLinkValue(db, propname, prop, value, idre=re.compile('\d+')): + ''' Convert the link value (may be id or key value) to an id value. ''' + linkcl = db.classes[prop.classname] + if not idre.match(value): + if linkcl.getkey(): + try: + value = linkcl.lookup(value) + except KeyError, message: + raise HyperdbValueError, 'property %s: %r is not a %s.'%( + propname, value, prop.classname) + else: + raise HyperdbValueError, 'you may only enter ID values '\ + 'for property %s'%propname + return value + +def fixNewlines(text): + """ Homogenise line endings. + + Different web clients send different line ending values, but + other systems (eg. email) don't necessarily handle those line + endings. Our solution is to convert all line endings to LF. + """ + text = text.replace('\r\n', '\n') + return text.replace('\r', '\n') + +def rawToHyperdb(db, klass, itemid, propname, value, + pwre=re.compile(r'{(\w+)}(.+)')): + ''' Convert the raw (user-input) value to a hyperdb-storable value. The + value is for the "propname" property on itemid (may be None for a + new item) of "klass" in "db". + + The value is usually a string, but in the case of multilink inputs + it may be either a list of strings or a string with comma-separated + values. + ''' + properties = klass.getprops() + + # ensure it's a valid property name + propname = propname.strip() + try: + proptype = properties[propname] + except KeyError: + raise HyperdbValueError, '%r is not a property of %s'%(propname, + klass.classname) + + # if we got a string, strip it now + if isinstance(value, type('')): + value = value.strip() + + # convert the input value to a real property value + if isinstance(proptype, String): + # fix the CRLF/CR -> LF stuff + value = fixNewlines(value) + if isinstance(proptype, Password): + m = pwre.match(value) + if m: + # password is being given to us encrypted + p = password.Password() + p.scheme = m.group(1) + if p.scheme not in 'SHA crypt plaintext'.split(): + raise HyperdbValueError, 'property %s: unknown encryption '\ + 'scheme %r'%(propname, p.scheme) + p.password = m.group(2) + value = p + else: + try: + value = password.Password(value) + except password.PasswordValueError, message: + raise HyperdbValueError, 'property %s: %s'%(propname, message) + elif isinstance(proptype, Date): + try: + tz = db.getUserTimezone() + value = date.Date(value).local(tz) + except ValueError, message: + raise HyperdbValueError, 'property %s: %r is an invalid '\ + 'date (%s)'%(propname, value, message) + elif isinstance(proptype, Interval): + try: + value = date.Interval(value) + except ValueError, message: + raise HyperdbValueError, 'property %s: %r is an invalid '\ + 'date interval (%s)'%(propname, value, message) + elif isinstance(proptype, Link): + if value == '-1' or not value: + value = None + else: + value = convertLinkValue(db, propname, proptype, value) + + elif isinstance(proptype, Multilink): + # get the current item value if it's not a new item + if itemid and not itemid.startswith('-'): + curvalue = klass.get(itemid, propname) + else: + curvalue = [] + + # if the value is a comma-separated string then split it now + if isinstance(value, type('')): + value = value.split(',') + + # handle each add/remove in turn + # keep an extra list for all items that are + # definitely in the new list (in case of e.g. + # =A,+B, which should replace the old + # list with A,B) + set = 1 + newvalue = [] + for item in value: + item = item.strip() + + # skip blanks + if not item: continue + + # handle +/- + remove = 0 + if item.startswith('-'): + remove = 1 + item = item[1:] + set = 0 + elif item.startswith('+'): + item = item[1:] + set = 0 + + # look up the value + itemid = convertLinkValue(db, propname, proptype, item) + + # perform the add/remove + if remove: + try: + curvalue.remove(itemid) + except ValueError: + raise HyperdbValueError, 'property %s: %r is not ' \ + 'currently an element'%(propname, item) + else: + newvalue.append(itemid) + if itemid not in curvalue: + curvalue.append(itemid) + + # that's it, set the new Multilink property value, + # or overwrite it completely + if set: + value = newvalue + else: + value = curvalue + + # TODO: one day, we'll switch to numeric ids and this will be + # unnecessary :( + value = [int(x) for x in value] + value.sort() + value = [str(x) for x in value] + elif isinstance(proptype, Boolean): + value = value.strip() + value = value.lower() in ('yes', 'true', 'on', '1') + elif isinstance(proptype, Number): + value = value.strip() + try: + value = float(value) + except ValueError: + raise HyperdbValueError, 'property %s: %r is not a number'%( + propname, value) + return value + +class FileClass: + ''' A class that requires the "content" property and stores it on + disk. + ''' + pass + class Node: ''' A convenience wrapper for the given 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) + def get(self, name, default=None): + if self.has_key(name): + return self[name] + else: + return default def __getattr__(self, name): 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 @@ -585,7 +796,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}) @@ -607,399 +818,4 @@ def Choice(name, db, *options): cl.create(name=options[i], order=i) return hyperdb.Link(name) -# -# $Log: not supported by cvs2svn $ -# Revision 1.80 2002/08/16 04:28:13 richard -# added is_retired query to Class -# -# Revision 1.79 2002/07/29 23:30:14 richard -# documentation reorg post-new-security -# -# Revision 1.78 2002/07/21 03:26:37 richard -# Gordon, does this help? -# -# Revision 1.77 2002/07/18 11:27:47 richard -# ws -# -# Revision 1.76 2002/07/18 11:17:30 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.75 2002/07/14 02:05:53 richard -# . all storage-specific code (ie. backend) is now implemented by the backends -# -# Revision 1.74 2002/07/10 00:24:10 richard -# braino -# -# Revision 1.73 2002/07/10 00:19:48 richard -# Added explicit closing of backend database handles. -# -# Revision 1.72 2002/07/09 21:53:38 gmcm -# Optimize Class.find so that the propspec can contain a set of ids to match. -# This is used by indexer.search so it can do just one find for all the index matches. -# This was already confusing code, but for common terms (lots of index matches), -# it is enormously faster. -# -# Revision 1.71 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.70 2002/06/27 12:06:20 gmcm -# Improve an error message. -# -# Revision 1.69 2002/06/17 23:15:29 richard -# Can debug to stdout now -# -# Revision 1.68 2002/06/11 06:52:03 richard -# . #564271 ] find() and new properties -# -# Revision 1.67 2002/06/11 05:02:37 richard -# . #565979 ] code error in hyperdb.Class.find -# -# Revision 1.66 2002/05/25 07:16:24 rochecompaan -# Merged search_indexing-branch with HEAD -# -# Revision 1.65 2002/05/22 04:12:05 richard -# . applied patch #558876 ] cgi client customization -# ... with significant additions and modifications ;) -# - extended handling of ML assignedto to all places it's handled -# - added more NotFound info -# -# Revision 1.64 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.63 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.62 2002/04/03 07:05:50 richard -# d'oh! killed retirement of nodes :( -# all better now... -# -# Revision 1.61 2002/04/03 06:11:51 richard -# Fix for old databases that contain properties that don't exist any more. -# -# Revision 1.60 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.59.2.2 2002/04/20 13:23:33 rochecompaan -# We now have a separate search page for nodes. Search links for -# different classes can be customized in instance_config similar to -# index links. -# -# Revision 1.59.2.1 2002/04/19 19:54:42 rochecompaan -# cgi_client.py -# removed search link for the time being -# moved rendering of matches to htmltemplate -# hyperdb.py -# filtering of nodes on full text search incorporated in filter method -# roundupdb.py -# added paramater to call of filter method -# roundup_indexer.py -# added search method to RoundupIndexer class -# -# Revision 1.59 2002/03/12 22:52:26 richard -# more pychecker warnings removed -# -# Revision 1.58 2002/02/27 03:23:16 richard -# Ran it through pychecker, made fixes -# -# Revision 1.57 2002/02/20 05:23:24 richard -# Didn't accomodate new values for new properties -# -# Revision 1.56 2002/02/20 05:05:28 richard -# . Added simple editing for classes that don't define a templated interface. -# - access using the admin "class list" interface -# - limited to admin-only -# - requires the csv module from object-craft (url given if it's missing) -# -# Revision 1.55 2002/02/15 07:27:12 richard -# Oops, precedences around the way w0rng. -# -# Revision 1.54 2002/02/15 07:08:44 richard -# . Alternate email addresses are now available for users. See the MIGRATION -# file for info on how to activate the feature. -# -# Revision 1.53 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.52 2002/01/21 16:33:19 rochecompaan -# You can now use the roundup-admin tool to pack the database -# -# Revision 1.51 2002/01/21 03:01:29 richard -# brief docco on the do_journal argument -# -# Revision 1.50 2002/01/19 13:16:04 rochecompaan -# Journal entries for link and multilink properties can now be switched on -# or off. -# -# Revision 1.49 2002/01/16 07:02:57 richard -# . lots of date/interval related changes: -# - more relaxed date format for input -# -# Revision 1.48 2002/01/14 06:32:34 richard -# . #502951 ] adding new properties to old database -# -# Revision 1.47 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.46 2002/01/07 10:42:23 richard -# oops -# -# Revision 1.45 2002/01/02 04:18:17 richard -# hyperdb docstrings -# -# Revision 1.44 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.43 2001/12/20 06:13:24 rochecompaan -# Bugs fixed: -# . Exception handling in hyperdb for strings-that-look-like numbers got -# lost somewhere -# . Internet Explorer submits full path for filename - we now strip away -# the path -# Features added: -# . Link and multilink properties are now displayed sorted in the cgi -# interface -# -# Revision 1.42 2001/12/16 10:53:37 richard -# take a copy of the node dict so that the subsequent set -# operation doesn't modify the oldvalues structure -# -# Revision 1.41 2001/12/15 23:47:47 richard -# Cleaned up some bare except statements -# -# Revision 1.40 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.39 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.38 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.37 2001/11/28 21:55:35 richard -# . login_action and newuser_action return values were being ignored -# . Woohoo! Found that bloody re-login bug that was killing the mail -# gateway. -# (also a minor cleanup in hyperdb) -# -# Revision 1.36 2001/11/27 03:16:09 richard -# Another place that wasn't handling missing properties. -# -# Revision 1.35 2001/11/22 15:46:42 jhermann -# Added module docstrings to all modules. -# -# Revision 1.34 2001/11/21 04:04:43 richard -# *sigh* more missing value handling -# -# Revision 1.33 2001/11/21 03:40:54 richard -# more new property handling -# -# Revision 1.32 2001/11/21 03:11:28 richard -# Better handling of new properties. -# -# Revision 1.31 2001/11/12 22:01:06 richard -# Fixed issues with nosy reaction and author copies. -# -# Revision 1.30 2001/11/09 10:11:08 richard -# . roundup-admin now handles all hyperdb exceptions -# -# Revision 1.29 2001/10/27 00:17:41 richard -# Made Class.stringFind() do caseless matching. -# -# Revision 1.28 2001/10/21 04:44:50 richard -# bug #473124: UI inconsistency with Link fields. -# This also prompted me to fix a fairly long-standing usability issue - -# that of being able to turn off certain filters. -# -# Revision 1.27 2001/10/20 23:44:27 richard -# Hyperdatabase sorts strings-that-look-like-numbers as numbers now. -# -# Revision 1.26 2001/10/16 03:48:01 richard -# admin tool now complains if a "find" is attempted with a non-link property. -# -# Revision 1.25 2001/10/11 00:17:51 richard -# Reverted a change in hyperdb so the default value for missing property -# values in a create() is None and not '' (the empty string.) This obviously -# breaks CSV import/export - the string 'None' will be created in an -# export/import operation. -# -# Revision 1.24 2001/10/10 03:54:57 richard -# Added database importing and exporting through CSV files. -# Uses the csv module from object-craft for exporting if it's available. -# Requires the csv module for importing. -# -# Revision 1.23 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.22 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.21 2001/10/05 02:23:24 richard -# . roundup-admin create now prompts for property info if none is supplied -# on the command-line. -# . hyperdb Class getprops() method may now return only the mutable -# properties. -# . Login now uses cookies, which makes it a whole lot more flexible. We can -# now support anonymous user access (read-only, unless there's an -# "anonymous" user, in which case write access is permitted). Login -# handling has been moved into cgi_client.Client.main() -# . The "extended" schema is now the default in roundup init. -# . The schemas have had their page headings modified to cope with the new -# login handling. Existing installations should copy the interfaces.py -# file from the roundup lib directory to their instance home. -# . Incorrectly had a Bizar Software copyright on the cgitb.py module from -# Ping - has been removed. -# . Fixed a whole bunch of places in the CGI interface where we should have -# been returning Not Found instead of throwing an exception. -# . Fixed a deviation from the spec: trying to modify the 'id' property of -# an item now throws an exception. -# -# Revision 1.20 2001/10/04 02:12:42 richard -# Added nicer command-line item adding: passing no arguments will enter an -# interactive more which asks for each property in turn. While I was at it, I -# fixed an implementation problem WRT the spec - I wasn't raising a -# ValueError if the key property was missing from a create(). Also added a -# protected=boolean argument to getprops() so we can list only the mutable -# properties (defaults to yes, which lists the immutables). -# -# Revision 1.19 2001/08/29 04:47:18 richard -# Fixed CGI client change messages so they actually include the properties -# changed (again). -# -# Revision 1.18 2001/08/16 07:34:59 richard -# better CGI text searching - but hidden filter fields are disappearing... -# -# Revision 1.17 2001/08/16 06:59:58 richard -# all searches use re now - and they're all case insensitive -# -# Revision 1.16 2001/08/15 23:43:18 richard -# Fixed some isFooTypes that I missed. -# Refactored some code in the CGI code. -# -# Revision 1.15 2001/08/12 06:32:36 richard -# using isinstance(blah, Foo) now instead of isFooType -# -# Revision 1.14 2001/08/07 00:24:42 richard -# stupid typo -# -# Revision 1.13 2001/08/07 00:15:51 richard -# Added the copyright/license notice to (nearly) all files at request of -# Bizar Software. -# -# Revision 1.12 2001/08/02 06:38:17 richard -# Roundupdb now appends "mailing list" information to its messages which -# include the e-mail address and web interface address. Templates may -# override this in their db classes to include specific information (support -# instructions, etc). -# -# Revision 1.11 2001/08/01 04:24:21 richard -# mailgw was assuming certain properties existed on the issues being created. -# -# Revision 1.10 2001/07/30 02:38:31 richard -# get() now has a default arg - for migration only. -# -# Revision 1.9 2001/07/29 09:28:23 richard -# Fixed sorting by clicking on column headings. -# -# Revision 1.8 2001/07/29 08:27:40 richard -# Fixed handling of passed-in values in form elements (ie. during a -# drill-down) -# -# Revision 1.7 2001/07/29 07:01:39 richard -# Added vim command to all source so that we don't get no steenkin' tabs :) -# -# Revision 1.6 2001/07/29 05:36:14 richard -# Cleanup of the link label generation. -# -# Revision 1.5 2001/07/29 04:05:37 richard -# Added the fabricated property "id". -# -# Revision 1.4 2001/07/27 06:25:35 richard -# Fixed some of the exceptions so they're the right type. -# Removed the str()-ification of node ids so we don't mask oopsy errors any -# more. -# -# Revision 1.3 2001/07/27 05:17:14 richard -# just some comments -# -# Revision 1.2 2001/07/22 12:09:32 richard -# Final commit of Grande Splite -# -# Revision 1.1 2001/07/22 11:58:35 richard -# More Grande Splite -# -# # vim: set filetype=python ts=4 sw=4 et si