Skip to content

Commit c8e5259

Browse files
author
Richard Jones
committed
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.
1 parent 09c9a42 commit c8e5259

File tree

14 files changed

+313
-79
lines changed

14 files changed

+313
-79
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ are given with the most recent entry first.
33

44
200?-??-?? 0.7.0
55
Feature:
6+
- added new "actor" automatic property (indicates user who cause the last
7+
"activity"
68
- simple support for collision detection (sf rfe 648763)
79
- support confirming registration by replying to the email (sf bug 763668)
810
- support setgid and running on port < 1024 (sf patch 777528)

TODO.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ doing before the next release:
33

44
- sessions, otks and indexing in RDBMSes
55
- add tests for group-by-multilink so I finally implement it for the RDBMSes
6-
- full coverage analysis for unit tests
76
- s/getnode/getitem in backends (and s/Node/Item)
8-
- activity_by meta-property
7+
- have rdbms backends look up the journal for actor if it's not set
8+
- ensure index creation is triggered by the version 1->2 update
99

1010
- migrate to numeric ID values (fixes bug 817217)

doc/upgrading.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ add::
5151
p = db.security.getPermission('View', cl)
5252
db.security.addPermissionToRole('User', p)
5353

54+
0.7.0 New "actor" property
55+
--------------------------
56+
57+
Roundup's database has a new per-item property "actor" which reflects the
58+
user performing the last "actvitiy". See the classic template for ways to
59+
integrate this new property into your interface.
60+
5461

5562
0.7.0 Extending the cgi interface
5663
---------------------------------

roundup/backends/back_anydbm.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
#$Id: back_anydbm.py,v 1.136 2004-03-12 05:36:26 richard Exp $
18+
#$Id: back_anydbm.py,v 1.137 2004-03-15 05:50:20 richard Exp $
1919
'''This module defines a backend that saves the hyperdatabase in a
2020
database chosen by anydbm. It is guaranteed to always be available in python
2121
versions >2.1.1 (the dumbdbm fallback in 2.1.1 and earlier has several
@@ -264,6 +264,7 @@ def addnode(self, classname, nodeid, node):
264264
# calling code's node assumptions)
265265
node = node.copy()
266266
node['creator'] = self.getuid()
267+
node['actor'] = self.getuid()
267268
node['creation'] = node['activity'] = date.Date()
268269

269270
self.newnodes.setdefault(classname, {})[nodeid] = 1
@@ -281,6 +282,7 @@ def setnode(self, classname, nodeid, node):
281282
# calling code's node assumptions)
282283
node = node.copy()
283284
node['activity'] = date.Date()
285+
node['actor'] = self.getuid()
284286

285287
# can't set without having already loaded the node
286288
self.cache[classname][nodeid] = node
@@ -731,10 +733,10 @@ def __init__(self, db, classname, **properties):
731733
or a ValueError is raised. The keyword arguments in 'properties'
732734
must map names to property objects, or a TypeError is raised.
733735
'''
734-
if (properties.has_key('creation') or properties.has_key('activity')
735-
or properties.has_key('creator')):
736-
raise ValueError, '"creation", "activity" and "creator" are '\
737-
'reserved'
736+
for name in 'creation activity creator actor'.split():
737+
if properties.has_key(name):
738+
raise ValueError, '"creation", "activity", "creator" and '\
739+
'"actor" are reserved'
738740

739741
self.classname = classname
740742
self.properties = properties
@@ -1010,6 +1012,8 @@ def import_list(self, propnames, proplist):
10101012
creation = None
10111013
if d.has_key('activity'):
10121014
del d['activity']
1015+
if d.has_key('actor'):
1016+
del d['actor']
10131017
self.db.addjournal(self.classname, newid, 'create', {}, creator,
10141018
creation)
10151019
return newid
@@ -1063,7 +1067,27 @@ def get(self, nodeid, propname, default=_marker, cache=1):
10631067
journal = self.db.getjournal(self.classname, nodeid)
10641068
if journal:
10651069
num_re = re.compile('^\d+$')
1066-
value = self.db.getjournal(self.classname, nodeid)[0][2]
1070+
value = journal[0][2]
1071+
if num_re.match(value):
1072+
return value
1073+
else:
1074+
# old-style "username" journal tag
1075+
try:
1076+
return self.db.user.lookup(value)
1077+
except KeyError:
1078+
# user's been retired, return admin
1079+
return '1'
1080+
else:
1081+
return self.db.getuid()
1082+
if propname == 'actor':
1083+
if d.has_key('actor'):
1084+
return d['actor']
1085+
if not self.do_journal:
1086+
raise ValueError, 'Journalling is disabled for this class'
1087+
journal = self.db.getjournal(self.classname, nodeid)
1088+
if journal:
1089+
num_re = re.compile('^\d+$')
1090+
value = journal[-1][2]
10671091
if num_re.match(value):
10681092
return value
10691093
else:
@@ -1901,6 +1925,7 @@ def getprops(self, protected=1):
19011925
d['creation'] = hyperdb.Date()
19021926
d['activity'] = hyperdb.Date()
19031927
d['creator'] = hyperdb.Link('user')
1928+
d['actor'] = hyperdb.Link('user')
19041929
return d
19051930

19061931
def addprop(self, **properties):

roundup/backends/back_mysql.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@ def create_version_2_tables(self):
128128
's_last_use FLOAT(20), s_user VARCHAR(255))')
129129
self.cursor.execute('CREATE INDEX sessions_key_idx ON sessions(s_key)')
130130

