Skip to content

Commit 51fafc1

Browse files
author
Andrey Lebedev
committed
added ability to restore retired nodes
1 parent 7728b14 commit 51fafc1

File tree

7 files changed

+126
-21
lines changed

7 files changed

+126
-21
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Feature:
4444
- added support for searching on ranges of dates (see doc/user_guide.txt in
4545
chapter "Searching Page" for details)
4646
- role names made case insensitive
47+
- added ability to restore retired nodes
4748

4849

4950
Fixed:

doc/design.txt

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ Here is the interface provided by the hyperdatabase::
308308
The 'journaltag' is a token that will be attached to the journal
309309
entries for any edits done on the database. If 'journaltag' is
310310
None, the database is opened in read-only mode: the Class.create(),
311-
Class.set(), and Class.retire() methods are disabled.
311+
Class.set(), Class.retire(), and Class.restore() methods are
312+
disabled.
312313
"""
313314

314315
def __getattr__(self, classname):
@@ -379,6 +380,12 @@ Here is the interface provided by the hyperdatabase::
379380
reuse the values of their key properties.
380381
"""
381382

383+
def restore(self, nodeid):
384+
'''Restpre a retired node.
385+
386+
Make node available for all operations like it was before retirement.
387+
'''
388+
382389
def history(self, itemid):
383390
"""Retrieve the journal of edits on a particular item.
384391

@@ -793,7 +800,7 @@ There are two kinds of detectors:
793800
2. a reactor is triggered just after an item has been modified
794801

795802
When the Roundup database is about to perform a
796-
``create()``, ``set()``, or ``retire()``
803+
``create()``, ``set()``, ``retire()``, or ``restore``
797804
operation, it first calls any *auditors* that
798805
have been registered for that operation on that class.
799806
Any auditor may raise a *Reject* exception
@@ -814,14 +821,14 @@ register detectors on a given class of items::
814821
def audit(self, event, detector):
815822
"""Register an auditor on this class.
816823

817-
'event' should be one of "create", "set", or "retire".
824+
'event' should be one of "create", "set", "retire", or "restore".
818825
'detector' should be a function accepting four arguments.
819826
"""
820827

821828
def react(self, event, detector):
822829
"""Register a reactor on this class.
823830

