[gen] First draft of a system for locking pages when editing it.

This commit is contained in:
Gaetan Delannay 2013-01-14 16:58:30 +01:00
parent 27197f5b9d
commit bdf41adf36
11 changed files with 71 additions and 8 deletions

View file

@ -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

View file

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 B

BIN
gen/ui/edit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

View file

@ -8,6 +8,7 @@
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType=layoutType);
phase phaseInfo/name;
page request/page|python:contextObj.getDefaultEditPage();
dummy python: contextObj.setLock(user, page);
confirmMsg request/confirmMsg | nothing;
groupedWidgets python: contextObj.getGroupedAppyTypes(layoutType, page, cssJs=cssJs);"
tal:on-error="structure python: tool.manageError(error)">

BIN
gen/ui/locked.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

View file

@ -115,7 +115,8 @@
</tal:comment>
<table metal:define-macro="phases" class="portletContent"
tal:define="singlePhase python: phases and (len(phases) == 1);
page python: req.get('page', 'main')">
page python: req.get('page', 'main');
mayEdit python: contextObj.allows('Modify portal content')">
<tal:phase repeat="phase phases">
<tal:comment replace="nothing">The box containing phase-related information</tal:comment>
<tr tal:define="singlePage python: len(phase['pages']) == 1">
@ -132,10 +133,19 @@
<a tal:attributes="href python: contextObj.getUrl(page=aPage)"
tal:content="structure python: _('%s_page_%s' % (contextObj.meta_type, aPage))">
</a>
<a tal:condition="python: contextObj.allows('Modify portal content') and phase['pagesInfo'][aPage]['showOnEdit']"
<tal:icons define="locked python: contextObj.isLocked(user, aPage);
editable python: mayEdit and phase['pagesInfo'][aPage]['showOnEdit']">
<a tal:condition="python: editable and not locked"
tal:attributes="href python: contextObj.getUrl(mode='edit', page=aPage)">
<img title="Edit" tal:attributes="src string: $appUrl/ui/edit.gif"/>
<img tal:attributes="src string: $appUrl/ui/edit.png;
title python: _('object_edit')"/>
</a>
<a tal:condition="python: editable and locked">
<img style="cursor: help"
tal:attributes="src string: $appUrl/ui/locked.png;
title python: _('page_locked', mapping={'user':tool.getUserName(locked)})"/>
</a>
</tal:icons>
</div>
<tal:comment replace="nothing">Next lines: links</tal:comment>
<tal:links define="links python: phase['pagesInfo'][aPage].get('links')" tal:condition="links">

View file

@ -95,7 +95,7 @@
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (className, searchName, repeat['obj'].number()+startNumber, totalNumber);"
tal:attributes="href python: obj.getUrl(mode='edit', page=obj.getDefaultEditPage(), nav=navInfo)"
tal:condition="obj/mayEdit">
<img tal:attributes="src string: $appUrl/ui/edit.gif;
<img tal:attributes="src string: $appUrl/ui/edit.png;
title python: _('object_edit')"/></a><img
tal:condition="obj/mayDelete" style="cursor:pointer"
tal:attributes="src string: $appUrl/ui/delete.png;

View file

@ -175,7 +175,7 @@
<span tal:content="python: userInfo[0]"></span>
<a tal:condition="python: userInfo[1]"
tal:attributes="href python: userInfo[1]">
<img tal:attributes="src string: $appUrl/ui/edit.gif"/>
<img tal:attributes="src string: $appUrl/ui/edit.png"/>
</a>
</td>
</tr>

View file

@ -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)">

View file

@ -27,7 +27,7 @@
<td tal:condition="python: not appyType['noForm'] and obj.mayEdit() and appyType['delete']">
<a tal:define="navInfo python:'ref.%s.%s:%s.%d.%d' % (contextObj.UID(), fieldName, appyType['pageName'], repeat['obj'].number()+startNumber, totalNumber);"
tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)">
<img tal:attributes="src string: $appUrl/ui/edit.gif;
<img tal:attributes="src string: $appUrl/ui/edit.png;
title python: _('object_edit')"/>
</a>
</td>