summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 4a20408)
raw | patch | inline | side by side (parent: 4a20408)
author | kedder <kedder@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Sun, 16 Mar 2003 22:24:56 +0000 (22:24 +0000) | ||
committer | kedder <kedder@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Sun, 16 Mar 2003 22:24:56 +0000 (22:24 +0000) |
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1593 57a73879-2fb5-44c3-a270-3262357dd7e2
diff --git a/CHANGES.txt b/CHANGES.txt
index 1ce4dce5d000a92bc3ef27f1745df69a1393a3d8..aafba74e8166a811b8952a2609eb3ca7968abd61 100644 (file)
--- a/CHANGES.txt
+++ b/CHANGES.txt
- added support for searching on ranges of dates (see doc/user_guide.txt in
chapter "Searching Page" for details)
- role names made case insensitive
+- added ability to restore retired nodes
Fixed:
diff --git a/doc/design.txt b/doc/design.txt
index 5d7ac09cd88a9ad8a203aea4285b8eaa631bd52f..3422a661a227040899744d15d4590020e4376f27 100644 (file)
--- a/doc/design.txt
+++ b/doc/design.txt
The 'journaltag' is a token that will be attached to the journal
entries for any edits done on the database. If 'journaltag' is
None, the database is opened in read-only mode: the Class.create(),
- Class.set(), and Class.retire() methods are disabled.
+ Class.set(), Class.retire(), and Class.restore() methods are
+ disabled.
"""
def __getattr__(self, classname):
reuse the values of their key properties.
"""
+ def restore(self, nodeid):
+ '''Restpre a retired node.
+
+ Make node available for all operations like it was before retirement.
+ '''
+
def history(self, itemid):
"""Retrieve the journal of edits on a particular item.
2. a reactor is triggered just after an item has been modified
When the Roundup database is about to perform a
-``create()``, ``set()``, or ``retire()``
+``create()``, ``set()``, ``retire()``, or ``restore``
operation, it first calls any *auditors* that
have been registered for that operation on that class.
Any auditor may raise a *Reject* exception
def audit(self, event, detector):
"""Register an auditor on this class.
- 'event' should be one of "create", "set", or "retire".
+ 'event' should be one of "create", "set", "retire", or "restore".
'detector' should be a function accepting four arguments.
"""
def react(self, event, detector):
"""Register a reactor on this class.
- 'event' should be one of "create", "set", or "retire".
+ 'event' should be one of "create", "set", "retire", or "restore".
'detector' should be a function accepting four arguments.
"""
contains only the names and values of properties that are about
to be changed.
-For a ``retire()`` operation, newdata is None.
+For a ``retire()`` or ``restore()`` operation, newdata is None.
Reactors are called with the arguments::
For a ``set()`` operation, ``olddata``
contains the names and previous values of properties that were changed.
-For a ``retire()`` operation, ``itemid`` is the
-id of the retired item and ``olddata`` is None.
+For a ``retire()`` or ``restore()`` operation, ``itemid`` is the id of
+the retired or restored item and ``olddata`` is None.
Detector Example
~~~~~~~~~~~~~~~~
diff --git a/roundup/admin.py b/roundup/admin.py
index 1f2e4d00b5afe4932e905b4979e25b0246ce1c92..ad2910ad0cc15070f9cb40e4226cfb0f4234e4c0 100644 (file)
--- a/roundup/admin.py
+++ b/roundup/admin.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: admin.py,v 1.42 2003-03-06 07:33:29 richard Exp $
+# $Id: admin.py,v 1.43 2003-03-16 22:24:54 kedder Exp $
'''Administration commands for maintaining Roundup trackers.
'''
raise UsageError, _('no such %(classname)s node "%(nodeid)s"')%locals()
return 0
+ def do_restore(self, args):
+ '''Usage: restore designator[,designator]*
+ Restore the retired node specified by designator.
+
+ The given nodes will become available for users again.
+ '''
+ if len(args) < 1:
+ raise UsageError, _('Not enough arguments supplied')
+ designators = args[0].split(',')
+ for designator in designators:
+ try:
+ classname, nodeid = hyperdb.splitDesignator(designator)
+ except hyperdb.DesignatorError, message:
+ raise UsageError, message
+ try:
+ self.db.getclass(classname).restore(nodeid)
+ except KeyError:
+ raise UsageError, _('no such class "%(classname)s"')%locals()
+ except IndexError:
+ raise UsageError, _('no such %(classname)s node "%(nodeid)s"')%locals()
+ return 0
+
def do_export(self, args):
'''Usage: export [class[,class]] export_dir
Export the database to colon-separated-value files.
index 2607654ea55e48a652e4036ab52bfaf7abf310e3..258b4daf1ee7e947db0eeb0735ff9a06afcc71c5 100644 (file)
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-#$Id: back_anydbm.py,v 1.111 2003-03-10 00:22:20 richard Exp $
+#$Id: back_anydbm.py,v 1.112 2003-03-16 22:24:54 kedder 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
The 'journaltag' is a token that will be attached to the journal
entries for any edits done on the database. If 'journaltag' is
None, the database is opened in read-only mode: the Class.create(),
- Class.set(), and Class.retire() methods are disabled.
- '''
+ Class.set(), Class.retire(), and Class.restore() methods are
+ disabled.
+ '''
self.config, self.journaltag = config, journaltag
self.dir = config.DATABASE
self.classes = {}
# do the db-related init stuff
db.addclass(self)
- self.auditors = {'create': [], 'set': [], 'retire': []}
- self.reactors = {'create': [], 'set': [], 'retire': []}
+ self.auditors = {'create': [], 'set': [], 'retire': [], 'restore': []}
+ self.reactors = {'create': [], 'set': [], 'retire': [], 'restore': []}
def enableJournalling(self):
'''Turn journalling on for this class
self.fireReactors('retire', nodeid, None)
+ def restore(self, nodeid):
+ '''Restpre a retired node.
+
+ Make node available for all operations like it was before retirement.
+ '''
+ if self.db.journaltag is None:
+ raise DatabaseError, 'Database open read-only'
+
+ self.fireAuditors('restore', nodeid, None)
+
+ node = self.db.getnode(self.classname, nodeid)
+ del node[self.db.RETIRED_FLAG]
+ self.db.setnode(self.classname, nodeid, node)
+ if self.do_journal:
+ self.db.addjournal(self.classname, nodeid, 'restored', None)
+
+ self.fireReactors('restore', nodeid, None)
+
def is_retired(self, nodeid, cldb=None):
'''Return true if the node is retired.
'''
index fec52eb95579bc27887062c38f31dc3bc0b8e21e..8eae6946c454d0ad967735273f7a349ef859a049 100755 (executable)
-# $Id: back_metakit.py,v 1.41 2003-03-10 20:24:30 kedder Exp $
+# $Id: back_metakit.py,v 1.42 2003-03-16 22:24:54 kedder Exp $
'''
Metakit backend for Roundup, originally by Gordon McMillan.
_STRINGTYPE = type('')
_LISTTYPE = type([])
-_CREATE, _SET, _RETIRE, _LINK, _UNLINK = range(5)
+_CREATE, _SET, _RETIRE, _LINK, _UNLINK, _RESTORE = range(6)
_actionnames = {
_CREATE : 'create',
_SET : 'set',
_RETIRE : 'retire',
+ _RESTORE : 'restore',
_LINK : 'link',
_UNLINK : 'unlink',
}
'creator' : hyperdb.Link('user') }
# event -> list of callables
- self.auditors = {'create': [], 'set': [], 'retire': []}
- self.reactors = {'create': [], 'set': [], 'retire': []}
+ self.auditors = {'create': [], 'set': [], 'retire': [], 'restore': []}
+ self.reactors = {'create': [], 'set': [], 'retire': [], 'restore': []}
view = self.__getview()
self.maxid = 1
self.db.dirty = 1
self.fireReactors('retire', nodeid, None)
+ def restore(self, nodeid):
+ '''Restpre a retired node.
+
+ Make node available for all operations like it was before retirement.
+ '''
+ if self.db.journaltag is None:
+ raise hyperdb.DatabaseError, 'Database open read-only'
+ self.fireAuditors('restore', nodeid, None)
+ view = self.getview(1)
+ ndx = view.find(id=int(nodeid))
+ if ndx < 0:
+ raise KeyError, "nodeid %s not found" % nodeid
+
+ row = view[ndx]
+ oldvalues = self.uncommitted.setdefault(row.id, {})
+ oldval = oldvalues['_isdel'] = row._isdel
+ row._isdel = 0
+
+ if self.do_journal:
+ self.db.addjournal(self.classname, nodeid, _RESTORE, {})
+ if self.keyname:
+ iv = self.getindexview(1)
+ ndx = iv.find(k=getattr(row, self.keyname),i=row.id)
+ if ndx > -1:
+ iv.delete(ndx)
+ self.db.dirty = 1
+ self.fireReactors('restore', nodeid, None)
+
def is_retired(self, nodeid):
view = self.getview(1)
# node must exist & not be retired
index b3a9c9ca47c06d9c8e11c4b87889d8f3120261f2..1c60cc7097d61abc8d764eb722eaa1d060f5c91c 100644 (file)
-# $Id: rdbms_common.py,v 1.43 2003-03-14 02:51:25 richard Exp $
+# $Id: rdbms_common.py,v 1.44 2003-03-16 22:24:55 kedder Exp $
''' Relational database (SQL) backend common code.
Basics:
# do the db-related init stuff
db.addclass(self)
- self.auditors = {'create': [], 'set': [], 'retire': []}
- self.reactors = {'create': [], 'set': [], 'retire': []}
+ self.auditors = {'create': [], 'set': [], 'retire': [], 'restore': []}
+ self.reactors = {'create': [], 'set': [], 'retire': [], 'restore': []}
def schema(self):
''' A dumpable version of the schema that we can store in the
if __debug__:
print >>hyperdb.DEBUG, 'retire', (self, sql, nodeid)
self.db.cursor.execute(sql, (1, nodeid))
+ if self.do_journal:
+ self.db.addjournal(self.classname, nodeid, 'retired', None)
self.fireReactors('retire', nodeid, None)
+ def restore(self, nodeid):
+ '''Restpre a retired node.
+
+ Make node available for all operations like it was before retirement.
+ '''
+ if self.db.journaltag is None:
+ raise DatabaseError, 'Database open read-only'
+
+ self.fireAuditors('restore', nodeid, None)
+
+ # use the arg for __retired__ to cope with any odd database type
+ # conversion (hello, sqlite)
+ sql = 'update _%s set __retired__=%s where id=%s'%(self.classname,
+ self.db.arg, self.db.arg)
+ if __debug__:
+ print >>hyperdb.DEBUG, 'restore', (self, sql, nodeid)
+ self.db.cursor.execute(sql, (0, nodeid))
+ if self.do_journal:
+ self.db.addjournal(self.classname, nodeid, 'restored', None)
+
+ self.fireReactors('restore', nodeid, None)
+
def is_retired(self, nodeid):
'''Return true if the node is rerired
'''
diff --git a/test/test_db.py b/test/test_db.py
index 43ed8b18970c7f60dd6d0aa5c1dd0d1fa3fec316..e6860945038bc569623361e30ef7efece4a5b2ef 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.76 2003-03-10 18:16:42 kedder Exp $
+# $Id: test_db.py,v 1.77 2003-03-16 22:24:56 kedder Exp $
import unittest, os, shutil, time
self.db.commit()
self.assertEqual(self.db.status.get('1', 'name'), b)
self.assertNotEqual(a, self.db.status.list())
+ # try to restore retired node
+ self.db.status.restore('1')
+ self.assertEqual(a, self.db.status.list())
def testSerialisation(self):
nid = self.db.issue.create(title="spam", status='1',