summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 0f59079)
raw | patch | inline | side by side (parent: 0f59079)
author | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Mon, 17 Dec 2001 03:52:48 +0000 (03:52 +0000) | ||
committer | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Mon, 17 Dec 2001 03:52:48 +0000 (03:52 +0000) |
storing more than one file per node - if a property name is supplied,
the file is called designator.property.
I decided not to migrate the existing files stored over to the new naming
scheme - the FileClass just doesn't specify the property name.
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@473 57a73879-2fb5-44c3-a270-3262357dd7e2
the file is called designator.property.
I decided not to migrate the existing files stored over to the new naming
scheme - the FileClass just doesn't specify the property name.
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@473 57a73879-2fb5-44c3-a270-3262357dd7e2
diff --git a/CHANGES.txt b/CHANGES.txt
index 7d44745c06f6471c2f8c3c8d5797a7c37be48683..e2478978327590755da6e959588e553ad675ea62 100644 (file)
--- a/CHANGES.txt
+++ b/CHANGES.txt
This file contains the changes to the Roundup system over time. The entries
are given with the most recent entry first.
-2001-11-?? - 0.3.1
+2001-12-?? - 0.3.1b1
Feature:
. Added INSTANCE_NAME to configuration - used in web and email to identify
the instance.
diff --git a/doc/announcement.txt b/doc/announcement.txt
index f1ca352e1275dd8ebb720d422362eb007707e593..cdba40605b5220c517128612d8ccb839d52170f8 100644 (file)
--- a/doc/announcement.txt
+++ b/doc/announcement.txt
- Roundup 0.3.0 - an issue tracking system
+ Roundup 0.3.1b1 - an issue tracking system
-This release contains several new features which will require migration, so
-please read MIGRATION.txt!
+If you are upgrading from pre-0.3.0, please read MIGRATION.txt.
+
+Roundup requires python 2.1.1 for correct operation. Support for dumbdbm has
+been disabled until python 2.1.2 and 2.2 are released.
Big stuff in this release:
- - lots of bug fixes, thanks to all users for their great feedback!
- - much more flexible administration tool
- - much better handling of errors
- - more configuration options
- - CGI login uses cookies instead of basic auth
- - passwords are encoded in the database
- - much, much more: see the CHANGES file for details.
+ - Use of transactions to prevent partial data commits
+ - Zope Product front-end
+ - Nicer, more consistent change message generation
+ - Several bug fixes
+ - Much, much more: see the CHANGES file for details.
Source and documentation is available at the website:
http://roundup.sourceforge.net/
diff --git a/doc/index.html b/doc/index.html
index 59d2f35b4e46eea683fb7c7415607944933f14ea..b53b2b6659ce9485e3c59c85cce5fd5d4d0150a6 100644 (file)
--- a/doc/index.html
+++ b/doc/index.html
<title>Roundup: an Issue-Tracking System for Knowledge Workers</title>
</head><body>
-<h1 align=center>Roundup (0.3.0)</h1>
+<h1 align=center>Roundup (0.3.1)</h1>
<h3 align=center>An Issue-Tracking System for Knowledge Workers</h2>
<h1>Contents</h1>
<h2><a name="requires">Prerequisites</a></h2>
-<dl>
- <dd>Python 2.1.1 is required for the correct operation of roundup
-</dl>
+<p>
+Python 2.1.1 is required for the correct operation of roundup.
+</p>
+<p>
Download the latest version from
<a href="http://www.python.org/">http://www.python.org/</a>.
+</p>
</table>
<p>
-<table border=1 cellspacing=0>
+<table width=100% border=1 cellspacing=0>
<tr><th colspan=2>Command Help</th></tr>
-<tr><td valign=top><strong>history</strong></td>
- <td><tt>history designator</tt><p>
- Lists the journal entries for the node identified by the designator.
-</td></tr>
-
+
+
+<tr><td valign=top><strong>commit</strong></td>
+ <td><tt>Usage: commit</tt><p>
+<pre>
+The changes made during an interactive session are not
+automatically written to the database - they must be committed
+using this command.
+
+One-off commands on the command-line are automatically committed if
+they are successful.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>create</strong></td>
+ <td><tt>Usage: create classname property=value ...</tt><p>
+<pre>
+This creates a new entry of the given class using the property
+name=value arguments provided on the command line after the "create"
+command.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>display</strong></td>
+ <td><tt>Usage: display designator</tt><p>
+<pre>
+This lists the properties and their associated values for the given
+node.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>export</strong></td>
+ <td><tt>Usage: export class[,class] destination_dir</tt><p>
+<pre>
+This action exports the current data from the database into
+tab-separated-value files that are placed in the nominated destination
+directory. The journals are not exported.
+
+</pre></td></tr>
+
+
<tr><td valign=top><strong>find</strong></td>
- <td><tt>find classname propname=value ...</tt><p>
- Find the nodes of the given class with a given property value. The
- value may be either the nodeid of the linked node, or its key value.
-</td></tr>
-
+ <td><tt>Usage: find classname propname=value ...</tt><p>
+<pre>
+Find the nodes of the given class with a given link property value. The
+value may be either the nodeid of the linked node, or its key value.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>get</strong></td>
+ <td><tt>Usage: get property designator[,designator]*</tt><p>
+<pre>
+Retrieves the property value of the nodes specified by the designators.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>help</strong></td>
+ <td><tt>Usage: help topic</tt><p>
+<pre>
+commands -- list commands
+<command> -- help specific to a command
+initopts -- init command options
+all -- all available help
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>history</strong></td>
+ <td><tt>Usage: history designator</tt><p>
+<pre>
+Lists the journal entries for the node identified by the designator.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>import</strong></td>
+ <td><tt>Usage: import class file</tt><p>
+<pre>
+The file must define the same properties as the class (including having
+a "header" line with those property names.) The new nodes are added to
+the existing database - if you want to create a new database using the
+imported data, then create a new database (or, tediously, retire all
+the old data.)
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>initialise</strong></td>
+ <td><tt>Usage: initialise [template [backend [admin password]]]</tt><p>
+<pre>
+The command will prompt for the instance home directory (if not supplied
+through INSTANCE_HOME or the -i option. The template, backend and admin
+password may be specified on the command-line as arguments, in that
+order.
+
+See also initopts help.
+
+</pre></td></tr>
+
+
<tr><td valign=top><strong>list</strong></td>
- <td><tt>list classname [property]</tt><p>
- Lists all instances of the given class along. If the property is not
- specified, the "label" property is used. The label property is tried
- in order: the key, "name", "title" and then the first property,
- alphabetically.
-</td></tr>
-
+ <td><tt>Usage: list classname [property]</tt><p>
+<pre>
+Lists all instances of the given class. If the property is not
+specified, the "label" property is used. The label property is tried
+in order: the key, "name", "title" and then the first property,
+alphabetically.
+
+</pre></td></tr>
+
+
<tr><td valign=top><strong>retire</strong></td>
- <td><tt>retire designator[,designator]*</tt><p>
- This action indicates that a particular node is not to be retrieved by
- the list or find commands, and its key value may be re-used.
-</td></tr>
-
-<tr><td valign=top><strong>create</strong></td>
- <td><tt>create classname property=value ...</tt><p>
- This creates a new entry of the given class using the property
- name=value arguments provided on the command line after the "create"
- command.
-</td></tr>
-
-<tr><td valign=top><strong>get</strong></td>
- <td><tt>get property designator[,designator]*</tt><p>
- Retrieves the property value of the nodes specified by the designators.
-</td></tr>
-
-<tr><td valign=top><strong>spec</strong></td>
- <td><tt>spec classname</tt><p>
- This lists the properties for a given class.
-</td></tr>
-
+ <td><tt>Usage: retire designator[,designator]*</tt><p>
+<pre>
+This action indicates that a particular node is not to be retrieved by
+the list or find commands, and its key value may be re-used.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>rollback</strong></td>
+ <td><tt>Usage: rollback</tt><p>
+<pre>
+The changes made during an interactive session are not
+automatically written to the database - they must be committed
+manually. This command undoes all those changes, so a commit
+immediately after would make no changes to the database.
+
+</pre></td></tr>
+
+
<tr><td valign=top><strong>set</strong></td>
- <td><tt>set designator[,designator]* propname=value ...</tt><p>
- Sets the property to the value for all designators given.
-</td></tr>
+ <td><tt>Usage: set designator[,designator]* propname=value ...</tt><p>
+<pre>
+Sets the property to the value for all designators given.
-<tr><td valign=top><strong>init</strong></td>
- <td><tt>init [template [backend [admin password]]]</tt><p>
- The command will prompt for the instance home directory (if not supplied
- through INSTANCE_HOME or the -i option. The template, backend and admin
- password may be specified on the command-line as arguments, in that order.
-</td></tr>
+</pre></td></tr>
-<tr><td valign=top><strong>export</strong></td>
- <td><tt>export class[,class] destination_dir</tt><p>
- This action exports the current data from the database into
- comma-separated files that are placed in the nominated destination
- directory. The journals are not exported.
-</td></tr>
-<tr><td valign=top><strong>import</strong></td>
- <td><tt>import class file</tt><p>
- The file must define the same properties as the class (including having
- a "header" line with those property names.)
-</td></tr>
-
-<tr><td valign=top><strong>freshen</strong></td>
- <td><tt>freshen</tt><p>
- <strong>**DO NOT USE**</strong>
- <p>
- This currently kills databases!!!!
- <p>
- This action should generally not be used. It reads in an instance
- database and writes it again. In the future, is may also update
- instance code to account for changes in templates. It's probably wise
- not to use it anyway. Until we're sure it won't break things...
-</td></tr>
+<tr><td valign=top><strong>specification</strong></td>
+ <td><tt>Usage: specification classname</tt><p>
+<pre>
+This lists the properties for a given class.
-<tr><td><strong>help</strong></td>
- <td><tt>help [command]</tt><p>
- Short help about roundup-admin or the specific command.
- </td></tr>
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>table</strong></td>
+ <td><tt>Usage: table classname [property[,property]*]</tt><p>
+<pre>
+Lists all instances of the given class. If the properties are not
+specified, all properties are displayed. By default, the column widths
+are the width of the property names. The width may be explicitly defined
+by defining the property as "name:width". For example::
+ roundup> table priority id,name:10
+ Id Name
+ 1 fatal-bug
+ 2 bug
+ 3 usability
+ 4 feature
+
+</pre></td></tr>
-<tr><td><strong>morehelp</strong></td>
- <td><tt>morehelp</tt><p>
- All available help from the roundup-admin tool.
- </td></tr>
</table>
<p>
<p> </p>
<hr>
-$Id: index.html,v 1.21 2001-12-13 00:20:01 richard Exp $
+$Id: index.html,v 1.22 2001-12-17 03:52:47 richard Exp $
<p> </p>
</body></html>
diff --git a/roundup-admin b/roundup-admin
index 2a4162e98565976e44e6abcc33b148dd980c62fc..6a1d5c078efa1716ccc4a5e9e17d209857a4a983 100755 (executable)
--- a/roundup-admin
+++ b/roundup-admin
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: roundup-admin,v 1.54 2001-12-15 23:09:23 richard Exp $
+# $Id: roundup-admin,v 1.55 2001-12-17 03:52:47 richard Exp $
# python version check
from roundup import version_check
print '\n'.join(commands)
print
+ def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')):
+ commands = self.commands.values()
+ def sortfun(a, b):
+ return cmp(a.__name__, b.__name__)
+ commands.sort(sortfun)
+ for command in commands:
+ h = command.__doc__.split('\n')
+ name = command.__name__[3:]
+ usage = h[0]
+ print '''
+<tr><td valign=top><strong>%(name)s</strong></td>
+ <td><tt>%(usage)s</tt><p>
+<pre>'''%locals()
+ indent = indent_re.match(h[3])
+ if indent: indent = len(indent.group(1))
+ for line in h[3:]:
+ if indent:
+ print line[indent:]
+ else:
+ print line
+ print '</pre></td></tr>\n'
+
def help_all(self):
print '''
All commands (except help) require an instance specifier. This is just the path
#
# $Log: not supported by cvs2svn $
+# Revision 1.54 2001/12/15 23:09:23 richard
+# Some cleanups in roundup-admin, also made it work again...
+#
# Revision 1.53 2001/12/13 00:20:00 richard
# . Centralised the python version check code, bumped version to 2.1.1 (really
# needs to be 2.1.2, but that isn't released yet :)
index b11a461513e5fe6a29b1a9dd89a674f4e0164038..0d52750cf605106f40fe1de70129962023924575 100644 (file)
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-#$Id: back_anydbm.py,v 1.18 2001-12-16 10:53:38 richard Exp $
+#$Id: back_anydbm.py,v 1.19 2001-12-17 03:52:48 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
res = res + db.keys()
return res
+
+ #
+ # Files - special node properties
+ #
+ def filename(self, classname, nodeid, property=None):
+ '''Determine what the filename for the given node and optionally property is.
+ '''
+ # TODO: split into multiple files directories
+ if property:
+ return os.path.join(self.dir, 'files', '%s%s.%s'%(classname,
+ nodeid, property))
+ else:
+ # roundupdb.FileClass never specified the property name, so don't include it
+ return os.path.join(self.dir, 'files', '%s%s'%(classname,
+ nodeid))
+
+ def storefile(self, classname, nodeid, property, content):
+ '''Store the content of the file in the database. The property may be None, in
+ which case the filename does not indicate which property is being saved.
+ '''
+ name = self.filename(classname, nodeid, property)
+ open(name + '.tmp', 'wb').write(content)
+ self.transactions.append((self._doStoreFile, (name, )))
+
+ def getfile(self, classname, nodeid, property):
+ '''Store the content of the file in the database.
+ '''
+ return open(self.filename(classname, nodeid, property), 'rb').read()
+
+
#
# Journal
#
'''
if DEBUG:
print 'commit', (self,)
- # lock the DB
+ # TODO: lock the DB
+
+ # keep a handle to all the database files opened
+ self.databases = {}
+
+ # now, do all the transactions
for method, args in self.transactions:
- # TODO: optimise this, duh!
method(*args)
- # unlock the DB
+
+ # now close all the database files
+ for db in self.databases.values():
+ db.close()
+ del self.databases
+ # TODO: unlock the DB
# all transactions committed, back to normal
self.cache = {}
def _doSaveNode(self, classname, nodeid, node):
if DEBUG:
print '_doSaveNode', (self, classname, nodeid, node)
- db = self.getclassdb(classname, 'c')
+
+ # get the database handle
+ db_name = 'nodes.%s'%classname
+ if self.databases.has_key(db_name):
+ db = self.databases[db_name]
+ else:
+ db = self.databases[db_name] = self.getclassdb(classname, 'c')
+
# now save the marshalled data
db[nodeid] = marshal.dumps(node)
- db.close()
def _doSaveJournal(self, classname, nodeid, action, params):
if DEBUG:
print '_doSaveJournal', (self, classname, nodeid, action, params)
entry = (nodeid, date.Date().get_tuple(), self.journaltag, action,
params)
- db = self._opendb('journals.%s'%classname, 'c')
+
+ # get the database handle
+ db_name = 'journals.%s'%classname
+ if self.databases.has_key(db_name):
+ db = self.databases[db_name]
+ else:
+ db = self.databases[db_name] = self._opendb(db_name, 'c')
+
+ # now insert the journal entry
if db.has_key(nodeid):
s = db[nodeid]
l = marshal.loads(db[nodeid])
else:
l = [entry]
db[nodeid] = marshal.dumps(l)
- db.close()
+
+ def _doStoreFile(self, name, **databases):
+ # the file is currently ".tmp" - move it to its real name to commit
+ os.rename(name+".tmp", name)
def rollback(self):
''' Reverse all actions from the current transaction.
'''
if DEBUG:
print 'rollback', (self, )
+ for method, args in self.transactions:
+ # delete temporary files
+ if method == self._doStoreFile:
+ os.remove(args[0]+".tmp")
self.cache = {}
self.dirtynodes = {}
self.newnodes = {}
#
#$Log: not supported by cvs2svn $
+#Revision 1.18 2001/12/16 10:53:38 richard
+#take a copy of the node dict so that the subsequent set
+#operation doesn't modify the oldvalues structure
+#
#Revision 1.17 2001/12/14 23:42:57 richard
#yuck, a gdbm instance tests false :(
#I've left the debugging code in - it should be removed one day if we're ever
diff --git a/roundup/roundupdb.py b/roundup/roundupdb.py
index f57139ed49d33716f6779915d051773325dc5a58..50cd90c4dcca11d9a69076be33364dba63e56d3a 100644 (file)
--- a/roundup/roundupdb.py
+++ b/roundup/roundupdb.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: roundupdb.py,v 1.33 2001-12-16 10:53:37 richard Exp $
+# $Id: roundupdb.py,v 1.34 2001-12-17 03:52:48 richard Exp $
__doc__ = """
Extending hyperdb with types specific to issue-tracking.
content = propvalues['content']
del propvalues['content']
newid = Class.create(self, **propvalues)
- self.setcontent(self.classname, newid, content)
+ self.db.storefile(self.classname, newid, None, content)
return newid
- def filename(self, classname, nodeid):
- # TODO: split into multiple files directories
- return os.path.join(self.db.dir, 'files', '%s%s'%(classname, nodeid))
-
- def setcontent(self, classname, nodeid, content):
- ''' set the content file for this file
- '''
- open(self.filename(classname, nodeid), 'wb').write(content)
-
- def getcontent(self, classname, nodeid):
- ''' get the content file for this file
- '''
- return open(self.filename(classname, nodeid), 'rb').read()
-
def get(self, nodeid, propname, default=_marker):
''' trap the content propname and get it from the file
'''
if propname == 'content':
- return self.getcontent(self.classname, nodeid)
+ return self.db.getfile(self.classname, nodeid, None)
if default is not _marker:
return Class.get(self, nodeid, propname, default)
else:
#
# $Log: not supported by cvs2svn $
+# Revision 1.33 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.32 2001/12/15 23:48:35 richard
# Added ROUNDUPDBSENDMAILDEBUG so one can test the sendmail method without
# actually sending mail :)
diff --git a/test/test_db.py b/test/test_db.py
index 08b918adebc927eff22b37d4f5f6b738fbf7dd50..c0919f569337b9f11da1467c0857dc3929ae4cb0 100644 (file)
--- a/test/test_db.py
+++ b/test/test_db.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: test_db.py,v 1.11 2001-12-10 23:17:20 richard Exp $
+# $Id: test_db.py,v 1.12 2001-12-17 03:52:48 richard Exp $
import unittest, os, shutil
from roundup.hyperdb import String, Password, Link, Multilink, Date, \
Interval, Class, DatabaseError
+from roundup.roundupdb import FileClass
def setupSchema(db, create):
status = Class(db, "status", name=String())
Class(db, "user", username=String(), password=Password())
Class(db, "issue", title=String(), status=Link("status"),
nosy=Multilink("user"))
+ FileClass(db, "file", name=String(), type=String())
db.commit()
class MyTestCase(unittest.TestCase):
# remove previous test, ignore errors
if os.path.exists('_test_dir'):
shutil.rmtree('_test_dir')
- os.mkdir('_test_dir')
+ os.makedirs('_test_dir/files')
self.db = anydbm.Database('_test_dir', 'test')
setupSchema(self.db, 1)
def testTransactions(self):
num_issues = len(self.db.issue.list())
+ files_dir = os.path.join('_test_dir', 'files')
+ if os.path.exists(files_dir):
+ num_files = len(os.listdir(files_dir))
+ else:
+ num_files = 0
self.db.issue.create(title="don't commit me!", status='1')
self.assertNotEqual(num_issues, len(self.db.issue.list()))
self.db.rollback()
self.assertNotEqual(num_issues, len(self.db.issue.list()))
self.db.rollback()
self.assertNotEqual(num_issues, len(self.db.issue.list()))
+ self.db.file.create(name="test", type="text/plain", content="hi")
+ self.db.rollback()
+ self.assertEqual(num_files, len(os.listdir(files_dir)))
+ self.db.file.create(name="test", type="text/plain", content="hi")
+ self.db.commit()
+ self.assertNotEqual(num_files, len(os.listdir(files_dir)))
+ num_files2 = len(os.listdir(files_dir))
+ self.db.file.create(name="test", type="text/plain", content="hi")
+ self.db.rollback()
+ self.assertNotEqual(num_files, len(os.listdir(files_dir)))
+ self.assertEqual(num_files2, len(os.listdir(files_dir)))
+
def testExceptions(self):
# this tests the exceptions that should be raised
# remove previous test, ignore errors
if os.path.exists('_test_dir'):
shutil.rmtree('_test_dir')
- os.mkdir('_test_dir')
+ os.makedirs('_test_dir/files')
db = anydbm.Database('_test_dir', 'test')
setupSchema(db, 1)
self.db = anydbm.Database('_test_dir')
# remove previous test, ignore errors
if os.path.exists('_test_dir'):
shutil.rmtree('_test_dir')
- os.mkdir('_test_dir')
+ os.makedirs('_test_dir/files')
self.db = bsddb.Database('_test_dir', 'test')
setupSchema(self.db, 1)
# remove previous test, ignore errors
if os.path.exists('_test_dir'):
shutil.rmtree('_test_dir')
- os.mkdir('_test_dir')
+ os.makedirs('_test_dir/files')
db = bsddb.Database('_test_dir', 'test')
setupSchema(db, 1)
self.db = bsddb.Database('_test_dir')
# remove previous test, ignore errors
if os.path.exists('_test_dir'):
shutil.rmtree('_test_dir')
- os.mkdir('_test_dir')
+ os.makedirs('_test_dir/files')
self.db = bsddb3.Database('_test_dir', 'test')
setupSchema(self.db, 1)
# remove previous test, ignore errors
if os.path.exists('_test_dir'):
shutil.rmtree('_test_dir')
- os.mkdir('_test_dir')
+ os.makedirs('_test_dir/files')
db = bsddb3.Database('_test_dir', 'test')
setupSchema(db, 1)
self.db = bsddb3.Database('_test_dir')
#
# $Log: not supported by cvs2svn $
+# Revision 1.11 2001/12/10 23:17:20 richard
+# Added transaction tests to test_db
+#
# Revision 1.10 2001/12/03 21:33:39 richard
# Fixes so the tests use commit and not close
#