summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 2ccfe94)
raw | patch | inline | side by side (parent: 2ccfe94)
author | jlgijsbers <jlgijsbers@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Sat, 14 Feb 2004 02:06:27 +0000 (02:06 +0000) | ||
committer | jlgijsbers <jlgijsbers@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Sat, 14 Feb 2004 02:06:27 +0000 (02:06 +0000) |
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@2082 57a73879-2fb5-44c3-a270-3262357dd7e2
CHANGES.txt | patch | blob | history | |
roundup/cgi/actions.py | patch | blob | history | |
roundup/cgi/templating.py | patch | blob | history | |
templates/classic/html/_generic.collision.html | [new file with mode: 0644] | patch | blob |
templates/minimal/html/_generic.collision.html | [new file with mode: 0644] | patch | blob |
test/test_actions.py | patch | blob | history |
diff --git a/CHANGES.txt b/CHANGES.txt
index 9631b2898e5f1ed74546ee8f01dc53f90ada126a..233b45dfb11d5ecfbf67b9d62fb51bee216eb24e 100644 (file)
--- a/CHANGES.txt
+++ b/CHANGES.txt
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
diff --git a/roundup/cgi/actions.py b/roundup/cgi/actions.py
index de4143d4fa9b1ff07d39f1a487ac3420768ff480..28c97883165a440f3561e8115bc78f2e82788fd1 100755 (executable)
--- a/roundup/cgi/actions.py
+++ b/roundup/cgi/actions.py
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
index dc1530951eb343f35c932b4a84b14cfb5a9682c0..a2cc1843634de3ccc02392268dd0d7583fdab3a7 100644 (file)
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.
diff --git a/templates/classic/html/_generic.collision.html b/templates/classic/html/_generic.collision.html
--- /dev/null
@@ -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
diff --git a/templates/minimal/html/_generic.collision.html b/templates/minimal/html/_generic.collision.html
--- /dev/null
@@ -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
diff --git a/test/test_actions.py b/test/test_actions.py
index 10cd7334e26e44f3bf484eb55b02e00ddbb042bb..2c807c125a332606d0d5025e6420490d0f991154 100755 (executable)
--- a/test/test_actions.py
+++ b/test/test_actions.py
+from __future__ import nested_scopes\r
+\r
import unittest\r
from cgi import FieldStorage, MiniFieldStorage\r
\r
from roundup import hyperdb\r
+from roundup.date import Date, Interval\r
from roundup.cgi.actions import *\r
from roundup.cgi.exceptions import Redirect, Unauthorised\r
\r
\r
# The single value gets replaced with the tokenized list.\r
self.assertEqual([x.value for x in self.form['foo']], ['hello', 'world'])\r
+\r
+class CollisionDetectionTestCase(ActionTestCase):\r
+ def setUp(self):\r
+ ActionTestCase.setUp(self)\r
+ self.action = EditItemAction(self.client)\r
+ self.now = Date('.')\r
+ \r
+ def testLastUserActivity(self):\r
+ self.assertEqual(self.action.lastUserActivity(), None)\r
+\r
+ self.client.form.value.append(MiniFieldStorage('@lastactivity', str(self.now))) \r
+ self.assertEqual(self.action.lastUserActivity(), self.now)\r
+\r
+ def testLastNodeActivity(self):\r
+ self.action.classname = 'issue'\r
+ self.action.nodeid = '1'\r
+\r
+ def get(nodeid, propname):\r
+ self.assertEqual(nodeid, '1')\r
+ self.assertEqual(propname, 'activity')\r
+ return self.now\r
+ self.client.db.issue.get = get\r
+\r
+ self.assertEqual(self.action.lastNodeActivity(), self.now)\r
+\r
+ def testCollision(self):\r
+ self.failUnless(self.action.detectCollision(self.now, self.now + Interval("1d")))\r
+ self.failIf(self.action.detectCollision(self.now, self.now - Interval("1d")))\r
+ self.failIf(self.action.detectCollision(None, self.now)) \r
\r
def test_suite():\r
suite = unittest.TestSuite()\r
suite.addTest(unittest.makeSuite(RetireActionTestCase))\r
suite.addTest(unittest.makeSuite(StandardSearchActionTestCase))\r
suite.addTest(unittest.makeSuite(FakeFilterVarsTestCase))\r
- suite.addTest(unittest.makeSuite(ShowActionTestCase)) \r
+ suite.addTest(unittest.makeSuite(ShowActionTestCase))\r
+ suite.addTest(unittest.makeSuite(CollisionDetectionTestCase))\r
return suite\r
\r
if __name__ == '__main__':\r