From: richard Date: Mon, 15 Mar 2004 05:50:20 +0000 (+0000) Subject: Added the "actor" property. Metakit backend not done (still not confident X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=d6f5fe8c05c4be3118dbc7cc16da052210fb792a;p=roundup.git Added the "actor" property. Metakit backend not done (still not confident I know how it's supposed to work ;) Currently it will come up as NULL in the RDBMS backends for older items. The *dbm backends will look up the journal. I hope to remedy the former before 0.7's release. Fixed a bunch of migration issues in the rdbms backends while I was at it (index changes for key prop changes) and simplified the class table update code for RDBMSes that have "alter table" in their command set (ie. not sqlite) ... migration from "version 1" to "version 2" still hasn't actually been tested yet though. git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@2147 57a73879-2fb5-44c3-a270-3262357dd7e2 --- diff --git a/CHANGES.txt b/CHANGES.txt index 4152d2f..710b104 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,6 +3,8 @@ are given with the most recent entry first. 200?-??-?? 0.7.0 Feature: +- added new "actor" automatic property (indicates user who cause the last + "activity" - simple support for collision detection (sf rfe 648763) - support confirming registration by replying to the email (sf bug 763668) - support setgid and running on port < 1024 (sf patch 777528) diff --git a/TODO.txt b/TODO.txt index f300d4f..11211bb 100644 --- a/TODO.txt +++ b/TODO.txt @@ -3,8 +3,8 @@ doing before the next release: - sessions, otks and indexing in RDBMSes - add tests for group-by-multilink so I finally implement it for the RDBMSes -- full coverage analysis for unit tests - s/getnode/getitem in backends (and s/Node/Item) -- activity_by meta-property +- have rdbms backends look up the journal for actor if it's not set +- ensure index creation is triggered by the version 1->2 update - migrate to numeric ID values (fixes bug 817217) diff --git a/doc/upgrading.txt b/doc/upgrading.txt index 47cd021..b53a457 100644 --- a/doc/upgrading.txt +++ b/doc/upgrading.txt @@ -51,6 +51,13 @@ add:: p = db.security.getPermission('View', cl) db.security.addPermissionToRole('User', p) +0.7.0 New "actor" property +-------------------------- + +Roundup's database has a new per-item property "actor" which reflects the +user performing the last "actvitiy". See the classic template for ways to +integrate this new property into your interface. + 0.7.0 Extending the cgi interface --------------------------------- diff --git a/roundup/backends/back_anydbm.py b/roundup/backends/back_anydbm.py index 4d3ad98..64c6236 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.136 2004-03-12 05:36:26 richard Exp $ +#$Id: back_anydbm.py,v 1.137 2004-03-15 05:50: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 versions >2.1.1 (the dumbdbm fallback in 2.1.1 and earlier has several @@ -264,6 +264,7 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): # calling code's node assumptions) node = node.copy() node['creator'] = self.getuid() + node['actor'] = self.getuid() node['creation'] = node['activity'] = date.Date() self.newnodes.setdefault(classname, {})[nodeid] = 1 @@ -281,6 +282,7 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): # calling code's node assumptions) node = node.copy() node['activity'] = date.Date() + node['actor'] = self.getuid() # can't set without having already loaded the node self.cache[classname][nodeid] = node @@ -731,10 +733,10 @@ class Class(hyperdb.Class): or a ValueError is raised. The keyword arguments in 'properties' must map names to property objects, or a TypeError is raised. ''' - if (properties.has_key('creation') or properties.has_key('activity') - or properties.has_key('creator')): - raise ValueError, '"creation", "activity" and "creator" are '\ - 'reserved' + for name in 'creation activity creator actor'.split(): + if properties.has_key(name): + raise ValueError, '"creation", "activity", "creator" and '\ + '"actor" are reserved' self.classname = classname self.properties = properties @@ -1010,6 +1012,8 @@ class Class(hyperdb.Class): creation = None if d.has_key('activity'): del d['activity'] + if d.has_key('actor'): + del d['actor'] self.db.addjournal(self.classname, newid, 'create', {}, creator, creation) return newid @@ -1063,7 +1067,27 @@ class Class(hyperdb.Class): journal = self.db.getjournal(self.classname, nodeid) if journal: num_re = re.compile('^\d+$') - value = self.db.getjournal(self.classname, nodeid)[0][2] + value = journal[0][2] + if num_re.match(value): + return value + else: + # old-style "username" journal tag + try: + return self.db.user.lookup(value) + except KeyError: + # user's been retired, return admin + return '1' + else: + return self.db.getuid() + if propname == 'actor': + if d.has_key('actor'): + return d['actor'] + if not self.do_journal: + raise ValueError, 'Journalling is disabled for this class' + journal = self.db.getjournal(self.classname, nodeid) + if journal: + num_re = re.compile('^\d+$') + value = journal[-1][2] if num_re.match(value): return value else: @@ -1901,6 +1925,7 @@ class Class(hyperdb.Class): d['creation'] = hyperdb.Date() d['activity'] = hyperdb.Date() d['creator'] = hyperdb.Link('user') + d['actor'] = hyperdb.Link('user') return d def addprop(self, **properties): diff --git a/roundup/backends/back_mysql.py b/roundup/backends/back_mysql.py index b5ffa5a..a02b445 100644 --- a/roundup/backends/back_mysql.py +++ b/roundup/backends/back_mysql.py @@ -128,6 +128,13 @@ class Database(Database): 's_last_use FLOAT(20), s_user VARCHAR(255))') self.cursor.execute('CREATE INDEX sessions_key_idx ON sessions(s_key)') + def add_actor_column(self): + # update existing tables to have the new actor column + tables = self.database_schema['tables'] + for name in tables.keys(): + self.cursor.execute('ALTER TABLE _%s add __actor ' + 'VARCHAR(255)'%name) + def __repr__(self): return ''%id(self) @@ -241,6 +248,16 @@ class Database(Database): print >>hyperdb.DEBUG, 'drop_index', (self, index_sql) self.cursor.execute(index_sql) + def drop_class_table_key_index(self, cn, key): + table_name = '_%s'%cn + index_name = '_%s_%s_idx'%(cn, key) + if not self.sql_index_exists(table_name, index_name): + return + sql = 'drop index %s on %s'%(index_name, table_name) + if __debug__: + print >>hyperdb.DEBUG, 'drop_index', (self, sql) + self.cursor.execute(sql) + class MysqlClass: # we're overriding this method for ONE missing bit of functionality. # look for "I can't believe it's not a toy RDBMS" below diff --git a/roundup/backends/back_postgresql.py b/roundup/backends/back_postgresql.py index c585056..fa9ba09 100644 --- a/roundup/backends/back_postgresql.py +++ b/roundup/backends/back_postgresql.py @@ -115,6 +115,13 @@ class Database(rdbms_common.Database): 's_last_use FLOAT(20), s_user VARCHAR(255))') self.cursor.execute('CREATE INDEX sessions_key_idx ON sessions(s_key)') + def add_actor_column(self): + # update existing tables to have the new actor column + tables = self.database_schema['tables'] + for name in tables.keys(): + self.cursor.execute('ALTER TABLE _%s add __actor ' + 'VARCHAR(255)'%name) + def __repr__(self): return '' % id(self) @@ -140,6 +147,7 @@ class Database(rdbms_common.Database): print >>hyperdb.DEBUG, 'create_class', (self, sql) self.cursor.execute(sql) + self.create_class_table_indexes(spec) return cols, mls def create_journal_table(self, spec): @@ -151,6 +159,7 @@ class Database(rdbms_common.Database): print >>hyperdb.DEBUG, 'create_class', (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), @@ -160,6 +169,7 @@ class Database(rdbms_common.Database): print >>hyperdb.DEBUG, 'create_class', (self, sql) self.cursor.execute(sql) + self.create_multilink_table_indexes(spec, ml) class Class(rdbms_common.Class): pass diff --git a/roundup/backends/back_sqlite.py b/roundup/backends/back_sqlite.py index 1f1013c..04100d0 100644 --- a/roundup/backends/back_sqlite.py +++ b/roundup/backends/back_sqlite.py @@ -1,4 +1,4 @@ -# $Id: back_sqlite.py,v 1.15 2004-03-12 04:08:59 richard Exp $ +# $Id: back_sqlite.py,v 1.16 2004-03-15 05:50:20 richard Exp $ '''Implements a backend for SQLite. See https://pysqlite.sourceforge.net/ for pysqlite info @@ -48,6 +48,97 @@ class Database(rdbms_common.Database): 's_last_use varchar, s_user varchar)') self.cursor.execute('create index sessions_key_idx on sessions(s_key)') + def add_actor_column(self): + # update existing tables to have the new actor column + tables = self.database_schema['tables'] + for classname, spec in self.classes.items(): + if tables.has_key(classname): + dbspec = tables[classname] + self.update_class(spec, dbspec, force=1, adding_actor=1) + + def update_class(self, spec, old_spec, force=0, adding_actor=0): + ''' Determine the differences between the current spec and the + database version of the spec, and update where necessary. + + If 'force' is true, update the database anyway. + + SQLite doesn't have ALTER TABLE, so we have to copy and + regenerate the tables with the new schema. + ''' + new_has = spec.properties.has_key + new_spec = spec.schema() + new_spec[1].sort() + old_spec[1].sort() + if not force and new_spec == old_spec: + # no changes + return 0 + + if __debug__: + print >>hyperdb.DEBUG, 'update_class FIRING' + + # detect multilinks that have been removed, and drop their table + old_has = {} + for name,prop in old_spec[1]: + old_has[name] = 1 + if new_has(name) or not isinstance(prop, hyperdb.Multilink): + continue + # it's a multilink, and it's been removed - drop the old + # table. First drop indexes. + self.drop_multilink_table_indexes(spec.classname, ml) + sql = 'drop table %s_%s'%(spec.classname, prop) + if __debug__: + print >>hyperdb.DEBUG, 'update_class', (self, sql) + self.cursor.execute(sql) + old_has = old_has.has_key + + # now figure how we populate the new table + if adding_actor: + fetch = ['_activity', '_creation', '_creator'] + else: + fetch = ['_actor', '_activity', '_creation', '_creator'] + properties = spec.getprops() + for propname,x in new_spec[1]: + prop = properties[propname] + if isinstance(prop, hyperdb.Multilink): + if force or not old_has(propname): + # we need to create the new table + self.create_multilink_table(spec, propname) + elif old_has(propname): + # we copy this col over from the old table + fetch.append('_'+propname) + + # select the data out of the old table + fetch.append('id') + fetch.append('__retired__') + fetchcols = ','.join(fetch) + cn = spec.classname + sql = 'select %s from _%s'%(fetchcols, cn) + if __debug__: + print >>hyperdb.DEBUG, 'update_class', (self, sql) + self.cursor.execute(sql) + olddata = self.cursor.fetchall() + + # TODO: update all the other index dropping code + self.drop_class_table_indexes(cn, old_spec[0]) + + # drop the old table + self.cursor.execute('drop table _%s'%cn) + + # create the new table + self.create_class_table(spec) + + if olddata: + # do the insert of the old data - the new columns will have + # NULL values + args = ','.join([self.arg for x in fetch]) + sql = 'insert into _%s (%s) values (%s)'%(cn, fetchcols, args) + if __debug__: + print >>hyperdb.DEBUG, 'update_class', (self, sql, olddata[0]) + for entry in olddata: + self.cursor.execute(sql, tuple(entry)) + + return 1 + def sql_close(self): ''' Squash any error caused by us already having closed the connection. diff --git a/roundup/backends/rdbms_common.py b/roundup/backends/rdbms_common.py index 40b0d24..c2d341c 100644 --- a/roundup/backends/rdbms_common.py +++ b/roundup/backends/rdbms_common.py @@ -1,4 +1,4 @@ -# $Id: rdbms_common.py,v 1.78 2004-03-12 05:36:26 richard Exp $ +# $Id: rdbms_common.py,v 1.79 2004-03-15 05:50:20 richard Exp $ ''' Relational database (SQL) backend common code. Basics: @@ -188,6 +188,8 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): # version 1 doesn't have the OTK, session and indexing in the # database self.create_version_2_tables() + # version 1 also didn't have the actor column + self.add_actor_column() else: return 0 @@ -210,7 +212,7 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): "properties" is a list of (name, prop) where prop may be an instance of a hyperdb "type" _or_ a string repr of that type. ''' - cols = ['_activity', '_creator', '_creation'] + cols = ['_actor', '_activity', '_creator', '_creation'] mls = [] # add the multilinks separately for col, prop in properties: @@ -240,62 +242,64 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): if __debug__: print >>hyperdb.DEBUG, 'update_class FIRING' + # detect key prop change for potential index change + keyprop_changes = 0 + if new_spec[0] != old_spec[0]: + keyprop_changes = {'remove': old_spec[0], 'add': new_spec[0]} + # detect multilinks that have been removed, and drop their table old_has = {} - for name,prop in old_spec[1]: + for name, prop in old_spec[1]: old_has[name] = 1 - if new_has(name) or not isinstance(prop, Multilink): + if new_has(name): continue - # it's a multilink, and it's been removed - drop the old - # table. First drop indexes. - self.drop_multilink_table_indexes(spec.classname, ml) - sql = 'drop table %s_%s'%(spec.classname, prop) + + if isinstance(prop, Multilink): + # first drop indexes. + self.drop_multilink_table_indexes(spec.classname, ml) + + # now the multilink table itself + sql = 'drop table %s_%s'%(spec.classname, prop) + 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) + del keyprop_changes['remove'] + + # drop the column + sql = 'alter table _%s drop column _%s'%(spec.classname, prop) + if __debug__: print >>hyperdb.DEBUG, 'update_class', (self, sql) self.cursor.execute(sql) old_has = old_has.has_key - # now figure how we populate the new table - fetch = ['_activity', '_creation', '_creator'] - properties = spec.getprops() - for propname,x in new_spec[1]: - prop = properties[propname] - if isinstance(prop, Multilink): - if force or not old_has(propname): - # we need to create the new table - self.create_multilink_table(spec, propname) - elif old_has(propname): - # we copy this col over from the old table - fetch.append('_'+propname) - - # select the data out of the old table - fetch.append('id') - fetch.append('__retired__') - fetchcols = ','.join(fetch) - cn = spec.classname - sql = 'select %s from _%s'%(fetchcols, cn) - if __debug__: - print >>hyperdb.DEBUG, 'update_class', (self, sql) - self.cursor.execute(sql) - olddata = self.cursor.fetchall() - - # TODO: update all the other index dropping code - self.drop_class_table_indexes(cn, old_spec[0]) + # if we didn't remove the key prop just then, but the key prop has + # changed, we still need to remove the old index + if keyprop_changes.has_key('remove'): + self.drop_class_table_key_index(spec.classname, + keyprop_changes['remove']) - # drop the old table - self.cursor.execute('drop table _%s'%cn) + # add new columns + for propname, x in new_spec[1]: + if old_has(propname): + continue + sql = 'alter table _%s add column _%s varchar(255)'%( + spec.classname, propname) + if __debug__: + print >>hyperdb.DEBUG, 'update_class', (self, sql) + self.cursor.execute(sql) - # create the new table - self.create_class_table(spec) + # if the new column is a key prop, we need an index! + if new_spec[0] == propname: + self.create_class_table_key_index(spec.classname, propname) + del keyprop_changes['add'] - if olddata: - # do the insert - args = ','.join([self.arg for x in fetch]) - sql = 'insert into _%s (%s) values (%s)'%(cn, fetchcols, args) - if __debug__: - print >>hyperdb.DEBUG, 'update_class', (self, sql, olddata[0]) - for entry in olddata: - self.cursor.execute(sql, tuple(entry)) + # if we didn't add the key prop just then, but the key prop has + # changed, we still need to add the new index + if keyprop_changes.has_key('add'): + self.create_class_table_key_index(spec.classname, + keyprop_changes['add']) return 1 @@ -352,10 +356,8 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): # drop the old table indexes first l = ['_%s_id_idx'%cn, '_%s_retired_idx'%cn] if key: - # key prop too? l.append('_%s_%s_idx'%(cn, key)) - # TODO: update all the other index dropping code table_name = '_%s'%cn for index_name in l: if not self.sql_index_exists(table_name, index_name): @@ -365,6 +367,28 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): print >>hyperdb.DEBUG, 'drop_index', (self, index_sql) self.cursor.execute(index_sql) + def create_class_table_key_index(self, cn, key): + ''' create the class table for the given spec + ''' + if __debug__: + print >>hyperdb.DEBUG, 'update_class setting keyprop %r'% \ + key + index_sql3 = 'create index _%s_%s_idx on _%s(_%s)'%(cn, key, + cn, key) + if __debug__: + print >>hyperdb.DEBUG, 'create_index', (self, index_sql3) + self.cursor.execute(index_sql3) + + def drop_class_table_key_index(self, cn, key): + table_name = '_%s'%cn + index_name = '_%s_%s_idx'%(cn, key) + if not self.sql_index_exists(table_name, index_name): + return + sql = 'drop index '+index_name + if __debug__: + print >>hyperdb.DEBUG, 'drop_index', (self, sql) + self.cursor.execute(sql) + def create_journal_table(self, spec): ''' create the journal table for a class given the spec and already-determined cols @@ -380,11 +404,11 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): def create_journal_table_indexes(self, spec): # index on nodeid - index_sql = 'create index %s_journ_idx on %s__journal(nodeid)'%( + sql = 'create index %s_journ_idx on %s__journal(nodeid)'%( spec.classname, spec.classname) if __debug__: - print >>hyperdb.DEBUG, 'create_index', (self, index_sql) - self.cursor.execute(index_sql) + print >>hyperdb.DEBUG, 'create_index', (self, sql) + self.cursor.execute(sql) def drop_journal_table_indexes(self, classname): index_name = '%s_journ_idx'%classname @@ -601,7 +625,7 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): # calling code's node assumptions) node = node.copy() node['creation'] = node['activity'] = date.Date() - node['creator'] = self.getuid() + node['actor'] = node['creator'] = self.getuid() # default the non-multilink columns for col, prop in cl.properties.items(): @@ -657,6 +681,7 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): # add the special props values = values.copy() values['activity'] = date.Date() + values['actor'] = self.getuid() # make db-friendly values = self.serialise(classname, values) @@ -1065,10 +1090,10 @@ class Class(hyperdb.Class): or a ValueError is raised. The keyword arguments in 'properties' must map names to property objects, or a TypeError is raised. ''' - if (properties.has_key('creation') or properties.has_key('activity') - or properties.has_key('creator')): - raise ValueError, '"creation", "activity" and "creator" are '\ - 'reserved' + for name in 'creation activity creator actor'.split(): + if properties.has_key(name): + raise ValueError, '"creation", "activity", "creator" and '\ + '"actor" are reserved' self.classname = classname self.properties = properties @@ -1132,8 +1157,10 @@ class Class(hyperdb.Class): if self.db.journaltag is None: raise DatabaseError, 'Database open read-only' - if propvalues.has_key('creation') or propvalues.has_key('activity'): - raise KeyError, '"creation" and "activity" are reserved' + if propvalues.has_key('creator') or propvalues.has_key('actor') or \ + propvalues.has_key('creation') or propvalues.has_key('activity'): + raise KeyError, '"creator", "actor", "creation" and '\ + '"activity" are reserved' # new node's id newid = self.db.newid(self.classname) @@ -1358,6 +1385,8 @@ class Class(hyperdb.Class): creation = None if d.has_key('activity'): del d['activity'] + if d.has_key('actor'): + del d['actor'] self.db.addjournal(self.classname, newid, 'create', {}, creator, creation) return newid @@ -1393,6 +1422,11 @@ class Class(hyperdb.Class): return d['creator'] else: return self.db.getuid() + if propname == 'actor': + if d.has_key('actor'): + return d['actor'] + else: + return self.db.getuid() # get the property (raises KeyErorr if invalid) prop = self.properties[propname] @@ -1433,8 +1467,10 @@ class Class(hyperdb.Class): if not propvalues: return propvalues - if propvalues.has_key('creation') or propvalues.has_key('activity'): - raise KeyError, '"creation" and "activity" are reserved' + if propvalues.has_key('creation') or propvalues.has_key('creator') or \ + propvalues.has_key('actor') or propvalues.has_key('activity'): + raise KeyError, '"creation", "creator", "actor" and '\ + '"activity" are reserved' if propvalues.has_key('id'): raise KeyError, '"id" is reserved' @@ -2175,6 +2211,7 @@ class Class(hyperdb.Class): d['creation'] = hyperdb.Date() d['activity'] = hyperdb.Date() d['creator'] = hyperdb.Link('user') + d['actor'] = hyperdb.Link('user') return d def addprop(self, **properties): @@ -2345,7 +2382,8 @@ class IssueClass(Class, roundupdb.IssueClass): '''The newly-created class automatically includes the "messages", "files", "nosy", and "superseder" properties. If the 'properties' dictionary attempts to specify any of these properties or a - "creation" or "activity" property, a ValueError is raised. + "creation", "creator", "activity" or "actor" property, a ValueError + is raised. ''' if not properties.has_key('title'): properties['title'] = hyperdb.String(indexme='yes') diff --git a/roundup/roundupdb.py b/roundup/roundupdb.py index 0a39ee3..f286dbd 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.100 2004-03-05 00:08:09 richard Exp $ +# $Id: roundupdb.py,v 1.101 2004-03-15 05:50:19 richard Exp $ """Extending hyperdb with types specific to issue-tracking. """ @@ -408,7 +408,7 @@ class IssueClass: for key in oldvalues.keys(): if key in ['files','messages']: continue - if key in ('activity', 'creator', 'creation'): + if key in ('actor', 'activity', 'creator', 'creation'): continue # not all keys from oldvalues might be available in database # this happens when property was deleted diff --git a/templates/classic/dbinit.py b/templates/classic/dbinit.py index 9ed4040..0a7093b 100644 --- a/templates/classic/dbinit.py +++ b/templates/classic/dbinit.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: dbinit.py,v 1.4 2004-03-12 05:36:26 richard Exp $ +# $Id: dbinit.py,v 1.5 2004-03-15 05:50:20 richard Exp $ import os @@ -107,6 +107,7 @@ def open(name=None): db.security.addPermissionToRole('User', p) for cl in 'priority', 'status': p = db.security.getPermission('View', cl) + db.security.addPermissionToRole('User', p) # and give the regular users access to the web and email interface p = db.security.getPermission('Web Access') diff --git a/templates/classic/html/issue.index.html b/templates/classic/html/issue.index.html index dd93485..8deb1c1 100644 --- a/templates/classic/html/issue.index.html +++ b/templates/classic/html/issue.index.html @@ -17,10 +17,11 @@ You are not allowed to view this page. ID Creation Activity + Actor Topic Title Status - Created By + Creator Assigned To @@ -39,6 +40,8 @@ You are not allowed to view this page. tal:content="i/creation/reldate">    +     diff --git a/templates/classic/html/issue.item.html b/templates/classic/html/issue.item.html index 7423a8d..cc83601 100644 --- a/templates/classic/html/issue.item.html +++ b/templates/classic/html/issue.item.html @@ -95,7 +95,7 @@ python:db.user.classhelp('username,realname,address', property='nosy', width='60

