Mercurial > p > roundup > code
changeset 2014:366d3bbce982
Simple version of collision detection...
...with tests and a new generic template for classic and minimal.
| author | Johannes Gijsbers <jlgijsbers@users.sourceforge.net> |
|---|---|
| date | Sat, 14 Feb 2004 02:06:27 +0000 |
| parents | d116293863a4 |
| children | 95f2c726f664 |
| files | CHANGES.txt roundup/cgi/actions.py roundup/cgi/templating.py templates/classic/html/_generic.collision.html templates/minimal/html/_generic.collision.html test/test_actions.py |
| diffstat | 6 files changed, 90 insertions(+), 6 deletions(-) [+] |
line wrap: on
line diff
--- a/CHANGES.txt Sat Feb 14 01:55:35 2004 +0000 +++ b/CHANGES.txt Sat Feb 14 02:06:27 2004 +0000 @@ -3,6 +3,7 @@ 200?-??-?? 0.7.0 Feature: +- 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) - using Zope3's test runner now, allowing GC checks, nicer controls and
--- a/roundup/cgi/actions.py Sat Feb 14 01:55:35 2004 +0000 +++ b/roundup/cgi/actions.py Sat Feb 14 02:06:27 2004 +0000 @@ -435,12 +435,37 @@ return cl.create(**props) class EditItemAction(_EditAction): + def lastUserActivity(self): + if self.form.has_key(':lastactivity'): + return date.Date(self.form[':lastactivity'].value) + elif self.form.has_key('@lastactivity'): + return date.Date(self.form['@lastactivity'].value) + else: + return None + + def lastNodeActivity(self): + cl = getattr(self.client.db, self.classname) + return cl.get(self.nodeid, 'activity') + + def detectCollision(self, userActivity, nodeActivity): + # Result from lastUserActivity may be None. If it is, assume there's no + # conflict, or at least not one we can detect. + if userActivity: + return userActivity < nodeActivity + + def handleCollision(self): + self.client.template = 'collision' + def handle(self): """Perform an edit of an item in the database. See parsePropsFromForm and _editnodes for special variables. """ + if self.detectCollision(self.lastUserActivity(), self.lastNodeActivity()): + self.handleCollision() + return + props, links = self.client.parsePropsFromForm() # handle the props
--- a/roundup/cgi/templating.py Sat Feb 14 01:55:35 2004 +0000 +++ b/roundup/cgi/templating.py Sat Feb 14 02:06:27 2004 +0000 @@ -612,14 +612,17 @@ raise AttributeError, attr def designator(self): - ''' Return this item's designator (classname + id) ''' + """Return this item's designator (classname + id).""" return '%s%s'%(self._classname, self._nodeid) def submit(self, label="Submit Changes"): - ''' Generate a submit button (and action hidden element) - ''' - return self.input(type="hidden",name="@action",value="edit") + '\n' + \ - self.input(type="submit",name="submit",value=label) + """Generate a submit button. + + Also sneak in the lastactivity and action hidden elements. + """ + return self.input(type="hidden", name="@lastactivity", value=date.Date('.')) + '\n' + \ + self.input(type="hidden", name="@action", value="edit") + '\n' + \ + self.input(type="submit", name="submit", value=label) def journal(self, direction='descending'): ''' Return a list of HTMLJournalEntry instances.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/classic/html/_generic.collision.html Sat Feb 14 02:06:27 2004 +0000 @@ -0,0 +1,11 @@ +<tal:block metal:use-macro="templates/page/macros/icing"> + <title metal:fill-slot="head_title" + tal:content="python:context._classname.capitalize()+' Edit Collision'"></title> + <span metal:fill-slot="body_title" tal:omit-tag="python:1" + tal:content="python:context._classname.capitalize()+' Edit Collision'"></span> + <td class="content" metal:fill-slot="content"> + There has been a collision. Another user updated this node while you were + editing. Please <a tal:attributes="href context/designator">reload</a> + the node and review your edits. + </td> +</tal:block> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/minimal/html/_generic.collision.html Sat Feb 14 02:06:27 2004 +0000 @@ -0,0 +1,11 @@ +<tal:block metal:use-macro="templates/page/macros/icing"> + <title metal:fill-slot="head_title" + tal:content="python:context._classname.capitalize()+' Edit Collision'"></title> + <span metal:fill-slot="body_title" tal:omit-tag="python:1" + tal:content="python:context._classname.capitalize()+' Edit Collision'"></span> + <td class="content" metal:fill-slot="content"> + There has been a collision. Another user updated this node while you were + editing. Please <a tal:attributes="href context/designator">reload</a> + the node and review your edits. + </td> +</tal:block> \ No newline at end of file
--- a/test/test_actions.py Sat Feb 14 01:55:35 2004 +0000 +++ b/test/test_actions.py Sat Feb 14 02:06:27 2004 +0000 @@ -1,7 +1,10 @@ +from __future__ import nested_scopes + import unittest from cgi import FieldStorage, MiniFieldStorage from roundup import hyperdb +from roundup.date import Date, Interval from roundup.cgi.actions import * from roundup.cgi.exceptions import Redirect, Unauthorised @@ -130,13 +133,43 @@ # The single value gets replaced with the tokenized list. self.assertEqual([x.value for x in self.form['foo']], ['hello', 'world']) + +class CollisionDetectionTestCase(ActionTestCase): + def setUp(self): + ActionTestCase.setUp(self) + self.action = EditItemAction(self.client) + self.now = Date('.') + + def testLastUserActivity(self): + self.assertEqual(self.action.lastUserActivity(), None) + + self.client.form.value.append(MiniFieldStorage('@lastactivity', str(self.now))) + self.assertEqual(self.action.lastUserActivity(), self.now) + + def testLastNodeActivity(self): + self.action.classname = 'issue' + self.action.nodeid = '1' + + def get(nodeid, propname): + self.assertEqual(nodeid, '1') + self.assertEqual(propname, 'activity') + return self.now + self.client.db.issue.get = get + + self.assertEqual(self.action.lastNodeActivity(), self.now) + + def testCollision(self): + self.failUnless(self.action.detectCollision(self.now, self.now + Interval("1d"))) + self.failIf(self.action.detectCollision(self.now, self.now - Interval("1d"))) + self.failIf(self.action.detectCollision(None, self.now)) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(RetireActionTestCase)) suite.addTest(unittest.makeSuite(StandardSearchActionTestCase)) suite.addTest(unittest.makeSuite(FakeFilterVarsTestCase)) - suite.addTest(unittest.makeSuite(ShowActionTestCase)) + suite.addTest(unittest.makeSuite(ShowActionTestCase)) + suite.addTest(unittest.makeSuite(CollisionDetectionTestCase)) return suite if __name__ == '__main__':
