[gen] First draft of a system for locking pages when editing it.
This commit is contained in:
parent
27197f5b9d
commit
bdf41adf36
|
@ -98,6 +98,8 @@ class BaseMixin:
|
||||||
if not created:
|
if not created:
|
||||||
from DateTime import DateTime
|
from DateTime import DateTime
|
||||||
obj.modified = DateTime()
|
obj.modified = DateTime()
|
||||||
|
# Unlock the currently saved page on the object
|
||||||
|
if rq: self.removeLock(rq['page'])
|
||||||
obj.reindex()
|
obj.reindex()
|
||||||
return obj, msg
|
return obj, msg
|
||||||
|
|
||||||
|
@ -188,6 +190,53 @@ class BaseMixin:
|
||||||
obj = createObject(tool.getPath('/temp_folder'), id, className, appName)
|
obj = createObject(tool.getPath('/temp_folder'), id, className, appName)
|
||||||
return self.goto(obj.getUrl(**urlParams))
|
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):
|
def onCreateWithoutForm(self):
|
||||||
'''This method is called when a user wants to create a object from a
|
'''This method is called when a user wants to create a object from a
|
||||||
reference field, automatically (without displaying a form).'''
|
reference field, automatically (without displaying a form).'''
|
||||||
|
@ -263,6 +312,7 @@ class BaseMixin:
|
||||||
else:
|
else:
|
||||||
urlBack = self.getUrl()
|
urlBack = self.getUrl()
|
||||||
self.say(self.translate('object_canceled'))
|
self.say(self.translate('object_canceled'))
|
||||||
|
self.removeLock(rq['page'])
|
||||||
return self.goto(urlBack)
|
return self.goto(urlBack)
|
||||||
|
|
||||||
# Object for storing validation errors
|
# Object for storing validation errors
|
||||||
|
|
|
@ -201,6 +201,7 @@ appyLabels = [
|
||||||
('changes_show', 'Show changes'),
|
('changes_show', 'Show changes'),
|
||||||
('changes_hide', 'Hide changes'),
|
('changes_hide', 'Hide changes'),
|
||||||
('anonymous', 'an anonymous user'),
|
('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
|
# Some default values for labels whose ids are not fixed (so they can't be
|
||||||
|
|
BIN
gen/ui/edit.gif
BIN
gen/ui/edit.gif
Binary file not shown.
Before Width: | Height: | Size: 470 B |
BIN
gen/ui/edit.png
Normal file
BIN
gen/ui/edit.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 424 B |
|
@ -8,6 +8,7 @@
|
||||||
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType=layoutType);
|
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType=layoutType);
|
||||||
phase phaseInfo/name;
|
phase phaseInfo/name;
|
||||||
page request/page|python:contextObj.getDefaultEditPage();
|
page request/page|python:contextObj.getDefaultEditPage();
|
||||||
|
dummy python: contextObj.setLock(user, page);
|
||||||
confirmMsg request/confirmMsg | nothing;
|
confirmMsg request/confirmMsg | nothing;
|
||||||
groupedWidgets python: contextObj.getGroupedAppyTypes(layoutType, page, cssJs=cssJs);"
|
groupedWidgets python: contextObj.getGroupedAppyTypes(layoutType, page, cssJs=cssJs);"
|
||||||
tal:on-error="structure python: tool.manageError(error)">
|
tal:on-error="structure python: tool.manageError(error)">
|
||||||
|
|
BIN
gen/ui/locked.png
Normal file
BIN
gen/ui/locked.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 648 B |
|
@ -115,7 +115,8 @@
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
<table metal:define-macro="phases" class="portletContent"
|
<table metal:define-macro="phases" class="portletContent"
|
||||||
tal:define="singlePhase python: phases and (len(phases) == 1);
|
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:phase repeat="phase phases">
|
||||||
<tal:comment replace="nothing">The box containing phase-related information</tal:comment>
|
<tal:comment replace="nothing">The box containing phase-related information</tal:comment>
|
||||||
<tr tal:define="singlePage python: len(phase['pages']) == 1">
|
<tr tal:define="singlePage python: len(phase['pages']) == 1">
|
||||||
|
@ -132,10 +133,19 @@
|
||||||
<a tal:attributes="href python: contextObj.getUrl(page=aPage)"
|
<a tal:attributes="href python: contextObj.getUrl(page=aPage)"
|
||||||
tal:content="structure python: _('%s_page_%s' % (contextObj.meta_type, aPage))">
|
tal:content="structure python: _('%s_page_%s' % (contextObj.meta_type, aPage))">
|
||||||
</a>
|
</a>
|
||||||
<a tal:condition="python: contextObj.allows('Modify portal content') and phase['pagesInfo'][aPage]['showOnEdit']"
|
<tal:icons define="locked python: contextObj.isLocked(user, aPage);
|
||||||
tal:attributes="href python: contextObj.getUrl(mode='edit', page=aPage)">
|
editable python: mayEdit and phase['pagesInfo'][aPage]['showOnEdit']">
|
||||||
<img title="Edit" tal:attributes="src string: $appUrl/ui/edit.gif"/>
|
<a tal:condition="python: editable and not locked"
|
||||||
</a>
|
tal:attributes="href python: contextObj.getUrl(mode='edit', page=aPage)">
|
||||||
|
<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>
|
</div>
|
||||||
<tal:comment replace="nothing">Next lines: links</tal:comment>
|
<tal:comment replace="nothing">Next lines: links</tal:comment>
|
||||||
<tal:links define="links python: phase['pagesInfo'][aPage].get('links')" tal:condition="links">
|
<tal:links define="links python: phase['pagesInfo'][aPage].get('links')" tal:condition="links">
|
||||||
|
|
|
@ -95,7 +95,7 @@
|
||||||
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (className, searchName, repeat['obj'].number()+startNumber, totalNumber);"
|
<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:attributes="href python: obj.getUrl(mode='edit', page=obj.getDefaultEditPage(), nav=navInfo)"
|
||||||
tal:condition="obj/mayEdit">
|
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
|
title python: _('object_edit')"/></a><img
|
||||||
tal:condition="obj/mayDelete" style="cursor:pointer"
|
tal:condition="obj/mayDelete" style="cursor:pointer"
|
||||||
tal:attributes="src string: $appUrl/ui/delete.png;
|
tal:attributes="src string: $appUrl/ui/delete.png;
|
||||||
|
|
|
@ -175,7 +175,7 @@
|
||||||
<span tal:content="python: userInfo[0]"></span>
|
<span tal:content="python: userInfo[0]"></span>
|
||||||
<a tal:condition="python: userInfo[1]"
|
<a tal:condition="python: userInfo[1]"
|
||||||
tal:attributes="href 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>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
phase phaseInfo/name;
|
phase phaseInfo/name;
|
||||||
cssJs python: {};
|
cssJs python: {};
|
||||||
page req/page|python:contextObj.getDefaultViewPage();
|
page req/page|python:contextObj.getDefaultViewPage();
|
||||||
|
dummy python: contextObj.removeMyLock(user, page);
|
||||||
groupedWidgets python: contextObj.getGroupedAppyTypes(layoutType, page, cssJs=cssJs);"
|
groupedWidgets python: contextObj.getGroupedAppyTypes(layoutType, page, cssJs=cssJs);"
|
||||||
tal:on-error="structure python: tool.manageError(error)">
|
tal:on-error="structure python: tool.manageError(error)">
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<td tal:condition="python: not appyType['noForm'] and obj.mayEdit() and appyType['delete']">
|
<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);"
|
<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)">
|
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')"/>
|
title python: _('object_edit')"/>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|
Loading…
Reference in a new issue