Using Locking¶
Substance D allows you to lock content resources programmatically. When a resource is locked, its UI can change to indicate that it cannot be edited by someone other than the user holding the lock.
Locking a resource only locks the resource, not its children. The locking system is not recursive at this time.
Locking a Resource¶
To lock a resource:
from substanced.locking import lock_resource
from pyramid.security import has_permission
if has_permission('sdi.lock', someresource, request):
lock_resource(someresource, request.user, timeout=3600)
If the resource is already locked by the owner supplied as owner_or_ownerid
(the parameter filled by request.user
above), calling this function will
refresh the lock. If the resource is not already locked by another user,
calling this function will create a new lock. If the resource is already
locked by a different user, a substanced.locking.LockError
will be
raised.
Using the substanced.locking.lock_resource()
function has the side effect
of creating a “Lock Service” (named locks
) in the Substance D root if one
does not already exist.
Warning
Callers should assert that the owner has the sdi.lock
permission against
the resource before calling lock_resource()
to
ensure that a user can’t lock a resource he is not permitted to.
Unlocking a Resource¶
To unlock a resource:
from substanced.locking import unlock_resource
from pyramid.security import has_permission
if has_permission('sdi.lock', someresource, request):
unlock_resource(someresource, request.user)
If the resource is already locked by a user other than the owner supplied as
owner_or_ownerid
(the parameter filled by request.user
above) or the
resource isn’t already locked with this lock type, calling this function will
raise a substanced.locking.UnlockError
exception. Otherwise the lock
will be removed.
Using the substanced.locking.unlock_resource()
function has the side
effect of creating a “Lock Service” (named locks
) in the Substance D root
if one does not already exist.
Warning
Callers should assert that the owner has the sdi.lock
permission against
the resource before calling unlock_resource()
to
ensure that a user can’t lock a resource he is not permitted to.
To unlock a resource using an explicit lock token:
from substanced.locking import unlock_token
from pyramid.security import has_permission
if has_permission('sdi.lock', someresource, request):
unlock_token(someresource, token, request.user)
If the lock identified by token
belongs to a user other than the owner
supplied as owner_or_ownerid
(the parameter filled by request.user
above) or if no lock exists under token
, calling this function will
raise a substanced.locking.LockError
exception. Otherwise the lock
will be removed.
Using the substanced.locking.unlock_token()
function has the side
effect of creating a “Lock Service” (named locks
) in the Substance D root
if one does not already exist.
Warning
Callers should assert that the owner has the sdi.lock
permission against
the resource before calling unlock_token()
to
ensure that a user can’t lock a resource he is not permitted to.
Discovering Existing Locks¶
To discover any existing locks for a resource:
from substanced.locking import discover_resource_locks
locks = discover_resource_locks(someresource)
# "locks" will be a sequence
The substanced.locking.discover_resource_locks()
function will return a
sequence of substanced.locking.Lock
objects related to the resource
for the lock type provided to the function. By default, only valid locks are
returned. Invalid locks for the resource may exist, but they are not returned
unless the include_invalid
argument passed to
:discover_resource_locks()
is True
.
Under normal circumstances, the length of the sequence returned will be either
0 (if there are no locks) or 1 (if there is any lock). In some special
circumstances, however, when the substanced.locking.lock_resource()
API
is not used to create locks, there may be more than one lock related to a
resource of the same type.
By default, the discover_resource_locks
API returns locks for the
provided object, plus locks on any object in its lineage. To suppress
this default, pass include_lineage=False
, e.g.:
locks = discover_resource_locks(someresource)
# "locks" will be only those set on 'someresource'
In some applications, the important thing is to ensure that a particular
user could lock a resource before updating it (e.g., from a browser view
on a property sheet). The :could_lock_resource()
API is designed for these cases: if the supplied userid could not lock the
resource, it raises a substanced.locking.LockError
exception:
from substanced.locking import could_lock_resource, LockError
try:
could_lock_resource(someresource, request.user)
except LockError as e:
raise FormError('locked by "%s"' % e.lock.owner.__name__)
Viewing The Lock Service¶
Once some locks have been created, a lock service will have been created.
The lock service is an object named locks
in the Substance D root.
You can use the SDI UI of this locks service to delete and edit existing locks. It’s a good idea to periodically use the “Delete Expired” button in this UI to clear out any existing expired locks that were orphaned by buggy or interrupted clients.