跳转到主要内容

为Zope 3提供的符号链接。

项目描述

快捷方式

快捷方式是允许其他对象(它们的目标)似乎位于目标实际位置之外的对象。它们类似于类Unix操作系统中的一种符号链接。

创建快捷方式

通过使用目标、父对象和名称调用Shortcut类的构造函数来创建快捷方式

>>> 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'

快捷方式提供了一个属性来访问其目标

>>> sc.target
<__builtin__.MyTarget instance at ...>

快捷方式的__parent____name__与其目标独立

>>> sc.__parent__
'My Parent'
>>> sc.target.__parent__
'Original Parent'

>>> sc.__name__
'My Name'
>>> sc.target.__name__
'Original Name'

但是目标知道遍历父对象、遍历名称和快捷方式。这使得快捷方式可以拥有可能被视图和其他渲染或使用目标组件访问的注解。

>>> sc.target.__traversed_parent__
'My Parent'
>>> sc.target.__traversed_name__
'My Name'
>>> sc.target.__shortcut__ is sc
True

有关更多详细信息,请参阅proxy.txt和adapters.txt

适配器

适配器提供,允许快捷方式在遍历时表现得像目标一样。

ITraversable

首先,我们必须导入我们将要工作的接口

>>> 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 interfaces

如果我们有一个具有根的目标对象

>>> 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')

目标对象为目标和请求提供了一个多适配器到 ITraversable,以便它可以进行遍历

>>> class SpamTraversableAdapter:
...     interface.implements(ITraversable)
...     component.adapts(ISpam, IRequest)
...     def __init__(self, spam, request):
...         self.spam = spam
>>> component.provideAdapter(SpamTraversableAdapter, name='view')

有一个适配器可以在快捷方式和请求被适配到 ITraversable 时返回目标对象适配到 ITraversable。例如,如果我们创建了一个指向我们的目标的快捷方式

>>> from zc.shortcut.shortcut import Shortcut
>>> shortcut = Shortcut(target)
>>> shortcut_parent = Spam(root, 'shortcut_parent')
>>> shortcut.__parent__ = shortcut_parent
>>> shortcut.__name__ = 'shortcut'

然后使用请求调用适配器

>>> from zope.publisher.browser import TestRequest
>>> from zc.shortcut.adapters import ShortcutTraversalAdapterFactory

>>> request = TestRequest()
>>> adapter = ShortcutTraversalAdapterFactory(shortcut, request)

结果是目标的 ITraversal 适配器

>>> adapter
<...SpamTraversableAdapter instance at...>

>>> adapter.spam
<...Spam instance at...>

快捷方式遍历

快捷方式遍历非常复杂。首先考虑遍历快捷方式然后遍历以获取默认视图('index.html')的情况。在这种情况下,快捷方式将可用于视图,并且面包屑和其他关注遍历对象方式的视图元素只需查看快捷方式的 __parent__,或目标代理的 __traversed_parent__。这还不错。

如果通过快捷方式遍历到另一个内容对象,事情就更有趣了。一个简单的实现将通过将其转换为它的目标来遍历快捷方式,然后遍历目标以获取包含的内容对象。然而,内容对象的视图将不知道获取内容对象所使用的遍历路径:它们只有内容对象的 __parent__,这是快捷方式的目标 没有任何目标代理。从那里,它们将能够找到目标父对象,但不能找到遍历快捷方式的父对象。关注遍历路径的面包屑和其他组件将损坏。

为了解决这个用例,遍历快捷方式需要遍历目标,然后将得到的对象包装在另一个目标代理中,该代理将快捷方式的目标代理作为其遍历父对象引用。

遍历快捷方式并找到另一个快捷方式稍微复杂一些。在这种情况下,快捷方式的目标代理的父对象应该是快捷方式代理的父对象。

为 IPublishTraverse 提供了两个适配器:一个用于快捷方式,一个用于遍历代理。如果一个遍历目标没有提供 IPublishTraverse,那么它应该提供一个适配器

>>> 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)

如果提供了,则将使用适配器进行遍历

>>> 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.

注意,遍历的对象有一个遍历代理(但没有目标代理)。

>>> 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
True

为了进一步向下遍历并保留遍历信息,我们需要注册ProxyPublishTraverseAdapter。请注意,这次我们还将遍历到快捷方式,并从快捷方式和其目标查看遍历路径。

>>> 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
True

如果目标实现了IPublishTraverse接口...

>>> 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]

...那么将直接调用它的

publishTraverse()

>>> 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.

在快捷方式处结束遍历

当一个快捷方式是URL遍历的目标,而不是路径上的一个节点时,必须调用目标对象的叶子节点处理,以便快捷方式的行为与直接访问时相同。

当请求的URL表示一个对象(而不是一个视图)时,发布者使用IBrowserPublisher接口的browserDefault()方法来确定如何处理对象。此方法返回一个对象和一个应遍历的路径元素序列。

