{ "info": { "author": "Zope Project", "author_email": "zope3-dev@zope.org", "bugtrack_url": null, "classifiers": [], "description": "=========\nShortcuts\n=========\n\nShortcuts are objects that allow other objects (their ``target``) to appear to\nbe located in places other than the target's actual location. They are\nsomewhat like a symbolic link in Unix-like operating systems.\n\nCreating a shortcut\n===================\n\nShortcuts are created by calling the ``Shortcut`` class's constructor with a\ntarget, parent, and name::\n\n >>> from zc.shortcut.shortcut import Shortcut\n >>> class MyTarget:\n ... attr = 'hi'\n ... __parent__ = 'Original Parent'\n ... __name__ = 'Original Name'\n >>> target = MyTarget()\n >>> sc = Shortcut(target)\n >>> sc.__parent__ = 'My Parent'\n >>> sc.__name__ = 'My Name'\n\nA shortcut provides an attribute to access its target::\n\n >>> sc.target\n <__builtin__.MyTarget instance at ...>\n\nA shortcut's __parent__ and __name__ are independent of their target::\n\n >>> sc.__parent__\n 'My Parent'\n >>> sc.target.__parent__\n 'Original Parent'\n\n >>> sc.__name__\n 'My Name'\n >>> sc.target.__name__\n 'Original Name'\n\nBut the target knows the traversal parent, the traversal name, and the\nshortcut. This allows the shortcut to have annotations that may be accessed\nby views and other components that render or use the target.\n\n >>> sc.target.__traversed_parent__\n 'My Parent'\n >>> sc.target.__traversed_name__\n 'My Name'\n >>> sc.target.__shortcut__ is sc\n True\n\nSee proxy.txt and adapters.txt for more details\n\n========================\nShortcut-related proxies\n========================\n\nThe `zc.shortcut.proxy` module includes some code useful outside of\nthe shortcut package and some code specifically for shortcut usage.\n\nThe generally useful code includes a decorator class that puts decorator\ninterfaces before all of the interfaces of the wrapped object (the opposite of\nthe behavior of `zope.app.decorator.DecoratorSpecificationDescriptor`). It\nalso includes a special `implements()` function that should be used to declare\nthat a proxy implements a given set of interfaces. Using the\n`zope.interface.implements()` function instead will cause\n`interface.directlyProvides()` to fail on the proxied object (and will also\nhave side effects possibly causing other proxies with the same base class to\nalso be broken.\n\n >>> from zope import interface\n >>> from zc.shortcut import proxy\n >>> class I1(interface.Interface):\n ... pass\n ...\n >>> class I2(interface.Interface):\n ... pass\n ...\n >>> class I3(interface.Interface):\n ... pass\n ...\n >>> class I4(interface.Interface):\n ... pass\n ...\n >>> class D1(proxy.Decorator):\n ... proxy.implements(I1)\n ...\n >>> class D2(proxy.Decorator):\n ... proxy.implements(I2)\n ...\n >>> class X(object):\n ... interface.implements(I3)\n ...\n >>> x = X()\n >>> [i.getName() for i in interface.providedBy(D1(x))]\n ['I1', 'I3']\n >>> [i.getName() for i in interface.providedBy(D2(D1(x)))]\n ['I2', 'I1', 'I3']\n >>> dec_x = D2(D1(X()))\n >>> interface.directlyProvides(dec_x, I4)\n >>> [i.getName() for i in interface.providedBy(dec_x)]\n ['I2', 'I1', 'I4', 'I3']\n\n\nTarget proxies\n==============\n\nTarget proxies are the primary shortcut-specific proxy type.\nWhen a shortcut is asked for its target it actually returns a proxy:\n\n >>> from zc.shortcut.shortcut import Shortcut\n >>> class MyTarget:\n ... attr = 'hi'\n ... __parent__ = 'Original Parent'\n ... __name__ = 'Original Name'\n >>> target = MyTarget()\n >>> sc = Shortcut(target)\n >>> sc.__parent__ = 'My Parent'\n >>> sc.__name__ = 'My Name'\n >>> proxy = sc.target\n >>> proxy is target\n False\n\nThe proxy acts as the target:\n\n >>> proxy == target\n True\n\n >>> target.__parent__\n 'Original Parent'\n >>> proxy.__parent__\n 'Original Parent'\n\n >>> target.__name__\n 'Original Name'\n >>> proxy.__name__\n 'Original Name'\n\n >>> target.attr\n 'hi'\n >>> proxy.attr\n 'hi'\n\nThe proxy also has attributes point to the shortcut and its parent and\nname:\n\n >>> proxy.__shortcut__ is sc\n True\n >>> proxy.__traversed_parent__\n 'My Parent'\n >>> proxy.__traversed_name__\n 'My Name'\n\nAs discussed in adapters.txt, once a traversal passes through a shortcut, all\ncontained objects receive their own target proxies even if they did not\nthemselves come from a shortcut. They have `__traversed_parent__` and\n`__traversed_name__` attributes, pointing to the target proxy of the object\ntraversed to find them and the name used, respectively, but no `__shortcut__`\nattribute: they effectively implement `interfaces.ITraversalProxy` and not\n`interfaces.ITargetProxy`.\n\nTarget proxies and the zope interface package are able to coexist with one\nanother happily. For instance, consider the case of `directlyProvides()`:\n\n >>> list(interface.providedBy(target))\n []\n >>> import pprint\n >>> pprint.pprint(list(interface.providedBy(proxy)))\n []\n >>> class IDummy(interface.Interface):\n ... \"dummy interface\"\n ...\n >>> interface.directlyProvides(proxy, IDummy)\n >>> pprint.pprint(list(interface.providedBy(proxy)))\n [,\n ]\n >>> list(interface.providedBy(target))\n []\n\n========\nAdapters\n========\n\nAdapters are provided to allow a shortcut to act as the target would when\ntraversed.\n\nITraversable\n============\n\nFirst we have to import the interfaces we'll be working with::\n\n >>> from zope.publisher.interfaces import IRequest\n >>> from zope.publisher.interfaces.browser import IBrowserPublisher\n >>> from zope.traversing.interfaces import ITraversable\n >>> from zc.shortcut.interfaces import IShortcut\n >>> from zope.location.interfaces import ILocation\n >>> from zc.shortcut import interfaces\n\nIf we have a target object with a root::\n\n >>> from zope import interface, component\n >>> class ISpam(interface.Interface):\n ... pass\n\n >>> class Spam:\n ... interface.implements(ISpam, ILocation)\n ... def __init__(self, parent, name):\n ... self.__parent__ = parent\n ... self.__name__ = name\n\n >>> from zope.traversing.interfaces import IContainmentRoot\n >>> class DummyContainmentRoot(object):\n ... __parent__ = __name__ = None\n ... interface.implements(IContainmentRoot)\n ...\n >>> root = DummyContainmentRoot()\n\n >>> real_parent = Spam(root, 'real_parent')\n >>> target = Spam(real_parent, 'target')\n\nThe target object provides a multiadapter for the target and request to an\nITraversable so it can be traversed::\n\n >>> class SpamTraversableAdapter:\n ... interface.implements(ITraversable)\n ... component.adapts(ISpam, IRequest)\n ... def __init__(self, spam, request):\n ... self.spam = spam\n >>> component.provideAdapter(SpamTraversableAdapter, name='view')\n\nThere is an adapter to return the target object adapted to ITraversable when\na shortcut and request is adapted to ITraversable. For example if we create\na shortcut to our target::\n\n >>> from zc.shortcut.shortcut import Shortcut\n >>> shortcut = Shortcut(target)\n >>> shortcut_parent = Spam(root, 'shortcut_parent')\n >>> shortcut.__parent__ = shortcut_parent\n >>> shortcut.__name__ = 'shortcut'\n\nAnd call the adapter with a request::\n\n >>> from zope.publisher.browser import TestRequest\n >>> from zc.shortcut.adapters import ShortcutTraversalAdapterFactory\n\n >>> request = TestRequest()\n >>> adapter = ShortcutTraversalAdapterFactory(shortcut, request)\n\nThe result is the target's ITraversal adapter::\n\n >>> adapter\n <...SpamTraversableAdapter instance at...>\n\n >>> adapter.spam\n <...Spam instance at...>\n\nShortcut traversal\n==================\n\nShortcut traversal is unpleasantly tricky. First consider the case of\ntraversing a shortcut and then traversing to get the default view\n('index.html'). In that case, the shortcut will be available to the view,\nand breadcrumbs and other view elements that care about how the object was\ntraversed will merely need to look at the shortcut's __parent__, or the\ntarget proxy's __traversed_parent__. This is not too bad.\n\nIt becomes more interesting if one traverses through a shortcut to another\ncontent object. A naive implementation will traverse the shortcut by\nconverting it to its target, and then traversing the target to get the\ncontained content object. However, views for the content object will have no\nidea of the traversal path used to get to the content object: they will only\nhave the __parent__ of the content object, which is the shortcut's target\n*without any target proxy*. From there they will be able to find the target's\nparent, but not the traversed shortcut's parent. Breadcrumbs and other\ncomponents that care about traversed path will be broken.\n\nIn order to solve this use case, traversing a shortcut needs to traverse the\ntarget and then wrap the resulting object in another target proxy that\nholds a reference to the shortcut's target proxy as its traversed parent.\n\nTraversing a shortcut and finding another shortcut is slightly trickier again.\nIn this case, the shortcut's target's proxy should have a parent which is the\nshortcut's proxy's parent.\n\nTwo adapters are available for IPublishTraverse: one for shortcuts, and one\nfor traversal proxies. If a traversal target doesn't provide IPublishTraverse,\nthen it should provide an adapter::\n\n >>> from zc.shortcut import adapters\n >>> from zope.publisher.interfaces import IPublishTraverse\n >>> child_spam = Spam(real_parent, 'child_spam')\n >>> child_shortcut = Shortcut(child_spam)\n >>> child_shortcut.__parent__ = shortcut\n >>> child_shortcut.__name__ = 'child_shortcut'\n >>> class SpamPublishTraverseAdapter:\n ... interface.implements(IPublishTraverse)\n ... component.adapts(ISpam, IRequest)\n ... def __init__(self, spam, request):\n ... self.spam = spam\n ... def publishTraverse(self, request, name):\n ... print 'SpamPublishTraverseAdapter has been traversed.'\n ... return {'child_spam': child_spam,\n ... 'child_shortcut': child_shortcut}[name]\n >>> component.provideAdapter(SpamPublishTraverseAdapter)\n\nIf it does, the adapter will be used to do the traversal::\n\n >>> adapter = adapters.ShortcutPublishTraverseAdapter(shortcut, request)\n >>> adapter\n <...ShortcutPublishTraverseAdapter object at...>\n >>> from zope.interface.verify import verifyObject\n >>> verifyObject(IPublishTraverse, adapter)\n True\n >>> res = adapter.publishTraverse(request, 'child_spam')\n SpamPublishTraverseAdapter has been traversed.\n\nNotice that the traversed object has a traversal proxy (but not a target\nproxy).\n\n >>> interfaces.ITraversalProxy.providedBy(res)\n True\n >>> interfaces.ITargetProxy.providedBy(res)\n False\n >>> res.__traversed_parent__ == shortcut.target\n True\n >>> res.__traversed_name__\n 'child_spam'\n >>> res.__traversed_parent__.__shortcut__ is shortcut\n True\n >>> res.__traversed_parent__.__traversed_parent__ is shortcut_parent\n True\n\nTo traverse further down and still keep the traversal information, we need to\nregister the ProxyPublishTraverseAdapter. Notice that we will also traverse\nto a shortcut this time, and look at the traversal trail up from the shortcut\nand from its target.\n\n >>> component.provideAdapter(adapters.ProxyPublishTraverseAdapter)\n >>> from zope import component\n >>> adapter = component.getMultiAdapter((res, request), IPublishTraverse)\n >>> res = adapter.publishTraverse(request, 'child_shortcut')\n SpamPublishTraverseAdapter has been traversed.\n >>> res.__traversed_parent__ == child_spam\n True\n >>> res.__traversed_name__\n 'child_shortcut'\n >>> res.__traversed_parent__.__traversed_parent__ == shortcut.target\n True\n >>> res.target.__traversed_parent__.__traversed_parent__ == shortcut.target\n True\n\nIf, instead, the target implements IPublishTraverse itself...::\n\n >>> class SpamWithPublishTraverse(Spam):\n ... interface.implements(IPublishTraverse)\n ... def publishTraverse(self, request, name):\n ... print 'SpamWithPublishTraverse has been traversed.'\n ... return {'child_spam': child_spam,\n ... 'child_shortcut': child_shortcut}[name]\n\n...then it's `publishTraverse()` will be called directly::\n\n >>> spam = SpamWithPublishTraverse(real_parent, 'special_spam')\n >>> shortcut = Shortcut(spam)\n >>> shortcut.__parent__ = shortcut_parent\n >>> shortcut.__name__ = 'special_spam_shortcut'\n >>> adapter = adapters.ShortcutPublishTraverseAdapter(shortcut, request)\n >>> adapter\n <...ShortcutPublishTraverseAdapter object at...>\n\n >>> another = adapter.publishTraverse(request, 'child_spam')\n SpamWithPublishTraverse has been traversed.\n\nEnding traversal at a shortcut\n------------------------------\n\nWhen a shortcut is the target of a URL traversal, rather than a node\nalong the way, the leaf-node handling of the target object must be\ninvoked so that the shortcut behaves in the same way as the would\nwould when accessed directly.\n\nWhen a URL from a request represents an object (rather than a view),\nthe publisher uses the `browserDefault()` method of the\n`IBrowserPublisher` interface to determine how the object should be\nhandled. This method returns an object and a sequences of path\nelements that should be traversed.\n\nFor shortcuts, this is handled by delegating to the target of the\nshortcut, substituting a proxy for the target so the traversedURL view\nand breadcrumbs still work correctly.\n\nLet's start by defining an `IBrowserPublisher` for `ISpam` objects::\n\n >>> class SpamBrowserPublisherAdapter(SpamPublishTraverseAdapter):\n ... interface.implements(IBrowserPublisher)\n ... def browserDefault(self, request):\n ... print \"browserDefault for\", repr(self.spam)\n ... return self.spam, (\"@@foo.html\",)\n >>> component.provideAdapter(SpamBrowserPublisherAdapter,\n ... provides=IBrowserPublisher)\n\n >>> adapter.browserDefault(request) # doctest: +ELLIPSIS\n browserDefault for <...SpamWithPublishTraverse instance at 0x...>\n (<...SpamWithPublishTraverse instance at 0x...>, ('@@foo.html',))\n\n\ntraversedURL\n============\n\nIf shortcuts are traversed, an absolute url can lead a user to unexpected\nlocations--to the real location of the object, rather than to the traversed\nlocation. In order to get the traversed url, the adapters module provides a\ntraversedURL function, and the shortcut package also offers it from its\n__init__.py.\n\nGiven the result of the next-to-last shortcut traversal described\nabove, for instance, traversedURL returns a URL that behaves similarly to\nabsoluteURL except when it encounters target proxies, at which point the\ntraversal parents are used rather than the actual parents.\n\n >>> component.provideAdapter(adapters.TraversedURL)\n >>> component.provideAdapter(adapters.FallbackTraversedURL)\n >>> component.provideAdapter(adapters.RootTraversedURL)\n >>> adapters.traversedURL(res, request)\n 'http://127.0.0.1/shortcut_parent/shortcut/child_spam/child_shortcut'\n\nLike absoluteURL, the returned value is html escaped.\n\n >>> shortcut_parent.__name__ = 'shortcut parent'\n >>> adapters.traversedURL(res, request)\n 'http://127.0.0.1/shortcut%20parent/shortcut/child_spam/child_shortcut'\n\nAlso like absoluteURL, traversedURL is registered as a view so it can be used\nwithin page templates (as in context/@@traversedURL).\n\n >>> component.provideAdapter(adapters.traversedURL, name=\"traversedURL\")\n >>> component.getMultiAdapter((res, request), name='traversedURL')\n 'http://127.0.0.1/shortcut%20parent/shortcut/child_spam/child_shortcut'\n\nBreadcrumbs\n===========\n\nThe zc.displayname package provides a way to obtain breadcrumbs that is not\ntied to the zope IAbsoluteURL interface and that takes advantage of\nzc.displayname features like the display name generator. The zc.shortcut\npackage includes a breadcrumb adapter for the zc.displayname interface that is\naware of the traversal proxies that are part of the shortcut package.\n\n >>> import zc.displayname.adapters\n >>> component.provideAdapter(zc.displayname.adapters.Breadcrumbs)\n >>> component.provideAdapter(zc.displayname.adapters.TerminalBreadcrumbs)\n >>> component.provideAdapter(zc.displayname.adapters.DefaultDisplayNameGenerator)\n >>> component.provideAdapter(zc.displayname.adapters.SiteDisplayNameGenerator)\n >>> from zope.publisher.interfaces.http import IHTTPRequest\n >>> from zope.traversing.browser.interfaces import IAbsoluteURL\n >>> from zope.traversing import browser\n >>> component.provideAdapter(\n ... browser.AbsoluteURL, adapts=(None, IHTTPRequest),\n ... provides=IAbsoluteURL)\n >>> component.provideAdapter(\n ... browser.SiteAbsoluteURL, adapts=(IContainmentRoot, IHTTPRequest),\n ... provides=IAbsoluteURL)\n >>> component.provideAdapter(\n ... browser.AbsoluteURL, adapts=(None, IHTTPRequest),\n ... provides=interface.Interface, name='absolute_url')\n >>> component.provideAdapter(\n ... browser.SiteAbsoluteURL, adapts=(IContainmentRoot, IHTTPRequest),\n ... provides=interface.Interface, name='absolute_url')\n >>> component.provideAdapter(adapters.Breadcrumbs)\n >>> from zc.displayname.interfaces import IBreadcrumbs\n >>> bc = component.getMultiAdapter((res, request), IBreadcrumbs)\n >>> import pprint\n >>> pprint.pprint(bc()) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE\n ({'name': u'[root]',\n 'name_gen': ,\n 'object': <...DummyContainmentRoot object at ...>,\n 'url': 'http://127.0.0.1'},\n {'name': 'shortcut parent',\n 'name_gen': ,\n 'object': <...Spam instance at ...>,\n 'url': 'http://127.0.0.1/shortcut%20parent'},\n {'name': 'target',\n 'name_gen': ,\n 'object': <...Spam instance at ...>,\n 'url': 'http://127.0.0.1/shortcut%20parent/shortcut'},\n {'name': 'child_spam',\n 'name_gen': ,\n 'object': <...Spam instance at ...>,\n 'url': 'http://127.0.0.1/shortcut%20parent/shortcut/child_spam'},\n {'name': 'child_shortcut',\n 'name_gen': ,\n 'object': ,\n 'url': 'http://127.0.0.1/shortcut%20parent/shortcut/child_spam/child_shortcut'})\n >>> pprint.pprint(bc(6)) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE\n ({'name': u'[root]',\n 'name_gen': ,\n 'object': <...DummyContainmentRoot object at ...>,\n 'url': 'http://127.0.0.1'},\n {'name': 'sho...',\n 'name_gen': ,\n 'object': <...Spam instance at ...>,\n 'url': 'http://127.0.0.1/shortcut%20parent'},\n {'name': 'target',\n 'name_gen': ,\n 'object': <...Spam instance at ...>,\n 'url': 'http://127.0.0.1/shortcut%20parent/shortcut'},\n {'name': 'chi...',\n 'name_gen': ,\n 'object': <...Spam instance at ...>,\n 'url': 'http://127.0.0.1/shortcut%20parent/shortcut/child_spam'},\n {'name': 'chi...',\n 'name_gen': ,\n 'object': ,\n 'url': 'http://127.0.0.1/shortcut%20parent/shortcut/child_spam/child_shortcut'})\n\nCopy and Link\n=============\n\nThe zope.copypastemove package provides a number of interfaces to provide\ncopy, move, rename, and other similar operations. The shortcut package\nprovides a replacement implementation of copy for objects that looks up a\nrepository and uses it if available; an implementation of\ncopy that actually makes shortcuts (useful for immutable objects stored in a\nrepository); and an interface and two implementations, one for shortcuts and\none for other objects, for a new `link` operation, which makes a shortcut to\nthe selected object.\n\nCopying an Object\n-----------------\n\nIf you want copying an object to use repositories if they are available, this\nadapter provides the functionality. It is installed for all objects by\ndefault, but could also be configured only for certain interfaces.\n\nIn the example below, first we set up the dummy content objects, then we\nregister the necessary adapters, and then we set up some event listener code\nthat we use to show what events are being fired.\n\n >>> class IDummy(interface.Interface):\n ... pass\n ...\n >>> import zope.app.container.interfaces\n >>> class Dummy(object):\n ... interface.implements(\n ... IDummy, zope.app.container.interfaces.IContained)\n >>> class DummyContainer(dict):\n ... interface.implements(zope.app.container.interfaces.IContainer)\n ... __parent__ = __name__ = None\n ... def __repr__(self):\n ... return \"<%s at %d>\" % (self.__class__.__name__, id(self))\n ...\n >>> repo = DummyContainer()\n >>> folder = DummyContainer()\n >>> @component.adapter(IDummy)\n ... @interface.implementer(zope.app.container.interfaces.IContainer)\n ... def DummyRepoGetter(content):\n ... return repo\n ...\n >>> component.provideAdapter(\n ... DummyRepoGetter, name=interfaces.REPOSITORY_NAME)\n >>> from zope.app.container.contained import NameChooser\n >>> component.provideAdapter(NameChooser, adapts=(interface.Interface,))\n >>> # now, before we actually actually run the adding machinery, we'll\n >>> # set up some machinery that will let us look at events firing\n ...\n >>> heard_events = [] # we'll collect the events here\n >>> from zope import event\n >>> event.subscribers.append(heard_events.append)\n >>> import pprint\n >>> from zope import interface\n >>> showEventsStart = 0\n >>> def iname(ob):\n ... return iter(interface.providedBy(ob)).next().__name__\n ...\n >>> def getId(ob):\n ... if ob is None or isinstance(ob, (int, float, basestring, tuple)):\n ... return \"(%r)\" % (ob,)\n ... id = getattr(ob, 'id', getattr(ob, '__name__', None))\n ... if not id:\n ... id = \"a %s (%s)\" % (ob.__class__.__name__, iname(ob))\n ... return id\n ...\n >>> def showEvents(start=None): # to generate a friendly view of events\n ... global showEventsStart\n ... if start is None:\n ... start = showEventsStart\n ... res = [\n ... '%s fired for %s.' % (iname(ev), getId(ev.object))\n ... for ev in heard_events[start:]]\n ... res.sort()\n ... pprint.pprint(res)\n ... showEventsStart = len(heard_events)\n ...\n >>> component.provideAdapter(adapters.ObjectCopier)\n >>> from zope.app.container.contained import NameChooser\n >>> component.provideAdapter(NameChooser, adapts=(interface.Interface,))\n >>> dummy = Dummy()\n >>> repo['dummy'] = dummy\n >>> dummy.__parent__ = repo\n >>> dummy.__name__ = 'dummy'\n >>> dummy.id = 'foo'\n >>> from zope import copypastemove\n >>> copier = copypastemove.IObjectCopier(dummy)\n >>> verifyObject(copypastemove.IObjectCopier, copier)\n True\n >>> copier.copyTo(folder)\n 'dummy'\n >>> showEvents()\n ['IObjectCopiedEvent fired for foo.',\n 'IObjectCreatedEvent fired for a Shortcut (IShortcut).']\n >>> folder['dummy'].raw_target is not dummy\n True\n >>> folder['dummy'].raw_target is repo['dummy-2']\n True\n\n >>> folder['dummy'].raw_target.id\n 'foo'\n >>> folder.clear() # prepare for next test\n\nLinking\n-------\n\nIn addition to the copy and move operations, the shortcut package offers up a\nnew 'link' operation: this creates a shortcut to the selected object. In the\ncase of linking a shortcut, the provided adapter links instead to the original\nshortcut's target.\n\n >>> from zope.app.container.constraints import contains\n >>> class INoDummyContainer(interface.Interface):\n ... contains(ISpam) # won't contain shortcuts\n ...\n >>> badcontainer = DummyContainer()\n >>> interface.alsoProvides(badcontainer, INoDummyContainer)\n >>> component.provideAdapter(adapters.ObjectLinkerAdapter)\n >>> component.provideAdapter(adapters.ShortcutLinkerAdapter)\n >>> dummy_linker = interfaces.IObjectLinker(dummy)\n >>> shortcut_linker = interfaces.IObjectLinker(shortcut)\n >>> verifyObject(interfaces.IObjectLinker, dummy_linker)\n True\n >>> verifyObject(interfaces.IObjectLinker, shortcut_linker)\n True\n >>> dummy_linker.linkable()\n True\n >>> shortcut_linker.linkable()\n True\n >>> dummy_linker.linkableTo(badcontainer)\n False\n >>> shortcut_linker.linkableTo(badcontainer)\n False\n >>> dummy_linker.linkableTo(folder)\n True\n >>> shortcut_linker.linkableTo(folder)\n True\n >>> dummy_linker.linkTo(badcontainer)\n Traceback (most recent call last):\n ...\n Invalid: ('Not linkableTo target with name', , 'dummy')\n >>> shortcut_linker.linkTo(badcontainer)\n Traceback (most recent call last):\n ...\n Invalid: ('Not linkableTo target with name', , 'special_spam_shortcut')\n >>> dummy_linker.linkTo(folder)\n 'dummy'\n >>> showEvents()\n ['IObjectCreatedEvent fired for a Shortcut (IShortcut).']\n >>> folder['dummy'].raw_target is dummy\n True\n >>> shortcut_linker.linkTo(folder)\n 'special_spam_shortcut'\n >>> showEvents()\n ['IObjectCopiedEvent fired for a Shortcut (IShortcut).']\n >>> folder['special_spam_shortcut'].raw_target is spam\n True\n >>> dummy_linker.linkTo(folder, 'dummy2')\n 'dummy2'\n >>> showEvents()\n ['IObjectCreatedEvent fired for a Shortcut (IShortcut).']\n >>> folder['dummy2'].raw_target is dummy\n True\n >>> shortcut_linker.linkTo(folder, 'shortcut2')\n 'shortcut2'\n >>> showEvents()\n ['IObjectCopiedEvent fired for a Shortcut (IShortcut).']\n >>> folder['shortcut2'].raw_target is spam\n True\n\nCopying as Linking\n------------------\n\nFor some objects--immutable objects that are primarily stored in a repository,\nfor instance--having a copy gesture actually create a link may be desirable.\nThe adapters module provides an ObjectCopierLinkingAdapter for these use cases.\nWhenever a copy is requested, a link is made instead. This adapter is not\nregistered for any interfaces by default: it is expected to be installed\nselectively.\n\n >>> class IImmutableDummy(IDummy):\n ... pass\n ...\n >>> immutable_dummy = Dummy()\n >>> interface.directlyProvides(immutable_dummy, IImmutableDummy)\n >>> originalcontainer = DummyContainer()\n >>> originalcontainer['immutable_dummy'] = immutable_dummy\n >>> immutable_dummy.__name__ = 'immutable_dummy'\n >>> immutable_dummy.__parent__ = originalcontainer\n >>> component.provideAdapter(\n ... adapters.ObjectCopierLinkingAdapter, adapts=(IImmutableDummy,))\n >>> copier = copypastemove.IObjectCopier(immutable_dummy)\n >>> copier.copyable()\n True\n >>> copier.copyableTo(badcontainer)\n False\n >>> copier.copyableTo(folder)\n True\n >>> copier.copyTo(folder)\n 'immutable_dummy'\n >>> showEvents()\n ['IObjectCreatedEvent fired for a Shortcut (IShortcut).']\n >>> folder['immutable_dummy'].raw_target is immutable_dummy\n True\n\n >>> event.subscribers.pop() is not None # cleanup\n True\n\n================\nShortcut IAdding\n================\n\nThe shortcut adding has a couple of different behaviors than the standard Zope\n3 adding. The differences are to support traversal proxies; and to provide\nmore flexibility for choosing the nextURL after an add.\n\nSupporting Traversal Proxies\n============================\n\nBoth the action method and the nextURL method redirect to the absoluteURL of\nthe container in the zope.app implementation. In the face of shortcuts and\ntraversal proxies, this can generate surprising behavior for users, directing\ntheir URL to a location other than where they thought they were working. The\nshortcut adding changes both of these methods to use traversedURL instead. As\na result, adding to a shortcut of a container returns the user to the\nshortcut, not the absolute path of the container's real location; and \nsubmitting the form of the default view of the adding redirects to within the \ncontext of the traversed shortcut(s), not the absoluteURL.\n\nThe action method changes are pertinent to redirecting to an adding view.\n\n >>> from zc.shortcut import adding, interfaces\n >>> from zope import interface, component\n >>> from zope.location.interfaces import ILocation\n >>> class ISpam(interface.Interface):\n ... pass\n ...\n >>> class Spam(dict):\n ... interface.implements(ISpam, ILocation)\n ... def __init__(self, parent, name):\n ... self.__parent__ = parent\n ... self.__name__ = name\n ...\n >>> from zope.traversing.interfaces import IContainmentRoot\n >>> class DummyContainmentRoot(object):\n ... interface.implements(IContainmentRoot)\n ...\n >>> root = DummyContainmentRoot()\n >>> real_parent = Spam(root, 'real_parent')\n >>> target = Spam(real_parent, 'target')\n >>> from zc.shortcut.shortcut import Shortcut\n >>> shortcut = Shortcut(target)\n >>> shortcut_parent = Spam(root, 'shortcut_parent')\n >>> shortcut.__parent__ = shortcut_parent\n >>> shortcut.__name__ = 'shortcut'\n >>> from zc.shortcut import adapters\n >>> component.provideAdapter(adapters.TraversedURL)\n >>> component.provideAdapter(adapters.FallbackTraversedURL)\n >>> component.provideAdapter(adapters.RootTraversedURL)\n >>> from zope.publisher.interfaces import IRequest\n >>> @component.adapter(interfaces.IAdding, IRequest)\n ... @interface.implementer(interface.Interface)\n ... def dummyAddingView(adding, request):\n ... return 'this is a view'\n ...\n >>> component.provideAdapter(dummyAddingView, name='foo_type')\n >>> from zope.publisher.browser import TestRequest\n >>> request = TestRequest()\n >>> adder = adding.Adding(shortcut.target, request)\n >>> adder.action('foo_type', 'foo_id')\n >>> request.response.getHeader('Location')\n 'http://127.0.0.1/shortcut_parent/shortcut/@@+/foo_type=foo_id'\n\nThe nextURL method changes are pertinent to the default behavior.\n\n >>> adder.contentName = 'foo_id'\n >>> target['foo_id'] = Spam(target, 'foo_id')\n >>> adder.nextURL()\n 'http://127.0.0.1/shortcut_parent/shortcut/@@contents.html'\n\nAdding Flexibility to 'nextURL'\n===============================\n\nThe nextURL method in the zope.app implementation of an adding defines \nprecisely what the nextURL should be: the @@contents.html view of the context.\nThe shortcut adding recreates this behavior, but only after seeing if different\nbehavior has been registered.\n\nnextURL tries to find an adapter named with the constant in\nzc.shortcut.interfaces.NEXT_URL_NAME, providing nothing, for the adding, the\nnew content as found in the container (so it may be a shortcut), and the\ncontext. If an adapter is registered, it should be a string of the nextURL to\nbe used; this value will be returned. If no adapter is registered or the\nregistered adapter returns None, the @@contents.html view of the context is\nreturned.\n\n >>> @component.adapter(interfaces.IAdding, ISpam, ISpam)\n ... @interface.implementer(interface.Interface)\n ... def sillyNextURL(adding, content, container):\n ... return '%s class added \"%s\" to \"%s\"' % (\n ... adding.__class__.__name__,\n ... content.__name__,\n ... container.__name__)\n ...\n >>> component.provideAdapter(sillyNextURL, name=interfaces.NEXT_URL_NAME)\n >>> adder.nextURL()\n 'Adding class added \"foo_id\" to \"target\"'\n\n==================\nShortcut factories\n==================\n\nShortcut factories are factories that place objects in a configured folder and\nthen return a shortcut to the new object. Because they create objects and\nplace them in containers, they fire an object creation event, and usually the\nconfigured folder fires an object added event.\n\n >>> from zc.shortcut import factory, interfaces, Shortcut\n >>> from zope import interface, component, event\n >>> class IDummy(interface.Interface):\n ... pass\n ...\n >>> from zope.location.interfaces import ILocation\n >>> class Dummy(object):\n ... interface.implements(IDummy, ILocation)\n ... def __init__(self, *args, **kwargs):\n ... self.args = args\n ... self.kwargs = kwargs\n ...\n >>> f = factory.Factory(Dummy, 'title', 'description')\n >>> from zope.interface import verify\n >>> verify.verifyObject(interfaces.IShortcutFactory, f)\n True\n\nThe factory always returns an interface declaration for a shortcut from\ngetInterfaces, while getTargetInterfaces returns the declaration for the\ncreated object.\n\n >>> f.getInterfaces() == interface.implementedBy(Shortcut)\n True\n >>> f.getTargetInterfaces() == interface.implementedBy(Dummy)\n True\n\nfactories will fail to create an object if a container has not been \nregistered as a repository.\n\n >>> f() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE\n Traceback (most recent call last):\n ...\n ComponentLookupError: (, <...IContainer>, 'shortcutTargetRepository') \n\nIf we register a repository then the factory will fire a creation event, add \nthe object to the repository, and return a shortcut to the new object.\n\n >>> import zope.app.container.interfaces\n >>> class DummyContainer(dict):\n ... interface.implements(zope.app.container.interfaces.IContainer)\n ...\n >>> repo = DummyContainer()\n >>> @component.adapter(IDummy)\n ... @interface.implementer(zope.app.container.interfaces.IContainer)\n ... def DummyRepoGetter(content):\n ... return repo\n ... \n >>> component.provideAdapter(\n ... DummyRepoGetter, name=interfaces.REPOSITORY_NAME)\n >>> from zope.app.container.contained import NameChooser\n >>> component.provideAdapter(NameChooser, adapts=(interface.Interface,))\n >>> # now, before we actually actually run the adding machinery, we'll\n >>> # set up some machinery that will let us look at events firing\n ...\n >>> heard_events = [] # we'll collect the events here\n >>> event.subscribers.append(heard_events.append)\n >>> import pprint\n >>> from zope import interface\n >>> showEventsStart = 0\n >>> def iname(ob):\n ... return iter(interface.providedBy(ob)).next().__name__\n ...\n >>> def getId(ob):\n ... if ob is None or isinstance(ob, (int, float, basestring, tuple)):\n ... return \"(%r)\" % (ob,)\n ... id = getattr(ob, 'id', getattr(ob, '__name__', None))\n ... if not id:\n ... id = \"a %s (%s)\" % (ob.__class__.__name__, iname(ob))\n ... return id\n ...\n >>> def showEvents(start=None): # to generate a friendly view of events\n ... global showEventsStart\n ... if start is None:\n ... start = showEventsStart\n ... res = [\n ... '%s fired for %s.' % (iname(ev), getId(ev.object))\n ... for ev in heard_events[start:]]\n ... res.sort()\n ... pprint.pprint(res)\n ... showEventsStart = len(heard_events)\n ...\n >>> sc = f(12, 'foo', 'barbaz', sloop=19)\n >>> showEvents()\n ['IObjectCreatedEvent fired for a Dummy (IDummy).']\n >>> repo['Dummy'].args\n (12, 'foo', 'barbaz')\n >>> repo['Dummy'].kwargs\n {'sloop': 19}\n >>> sc.raw_target is repo['Dummy']\n True\n\n >>> event.subscribers.pop() is not None # cleanup\n True\n\nUsing alternate shortcut implementations\n========================================\n\nThe shortcut factory takes an optional keyword parameter to specify\nthe factory used to create the shortcut. By default,\n`zc.shortcut.Shortcut` is used, but more specialized shortcuts may be\nneeded for some applications. This allows the factory to be used\nregardless of the specific shortcut implementation.\n\nLet's create an alternate class that can be used as a shortcut (it\ndoesn't really matter that the example class isn't useful)::\n\n >>> class AlternateShortcut(object):\n ... interface.implements(interfaces.IShortcut)\n ... def __init__(self, object):\n ... self.raw_target = object\n ... self.target = object\n\nNow we can create a factory that creates instances of this class\ninstead of the default shortcut class::\n\n >>> f = factory.Factory(Dummy, 'title', 'description',\n ... shortcut_factory=AlternateShortcut)\n\nUsing the factory returns an instance of our alternate shortcut\nimplementation::\n\n >>> sc = f(1, 2, 3)\n\n >>> isinstance(sc, AlternateShortcut)\n True\n >>> isinstance(sc.raw_target, Dummy)\n True\n >>> sc.target.args\n (1, 2, 3)", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "UNKNOWN", "keywords": "zope zope3", "license": "ZPL 2.1", "maintainer": null, "maintainer_email": null, "name": "zc.shortcut", "package_url": "https://pypi.org/project/zc.shortcut/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/zc.shortcut/", "project_urls": { "Download": "UNKNOWN", "Homepage": "UNKNOWN" }, "release_url": "https://pypi.org/project/zc.shortcut/1.0/", "requires_dist": null, "requires_python": null, "summary": "Symlinks for Zope 3.", "version": "1.0" }, "last_serial": 802206, "releases": { "1.0": [ { "comment_text": "", "digests": { "md5": "903d44b8de6089094a07933034b219c1", "sha256": "d069b7d541de30d333d03600b47688fe697003b9a3b64672e45fee6d45bf38b7" }, "downloads": -1, "filename": "zc.shortcut-1.0-py2.4.egg", "has_sig": false, "md5_digest": "903d44b8de6089094a07933034b219c1", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 55176, "upload_time": "2006-12-07T18:53:38", "url": "https://files.pythonhosted.org/packages/95/c8/8355272249c0fe351113d15b2454fa2b7e968dde11e8253d55292870531a/zc.shortcut-1.0-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "351645968415dc8371efd675d6c7074c", "sha256": "5519ffce9caca1395aa06a8897c6f7d594ab370493c111c06f878898cc4cd219" }, "downloads": -1, "filename": "zc.shortcut-1.0.tar.gz", "has_sig": false, "md5_digest": "351645968415dc8371efd675d6c7074c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 38076, "upload_time": "2006-12-07T18:53:37", "url": "https://files.pythonhosted.org/packages/58/e3/0a692d38d41705d17e764ca7eca2453e6c89d82a6fa118cede757ca9cb80/zc.shortcut-1.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "903d44b8de6089094a07933034b219c1", "sha256": "d069b7d541de30d333d03600b47688fe697003b9a3b64672e45fee6d45bf38b7" }, "downloads": -1, "filename": "zc.shortcut-1.0-py2.4.egg", "has_sig": false, "md5_digest": "903d44b8de6089094a07933034b219c1", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 55176, "upload_time": "2006-12-07T18:53:38", "url": "https://files.pythonhosted.org/packages/95/c8/8355272249c0fe351113d15b2454fa2b7e968dde11e8253d55292870531a/zc.shortcut-1.0-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "351645968415dc8371efd675d6c7074c", "sha256": "5519ffce9caca1395aa06a8897c6f7d594ab370493c111c06f878898cc4cd219" }, "downloads": -1, "filename": "zc.shortcut-1.0.tar.gz", "has_sig": false, "md5_digest": "351645968415dc8371efd675d6c7074c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 38076, "upload_time": "2006-12-07T18:53:37", "url": "https://files.pythonhosted.org/packages/58/e3/0a692d38d41705d17e764ca7eca2453e6c89d82a6fa118cede757ca9cb80/zc.shortcut-1.0.tar.gz" } ] }