824-
'event' should be one of "create", "set", or "retire".
831+
'event' should be one of "create", "set", "retire", or "restore".
825832
'detector' should be a function accepting four arguments.
826833
"""
827834

@@ -842,7 +849,7 @@ For a ``set()`` operation, newdata
842849
contains only the names and values of properties that are about
843850
to be changed.
844851

845-
For a ``retire()`` operation, newdata is None.
852+
For a ``retire()`` or ``restore()`` operation, newdata is None.
846853

847854
Reactors are called with the arguments::
848855

@@ -859,8 +866,8 @@ newly-created item and ``olddata`` is None.
859866
For a ``set()`` operation, ``olddata``
860867
contains the names and previous values of properties that were changed.
861868

862-
For a ``retire()`` operation, ``itemid`` is the
863-
id of the retired item and ``olddata`` is None.
869+
For a ``retire()`` or ``restore()`` operation, ``itemid`` is the id of
870+
the retired or restored item and ``olddata`` is None.
864871

865872
Detector Example
866873
~~~~~~~~~~~~~~~~

roundup/admin.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1717
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1818
#
19-
# $Id: admin.py,v 1.42 2003-03-06 07:33:29 richard Exp $
19+
# $Id: admin.py,v 1.43 2003-03-16 22:24:54 kedder Exp $
2020

2121
'''Administration commands for maintaining Roundup trackers.
2222
'''
@@ -886,6 +886,28 @@ def do_retire(self, args):
886886
raise UsageError, _('no such %(classname)s node "%(nodeid)s"')%locals()
887887
return 0
888888

889+
def do_restore(self, args):
890+
'''Usage: restore designator[,designator]*
891+
Restore the retired node specified by designator.
892+
893+
The given nodes will become available for users again.
894+
'''
895+
if len(args) < 1:
896+
raise UsageError, _('Not enough arguments supplied')
897+
designators = args[0].split(',')
898+
for designator in designators:
899+
try:
900+
classname, nodeid = hyperdb.splitDesignator(designator)
901+
except hyperdb.DesignatorError, message:
902+
raise UsageError, message
903+
try:
904+
self.db.getclass(classname).restore(nodeid)
905+
except KeyError:
906+
raise UsageError, _('no such class "%(classname)s"')%locals()
907+
except IndexError:
908+
raise UsageError, _('no such %(classname)s node "%(nodeid)s"')%locals()
909+
return 0
910+
889911
def do_export(self, args):
890912
'''Usage: export [class[,class]] export_dir
891913
Export the database to colon-separated-value files.

roundup/backends/back_anydbm.py

Lines changed: 24 additions & 5 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.111 2003-03-10 00:22:20 richard Exp $
18+
#$Id: back_anydbm.py,v 1.112 2003-03-16 22:24:54 kedder Exp $
1919
'''
2020
This module defines a backend that saves the hyperdatabase in a database
2121
chosen by anydbm. It is guaranteed to always be available in python
@@ -57,8 +57,9 @@ def __init__(self, config, journaltag=None):
5757
The 'journaltag' is a token that will be attached to the journal
5858
entries for any edits done on the database. If 'journaltag' is
5959
None, the database is opened in read-only mode: the Class.create(),
60-
Class.set(), and Class.retire() methods are disabled.
61-
'''
60+
Class.set(), Class.retire(), and Class.restore() methods are
61+
disabled.
62+
'''
6263
self.config, self.journaltag = config, journaltag
6364
self.dir = config.DATABASE
6465
self.classes = {}
@@ -710,8 +711,8 @@ def __init__(self, db, classname, **properties):
710711
# do the db-related init stuff
711712
db.addclass(self)
712713

713-
self.auditors = {'create': [], 'set': [], 'retire': []}
714-
self.reactors = {'create': [], 'set': [], 'retire': []}
714+
self.auditors = {'create': [], 'set': [], 'retire': [], 'restore': []}
715+
self.reactors = {'create': [], 'set': [], 'retire': [], 'restore': []}
715716

716717
def enableJournalling(self):
717718
'''Turn journalling on for this class
@@ -1319,6 +1320,24 @@ def retire(self, nodeid):
13191320

13201321
self.fireReactors('retire', nodeid, None)
13211322

1323+
def restore(self, nodeid):
1324+
'''Restpre a retired node.
1325+
1326+
Make node available for all operations like it was before retirement.
1327+
'''
1328+
if self.db.journaltag is None:
1329+
raise DatabaseError, 'Database open read-only'
1330+
1331+
self.fireAuditors('restore', nodeid, None)
1332+
1333+
node = self.db.getnode(self.classname, nodeid)
1334+
del node[self.db.RETIRED_FLAG]
1335+
self.db.setnode(self.classname, nodeid, node)
1336+
if self.do_journal:
1337+
self.db.addjournal(self.classname, nodeid, 'restored', None)
1338+
1339+
self.fireReactors('restore', nodeid, None)
1340+
13221341
def is_retired(self, nodeid, cldb=None):
13231342
'''Return true if the node is retired.
13241343
'''

roundup/backends/back_metakit.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: back_metakit.py,v 1.41 2003-03-10 20:24:30 kedder Exp $
1+
# $Id: back_metakit.py,v 1.42 2003-03-16 22:24:54 kedder Exp $
22
'''
33
Metakit backend for Roundup, originally by Gordon McMillan.
44
@@ -279,12 +279,13 @@ def setid(self, classname, maxid):
279279

280280
_STRINGTYPE = type('')
281281
_LISTTYPE = type([])
282-
_CREATE, _SET, _RETIRE, _LINK, _UNLINK = range(5)
282+
_CREATE, _SET, _RETIRE, _LINK, _UNLINK, _RESTORE = range(6)
283283

284284
_actionnames = {
285285
_CREATE : 'create',
286286
_SET : 'set',
287287
_RETIRE : 'retire',
288+
_RESTORE : 'restore',
288289
_LINK : 'link',
289290
_UNLINK : 'unlink',
290291
}
@@ -307,8 +308,8 @@ def __init__(self, db, classname, **properties):
307308
'creator' : hyperdb.Link('user') }
308309

309310
# event -> list of callables
310-
self.auditors = {'create': [], 'set': [], 'retire': []}
311-
self.reactors = {'create': [], 'set': [], 'retire': []}
311+
self.auditors = {'create': [], 'set': [], 'retire': [], 'restore': []}
312+
self.reactors = {'create': [], 'set': [], 'retire': [], 'restore': []}
312313

313314
view = self.__getview()
314315
self.maxid = 1
@@ -676,6 +677,34 @@ def retire(self, nodeid):
676677
self.db.dirty = 1
677678
self.fireReactors('retire', nodeid, None)
678679

680+
def restore(self, nodeid):
681+
'''Restpre a retired node.
682+
683+
Make node available for all operations like it was before retirement.
684+
'''
685+
if self.db.journaltag is None:
686+
raise hyperdb.DatabaseError, 'Database open read-only'
687+
self.fireAuditors('restore', nodeid, None)
688+
view = self.getview(1)
689+
ndx = view.find(id=int(nodeid))
690+
if ndx < 0:
691+
raise KeyError, "nodeid %s not found" % nodeid
692+
693+
row = view[ndx]
694+
oldvalues = self.uncommitted.setdefault(row.id, {})
695+
oldval = oldvalues['_isdel'] = row._isdel
696+
row._isdel = 0
697+
698+
if self.do_journal:
699+
self.db.addjournal(self.classname, nodeid, _RESTORE, {})
700+
if self.keyname:
701+
iv = self.getindexview(1)
702+
ndx = iv.find(k=getattr(row, self.keyname),i=row.id)
703+
if ndx > -1:
704+
iv.delete(ndx)
705+
self.db.dirty = 1
706+
self.fireReactors('restore', nodeid, None)
707+
679708
def is_retired(self, nodeid):
680709
view = self.getview(1)
681710
# node must exist & not be retired

roundup/backends/rdbms_common.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: rdbms_common.py,v 1.43 2003-03-14 02:51:25 richard Exp $
1+
# $Id: rdbms_common.py,v 1.44 2003-03-16 22:24:55 kedder Exp $
22
''' Relational database (SQL) backend common code.
33
44
Basics:
@@ -894,8 +894,8 @@ def __init__(self, db, classname, **properties):
894894
# do the db-related init stuff
895895
db.addclass(self)
896896

897-
self.auditors = {'create': [], 'set': [], 'retire': []}
898-
self.reactors = {'create': [], 'set': [], 'retire': []}
897+
self.auditors = {'create': [], 'set': [], 'retire': [], 'restore': []}
898+
self.reactors = {'create': [], 'set': [], 'retire': [], 'restore': []}
899899

900900
def schema(self):
901901
''' A dumpable version of the schema that we can store in the
@@ -1473,9 +1473,33 @@ def retire(self, nodeid):
14731473
if __debug__:
14741474
print >>hyperdb.DEBUG, 'retire', (self, sql, nodeid)
14751475
self.db.cursor.execute(sql, (1, nodeid))
1476+
if self.do_journal:
1477+
self.db.addjournal(self.classname, nodeid, 'retired', None)
14761478

14771479
self.fireReactors('retire', nodeid, None)
14781480

1481+
def restore(self, nodeid):
1482+
'''Restpre a retired node.
1483+
1484+
Make node available for all operations like it was before retirement.
1485+
'''
1486+
if self.db.journaltag is None:
1487+
raise DatabaseError, 'Database open read-only'
1488+
1489+
self.fireAuditors('restore', nodeid, None)
1490+
1491+
# use the arg for __retired__ to cope with any odd database type
1492+
# conversion (hello, sqlite)
1493+
sql = 'update _%s set __retired__=%s where id=%s'%(self.classname,
1494+
self.db.arg, self.db.arg)
1495+
if __debug__:
1496+
print >>hyperdb.DEBUG, 'restore', (self, sql, nodeid)
1497+
self.db.cursor.execute(sql, (0, nodeid))
1498+
if self.do_journal:
1499+
self.db.addjournal(self.classname, nodeid, 'restored', None)
1500+
1501+
self.fireReactors('restore', nodeid, None)
1502+
14791503
def is_retired(self, nodeid):
14801504
'''Return true if the node is rerired
14811505
'''

test/test_db.py

Lines changed: 4 additions & 1 deletion
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: test_db.py,v 1.76 2003-03-10 18:16:42 kedder Exp $
18+
# $Id: test_db.py,v 1.77 2003-03-16 22:24:56 kedder Exp $
1919

2020
import unittest, os, shutil, time
2121

@@ -286,6 +286,9 @@ def testRetire(self):
286286
self.db.commit()
287287
self.assertEqual(self.db.status.get('1', 'name'), b)
288288
self.assertNotEqual(a, self.db.status.list())
289+
# try to restore retired node
290+
self.db.status.restore('1')
291+
self.assertEqual(a, self.db.status.list())
289292

290293
def testSerialisation(self):
291294
nid = self.db.issue.create(title="spam", status='1',

0 commit comments

Comments
 (0)