From bdf41adf362a17c82fa88cd506cac0834e384d3b Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Mon, 14 Jan 2013 16:58:30 +0100 Subject: [PATCH] [gen] First draft of a system for locking pages when editing it. --- gen/mixins/__init__.py | 50 +++++++++++++++++++++++++++++++++++++++++ gen/po.py | 1 + gen/ui/edit.gif | Bin 470 -> 0 bytes gen/ui/edit.png | Bin 0 -> 424 bytes gen/ui/edit.pt | 1 + gen/ui/locked.png | Bin 0 -> 648 bytes gen/ui/portlet.pt | 20 ++++++++++++----- gen/ui/result.pt | 2 +- gen/ui/template.pt | 2 +- gen/ui/view.pt | 1 + gen/ui/widgets/ref.pt | 2 +- 11 files changed, 71 insertions(+), 8 deletions(-) delete mode 100644 gen/ui/edit.gif create mode 100644 gen/ui/edit.png create mode 100644 gen/ui/locked.png diff --git a/gen/mixins/__init__.py b/gen/mixins/__init__.py index 1f20560..7061dab 100644 --- a/gen/mixins/__init__.py +++ b/gen/mixins/__init__.py @@ -98,6 +98,8 @@ class BaseMixin: if not created: from DateTime import DateTime obj.modified = DateTime() + # Unlock the currently saved page on the object + if rq: self.removeLock(rq['page']) obj.reindex() return obj, msg @@ -188,6 +190,53 @@ class BaseMixin: obj = createObject(tool.getPath('/temp_folder'), id, className, appName) return self.goto(obj.getUrl(**urlParams)) + def setLock(self, user, page): + '''A p_user edits a given p_page on this object: we will set a lock, to + prevent other users to edit this page at the same time.''' + if not hasattr(self.aq_base, 'locks'): + # Create the persistent mapping that will store the lock + # ~{s_page: s_userId}~ + from persistent.mapping import PersistentMapping + self.locks = PersistentMapping() + # Raise an error is the page is already locked by someone else. If the + # page is already locked by the same user, we don't mind: he could have + # used back/forward buttons of its browser... + userId = user.getId() + if (page in self.locks) and (userId != self.locks[page]): + from AccessControl import Unauthorized + raise Unauthorized('This page is locked.') + # Set the lock + self.locks[page] = userId + + def isLocked(self, user, page): + '''Is this page locked? If the page is locked by the same user, we don't + mind and consider the page as unlocked. If the page is locked, this + method returns the id of the user that has locked the page.''' + if hasattr(self.aq_base, 'locks') and (page in self.locks): + if (user.getId() != self.locks[page]): return self.locks[page] + + def removeLock(self, page): + '''Removes the lock on the current page. This happens after the page has + been saved: the lock must be released.''' + if page not in self.locks: return + # Raise an error if the user that saves changes is not the one that + # has locked the page. + userId = self.getUser().getId() + if self.locks[page] != userId: + from AccessControl import Unauthorized + raise Unauthorized('This page was locked by someone else.') + # Remove the lock + del self.locks[page] + + def removeMyLock(self, user, page): + '''If p_user has set a lock on p_page, this method removes it. This + method is called when the user that locked a page consults + view.pt for this page. In this case, we consider that the user has + left the edit page in an unexpected way and we remove the lock.''' + if hasattr(self.aq_base, 'locks') and (page in self.locks) and \ + (user.getId() == self.locks[page]): + del self.locks[page] + def onCreateWithoutForm(self): '''This method is called when a user wants to create a object from a reference field, automatically (without displaying a form).''' @@ -263,6 +312,7 @@ class BaseMixin: else: urlBack = self.getUrl() self.say(self.translate('object_canceled')) + self.removeLock(rq['page']) return self.goto(urlBack) # Object for storing validation errors diff --git a/gen/po.py b/gen/po.py index 83b3308..fc21943 100644 --- a/gen/po.py +++ b/gen/po.py @@ -201,6 +201,7 @@ appyLabels = [ ('changes_show', 'Show changes'), ('changes_hide', 'Hide changes'), ('anonymous', 'an anonymous user'), + ('page_locked', 'This page is locked by ${user}.'), ] # Some default values for labels whose ids are not fixed (so they can't be diff --git a/gen/ui/edit.gif b/gen/ui/edit.gif deleted file mode 100644 index 5fd9cfa657a001ee68921d1554819b9497d91fde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 470 zcmd6kJxc;%0D#}ruqDW$9}qM}&>%*kG(}PnZPDP;Qd>0y4Gs2jew_q0l$k@h6x2{_ z;Z_Kd(vXQ}p4Oe`-FaW$-FxbB*Jb~r=NCK=k9T%vVsg1341lx)Kqx2*_n7E2GtSHk z$ful=#QvCmRh4qNXM!k-0yVc0!?1jVs*-p~*E^JWEWy(rU?~$S8^! z@og(I$JDDFi@Q9FJVyWkp3sLZlF~Gls+D!Uz2Nqe#K%o!OjX4~A*;xIgQPx#>QGEnMfLUX^z`k+n+X5RGt6cH*1R+F^6dWqfA;qB)YQr3*M(N_5J<&0Ga>w_4D)b?Dh5Xp?{ZjXKwH8 z=KA{e=;hq<^6%~HfrG3>34*R=j7Y;s_Cb1pyJ=t@9yU7>EY$$+3Duqp_zo!Y|b(O0004WQchC diff --git a/gen/ui/locked.png b/gen/ui/locked.png new file mode 100644 index 0000000000000000000000000000000000000000..8b81e7ff284100752e155dff383c18bd00107eee GIT binary patch literal 648 zcmV;30(bq1P)WdKHUATcr^L}hv)GB7YRATlyKF)%tYH6SZ6F)%P+<{wS~000McNliru z(*hb477vONgHQkf010qNS#tmY3h)2`3h)6!tTdPa000DMK}|sb0I`n?{9y$E00H1h zL_t(|+NDy@YZE~f{$`WrhuKPySQAA=4|-5UL@Ysj^nd8hiS;2Kdj#HUllo z8f~>&*KFH9Nwz?Ckui3oR;%3`NI(gPUDtho|G}f2_3e8bT8ASerBbE5)1bTYdcFQ| zZM?C8k+I47`6u~>51*b--wCz*ER>uRr zeV-UkHLH%}72i$a+1i|RAKlWyIlu9^60fuoN4rrzunmYfG3Rj9y^HEzZv5(CEO81y zUYkzkSk-KQ`3%0SF=Q~vI7Aru=z0O7P{Z@#ja`PhX$8v2D-^Gzc;YIGcnR19E(0MI z;kZD@0aiO(XrN-PsAlqHZzKK#l1tJ_)zheV5(%VKYS5UK?$7C;0+>qp-G76P-YrWc z5ZrIlD9FnLDKc3)8S0<dA!cTgY+CR4-a*;u;!NrNF3LWTlP5a1_; iES|Z7@j-3=)A|j?vD&^)Yn&Va0000 + page python: req.get('page', 'main'); + mayEdit python: contextObj.allows('Modify portal content')"> The box containing phase-related information @@ -132,10 +133,19 @@ - - - + + + + + + + + Next lines: links diff --git a/gen/ui/result.pt b/gen/ui/result.pt index 496fceb..278db68 100644 --- a/gen/ui/result.pt +++ b/gen/ui/result.pt @@ -95,7 +95,7 @@ - - + diff --git a/gen/ui/view.pt b/gen/ui/view.pt index 0e3d108..d0e5695 100644 --- a/gen/ui/view.pt +++ b/gen/ui/view.pt @@ -8,6 +8,7 @@ phase phaseInfo/name; cssJs python: {}; page req/page|python:contextObj.getDefaultViewPage(); + dummy python: contextObj.removeMyLock(user, page); groupedWidgets python: contextObj.getGroupedAppyTypes(layoutType, page, cssJs=cssJs);" tal:on-error="structure python: tool.manageError(error)"> diff --git a/gen/ui/widgets/ref.pt b/gen/ui/widgets/ref.pt index 739dd54..e8434e8 100644 --- a/gen/ui/widgets/ref.pt +++ b/gen/ui/widgets/ref.pt @@ -27,7 +27,7 @@
-