Package: zc.shortcut
ShortcutsShortcuts are objects that allow other objects (theirtarget) to appear to
be located in places other than the target’s actual location.  They are
somewhat like a symbolic link in Unix-like operating systems.Creating a shortcutShortcuts are created by calling theShortcutclass’s constructor with a
target, parent, and name:>>> from zc.shortcut.shortcut import Shortcut
>>> class MyTarget:
...     attr = 'hi'
...     __parent__ = 'Original Parent'
...     __name__ = 'Original Name'
>>> target = MyTarget()
>>> sc = Shortcut(target)
>>> sc.__parent__ = 'My Parent'
>>> sc.__name__ = 'My Name'A shortcut provides an attribute to access its target:>>> sc.target
<__builtin__.MyTarget instance at ...>A shortcut’s __parent__ and __name__ are independent of their target:>>> sc.__parent__
'My Parent'
>>> sc.target.__parent__
'Original Parent'

>>> sc.__name__
'My Name'
>>> sc.target.__name__
'Original Name'But the target knows the traversal parent, the traversal name, and the
shortcut.  This allows the shortcut to have annotations that may be accessed
by views and other components that render or use the target.>>> sc.target.__traversed_parent__
'My Parent'
>>> sc.target.__traversed_name__
'My Name'
>>> sc.target.__shortcut__ is sc
TrueSee proxy.txt and adapters.txt for more detailsShortcut-related proxiesThezc.shortcut.proxymodule includes some code useful outside of
the shortcut package and some code specifically for shortcut usage.The generally useful code includes a decorator class that puts decorator
interfaces before all of the interfaces of the wrapped object (the opposite of
the behavior ofzope.app.decorator.DecoratorSpecificationDescriptor).  It
also includes a specialimplements()function that should be used to declare
that a proxy implements a given set of interfaces.  Using thezope.interface.implements()function instead will causeinterface.directlyProvides()to fail on the proxied object (and will also
have side effects possibly causing other proxies with the same base class to
also be broken.>>> from zope import interface
>>> from zc.shortcut import proxy
>>> class I1(interface.Interface):
...     pass
...
>>> class I2(interface.Interface):
...     pass
...
>>> class I3(interface.Interface):
...     pass
...
>>> class I4(interface.Interface):
...     pass
...
>>> class D1(proxy.Decorator):
...     proxy.implements(I1)
...
>>> class D2(proxy.Decorator):
...     proxy.implements(I2)
...
>>> class X(object):
...     interface.implements(I3)
...
>>> x = X()
>>> [i.getName() for i in interface.providedBy(D1(x))]
['I1', 'I3']
>>> [i.getName() for i in interface.providedBy(D2(D1(x)))]
['I2', 'I1', 'I3']
>>> dec_x = D2(D1(X()))
>>> interface.directlyProvides(dec_x, I4)
>>> [i.getName() for i in interface.providedBy(dec_x)]
['I2', 'I1', 'I4', 'I3']Target proxiesTarget proxies are the primary shortcut-specific proxy type.
When a shortcut is asked for its target it actually returns a proxy:>>> from zc.shortcut.shortcut import Shortcut
>>> class MyTarget:
...     attr = 'hi'
...     __parent__ = 'Original Parent'
...     __name__ = 'Original Name'
>>> target = MyTarget()
>>> sc = Shortcut(target)
>>> sc.__parent__ = 'My Parent'
>>> sc.__name__ = 'My Name'
>>> proxy = sc.target
>>> proxy is target
FalseThe proxy acts as the target:>>> proxy == target
True>>> target.__parent__
'Original Parent'
>>> proxy.__parent__
'Original Parent'>>> target.__name__
'Original Name'
>>> proxy.__name__
'Original Name'>>> target.attr
'hi'
>>> proxy.attr
'hi'The proxy also has attributes point to the shortcut and its parent and
name:>>> proxy.__shortcut__ is sc
True
>>> proxy.__traversed_parent__
'My Parent'
>>> proxy.__traversed_name__
'My Name'As discussed in adapters.txt, once a traversal passes through a shortcut, all
contained objects receive their own target proxies even if they did not
themselves come from a shortcut.  They have__traversed_parent__and__traversed_name__attributes, pointing to the target proxy of the object
traversed to find them and the name used, respectively, but no__shortcut__attribute: they effectively implementinterfaces.ITraversalProxyand notinterfaces.ITargetProxy.Target proxies and the zope interface package are able to coexist with one
another happily.  For instance, consider the case ofdirectlyProvides():>>> list(interface.providedBy(target))
[]
>>> import pprint
>>> pprint.pprint(list(interface.providedBy(proxy)))
[<InterfaceClass zc.shortcut.interfaces.ITargetProxy>]
>>> class IDummy(interface.Interface):
...     "dummy interface"
...
>>> interface.directlyProvides(proxy, IDummy)
>>> pprint.pprint(list(interface.providedBy(proxy)))
[<InterfaceClass zc.shortcut.interfaces.ITargetProxy>,
 <InterfaceClass __builtin__.IDummy>]
>>> list(interface.providedBy(target))
[<InterfaceClass __builtin__.IDummy>]AdaptersAdapters are provided to allow a shortcut to act as the target would when
traversed.ITraversableFirst we have to import the interfaces we’ll be working with:>>> from zope.publisher.interfaces import IRequest
>>> from zope.publisher.interfaces.browser import IBrowserPublisher
>>> from zope.traversing.interfaces import ITraversable
>>> from zc.shortcut.interfaces import IShortcut
>>> from zope.location.interfaces import ILocation
>>> from zc.shortcut import interfacesIf we have a target object with a root:>>> from zope import interface, component
>>> class ISpam(interface.Interface):
...     pass

>>> class Spam:
...     interface.implements(ISpam, ILocation)
...     def __init__(self, parent, name):
...         self.__parent__ = parent
...         self.__name__ = name

>>> from zope.traversing.interfaces import IContainmentRoot
>>> class DummyContainmentRoot(object):
...     __parent__ = __name__ = None
...     interface.implements(IContainmentRoot)
...
>>> root = DummyContainmentRoot()

>>> real_parent = Spam(root, 'real_parent')
>>> target = Spam(real_parent, 'target')The target object provides a multiadapter for the target and request to an
ITraversable so it can be traversed:>>> class SpamTraversableAdapter:
...     interface.implements(ITraversable)
...     component.adapts(ISpam, IRequest)
...     def __init__(self, spam, request):
...         self.spam = spam
>>> component.provideAdapter(SpamTraversableAdapter, name='view')There is an adapter to return the target object adapted to ITraversable when
a shortcut and request is adapted to ITraversable.  For example if we create
a shortcut to our target:>>> from zc.shortcut.shortcut import Shortcut
>>> shortcut = Shortcut(target)
>>> shortcut_parent = Spam(root, 'shortcut_parent')
>>> shortcut.__parent__ = shortcut_parent
>>> shortcut.__name__ = 'shortcut'And call the adapter with a request:>>> from zope.publisher.browser import TestRequest
>>> from zc.shortcut.adapters import ShortcutTraversalAdapterFactory

>>> request = TestRequest()
>>> adapter = ShortcutTraversalAdapterFactory(shortcut, request)The result is the target’s ITraversal adapter:>>> adapter
<...SpamTraversableAdapter instance at...>

>>> adapter.spam
<...Spam instance at...>Shortcut traversalShortcut traversal is unpleasantly tricky.  First consider the case of
traversing a shortcut and then traversing to get the default view
(‘index.html’).  In that case, the shortcut will be available to the view,
and breadcrumbs and other view elements that care about how the object was
traversed will merely need to look at the shortcut’s __parent__, or the
target proxy’s __traversed_parent__.  This is not too bad.It becomes more interesting if one traverses through a shortcut to another
content object.  A naive implementation will traverse the shortcut by
converting it to its target, and then traversing the target to get the
contained content object.  However, views for the content object will have no
idea of the traversal path used to get to the content object: they will only
have the __parent__ of the content object, which is the shortcut’s targetwithout any target proxy.  From there they will be able to find the target’s
parent, but not the traversed shortcut’s parent.  Breadcrumbs and other
components that care about traversed path will be broken.In order to solve this use case, traversing a shortcut needs to traverse the
target and then wrap the resulting object in another target proxy that
holds a reference to the shortcut’s target proxy as its traversed parent.Traversing a shortcut and finding another shortcut is slightly trickier again.
In this case, the shortcut’s target’s proxy should have a parent which is the
shortcut’s proxy’s parent.Two adapters are available for IPublishTraverse: one for shortcuts, and one
for traversal proxies. If a traversal target doesn’t provide IPublishTraverse,
then it should provide an adapter:>>> from zc.shortcut import adapters
>>> from zope.publisher.interfaces import IPublishTraverse
>>> child_spam = Spam(real_parent, 'child_spam')
>>> child_shortcut = Shortcut(child_spam)
>>> child_shortcut.__parent__ = shortcut
>>> child_shortcut.__name__ = 'child_shortcut'
>>> class SpamPublishTraverseAdapter:
...     interface.implements(IPublishTraverse)
...     component.adapts(ISpam, IRequest)
...     def __init__(self, spam, request):
...         self.spam = spam
...     def publishTraverse(self, request, name):
...         print 'SpamPublishTraverseAdapter has been traversed.'
...         return {'child_spam': child_spam,
...                 'child_shortcut': child_shortcut}[name]
>>> component.provideAdapter(SpamPublishTraverseAdapter)If it does, the adapter will be used to do the traversal:>>> adapter = adapters.ShortcutPublishTraverseAdapter(shortcut, request)
>>> adapter
<...ShortcutPublishTraverseAdapter object at...>
>>> from zope.interface.verify import verifyObject
>>> verifyObject(IPublishTraverse, adapter)
True
>>> res = adapter.publishTraverse(request, 'child_spam')
SpamPublishTraverseAdapter has been traversed.Notice that the traversed object has a traversal proxy (but not a target
proxy).>>> interfaces.ITraversalProxy.providedBy(res)
True
>>> interfaces.ITargetProxy.providedBy(res)
False
>>> res.__traversed_parent__ == shortcut.target
True
>>> res.__traversed_name__
'child_spam'
>>> res.__traversed_parent__.__shortcut__ is shortcut
True
>>> res.__traversed_parent__.__traversed_parent__ is shortcut_parent
TrueTo traverse further down and still keep the traversal information, we need to
register the ProxyPublishTraverseAdapter.  Notice that we will also traverse
to a shortcut this time, and look at the traversal trail up from the shortcut
and from its target.>>> component.provideAdapter(adapters.ProxyPublishTraverseAdapter)
>>> from zope import component
>>> adapter = component.getMultiAdapter((res, request), IPublishTraverse)
>>> res = adapter.publishTraverse(request, 'child_shortcut')
SpamPublishTraverseAdapter has been traversed.
>>> res.__traversed_parent__ == child_spam
True
>>> res.__traversed_name__
'child_shortcut'
>>> res.__traversed_parent__.__traversed_parent__ == shortcut.target
True
>>> res.target.__traversed_parent__.__traversed_parent__ == shortcut.target
TrueIf, instead, the target implements IPublishTraverse itself…:>>> class SpamWithPublishTraverse(Spam):
...     interface.implements(IPublishTraverse)
...     def publishTraverse(self, request, name):
...         print 'SpamWithPublishTraverse has been traversed.'
...         return {'child_spam': child_spam,
...                 'child_shortcut': child_shortcut}[name]…then it’spublishTraverse()will be called directly:>>> spam = SpamWithPublishTraverse(real_parent, 'special_spam')
>>> shortcut = Shortcut(spam)
>>> shortcut.__parent__ = shortcut_parent
>>> shortcut.__name__ = 'special_spam_shortcut'
>>> adapter = adapters.ShortcutPublishTraverseAdapter(shortcut, request)
>>> adapter
<...ShortcutPublishTraverseAdapter object at...>

>>> another = adapter.publishTraverse(request, 'child_spam')
SpamWithPublishTraverse has been traversed.Ending traversal at a shortcutWhen a shortcut is the target of a URL traversal, rather than a node
along the way, the leaf-node handling of the target object must be
invoked so that the shortcut behaves in the same way as the would
would when accessed directly.When a URL from a request represents an object (rather than a view),
the publisher uses thebrowserDefault()method of theIBrowserPublisherinterface to determine how the object should be
handled.  This method returns an object and a sequences of path
elements that should be traversed.For shortcuts, this is handled by delegating to the target of the
shortcut, substituting a proxy for the target so the traversedURL view
and breadcrumbs still work correctly.Let’s start by defining anIBrowserPublisherforISpamobjects:>>> class SpamBrowserPublisherAdapter(SpamPublishTraverseAdapter):
...     interface.implements(IBrowserPublisher)
...     def browserDefault(self, request):
...         print "browserDefault for", repr(self.spam)
...         return self.spam, ("@@foo.html",)
>>> component.provideAdapter(SpamBrowserPublisherAdapter,
...                          provides=IBrowserPublisher)

>>> adapter.browserDefault(request)  # doctest: +ELLIPSIS
browserDefault for <...SpamWithPublishTraverse instance at 0x...>
(<...SpamWithPublishTraverse instance at 0x...>, ('@@foo.html',))traversedURLIf shortcuts are traversed, an absolute url can lead a user to unexpected
locations–to the real location of the object, rather than to the traversed
location.  In order to get the traversed url, the adapters module provides a
traversedURL function, and the shortcut package also offers it from its
__init__.py.Given the result of the next-to-last shortcut traversal described
above, for instance, traversedURL returns a URL that behaves similarly to
absoluteURL except when it encounters target proxies, at which point the
traversal parents are used rather than the actual parents.>>> component.provideAdapter(adapters.TraversedURL)
>>> component.provideAdapter(adapters.FallbackTraversedURL)
>>> component.provideAdapter(adapters.RootTraversedURL)
>>> adapters.traversedURL(res, request)
'http://127.0.0.1/shortcut_parent/shortcut/child_spam/child_shortcut'Like absoluteURL, the returned value is html escaped.>>> shortcut_parent.__name__ = 'shortcut parent'
>>> adapters.traversedURL(res, request)
'http://127.0.0.1/shortcut%20parent/shortcut/child_spam/child_shortcut'Also like absoluteURL, traversedURL is registered as a view so it can be used
within page templates (as in context/@@traversedURL).>>> component.provideAdapter(adapters.traversedURL, name="traversedURL")
>>> component.getMultiAdapter((res, request), name='traversedURL')
'http://127.0.0.1/shortcut%20parent/shortcut/child_spam/child_shortcut'BreadcrumbsThe zc.displayname package provides a way to obtain breadcrumbs that is not
tied to the zope IAbsoluteURL interface and that takes advantage of
zc.displayname features like the display name generator.  The zc.shortcut
package includes a breadcrumb adapter for the zc.displayname interface that is
aware of the traversal proxies that are part of the shortcut package.>>> import zc.displayname.adapters
>>> component.provideAdapter(zc.displayname.adapters.Breadcrumbs)
>>> component.provideAdapter(zc.displayname.adapters.TerminalBreadcrumbs)
>>> component.provideAdapter(zc.displayname.adapters.DefaultDisplayNameGenerator)
>>> component.provideAdapter(zc.displayname.adapters.SiteDisplayNameGenerator)
>>> from zope.publisher.interfaces.http import IHTTPRequest
>>> from zope.traversing.browser.interfaces import IAbsoluteURL
>>> from zope.traversing import browser
>>> component.provideAdapter(
...     browser.AbsoluteURL, adapts=(None, IHTTPRequest),
...     provides=IAbsoluteURL)
>>> component.provideAdapter(
...     browser.SiteAbsoluteURL, adapts=(IContainmentRoot, IHTTPRequest),
...     provides=IAbsoluteURL)
>>> component.provideAdapter(
...     browser.AbsoluteURL, adapts=(None, IHTTPRequest),
...     provides=interface.Interface, name='absolute_url')
>>> component.provideAdapter(
...     browser.SiteAbsoluteURL, adapts=(IContainmentRoot, IHTTPRequest),
...     provides=interface.Interface, name='absolute_url')
>>> component.provideAdapter(adapters.Breadcrumbs)
>>> from zc.displayname.interfaces import IBreadcrumbs
>>> bc = component.getMultiAdapter((res, request), IBreadcrumbs)
>>> import pprint
>>> pprint.pprint(bc()) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
({'name': u'[root]',
  'name_gen': <zc.displayname.adapters.SiteDisplayNameGenerator object at ...>,
  'object': <...DummyContainmentRoot object at ...>,
  'url': 'http://127.0.0.1'},
 {'name': 'shortcut parent',
  'name_gen': <zc.displayname.adapters.DefaultDisplayNameGenerator object at ...>,
  'object': <...Spam instance at ...>,
  'url': 'http://127.0.0.1/shortcut%20parent'},
 {'name': 'target',
  'name_gen': <zc.displayname.adapters.DefaultDisplayNameGenerator object at ...>,
  'object': <...Spam instance at ...>,
  'url': 'http://127.0.0.1/shortcut%20parent/shortcut'},
 {'name': 'child_spam',
  'name_gen': <zc.displayname.adapters.DefaultDisplayNameGenerator object at ...>,
  'object': <...Spam instance at ...>,
  'url': 'http://127.0.0.1/shortcut%20parent/shortcut/child_spam'},
 {'name': 'child_shortcut',
  'name_gen': <zc.displayname.adapters.DefaultDisplayNameGenerator object at ...>,
  'object': <zc.shortcut.shortcut.Shortcut object at ...>,
  'url': 'http://127.0.0.1/shortcut%20parent/shortcut/child_spam/child_shortcut'})
>>> pprint.pprint(bc(6)) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
({'name': u'[root]',
  'name_gen': <zc.displayname.adapters.SiteDisplayNameGenerator object at ...>,
  'object': <...DummyContainmentRoot object at ...>,
  'url': 'http://127.0.0.1'},
 {'name': 'sho...',
  'name_gen': <zc.displayname.adapters.DefaultDisplayNameGenerator object at ...>,
  'object': <...Spam instance at ...>,
  'url': 'http://127.0.0.1/shortcut%20parent'},
 {'name': 'target',
  'name_gen': <zc.displayname.adapters.DefaultDisplayNameGenerator object at ...>,
  'object': <...Spam instance at ...>,
  'url': 'http://127.0.0.1/shortcut%20parent/shortcut'},
 {'name': 'chi...',
  'name_gen': <zc.displayname.adapters.DefaultDisplayNameGenerator object at ...>,
  'object': <...Spam instance at ...>,
  'url': 'http://127.0.0.1/shortcut%20parent/shortcut/child_spam'},
 {'name': 'chi...',
  'name_gen': <zc.displayname.adapters.DefaultDisplayNameGenerator object at ...>,
  'object': <zc.shortcut.shortcut.Shortcut object at ...>,
  'url': 'http://127.0.0.1/shortcut%20parent/shortcut/child_spam/child_shortcut'})Copy and LinkThe zope.copypastemove package provides a number of interfaces to provide
copy, move, rename, and other similar operations.  The shortcut package
provides a replacement implementation of copy for objects that looks up a
repository and uses it if available; an implementation of
copy that actually makes shortcuts (useful for immutable objects stored in a
repository); and an interface and two implementations, one for shortcuts and
one for other objects, for a newlinkoperation, which makes a shortcut to
the selected object.Copying an ObjectIf you want copying an object to use repositories if they are available, this
adapter provides the functionality.  It is installed for all objects by
default, but could also be configured only for certain interfaces.In the example below, first we set up the dummy content objects, then we
register the necessary adapters, and then we set up some event listener code
that we use to show what events are being fired.>>> class IDummy(interface.Interface):
...     pass
...
>>> import zope.app.container.interfaces
>>> class Dummy(object):
...     interface.implements(
...         IDummy, zope.app.container.interfaces.IContained)
>>> class DummyContainer(dict):
...     interface.implements(zope.app.container.interfaces.IContainer)
...     __parent__ = __name__ = None
...     def __repr__(self):
...         return "<%s at %d>" % (self.__class__.__name__, id(self))
...
>>> repo = DummyContainer()
>>> folder = DummyContainer()
>>> @component.adapter(IDummy)
... @interface.implementer(zope.app.container.interfaces.IContainer)
... def DummyRepoGetter(content):
...     return repo
...
>>> component.provideAdapter(
...     DummyRepoGetter, name=interfaces.REPOSITORY_NAME)
>>> from zope.app.container.contained import NameChooser
>>> component.provideAdapter(NameChooser, adapts=(interface.Interface,))
>>> # now, before we actually actually run the adding machinery, we'll
>>> # set up some machinery that will let us look at events firing
...
>>> heard_events = [] # we'll collect the events here
>>> from zope import event
>>> event.subscribers.append(heard_events.append)
>>> import pprint
>>> from zope import interface
>>> showEventsStart = 0
>>> def iname(ob):
...     return iter(interface.providedBy(ob)).next().__name__
...
>>> def getId(ob):
...     if ob is None or isinstance(ob, (int, float, basestring, tuple)):
...         return "(%r)" % (ob,)
...     id = getattr(ob, 'id', getattr(ob, '__name__', None))
...     if not id:
...         id = "a %s (%s)" % (ob.__class__.__name__, iname(ob))
...     return id
...
>>> def showEvents(start=None): # to generate a friendly view of events
...     global showEventsStart
...     if start is None:
...         start = showEventsStart
...     res = [
...         '%s fired for %s.' % (iname(ev), getId(ev.object))
...         for ev in heard_events[start:]]
...     res.sort()
...     pprint.pprint(res)
...     showEventsStart = len(heard_events)
...
>>> component.provideAdapter(adapters.ObjectCopier)
>>> from zope.app.container.contained import NameChooser
>>> component.provideAdapter(NameChooser, adapts=(interface.Interface,))
>>> dummy = Dummy()
>>> repo['dummy'] = dummy
>>> dummy.__parent__ = repo
>>> dummy.__name__ = 'dummy'
>>> dummy.id = 'foo'
>>> from zope import copypastemove
>>> copier = copypastemove.IObjectCopier(dummy)
>>> verifyObject(copypastemove.IObjectCopier, copier)
True
>>> copier.copyTo(folder)
'dummy'
>>> showEvents()
['IObjectCopiedEvent fired for foo.',
 'IObjectCreatedEvent fired for a Shortcut (IShortcut).']
>>> folder['dummy'].raw_target is not dummy
True
>>> folder['dummy'].raw_target is repo['dummy-2']
True>>> folder['dummy'].raw_target.id
'foo'
>>> folder.clear() # prepare for next testLinkingIn addition to the copy and move operations, the shortcut package offers up a
new ‘link’ operation: this creates a shortcut to the selected object.  In the
case of linking a shortcut, the provided adapter links instead to the original
shortcut’s target.>>> from zope.app.container.constraints import contains
>>> class INoDummyContainer(interface.Interface):
...     contains(ISpam) # won't contain shortcuts
...
>>> badcontainer = DummyContainer()
>>> interface.alsoProvides(badcontainer, INoDummyContainer)
>>> component.provideAdapter(adapters.ObjectLinkerAdapter)
>>> component.provideAdapter(adapters.ShortcutLinkerAdapter)
>>> dummy_linker = interfaces.IObjectLinker(dummy)
>>> shortcut_linker = interfaces.IObjectLinker(shortcut)
>>> verifyObject(interfaces.IObjectLinker, dummy_linker)
True
>>> verifyObject(interfaces.IObjectLinker, shortcut_linker)
True
>>> dummy_linker.linkable()
True
>>> shortcut_linker.linkable()
True
>>> dummy_linker.linkableTo(badcontainer)
False
>>> shortcut_linker.linkableTo(badcontainer)
False
>>> dummy_linker.linkableTo(folder)
True
>>> shortcut_linker.linkableTo(folder)
True
>>> dummy_linker.linkTo(badcontainer)
Traceback (most recent call last):
...
Invalid: ('Not linkableTo target with name', <DummyContainer...>, 'dummy')
>>> shortcut_linker.linkTo(badcontainer)
Traceback (most recent call last):
...
Invalid: ('Not linkableTo target with name', <DummyContainer...>, 'special_spam_shortcut')
>>> dummy_linker.linkTo(folder)
'dummy'
>>> showEvents()
['IObjectCreatedEvent fired for a Shortcut (IShortcut).']
>>> folder['dummy'].raw_target is dummy
True
>>> shortcut_linker.linkTo(folder)
'special_spam_shortcut'
>>> showEvents()
['IObjectCopiedEvent fired for a Shortcut (IShortcut).']
>>> folder['special_spam_shortcut'].raw_target is spam
True
>>> dummy_linker.linkTo(folder, 'dummy2')
'dummy2'
>>> showEvents()
['IObjectCreatedEvent fired for a Shortcut (IShortcut).']
>>> folder['dummy2'].raw_target is dummy
True
>>> shortcut_linker.linkTo(folder, 'shortcut2')
'shortcut2'
>>> showEvents()
['IObjectCopiedEvent fired for a Shortcut (IShortcut).']
>>> folder['shortcut2'].raw_target is spam
TrueCopying as LinkingFor some objects–immutable objects that are primarily stored in a repository,
for instance–having a copy gesture actually create a link may be desirable.
The adapters module provides an ObjectCopierLinkingAdapter for these use cases.
Whenever a copy is requested, a link is made instead.  This adapter is not
registered for any interfaces by default: it is expected to be installed
selectively.>>> class IImmutableDummy(IDummy):
...     pass
...
>>> immutable_dummy = Dummy()
>>> interface.directlyProvides(immutable_dummy, IImmutableDummy)
>>> originalcontainer = DummyContainer()
>>> originalcontainer['immutable_dummy'] = immutable_dummy
>>> immutable_dummy.__name__ = 'immutable_dummy'
>>> immutable_dummy.__parent__ = originalcontainer
>>> component.provideAdapter(
...     adapters.ObjectCopierLinkingAdapter, adapts=(IImmutableDummy,))
>>> copier = copypastemove.IObjectCopier(immutable_dummy)
>>> copier.copyable()
True
>>> copier.copyableTo(badcontainer)
False
>>> copier.copyableTo(folder)
True
>>> copier.copyTo(folder)
'immutable_dummy'
>>> showEvents()
['IObjectCreatedEvent fired for a Shortcut (IShortcut).']
>>> folder['immutable_dummy'].raw_target is immutable_dummy
True>>> event.subscribers.pop() is not None # cleanup
TrueShortcut IAddingThe shortcut adding has a couple of different behaviors than the standard Zope
3 adding.  The differences are to support traversal proxies; and to provide
more flexibility for choosing the nextURL after an add.Supporting Traversal ProxiesBoth the action method and the nextURL method redirect to the absoluteURL of
the container in the zope.app implementation.  In the face of shortcuts and
traversal proxies, this can generate surprising behavior for users, directing
their URL to a location other than where they thought they were working.  The
shortcut adding changes both of these methods to use traversedURL instead.  As
a result, adding to a shortcut of a container returns the user to the
shortcut, not the absolute path of the container’s real location; and
submitting the form of the default view of the adding redirects to within the
context of the traversed shortcut(s), not the absoluteURL.The action method changes are pertinent to redirecting to an adding view.>>> from zc.shortcut import adding, interfaces
>>> from zope import interface, component
>>> from zope.location.interfaces import ILocation
>>> class ISpam(interface.Interface):
...     pass
...
>>> class Spam(dict):
...     interface.implements(ISpam, ILocation)
...     def __init__(self, parent, name):
...         self.__parent__ = parent
...         self.__name__ = name
...
>>> from zope.traversing.interfaces import IContainmentRoot
>>> class DummyContainmentRoot(object):
...     interface.implements(IContainmentRoot)
...
>>> root = DummyContainmentRoot()
>>> real_parent = Spam(root, 'real_parent')
>>> target = Spam(real_parent, 'target')
>>> from zc.shortcut.shortcut import Shortcut
>>> shortcut = Shortcut(target)
>>> shortcut_parent = Spam(root, 'shortcut_parent')
>>> shortcut.__parent__ = shortcut_parent
>>> shortcut.__name__ = 'shortcut'
>>> from zc.shortcut import adapters
>>> component.provideAdapter(adapters.TraversedURL)
>>> component.provideAdapter(adapters.FallbackTraversedURL)
>>> component.provideAdapter(adapters.RootTraversedURL)
>>> from zope.publisher.interfaces import IRequest
>>> @component.adapter(interfaces.IAdding, IRequest)
... @interface.implementer(interface.Interface)
... def dummyAddingView(adding, request):
...     return 'this is a view'
...
>>> component.provideAdapter(dummyAddingView, name='foo_type')
>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest()
>>> adder = adding.Adding(shortcut.target, request)
>>> adder.action('foo_type', 'foo_id')
>>> request.response.getHeader('Location')
'http://127.0.0.1/shortcut_parent/shortcut/@@+/foo_type=foo_id'The nextURL method changes are pertinent to the default behavior.>>> adder.contentName = 'foo_id'
>>> target['foo_id'] = Spam(target, 'foo_id')
>>> adder.nextURL()
'http://127.0.0.1/shortcut_parent/shortcut/@@contents.html'Adding Flexibility to ‘nextURL’The nextURL method in the zope.app implementation of an adding defines
precisely what the nextURL should be: the @@contents.html view of the context.
The shortcut adding recreates this behavior, but only after seeing if different
behavior has been registered.nextURL tries to find an adapter named with the constant in
zc.shortcut.interfaces.NEXT_URL_NAME, providing nothing, for the adding, the
new content as found in the container (so it may be a shortcut), and the
context.  If an adapter is registered, it should be a string of the nextURL to
be used; this value will be returned. If no adapter is registered or the
registered adapter returns None, the @@contents.html view of the context is
returned.>>> @component.adapter(interfaces.IAdding, ISpam, ISpam)
... @interface.implementer(interface.Interface)
... def sillyNextURL(adding, content, container):
...     return '%s class added "%s" to "%s"' % (
...         adding.__class__.__name__,
...         content.__name__,
...         container.__name__)
...
>>> component.provideAdapter(sillyNextURL, name=interfaces.NEXT_URL_NAME)
>>> adder.nextURL()
'Adding class added "foo_id" to "target"'Shortcut factoriesShortcut factories are factories that place objects in a configured folder and
then return a shortcut to the new object.  Because they create objects and
place them in containers, they fire an object creation event, and usually the
configured folder fires an object added event.>>> from zc.shortcut import factory, interfaces, Shortcut
>>> from zope import interface, component, event
>>> class IDummy(interface.Interface):
...     pass
...
>>> from zope.location.interfaces import ILocation
>>> class Dummy(object):
...     interface.implements(IDummy, ILocation)
...     def __init__(self, *args, **kwargs):
...         self.args = args
...         self.kwargs = kwargs
...
>>> f = factory.Factory(Dummy, 'title', 'description')
>>> from zope.interface import verify
>>> verify.verifyObject(interfaces.IShortcutFactory, f)
TrueThe factory always returns an interface declaration for a shortcut from
getInterfaces, while getTargetInterfaces returns the declaration for the
created object.>>> f.getInterfaces() == interface.implementedBy(Shortcut)
True
>>> f.getTargetInterfaces() == interface.implementedBy(Dummy)
Truefactories will fail to create an object if a container has not been
registered as a repository.>>> f() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
ComponentLookupError: (<Dummy...>, <...IContainer>, 'shortcutTargetRepository')If we register a repository then the factory will fire a creation event, add
the object to the repository, and return a shortcut to the new object.>>> import zope.app.container.interfaces
>>> class DummyContainer(dict):
...     interface.implements(zope.app.container.interfaces.IContainer)
...
>>> repo = DummyContainer()
>>> @component.adapter(IDummy)
... @interface.implementer(zope.app.container.interfaces.IContainer)
... def DummyRepoGetter(content):
...     return repo
...
>>> component.provideAdapter(
...     DummyRepoGetter, name=interfaces.REPOSITORY_NAME)
>>> from zope.app.container.contained import NameChooser
>>> component.provideAdapter(NameChooser, adapts=(interface.Interface,))
>>> # now, before we actually actually run the adding machinery, we'll
>>> # set up some machinery that will let us look at events firing
...
>>> heard_events = [] # we'll collect the events here
>>> event.subscribers.append(heard_events.append)
>>> import pprint
>>> from zope import interface
>>> showEventsStart = 0
>>> def iname(ob):
...     return iter(interface.providedBy(ob)).next().__name__
...
>>> def getId(ob):
...     if ob is None or isinstance(ob, (int, float, basestring, tuple)):
...         return "(%r)" % (ob,)
...     id = getattr(ob, 'id', getattr(ob, '__name__', None))
...     if not id:
...         id = "a %s (%s)" % (ob.__class__.__name__, iname(ob))
...     return id
...
>>> def showEvents(start=None): # to generate a friendly view of events
...     global showEventsStart
...     if start is None:
...         start = showEventsStart
...     res = [
...         '%s fired for %s.' % (iname(ev), getId(ev.object))
...         for ev in heard_events[start:]]
...     res.sort()
...     pprint.pprint(res)
...     showEventsStart = len(heard_events)
...
>>> sc = f(12, 'foo', 'barbaz', sloop=19)
>>> showEvents()
['IObjectCreatedEvent fired for a Dummy (IDummy).']
>>> repo['Dummy'].args
(12, 'foo', 'barbaz')
>>> repo['Dummy'].kwargs
{'sloop': 19}
>>> sc.raw_target is repo['Dummy']
True>>> event.subscribers.pop() is not None # cleanup
TrueUsing alternate shortcut implementationsThe shortcut factory takes an optional keyword parameter to specify
the factory used to create the shortcut.  By default,zc.shortcut.Shortcutis used, but more specialized shortcuts may be
needed for some applications.  This allows the factory to be used
regardless of the specific shortcut implementation.Let’s create an alternate class that can be used as a shortcut (it
doesn’t really matter that the example class isn’t useful):>>> class AlternateShortcut(object):
...     interface.implements(interfaces.IShortcut)
...     def __init__(self, object):
...         self.raw_target = object
...         self.target = objectNow we can create a factory that creates instances of this class
instead of the default shortcut class:>>> f = factory.Factory(Dummy, 'title', 'description',
...                     shortcut_factory=AlternateShortcut)Using the factory returns an instance of our alternate shortcut
implementation:>>> sc = f(1, 2, 3)

>>> isinstance(sc, AlternateShortcut)
True
>>> isinstance(sc.raw_target, Dummy)
True
>>> sc.target.args
(1, 2, 3)