activity info + changed ${context/activity} by ${context/actor}.">activity info

diff --git a/templates/classic/html/issue.search.html b/templates/classic/html/issue.search.html index 102d721..69ec8b6 100644 --- a/templates/classic/html/issue.search.html +++ b/templates/classic/html/issue.search.html @@ -87,6 +87,17 @@ + + + + + + + + diff --git a/test/db_test_base.py b/test/db_test_base.py index c7a52d2..b2ffcd1 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.15 2004-03-12 04:09:00 richard Exp $ +# $Id: db_test_base.py,v 1.16 2004-03-15 05:50:20 richard Exp $ import unittest, os, shutil, errno, imp, sys, time, pprint @@ -43,6 +43,8 @@ def setupSchema(db, create, module): if create: user.create(username="admin", roles='Admin', password=password.Password('sekrit')) + user.create(username="fred", roles='User', + password=password.Password('sekrit')) status.create(name="unread") status.create(name="in-progress") status.create(name="testing") @@ -83,6 +85,30 @@ class DBTest(MyTestCase): def testRefresh(self): self.db.refresh_database() + # + # automatic properties (well, the two easy ones anyway) + # + def testCreatorProperty(self): + id1 = self.db.issue.create() + 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() + self.assertNotEqual(id1, id2) + self.assertNotEqual(i.get(id1, 'creator'), i.get(id2, 'creator')) + + def testActorProperty(self): + id1 = self.db.issue.create() + self.db.commit() + self.db.close() + self.db = self.module.Database(config, 'fred') + setupSchema(self.db, 0, self.module) + i = self.db.issue + i.set(id1, title='asfasd') + self.assertNotEqual(i.get(id1, 'creator'), i.get(id1, 'actor')) + # # basic operations # @@ -417,6 +443,7 @@ class DBTest(MyTestCase): ar(KeyError, self.db.status.create, creation=1) ar(KeyError, self.db.status.create, creator=1) ar(KeyError, self.db.status.create, activity=1) + ar(KeyError, self.db.status.create, actor=1) # invalid property name ar(KeyError, self.db.status.create, foo='foo') # key name clash @@ -891,7 +918,7 @@ class DBTest(MyTestCase): props = self.db.issue.getprops() keys = props.keys() keys.sort() - self.assertEqual(keys, ['activity', 'assignedto', 'creation', + self.assertEqual(keys, ['activity', 'actor', 'assignedto', 'creation', 'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages', 'nosy', 'status', 'superseder', 'title']) self.assertEqual(self.db.issue.get('1', "fixer"), None) @@ -905,7 +932,7 @@ class DBTest(MyTestCase): props = self.db.issue.getprops() keys = props.keys() keys.sort() - self.assertEqual(keys, ['activity', 'assignedto', 'creation', + self.assertEqual(keys, ['activity', 'actor', 'assignedto', 'creation', 'creator', 'deadline', 'files', 'foo', 'id', 'messages', 'nosy', 'status', 'superseder']) self.assertEqual(self.db.issue.list(), ['1']) @@ -920,7 +947,7 @@ class DBTest(MyTestCase): props = self.db.issue.getprops() keys = props.keys() keys.sort() - self.assertEqual(keys, ['activity', 'assignedto', 'creation', + self.assertEqual(keys, ['activity', 'actor', 'assignedto', 'creation', 'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages', 'nosy', 'status', 'superseder']) self.assertEqual(self.db.issue.list(), ['1']) @@ -963,6 +990,8 @@ class SchemaTest(MyTestCase): activity=String()) self.assertRaises(ValueError, self.module.Class, self.db, "a", creator=String()) + self.assertRaises(ValueError, self.module.Class, self.db, "a", + actor=String()) def init_a(self): self.db = self.module.Database(config, 'admin')
 
Actor: + +