References¶
Objects that live in the Substance D resource tree can be related to one another using references.
The most user-visible facet of references is the SDI “References” tab, which is presented to SDI admin users when the object they’re looking at is involved in a reference relation. For example, you’ll notice that the built-in user and group implementations already have references to each other, and you can visit their References tabs to see them. Likewise, when you use the Security tab to change the ACL associated with an object, and include in the ACL a user or group that lives in the principals folder, a relation is formed between the ACL-bearing object and the principal. So, as you can see, references aren’t just for application developers; Substance D itself uses references under the hood to do its job too.
A reference has a type and a direction. A reference is formed using methods of the object map.
from substanced.interfaces import ReferenceType
from substanced.objectmap import find_objectmap
class ContextToRoot(ReferenceType):
pass
def connect_reference(context, request):
objectmap = find_objectmap(context)
root = request.root
objectmap.connect(context, root, ContextToRoot)
A reference type is a class (not an instance) that inherits from
substanced.interfaces.ReferenceType
. The reference’s name should
indicate its directionality.
Warning
One caveat: reference types are pickled, so if you move a reference type
from one location to another, you’ll have to leave behind a backwards
compatibility import in its original location “forever”, so choose its name
and location wisely. We recommend that you place it in an interfaces.py
file in your project.
A reference can be removed using the object map too:
from substanced.interfaces import ReferenceType
from substanced.objectmap import find_objectmap
class ContextToRoot(ReferenceType):
pass
def disconnect_reference(context, request):
objectmap = find_objectmap(context)
root = request.root
objectmap.disconnect(context, root, ContextToRoot)
The first two arguments to connect()
or
disconnect()
are source and target.
These can be either resource objects or oids. The third argument to these
functions is the reference type.
Once a reference is formed between two objects, you can see the reference within the “References” tab in the SDI. The References tab of either side of the reference (in the above example, either the root or the context) when visited in the SDI will display the reference to the other side.
Once a reference is made between two objects, the object map can be queried for objects which take part in the reference.
from substanced.interfaces import ReferenceType
from substanced.objectmap import find_objectmap
class ContextToRoot(ReferenceType):
pass
def query_reference_sources(context, request):
objectmap = find_objectmap(context)
return objectmap.sourceids(request.root, ContextToRoot)
def query_reference_targets(context, request):
objectmap = find_objectmap(context)
return objectmap.targetids(context, ContextToRoot)
The sourceids()
method returns the set of
objectids which are sources of the object and reference type it’s passed.
The targetids()
method returns the set of
objectids which are targets of the object and reference type it’s passed. If
no objects are involved in the relation, an empty set will be returned in
either case. sources()
and
targets()
methods also exist which are
analogous, but return the actual objects involved in the relation instead of the
objectids:
from substanced.interfaces import ReferenceType
from substanced.objectmap import find_objectmap
class ContextToRoot(ReferenceType):
pass
def query_reference_sources(context, request):
objectmap = find_objectmap(context)
return objectmap.sources(request.root, ContextToRoot)
def query_reference_targets(context, request):
objectmap = find_objectmap(context)
return objectmap.targets(context, ContextToRoot)
A reference type can claim that it is “integral”, which just means that the deletion of either the source or the target of a reference will be prevented. Here’s an example of a “source integral” reference type:
from substanced.interfaces import ReferenceType
class UserToGroup(ReferenceType):
source_integrity = True
This reference type will prevent any object on the “user” side of the
UserToGroup reference (as opposed to the group side) from being deleted. When
a user attempts to delete a user that’s related to a group using this reference
type, a substanced.objectmap.SourceIntegrityError
will be raised and
the deletion will be prevented. Only when the reference is removed or the
group is deleted will the user deletion be permitted.
The flip side of this is target integrity:
from substanced.interfaces import ReferenceType
class UserToGroup(ReferenceType):
target_integrity = True
This is the inverse. The reference will prevent any object on the “group” side
of the UserToGroup reference from being deleted unless the associated user is
first removed or the reference itself is no longer active. When a user
attempts to delete a user that’s related to a group using this reference type,
a substanced.objectmap.TargetIntegrityError
will be raised and the
deletion will be prevented.
substanced.objectmap.SourceIntegrityError
and
substanced.objectmap.TargetIntegrityError
both inherit from
substanced.objectmap.ReferentialIntegrityError
, so you can catch
either in your code.
There are convenience functions that you can add to your resource objects that
give them special behavior:
reference_sourceid_property()
,
reference_targetid_property()
,
reference_source_property()
,
reference_target_property()
,
multireference_sourceid_property()
,
multireference_targetid_property()
,
reference_source_property()
, and
reference_target_property()
.
Here’s use of a reference property:
1 2 3 4 5 6 7 8 9 | from persistent import Persistent
from substanced.objectmap import reference_sourceid_property
from substanced.interfaces import ReferenceType
class LineItemToOrder(ReferenceType):
pass
class LineItem(Persistent):
order = reference_target_property(LineItemToOrder)
|
Once you’ve seated a resource object in a folder, you can then begin to use its special properties:
1 2 3 4 5 | from mysystem import LineItem, Order
lineitem = LineItem()
folder['lineitem'] = lineitem
lineitem.order = Order()
|
This is just a nicer way to use the objectmap query API; you don’t have to
interact with it at all, just assign and ask for attributes of your object.
The multireference_*
variants are similar to the reference variants, but
they allow for more than one object on the “other side”.
ACLs and Principal References¶
When an ACL is modified on a resource, a statement is being made about a relationship between that resource and a principal or group of principals. Wouldn’t it be great if a reference was established, allowing you to then see such connections in the SDI?
This is indeed exactly how Substance D behaves: a source-integral PrincipalToACLBearing reference is set up between an ACL-bearing resource and the principals referred to within the ACL.