131+
def add_actor_column(self):
132+
# update existing tables to have the new actor column
133+
tables = self.database_schema['tables']
134+
for name in tables.keys():
135+
self.cursor.execute('ALTER TABLE _%s add __actor '
136+
'VARCHAR(255)'%name)
137+
131138
def __repr__(self):
132139
return '<myroundsql 0x%x>'%id(self)
133140

@@ -241,6 +248,16 @@ def drop_multilink_table_indexes(self, classname, ml):
241248
print >>hyperdb.DEBUG, 'drop_index', (self, index_sql)
242249
self.cursor.execute(index_sql)
243250

251+
def drop_class_table_key_index(self, cn, key):
252+
table_name = '_%s'%cn
253+
index_name = '_%s_%s_idx'%(cn, key)
254+
if not self.sql_index_exists(table_name, index_name):
255+
return
256+
sql = 'drop index %s on %s'%(index_name, table_name)
257+
if __debug__:
258+
print >>hyperdb.DEBUG, 'drop_index', (self, sql)
259+
self.cursor.execute(sql)
260+
244261
class MysqlClass:
245262
# we're overriding this method for ONE missing bit of functionality.
246263
# look for "I can't believe it's not a toy RDBMS" below

roundup/backends/back_postgresql.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ def create_version_2_tables(self):
115115
's_last_use FLOAT(20), s_user VARCHAR(255))')
116116
self.cursor.execute('CREATE INDEX sessions_key_idx ON sessions(s_key)')
117117

118+
def add_actor_column(self):
119+
# update existing tables to have the new actor column
120+
tables = self.database_schema['tables']
121+
for name in tables.keys():
122+
self.cursor.execute('ALTER TABLE _%s add __actor '
123+
'VARCHAR(255)'%name)
124+
118125
def __repr__(self):
119126
return '<roundpsycopgsql 0x%x>' % id(self)
120127

@@ -140,6 +147,7 @@ def create_class_table(self, spec):
140147
print >>hyperdb.DEBUG, 'create_class', (self, sql)
141148

142149
self.cursor.execute(sql)
150+
self.create_class_table_indexes(spec)
143151
return cols, mls
144152

145153
def create_journal_table(self, spec):
@@ -151,6 +159,7 @@ def create_journal_table(self, spec):
151159
print >>hyperdb.DEBUG, 'create_class', (self, sql)
152160

153161
self.cursor.execute(sql)
162+
self.create_journal_table_indexes(spec)
154163