对于快捷方式,这是通过委托给快捷方式的目标来处理的,用代理替换目标,以确保遍历URL视图和面包屑仍然正常工作。

让我们先定义一个针对ISpam对象的IBrowserPublisher

>>> 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',))

遍历URL

如果遍历了快捷方式,绝对URL可能会将用户带到意外的位置——到对象的实际位置,而不是遍历的位置。为了获取遍历URL,适配器模块提供了一个traversedURL函数,快捷方式包也从其__init__.py提供了它。

例如,给定上述描述的倒数第二个快捷方式遍历的结果,traversedURL返回一个类似于绝对URL的URL,但遇到目标代理时,将使用遍历父级而不是实际父级。

>>> 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'

与absoluteURL一样,返回的值是HTML转义的。

>>> shortcut_parent.__name__ = 'shortcut parent'
>>> adapters.traversedURL(res, request)
'http://127.0.0.1/shortcut%20parent/shortcut/child_spam/child_shortcut'

与absoluteURL一样,traversedURL注册为视图,因此可以在页面模板中使用(如在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'

快捷方式添加

快捷方式添加与标准Zope 3添加有几个不同的行为。这些差异是为了支持遍历代理;以及在添加后提供更多选择nextURL的灵活性。

支持遍历代理

在zope.app实现中,动作方法和nextURL方法都重定向到容器的绝对URL。面对快捷方式和遍历代理时,这可能会使用户产生意外的行为,将他们的URL重定向到他们认为不在的工作位置。快捷方式添加将这两个方法都改为使用遍历URL。因此,向容器的快捷方式添加将用户带回到快捷方式,而不是容器实际位置的绝对路径;并提交添加视图的默认视图表单将重定向到遍历快捷方式(s)的上下文中,而不是绝对URL。

动作方法更改与重定向到添加视图相关。

>>> 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'

nextURL方法更改与默认行为相关。

>>> adder.contentName = 'foo_id'
>>> target['foo_id'] = Spam(target, 'foo_id')
>>> adder.nextURL()
'http://127.0.0.1/shortcut_parent/shortcut/@@contents.html'

为“nextURL”添加灵活性

在zope.app添加实现中,nextURL方法精确地定义了nextURL应该是什么:上下文的@@contents.html视图。快捷方式添加重新创建了这种行为,但在查看是否已注册不同的行为之后。

nextURL试图找到一个名为zc.shortcut.interfaces.NEXT_URL_NAME常量的适配器,对于添加,它提供新内容(它可能是一个快捷方式),以及上下文。如果已注册适配器,则它应该是要使用的nextURL的字符串;将返回此值。如果没有注册适配器或注册的适配器返回None,则返回上下文的@@contents.html视图。

>>> @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"'

快捷方式工厂

快捷方式工厂是将对象放置在配置的文件夹中,然后返回对新对象的快捷方式的工厂。因为它们创建对象并将它们放置在容器中,所以它们触发对象创建事件,通常配置的文件夹触发对象添加事件。

>>> 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)
True

工厂总是从getInterfaces返回快捷方式的接口声明,而getTargetInterfaces返回创建对象的声明。

>>> f.getInterfaces() == interface.implementedBy(Shortcut)
True
>>> f.getTargetInterfaces() == interface.implementedBy(Dummy)
True

如果没有将容器注册为仓库,工厂将无法创建对象。

>>> f() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
ComponentLookupError: (<Dummy...>, <...IContainer>, 'shortcutTargetRepository')

如果我们注册了一个仓库,那么工厂将触发创建事件,将对象添加到仓库中,并返回对新对象的快捷方式。

>>> 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
True

使用替代快捷方式实现

快捷方式工厂接受一个可选的关键字参数,用于指定创建快捷方式的工厂。默认情况下,使用zc.shortcut.Shortcut,但对于某些应用程序可能需要更专业的快捷方式。这允许工厂在具体快捷方式实现未知的情况下使用。

让我们创建一个可以作为快捷方式使用的替代类(示例类实际上并不重要)

>>> class AlternateShortcut(object):
...     interface.implements(interfaces.IShortcut)
...     def __init__(self, object):
...         self.raw_target = object
...         self.target = object

现在我们可以创建一个工厂,它创建此类的实例而不是默认快捷方式类

>>> f = factory.Factory(Dummy, 'title', 'description',
...                     shortcut_factory=AlternateShortcut)

使用该工厂返回我们的替代快捷方式实现的实例

>>> sc = f(1, 2, 3)

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

项目详情


下载文件

下载适用于您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分布

zc.shortcut-1.0.tar.gz (38.1 kB 查看哈希值)

上传时间 源代码

构建分发

zc.shortcut-1.0-py2.4.egg (55.2 kB 查看哈希值)

上传时间 源代码

由以下支持