From 6c2d8fd3223c74f97da448f90f79fcee5aaacab3 Mon Sep 17 00:00:00 2001 From: richard Date: Fri, 19 Mar 2004 04:47:59 +0000 Subject: [PATCH] A few big changes in this commit: 1. The current indexer has been moved to backends/indexer_dbm in anticipation of my writing an indexer_rdbms, 2. Changed indexer invocation during create / set to follow the pattern set by the metakit backend, which was much cleaner, and 3. The "content" property of FileClass is now mutable in all but the metakit backend. Metakit needs to be changed to support the editing of "content". Hey, and I learnt today that the metakit backend implements its own indexer. How about that... :) git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@2157 57a73879-2fb5-44c3-a270-3262357dd7e2 --- CHANGES.txt | 1 + roundup/backends/back_anydbm.py | 100 +++++++++------- roundup/backends/back_metakit.py | 6 +- roundup/backends/back_mysql.py | 1 + roundup/backends/back_postgresql.py | 16 +-- roundup/backends/blobfiles.py | 21 ++-- .../{indexer.py => backends/indexer_dbm.py} | 11 +- roundup/backends/rdbms_common.py | 109 +++++++++--------- roundup/backends/sessions_dbm.py | 6 +- roundup/roundupdb.py | 7 +- test/db_test_base.py | 90 ++++++++++++--- test/session_common.py | 6 - test/test_indexer.py | 4 +- test/test_mailgw.py | 4 +- 14 files changed, 235 insertions(+), 147 deletions(-) rename roundup/{indexer.py => backends/indexer_dbm.py} (98%) diff --git a/CHANGES.txt b/CHANGES.txt index a217160..9b15a73 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -55,6 +55,7 @@ Fixed: - the mail gateway now searches recursively for the text/plain and the attachments of a message (sf bug 841241). - fixed display of feedback messages in some situations (sf bug 739545) +- fixed ability to edit "content" property (sf bug 914062) Cleanup: - replace curuserid attribute on Database with the extended getuid() method. diff --git a/roundup/backends/back_anydbm.py b/roundup/backends/back_anydbm.py index 7091d19..6fb115d 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.138 2004-03-18 01:58:45 richard Exp $ +#$Id: back_anydbm.py,v 1.139 2004-03-19 04:47:59 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 versions >2.1.1 (the dumbdbm fallback in 2.1.1 and earlier has several @@ -37,7 +37,7 @@ import whichdb, os, marshal, re, weakref, string, copy from roundup import hyperdb, date, password, roundupdb, security from blobfiles import FileStorage from sessions_dbm import Sessions, OneTimeKeys -from roundup.indexer import Indexer +from indexer_dbm import Indexer from roundup.backends import locking from roundup.hyperdb import String, Password, Date, Interval, Link, \ Multilink, DatabaseError, Boolean, Number, Node @@ -882,6 +882,7 @@ class Class(hyperdb.Class): elif isinstance(prop, String): if type(value) != type('') and type(value) != type(u''): raise TypeError, 'new property "%s" not a string'%key + self.db.indexer.add_text((self.classname, newid, key), value) elif isinstance(prop, Password): if not isinstance(value, password.Password): @@ -1143,6 +1144,15 @@ class Class(hyperdb.Class): These operations trigger detectors and can be vetoed. Attempts to modify the "creation" or "activity" properties cause a KeyError. ''' + self.fireAuditors('set', nodeid, propvalues) + oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid)) + propvalues = self.set_inner(nodeid, **propvalues) + self.fireReactors('set', nodeid, oldvalues) + return propvalues + + def set_inner(self, nodeid, **propvalues): + ''' Called by set, in-between the audit and react calls. + ''' if not propvalues: return propvalues @@ -1155,11 +1165,6 @@ class Class(hyperdb.Class): if self.db.journaltag is None: raise DatabaseError, 'Database open read-only' - self.fireAuditors('set', nodeid, propvalues) - # Take a copy of the node dict so that the subsequent set - # operation doesn't modify the oldvalues structure. - oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid)) - node = self.db.getnode(self.classname, nodeid) if node.has_key(self.db.RETIRED_FLAG): raise IndexError @@ -1290,6 +1295,8 @@ class Class(hyperdb.Class): elif isinstance(prop, String): if value is not None and type(value) != type('') and type(value) != type(u''): raise TypeError, 'new property "%s" not a string'%propname + self.db.indexer.add_text((self.classname, nodeid, propname), + value) elif isinstance(prop, Password): if not isinstance(value, password.Password): @@ -1331,9 +1338,7 @@ class Class(hyperdb.Class): if self.do_journal: self.db.addjournal(self.classname, nodeid, 'set', journalvalues) - self.fireReactors('set', nodeid, oldvalues) - - return propvalues + return propvalues def retire(self, nodeid): '''Retire a node. @@ -1946,20 +1951,18 @@ class Class(hyperdb.Class): self.properties.update(properties) def index(self, nodeid): - '''Add (or refresh) the node to search indexes - ''' + ''' Add (or refresh) the node to search indexes ''' # find all the String properties that have indexme for prop, propclass in self.getprops().items(): - if isinstance(propclass, String) and propclass.indexme: + if isinstance(propclass, hyperdb.String) and propclass.indexme: + # index them under (classname, nodeid, property) try: value = str(self.get(nodeid, prop)) except IndexError: - # node no longer exists - entry should be removed - self.db.indexer.purge_entry((self.classname, nodeid, prop)) - else: - # and index them under (classname, nodeid, property) - self.db.indexer.add_text((self.classname, nodeid, prop), - value) + # node has been destroyed + continue + self.db.indexer.add_text((self.classname, nodeid, prop), value) + # # Detector interface @@ -2012,8 +2015,15 @@ class FileClass(Class, hyperdb.FileClass): content = propvalues['content'] del propvalues['content'] + # make sure we have a MIME type + mime_type = propvalues.get('type', self.default_mime_type) + # do the database create - newid = Class.create_inner(self, **propvalues) + newid = self.create_inner(**propvalues) + + # and index! + self.db.indexer.add_text((self.classname, newid, 'content'), content, + mime_type) # fire reactors self.fireReactors('create', newid, None) @@ -2059,6 +2069,35 @@ class FileClass(Class, hyperdb.FileClass): else: return Class.get(self, nodeid, propname) + def set(self, itemid, **propvalues): + ''' Snarf the "content" propvalue and update it in a file + ''' + self.fireAuditors('set', itemid, propvalues) + oldvalues = copy.deepcopy(self.db.getnode(self.classname, itemid)) + + # now remove the content property so it's not stored in the db + content = None + if propvalues.has_key('content'): + content = propvalues['content'] + del propvalues['content'] + + # do the database create + propvalues = self.set_inner(itemid, **propvalues) + + # do content? + if content: + # store and index + self.db.storefile(self.classname, itemid, None, content) + mime_type = propvalues.get('type', self.get(itemid, 'type')) + if not mime_type: + mime_type = self.default_mime_type + self.db.indexer.add_text((self.classname, itemid, 'content'), + content, mime_type) + + # fire reactors + self.fireReactors('set', itemid, oldvalues) + return propvalues + def getprops(self, protected=1): ''' In addition to the actual properties on the node, these methods provide the "content" property. If the "protected" flag is true, @@ -2069,27 +2108,6 @@ class FileClass(Class, hyperdb.FileClass): d['content'] = hyperdb.String() return d - def index(self, nodeid): - ''' Index the node in the search index. - - We want to index the content in addition to the normal String - property indexing. - ''' - # perform normal indexing - Class.index(self, nodeid) - - # get the content to index - content = self.get(nodeid, 'content') - - # figure the mime type - if self.properties.has_key('type'): - mime_type = self.get(nodeid, 'type') - else: - mime_type = self.default_mime_type - - # and index! - self.db.indexer.add_text((self.classname, nodeid, 'content'), content, - mime_type) # deviation from spec - was called ItemClass class IssueClass(Class, roundupdb.IssueClass): diff --git a/roundup/backends/back_metakit.py b/roundup/backends/back_metakit.py index cf3f59f..ff54473 100755 --- a/roundup/backends/back_metakit.py +++ b/roundup/backends/back_metakit.py @@ -1,4 +1,4 @@ -# $Id: back_metakit.py,v 1.62 2004-03-18 01:58:45 richard Exp $ +# $Id: back_metakit.py,v 1.63 2004-03-19 04:47:59 richard Exp $ '''Metakit backend for Roundup, originally by Gordon McMillan. Known Current Bugs: @@ -45,7 +45,7 @@ from roundup import hyperdb, date, password, roundupdb, security import metakit from sessions_dbm import Sessions, OneTimeKeys import re, marshal, os, sys, time, calendar -from roundup import indexer +from indexer_dbm import Indexer import locking from roundup.date import Range @@ -1783,7 +1783,7 @@ class IssueClass(Class, roundupdb.IssueClass): CURVERSION = 2 -class Indexer(indexer.Indexer): +class Indexer(Indexer): disallows = {'THE':1, 'THIS':1, 'ZZZ':1, 'THAT':1, 'WITH':1} def __init__(self, path, datadb): self.path = os.path.join(path, 'index.mk4') diff --git a/roundup/backends/back_mysql.py b/roundup/backends/back_mysql.py index 3afd3d3..21f17d8 100644 --- a/roundup/backends/back_mysql.py +++ b/roundup/backends/back_mysql.py @@ -244,6 +244,7 @@ class Database(Database): '%s_%s_l_idx'%(classname, ml), '%s_%s_n_idx'%(classname, ml) ] + table_name = '%s_%s'%(classname, ml) for index_name in l: if not self.sql_index_exists(table_name, index_name): continue diff --git a/roundup/backends/back_postgresql.py b/roundup/backends/back_postgresql.py index a09086b..195bfa7 100644 --- a/roundup/backends/back_postgresql.py +++ b/roundup/backends/back_postgresql.py @@ -146,30 +146,26 @@ class Database(rdbms_common.Database): cols, mls = self.determine_columns(spec.properties.items()) cols.append('id') cols.append('__retired__') - scols = ',' . join(['"%s" VARCHAR(255)' % x for x in cols]) + scols = ',' . join(['"%s" VARCHAR(255)'%x for x in cols]) sql = 'CREATE TABLE "_%s" (%s)' % (spec.classname, scols) - if __debug__: - print >>hyperdb.DEBUG, 'create_class', (self, sql) - + print >>hyperdb.DEBUG, 'create_class_table', (self, sql) self.cursor.execute(sql) self.create_class_table_indexes(spec) return cols, mls def create_journal_table(self, spec): - cols = ',' . join(['"%s" VARCHAR(255)' % x - for x in 'nodeid date tag action params' . split()]) + cols = ',' . join(['"%s" VARCHAR(255)'%x + for x in 'nodeid date tag action params' . split()]) sql = 'CREATE TABLE "%s__journal" (%s)'%(spec.classname, cols) - if __debug__: - print >>hyperdb.DEBUG, 'create_class', (self, sql) - + print >>hyperdb.DEBUG, 'create_journal_table', (self, sql) self.cursor.execute(sql) self.create_journal_table_indexes(spec) def create_multilink_table(self, spec, ml): sql = '''CREATE TABLE "%s_%s" (linkid VARCHAR(255), - nodeid VARCHAR(255))''' % (spec.classname, ml) + nodeid VARCHAR(255))'''%(spec.classname, ml) if __debug__: print >>hyperdb.DEBUG, 'create_class', (self, sql) diff --git a/roundup/backends/blobfiles.py b/roundup/backends/blobfiles.py index 673bd93..c67a0e8 100644 --- a/roundup/backends/blobfiles.py +++ b/roundup/backends/blobfiles.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -#$Id: blobfiles.py,v 1.11 2004-02-11 23:55:09 richard Exp $ +#$Id: blobfiles.py,v 1.12 2004-03-19 04:47:59 richard Exp $ '''This module exports file storage for roundup backends. Files are stored into a directory hierarchy. ''' @@ -77,12 +77,14 @@ class FileStorage: if not os.path.exists(os.path.dirname(name)): os.makedirs(os.path.dirname(name)) - # open the temp file for writing - open(name + '.tmp', 'wb').write(content) - - # save off the commit action - self.transactions.append((self.doStoreFile, (classname, nodeid, - property))) + # save to a temp file + name = name + '.tmp' + # make sure we don't register the rename action more than once + if not os.path.exists(name): + # save off the rename action + self.transactions.append((self.doStoreFile, (classname, nodeid, + property))) + open(name, 'wb').write(content) def getfile(self, classname, nodeid, property): '''Get the content of the file in the database. @@ -115,6 +117,11 @@ class FileStorage: # determine the name of the file to write to name = self.filename(classname, nodeid, property) + # content is being updated (and some platforms, eg. win32, won't + # let us rename over the top of the old file) + if os.path.exists(name): + os.remove(name) + # the file is currently ".tmp" - move it to its real name to commit os.rename(name+".tmp", name) diff --git a/roundup/indexer.py b/roundup/backends/indexer_dbm.py similarity index 98% rename from roundup/indexer.py rename to roundup/backends/indexer_dbm.py index 4cd5d24..d554526 100644 --- a/roundup/indexer.py +++ b/roundup/backends/indexer_dbm.py @@ -14,7 +14,7 @@ # that promote freedom, but obviously am giving up any rights # to compel such. # -#$Id: indexer.py,v 1.18 2004-02-11 23:55:08 richard Exp $ +#$Id: indexer_dbm.py,v 1.1 2004-03-19 04:47:59 richard Exp $ '''This module provides an indexer class, RoundupIndexer, that stores text indices in a roundup instance. This class makes searching the content of messages, string properties and text files possible. @@ -22,7 +22,7 @@ messages, string properties and text files possible. __docformat__ = 'restructuredtext' import os, shutil, re, mimetypes, marshal, zlib, errno -from hyperdb import Link, Multilink +from roundup.hyperdb import Link, Multilink class Indexer: '''Indexes information from roundup's hyperdb to allow efficient @@ -129,7 +129,7 @@ class Indexer: """Split text/plain string into a list of words """ # case insensitive - text = text.upper() + text = str(text).upper() # Split the raw text, losing anything longer than 25 characters # since that'll be gibberish (encoded text or somesuch) or shorter @@ -341,4 +341,9 @@ class Indexer: return (hasattr(self,'fileids') and hasattr(self,'files') and hasattr(self,'words')) + + def rollback(self): + ''' load last saved index info. ''' + self.load_index(reload=1) + # vim: set filetype=python ts=4 sw=4 et si diff --git a/roundup/backends/rdbms_common.py b/roundup/backends/rdbms_common.py index 2894cc6..8064c3e 100644 --- a/roundup/backends/rdbms_common.py +++ b/roundup/backends/rdbms_common.py @@ -1,4 +1,4 @@ -# $Id: rdbms_common.py,v 1.81 2004-03-18 01:58:45 richard Exp $ +# $Id: rdbms_common.py,v 1.82 2004-03-19 04:47:59 richard Exp $ ''' Relational database (SQL) backend common code. Basics: @@ -39,7 +39,7 @@ from roundup.backends import locking # support from blobfiles import FileStorage -from roundup.indexer import Indexer +from indexer_dbm import Indexer from sessions_rdbms import Sessions, OneTimeKeys from roundup.date import Range @@ -249,7 +249,7 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): print >>hyperdb.DEBUG, 'update_class FIRING' # detect key prop change for potential index change - keyprop_changes = 0 + keyprop_changes = {} if new_spec[0] != old_spec[0]: keyprop_changes = {'remove': old_spec[0], 'add': new_spec[0]} @@ -260,20 +260,20 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): if new_has(name): continue - if isinstance(prop, Multilink): + if prop.find('Multilink to') != -1: # first drop indexes. - self.drop_multilink_table_indexes(spec.classname, ml) + self.drop_multilink_table_indexes(spec.classname, name) # now the multilink table itself - sql = 'drop table %s_%s'%(spec.classname, prop) + sql = 'drop table %s_%s'%(spec.classname, name) else: # if this is the key prop, drop the index first if old_spec[0] == prop: - self.drop_class_table_key_index(spec.classname, prop) + self.drop_class_table_key_index(spec.classname, name) del keyprop_changes['remove'] # drop the column - sql = 'alter table _%s drop column _%s'%(spec.classname, prop) + sql = 'alter table _%s drop column _%s'%(spec.classname, name) if __debug__: print >>hyperdb.DEBUG, 'update_class', (self, sql) @@ -974,8 +974,8 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): ''' Load the journal from the database ''' # now get the journal entries - sql = 'select %s from %s__journal where nodeid=%s'%(cols, classname, - self.arg) + sql = 'select %s from %s__journal where nodeid=%s order by date'%( + cols, classname, self.arg) if __debug__: print >>hyperdb.DEBUG, 'load_journal', (self, sql, nodeid) self.cursor.execute(sql, (nodeid,)) @@ -1019,14 +1019,8 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): self.sql_commit() # now, do all the other transaction stuff - reindex = {} for method, args in self.transactions: - reindex[method(*args)] = 1 - - # reindex the nodes that request it - for classname, nodeid in filter(None, reindex.keys()): - print >>hyperdb.DEBUG, 'commit.reindex', (classname, nodeid) - self.getclass(classname).index(nodeid) + method(*args) # save the indexer state self.indexer.save_index() @@ -1241,6 +1235,7 @@ class Class(hyperdb.Class): elif isinstance(prop, String): if type(value) != type('') and type(value) != type(u''): raise TypeError, 'new property "%s" not a string'%key + self.db.indexer.add_text((self.classname, newid, key), value) elif isinstance(prop, Password): if not isinstance(value, password.Password): @@ -1465,6 +1460,15 @@ class Class(hyperdb.Class): If the value of a Link or Multilink property contains an invalid node id, a ValueError is raised. ''' + self.fireAuditors('set', nodeid, propvalues) + oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid)) + propvalues = self.set_inner(nodeid, **propvalues) + self.fireReactors('set', nodeid, oldvalues) + return propvalues + + def set_inner(self, nodeid, **propvalues): + ''' Called by set, in-between the audit and react calls. + ''' if not propvalues: return propvalues @@ -1479,12 +1483,6 @@ class Class(hyperdb.Class): if self.db.journaltag is None: raise DatabaseError, 'Database open read-only' - self.fireAuditors('set', nodeid, propvalues) - # Take a copy of the node dict so that the subsequent set - # operation doesn't modify the oldvalues structure. - # XXX used to try the cache here first - oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid)) - node = self.db.getnode(self.classname, nodeid) if self.is_retired(nodeid): raise IndexError, 'Requested item is retired' @@ -1620,6 +1618,8 @@ class Class(hyperdb.Class): elif isinstance(prop, String): if value is not None and type(value) != type('') and type(value) != type(u''): raise TypeError, 'new property "%s" not a string'%propname + self.db.indexer.add_text((self.classname, nodeid, propname), + value) elif isinstance(prop, Password): if not isinstance(value, password.Password): @@ -1659,8 +1659,6 @@ class Class(hyperdb.Class): if self.do_journal: self.db.addjournal(self.classname, nodeid, 'set', journalvalues) - self.fireReactors('set', nodeid, oldvalues) - return propvalues def retire(self, nodeid): @@ -2234,15 +2232,8 @@ class Class(hyperdb.Class): # find all the String properties that have indexme for prop, propclass in self.getprops().items(): if isinstance(propclass, String) and propclass.indexme: - try: - value = str(self.get(nodeid, prop)) - except IndexError: - # node no longer exists - entry should be removed - self.db.indexer.purge_entry((self.classname, nodeid, prop)) - else: - # and index them under (classname, nodeid, property) - self.db.indexer.add_text((self.classname, nodeid, prop), - value) + self.db.indexer.add_text((self.classname, nodeid, prop), + str(self.get(nodeid, prop))) # @@ -2297,7 +2288,14 @@ class FileClass(Class, hyperdb.FileClass): del propvalues['content'] # do the database create - newid = Class.create_inner(self, **propvalues) + newid = self.create_inner(**propvalues) + + # figure the mime type + mime_type = propvalues.get('type', self.default_mime_type) + + # and index! + self.db.indexer.add_text((self.classname, newid, 'content'), content, + mime_type) # fire reactors self.fireReactors('create', newid, None) @@ -2354,27 +2352,34 @@ class FileClass(Class, hyperdb.FileClass): d['content'] = hyperdb.String() return d - def index(self, nodeid): - ''' Index the node in the search index. - - We want to index the content in addition to the normal String - property indexing. + def set(self, itemid, **propvalues): + ''' Snarf the "content" propvalue and update it in a file ''' - # perform normal indexing - Class.index(self, nodeid) + self.fireAuditors('set', itemid, propvalues) + oldvalues = copy.deepcopy(self.db.getnode(self.classname, itemid)) - # get the content to index - content = self.get(nodeid, 'content') + # now remove the content property so it's not stored in the db + content = None + if propvalues.has_key('content'): + content = propvalues['content'] + del propvalues['content'] - # figure the mime type - if self.properties.has_key('type'): - mime_type = self.get(nodeid, 'type') - else: - mime_type = self.default_mime_type + # do the database create + propvalues = self.set_inner(itemid, **propvalues) + + # do content? + if content: + # store and index + self.db.storefile(self.classname, itemid, None, content) + mime_type = propvalues.get('type', self.get(itemid, 'type')) + if not mime_type: + mime_type = self.default_mime_type + self.db.indexer.add_text((self.classname, itemid, 'content'), + content, mime_type) - # and index! - self.db.indexer.add_text((self.classname, nodeid, 'content'), content, - mime_type) + # fire reactors + self.fireReactors('set', itemid, oldvalues) + return propvalues # XXX deviation from spec - was called ItemClass class IssueClass(Class, roundupdb.IssueClass): diff --git a/roundup/backends/sessions_dbm.py b/roundup/backends/sessions_dbm.py index f8f0222..433db42 100644 --- a/roundup/backends/sessions_dbm.py +++ b/roundup/backends/sessions_dbm.py @@ -1,4 +1,4 @@ -#$Id: sessions_dbm.py,v 1.1 2004-03-18 01:58:45 richard Exp $ +#$Id: sessions_dbm.py,v 1.2 2004-03-19 04:47:59 richard Exp $ """This module defines a very basic store that's used by the CGI interface to store session and one-time-key information. @@ -63,7 +63,9 @@ class BasicDatabase: db = self.opendb('c') try: try: - return marshal.loads(db[infoid]) + d = marshal.loads(db[infoid]) + del d['__timestamp'] + return d except KeyError: raise KeyError, 'No such %s "%s"'%(self.name, infoid) finally: diff --git a/roundup/roundupdb.py b/roundup/roundupdb.py index f286dbd..46f383d 100644 --- a/roundup/roundupdb.py +++ b/roundup/roundupdb.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: roundupdb.py,v 1.101 2004-03-15 05:50:19 richard Exp $ +# $Id: roundupdb.py,v 1.102 2004-03-19 04:47:59 richard Exp $ """Extending hyperdb with types specific to issue-tracking. """ @@ -60,7 +60,7 @@ class Database: return timezone def confirm_registration(self, otk): - props = self.otks.getall(otk) + props = self.getOTKManager().getall(otk) for propname, proptype in self.user.getprops().items(): value = props.get(propname, None) if value is None: @@ -80,10 +80,9 @@ class Database: cl = self.user props['roles'] = self.config.NEW_WEB_USER_ROLES - del props['__time'] userid = cl.create(**props) # clear the props from the otk database - self.otks.destroy(otk) + self.getOTKManager().destroy(otk) self.commit() return userid diff --git a/test/db_test_base.py b/test/db_test_base.py index 2405c54..193e145 100644 --- a/test/db_test_base.py +++ b/test/db_test_base.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: db_test_base.py,v 1.17 2004-03-18 01:58:46 richard Exp $ +# $Id: db_test_base.py,v 1.18 2004-03-19 04:47:59 richard Exp $ import unittest, os, shutil, errno, imp, sys, time, pprint @@ -23,7 +23,6 @@ from roundup.hyperdb import String, Password, Link, Multilink, Date, \ Interval, DatabaseError, Boolean, Number, Node from roundup import date, password from roundup import init -from roundup.indexer import Indexer def setupSchema(db, create, module): status = module.Class(db, "status", name=String()) @@ -89,18 +88,20 @@ class DBTest(MyTestCase): # automatic properties (well, the two easy ones anyway) # def testCreatorProperty(self): - id1 = self.db.issue.create() + i = self.db.issue + id1 = i.create(title='spam') self.db.commit() self.db.close() self.db = self.module.Database(config, 'fred') setupSchema(self.db, 0, self.module) i = self.db.issue - id2 = i.create() + id2 = i.create(title='spam') self.assertNotEqual(id1, id2) self.assertNotEqual(i.get(id1, 'creator'), i.get(id2, 'creator')) def testActorProperty(self): - id1 = self.db.issue.create() + i = self.db.issue + id1 = i.create(title='spam') self.db.commit() self.db.close() self.db = self.module.Database(config, 'fred') @@ -121,6 +122,7 @@ class DBTest(MyTestCase): id1 = self.db.issue.create(title="spam", status='1') self.db.issue.set(id1) + # String def testStringChange(self): for commit in (0,1): # test set & retrieve @@ -142,6 +144,19 @@ class DBTest(MyTestCase): if commit: self.db.commit() self.assertEqual(self.db.issue.get(nid, "title"), None) + # FileClass "content" property (no unset test) + def testFileClassContentChange(self): + for commit in (0,1): + # test set & retrieve + nid = self.db.file.create(content="spam") + self.assertEqual(self.db.file.get(nid, 'content'), 'spam') + + # change and make sure we retrieve the correct value + self.db.file.set(nid, content='eggs') + if commit: self.db.commit() + self.assertEqual(self.db.file.get(nid, 'content'), 'eggs') + + # Link def testLinkChange(self): self.assertRaises(IndexError, self.db.issue.create, title="spam", status='100') @@ -161,6 +176,7 @@ class DBTest(MyTestCase): if commit: self.db.commit() self.assertEqual(self.db.issue.get(nid, "status"), None) + # Multilink def testMultilinkChange(self): for commit in (0,1): self.assertRaises(IndexError, self.db.issue.create, title="spam", @@ -175,8 +191,11 @@ class DBTest(MyTestCase): self.assertEqual(self.db.issue.get(nid, "nosy"), []) self.db.issue.set(nid, nosy=[u1,u2]) if commit: self.db.commit() - self.assertEqual(self.db.issue.get(nid, "nosy"), [u1,u2]) + l = [u1,u2]; l.sort() + m = self.db.issue.get(nid, "nosy"); m.sort() + self.assertEqual(l, m) + # Date def testDateChange(self): self.assertRaises(TypeError, self.db.issue.create, title='spam', deadline=1) @@ -201,6 +220,7 @@ class DBTest(MyTestCase): if commit: self.db.commit() self.assertEqual(self.db.issue.get(nid, "deadline"), None) + # Interval def testIntervalChange(self): self.assertRaises(TypeError, self.db.issue.create, title='spam', foo=1) @@ -230,6 +250,7 @@ class DBTest(MyTestCase): if commit: self.db.commit() self.assertEqual(self.db.issue.get(nid, "foo"), None) + # Boolean def testBooleanChange(self): userid = self.db.user.create(username='foo', assignable=1) self.assertEqual(1, self.db.user.get(userid, 'assignable')) @@ -241,6 +262,7 @@ class DBTest(MyTestCase): self.db.user.set(nid, assignable=None) self.assertEqual(self.db.user.get(nid, "assignable"), None) + # Number def testNumberChange(self): nid = self.db.user.create(username='foo', age=1) self.assertEqual(1, self.db.user.get(nid, 'age')) @@ -259,6 +281,7 @@ class DBTest(MyTestCase): self.db.user.set(nid, age=None) self.assertEqual(self.db.user.get(nid, "age"), None) + # Password def testPasswordChange(self): x = password.Password('x') userid = self.db.user.create(username='foo', password=x) @@ -277,6 +300,7 @@ class DBTest(MyTestCase): self.db.user.set(nid, assignable=None) self.assertEqual(self.db.user.get(nid, "assignable"), None) + # key value def testKeyValue(self): self.assertRaises(ValueError, self.db.user.create) @@ -295,6 +319,7 @@ class DBTest(MyTestCase): self.assertRaises(TypeError, self.db.issue.lookup, 'fubar') + # label property def testLabelProp(self): # key prop self.assertEqual(self.db.status.labelprop(), 'name') @@ -306,6 +331,7 @@ class DBTest(MyTestCase): # id self.assertEqual(self.db.stuff.labelprop(default_to_id=1), 'id') + # retirement def testRetire(self): self.db.issue.create(title="spam", status='1') b = self.db.status.get('1', 'name') @@ -609,6 +635,7 @@ class DBTest(MyTestCase): def testIndexerSearching(self): f1 = self.db.file.create(content='hello', type="text/plain") + # content='world' has the wrong content-type and won't be indexed f2 = self.db.file.create(content='world', type="text/frozz", comment='blah blah') i1 = self.db.issue.create(files=[f1, f2], title="flebble plop") @@ -623,15 +650,44 @@ class DBTest(MyTestCase): {i1: {}, i2: {}}) def testReindexing(self): - self.db.issue.create(title="frooz") + search = self.db.indexer.search + issue = self.db.issue + i1 = issue.create(title="flebble plop") + i2 = issue.create(title="flebble frooz") self.db.commit() - self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue), - {'1': {}}) - self.db.issue.set('1', title="dooble") + self.assertEquals(search(['plop'], issue), {i1: {}}) + self.assertEquals(search(['flebble'], issue), {i1: {}, i2: {}}) + + # change i1's title + issue.set(i1, title="plop") self.db.commit() - self.assertEquals(self.db.indexer.search(['dooble'], self.db.issue), - {'1': {}}) - self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue), {}) + self.assertEquals(search(['plop'], issue), {i1: {}}) + self.assertEquals(search(['flebble'], issue), {i2: {}}) + + # unset i1's title + issue.set(i1, title="") + self.db.commit() + self.assertEquals(search(['plop'], issue), {}) + self.assertEquals(search(['flebble'], issue), {i2: {}}) + + def testFileClassReindexing(self): + f1 = self.db.file.create(content='hello') + f2 = self.db.file.create(content='hello, world') + i1 = self.db.issue.create(files=[f1, f2]) + self.db.commit() + d = self.db.indexer.search(['hello'], self.db.issue) + d[i1]['files'].sort() + self.assertEquals(d, {i1: {'files': [f1, f2]}}) + self.assertEquals(self.db.indexer.search(['world'], self.db.issue), + {i1: {'files': [f2]}}) + self.db.file.set(f1, content="world") + self.db.commit() + d = self.db.indexer.search(['world'], self.db.issue) + d[i1]['files'].sort() + self.assertEquals(d, {i1: {'files': [f1, f2]}}) + self.assertEquals(self.db.indexer.search(['hello'], self.db.issue), + {i1: {'files': [f2]}}) + def testForcedReindexing(self): self.db.issue.create(title="flebble frooz") @@ -889,10 +945,14 @@ class DBTest(MyTestCase): ae(l, m) for id, props in items.items(): for name, value in props.items(): - ae(klass.get(id, name), value) + l = klass.get(id, name) + if isinstance(value, type([])): + value.sort() + l.sort() + ae(l, value) # make sure the retired items are actually imported - ae(self.db.user.get('3', 'username'), 'blop') + ae(self.db.user.get('4', 'username'), 'blop') ae(self.db.issue.get('2', 'title'), 'issue two') # make sure id counters are set correctly diff --git a/test/session_common.py b/test/session_common.py index 7029528..8a90770 100644 --- a/test/session_common.py +++ b/test/session_common.py @@ -32,12 +32,6 @@ class SessionTest(unittest.TestCase): self.sessions.set('random_key', text='nope') self.assertEqual(self.sessions.get('random_key', 'text'), 'nope') - def testSetOTK(self): - assert 0, 'not implemented' - - def testExpiry(self): - assert 0, 'not implemented' - class DBMTest(SessionTest): import roundup.backends.sessions_dbm as sessions_module diff --git a/test/test_indexer.py b/test/test_indexer.py index 18e56f9..8155577 100644 --- a/test/test_indexer.py +++ b/test/test_indexer.py @@ -18,11 +18,11 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -# $Id: test_indexer.py,v 1.3 2003-10-25 22:53:26 richard Exp $ +# $Id: test_indexer.py,v 1.4 2004-03-19 04:47:59 richard Exp $ import os, unittest, shutil -from roundup.indexer import Indexer +from roundup.backends.indexer_dbm import Indexer class IndexerTest(unittest.TestCase): def setUp(self): diff --git a/test/test_mailgw.py b/test/test_mailgw.py index aebded9..dcdcf01 100644 --- a/test/test_mailgw.py +++ b/test/test_mailgw.py @@ -8,7 +8,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # -# $Id: test_mailgw.py,v 1.64 2004-01-20 00:11:51 richard Exp $ +# $Id: test_mailgw.py,v 1.65 2004-03-19 04:47:59 richard Exp $ import unittest, tempfile, os, shutil, errno, imp, sys, difflib, rfc822 @@ -926,7 +926,7 @@ This is a followup def testRegistrationConfirmation(self): otk = "Aj4euk4LZSAdwePohj90SME5SpopLETL" - self.db.otks.set(otk, username='johannes', __time='') + self.db.getOTKManager().set(otk, username='johannes') self._handle_mail('''Content-Type: text/plain; charset="iso-8859-1" From: Chef -- 2.30.2