155164
def create_multilink_table(self, spec, ml):
156165
sql = '''CREATE TABLE "%s_%s" (linkid VARCHAR(255),
@@ -160,6 +169,7 @@ def create_multilink_table(self, spec, ml):
160169
print >>hyperdb.DEBUG, 'create_class', (self, sql)
161170

162171
self.cursor.execute(sql)
172+
self.create_multilink_table_indexes(spec, ml)
163173

164174
class Class(rdbms_common.Class):
165175
pass

roundup/backends/back_sqlite.py

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: back_sqlite.py,v 1.15 2004-03-12 04:08:59 richard Exp $
1+
# $Id: back_sqlite.py,v 1.16 2004-03-15 05:50:20 richard Exp $
22
'''Implements a backend for SQLite.
33
44
See https://pysqlite.sourceforge.net/ for pysqlite info
@@ -48,6 +48,97 @@ def create_version_2_tables(self):
4848
's_last_use varchar, s_user varchar)')
4949
self.cursor.execute('create index sessions_key_idx on sessions(s_key)')
5050

51+
def add_actor_column(self):
52+
# update existing tables to have the new actor column
53+
tables = self.database_schema['tables']
54+
for classname, spec in self.classes.items():
55+
if tables.has_key(classname):
56+
dbspec = tables[classname]
57+
self.update_class(spec, dbspec, force=1, adding_actor=1)
58+
59+
def update_class(self, spec, old_spec, force=0, adding_actor=0):
60+
''' Determine the differences between the current spec and the
61+
database version of the spec, and update where necessary.
62+
63+
If 'force' is true, update the database anyway.
64+
65+
SQLite doesn't have ALTER TABLE, so we have to copy and
66+
regenerate the tables with the new schema.
67+
'''
68+
new_has = spec.properties.has_key
69+
new_spec = spec.schema()
70+
new_spec[1].sort()
71+
old_spec[1].sort()
72+
if not force and new_spec == old_spec:
73+
# no changes
74+
return 0
75+
76+
if __debug__:
77+
print >>hyperdb.DEBUG, 'update_class FIRING'
78+
79+
# detect multilinks that have been removed, and drop their table
80+
old_has = {}
81+
for name,prop in old_spec[1]:
82+
old_has[name] = 1
83+
if new_has(name) or not isinstance(prop, hyperdb.Multilink):
84+
continue
85+
# it's a multilink, and it's been removed - drop the old
86+
# table. First drop indexes.
87+
self.drop_multilink_table_indexes(spec.classname, ml)
88+
sql = 'drop table %s_%s'%(spec.classname, prop)
89+
if __debug__:
90+
print >>hyperdb.DEBUG, 'update_class', (self, sql)
91+
self.cursor.execute(sql)
92+
old_has = old_has.has_key
93+
94+
# now figure how we populate the new table
95+
if adding_actor:
96+
fetch = ['_activity', '_creation', '_creator']
97+
else:
98+
fetch = ['_actor', '_activity', '_creation', '_creator']
99+
properties = spec.getprops()
100+
for propname,x in new_spec[1]:
101+
prop = properties[propname]
102+
if isinstance(prop, hyperdb.Multilink):
103+
if force or not old_has(propname):
104+
# we need to create the new table
105+
self.create_multilink_table(spec, propname)
106+
elif old_has(propname):
107+
# we copy this col over from the old table
108+
fetch.append('_'+propname)
109+
110+
# select the data out of the old table
111+
fetch.append('id')
112+
fetch.append('__retired__')
113+
fetchcols = ','.join(fetch)
114+
cn = spec.classname
115+
sql = 'select %s from _%s'%(fetchcols, cn)
116+
if __debug__:
117+
print >>hyperdb.DEBUG, 'update_class', (self, sql)
118+
self.cursor.execute(sql)
119+
olddata = self.cursor.fetchall()
120+
121+
# TODO: update all the other index dropping code
122+
self.drop_class_table_indexes(cn, old_spec[0])
123+
124+
# drop the old table
125+
self.cursor.execute('drop table _%s'%cn)
126+
127+
# create the new table
128+
self.create_class_table(spec)
129+
130+
if olddata:
131+
# do the insert of the old data - the new columns will have
132+
# NULL values
133+
args = ','.join([self.arg for x in fetch])
134+
sql = 'insert into _%s (%s) values (%s)'%(cn, fetchcols, args)
135+
if __debug__:
136+
print >>hyperdb.DEBUG, 'update_class', (self, sql, olddata[0])
137+
for entry in olddata:
138+
self.cursor.execute(sql, tuple(entry))
139+
140+
return 1
141+
51142
def sql_close(self):
52143
''' Squash any error caused by us already having closed the
53144
connection.

0 commit comments

Comments
 (0)