Edit me on GitHub

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.