diff --git a/browser/fields.py b/browser/fields.py
new file mode 100644
index 0000000..c745656
--- /dev/null
+++ b/browser/fields.py
@@ -0,0 +1,110 @@
+#############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser-Presentation related Fields.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import zope.schema
+from zope.component.exceptions import ComponentLookupError
+from zope.configuration.exceptions import ConfigurationError
+from zope.configuration.fields import GlobalObject
+from zope.interface.interfaces import IInterface
+from zope.app.publisher.interfaces.browser import IMenuItemType
+
+from zope.app import zapi
+
+
+class MenuField(GlobalObject):
+ r"""This fields represents a menu (item type).
+
+ Besides being able to look up the menu by importing it, we also try
+ to look up the name in the utility service.
+
+ >>> from zope.interface import directlyProvides
+ >>> from zope.interface.interface import InterfaceClass
+
+ >>> menu1 = InterfaceClass('menu1', (),
+ ... __doc__='Menu Item Type: menu1',
+ ... __module__='zope.app.menus')
+ >>> directlyProvides(menu1, IMenuItemType)
+
+ >>> menus = None
+ >>> class Resolver(object):
+ ... def resolve(self, path):
+ ... if path.startswith('zope.app.menus') and \
+ ... hasattr(menus, 'menu1') or \
+ ... path == 'zope.app.component.menus.menu1':
+ ... return menu1
+ ... raise ConfigurationError, 'menu1'
+
+ >>> field = MenuField()
+ >>> field = field.bind(Resolver())
+
+ Test 1: Import the menu
+ -----------------------
+
+ >>> field.fromUnicode('zope.app.component.menus.menu1') is menu1
+ True
+
+ Test 2: We have a shortcut name. Import the menu from `zope.app.menus1`.
+ ------------------------------------------------------------------------
+
+ >>> from types import ModuleType as module
+ >>> import sys
+ >>> menus = module('menus')
+ >>> old = sys.modules.get('zope.app.menus', None)
+ >>> sys.modules['zope.app.menus'] = menus
+ >>> setattr(menus, 'menu1', menu1)
+
+ >>> field.fromUnicode('menu1') is menu1
+ True
+
+ >>> if old is not None:
+ ... sys.modules['zope.app.menus'] = old
+
+ Test 3: Get the menu from the utility service
+ ---------------------------------------------
+
+ >>> from zope.app.testing import ztapi
+ >>> ztapi.provideUtility(IMenuItemType, menu1, 'menu1')
+
+ >>> field.fromUnicode('menu1') is menu1
+ True
+ """
+
+ def fromUnicode(self, u):
+ name = str(u.strip())
+
+ try:
+ value = zapi.queryUtility(IMenuItemType, name)
+ except ComponentLookupError:
+ # The component architecture is not up and running.
+ pass
+ else:
+ if value is not None:
+ self.validate(value)
+ return value
+
+ try:
+ value = self.context.resolve('zope.app.menus.'+name)
+ except ConfigurationError, v:
+ try:
+ value = self.context.resolve(name)
+ except ConfigurationError, v:
+ raise zope.schema.ValidationError(v)
+
+ self.validate(value)
+ return value
diff --git a/browser/i18nresourcemeta.py b/browser/i18nresourcemeta.py
new file mode 100644
index 0000000..4fe981a
--- /dev/null
+++ b/browser/i18nresourcemeta.py
@@ -0,0 +1,124 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser configuration code
+
+$Id$
+"""
+from zope.configuration.exceptions import ConfigurationError
+from zope.interface import Interface
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+from zope.security.proxy import Proxy
+from zope.security.checker import CheckerPublic, Checker
+
+from zope.app import zapi
+from zope.app.component.metaconfigure import handler
+from zope.app.publisher.fileresource import File, Image
+
+from i18nfileresource import I18nFileResourceFactory
+
+
+class I18nResource(object):
+
+ type = IBrowserRequest
+ default_allowed_attributes = '__call__'
+
+ def __init__(self, _context, name=None, defaultLanguage='en',
+ layer=IDefaultBrowserLayer, permission=None):
+ self._context = _context
+ self.name = name
+ self.defaultLanguage = defaultLanguage
+ self.layer = layer
+ self.permission = permission
+ self.__data = {}
+ self.__format = None
+
+ def translation(self, _context, language, file=None, image=None):
+
+ if file is not None and image is not None:
+ raise ConfigurationError(
+ "Can't use more than one of file, and image "
+ "attributes for resource directives"
+ )
+ elif file is not None:
+ if self.__format is not None and self.__format != File:
+ raise ConfigurationError(
+ "Can't use both files and images in the same "
+ "i18n-resource directive"
+ )
+ self.__data[language] = File(_context.path(file), self.name)
+ self.__format = File
+ elif image is not None:
+ if self.__format is not None and self.__format != Image:
+ raise ConfigurationError(
+ "Can't use both files and images in the same "
+ "i18n-resource directive"
+ )
+ self.__data[language] = Image(_context.path(image), self.name)
+ self.__format = Image
+ else:
+ raise ConfigurationError(
+ "At least one of the file, and image "
+ "attributes for resource directives must be specified"
+ )
+
+ return ()
+
+
+ def __call__(self, require = None):
+ if self.name is None:
+ return ()
+
+ if not self.__data.has_key(self.defaultLanguage):
+ raise ConfigurationError(
+ "A translation for the default language (%s) "
+ "must be specified" % self.defaultLanguage
+ )
+
+ permission = self.permission
+ factory = I18nFileResourceFactory(self.__data, self.defaultLanguage)
+
+ if permission:
+ if require is None:
+ require = {}
+
+ if permission == 'zope.Public':
+ permission = CheckerPublic
+
+ if require:
+ checker = Checker(require)
+
+ factory = self._proxyFactory(factory, checker)
+
+ self._context.action(
+ discriminator = ('i18n-resource', self.name, self.type, self.layer),
+ callable = handler,
+ args = ('provideAdapter',
+ (self.layer,), Interface, self.name, factory,
+ self._context.info)
+ )
+
+
+ def _proxyFactory(self, factory, checker):
+ def proxyView(request,
+ factory=factory, checker=checker):
+ resource = factory(request)
+
+ # We need this in case the resource gets unwrapped and
+ # needs to be rewrapped
+ resource.__Security_checker__ = checker
+
+ return Proxy(resource, checker)
+
+ return proxyView
diff --git a/browser/icon.py b/browser/icon.py
new file mode 100644
index 0000000..2525656
--- /dev/null
+++ b/browser/icon.py
@@ -0,0 +1,109 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Icon support
+
+$Id$
+"""
+import os
+import re
+
+from zope.interface import Interface
+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+from zope.configuration.exceptions import ConfigurationError
+
+from zope.app import zapi
+from zope.app.component.interface import provideInterface
+from zope.app.component.metaconfigure import handler
+from zope.app.publisher.browser import metaconfigure
+from zope.app.traversing.namespace import getResource
+
+IName = re.compile('I[A-Z][a-z]')
+
+class IconView(object):
+
+ def __init__(self, context, request, rname, alt):
+ self.context = context
+ self.request = request
+ self.rname = rname
+ self.alt = alt
+
+ def __call__(self):
+ # The context is important here, since it becomes the parent of the
+ # icon, which is needed to generate the absolute URL.
+ resource = getResource(self.context, self.rname, self.request)
+ src = resource()
+
+ return (''
+ % (src, self.alt))
+
+ def url(self):
+ resource = getResource(self.context, self.rname, self.request)
+ src = resource()
+ return src
+
+class IconViewFactory(object):
+
+ def __init__(self, rname, alt):
+ self.rname = rname
+ self.alt = alt
+
+ def __call__(self, context, request):
+ return IconView(context, request, self.rname, self.alt)
+
+def IconDirective(_context, name, for_, file=None, resource=None,
+ layer=IDefaultBrowserLayer, alt=None):
+
+ iname = for_.getName()
+
+ if alt is None:
+ alt = iname
+ if IName.match(alt):
+ alt = alt[1:] # Remove leading 'I'
+
+ if file is not None and resource is not None:
+ raise ConfigurationError(
+ "Can't use more than one of file, and resource "
+ "attributes for icon directives"
+ )
+ elif file is not None:
+ resource = '-'.join(for_.__module__.split('.'))
+ resource = "%s-%s-%s" % (resource, iname, name)
+ ext = os.path.splitext(file)[1]
+ if ext:
+ resource += ext
+ metaconfigure.resource(_context, image=file,
+ name=resource, layer=layer)
+ elif resource is None:
+ raise ConfigurationError(
+ "At least one of the file, and resource "
+ "attributes for resource directives must be specified"
+ )
+
+ vfactory = IconViewFactory(resource, alt)
+
+ _context.action(
+ discriminator = ('view', name, vfactory, layer),
+ callable = handler,
+ args = ('provideAdapter',
+ (for_, layer), Interface, name, vfactory, _context.info)
+ )
+
+ _context.action(
+ discriminator = None,
+ callable = provideInterface,
+ args = (for_.__module__+'.'+for_.getName(),
+ for_)
+ )
+
+
diff --git a/browser/menu.py b/browser/menu.py
new file mode 100644
index 0000000..ec8617b
--- /dev/null
+++ b/browser/menu.py
@@ -0,0 +1,556 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Menu Registration code.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+from zope.component.interfaces import IFactory
+from zope.configuration.exceptions import ConfigurationError
+from zope.interface import Interface, implements, classImplements
+from zope.interface import directlyProvides, providedBy
+from zope.interface.interface import InterfaceClass
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.security import checkPermission
+from zope.security.checker import InterfaceChecker, CheckerPublic
+from zope.security.interfaces import Unauthorized, Forbidden
+from zope.security.proxy import ProxyFactory, removeSecurityProxy
+
+from zope.app import zapi
+from zope.app.component.interface import provideInterface
+from zope.app.component.metaconfigure import adapter, proxify
+from zope.app.pagetemplate.engine import Engine
+from zope.app.publication.browser import PublicationTraverser
+from zope.app.publisher.browser import BrowserView
+from zope.app.publisher.interfaces.browser import IMenuAccessView
+from zope.app.publisher.interfaces.browser import IBrowserMenuItem
+from zope.app.publisher.interfaces.browser import IMenuItemType
+
+# Create special modules that contain all menu item types
+from types import ModuleType as module
+import sys
+menus = module('menus')
+sys.modules['zope.app.menus'] = menus
+
+
+_order_counter = {}
+
+class BrowserMenuItem(BrowserView):
+ """Browser Menu Item Base Class
+
+ >>> from zope.publisher.browser import TestRequest
+
+ >>> class ITestInterface(Interface):
+ ... pass
+
+ >>> from zope.publisher.interfaces.browser import IBrowserPublisher
+ >>> class TestObject(object):
+ ... implements(IBrowserPublisher, ITestInterface)
+ ...
+ ... def foo(self):
+ ... pass
+ ...
+ ... def browserDefault(self, r):
+ ... return self, ()
+ ...
+ ... def publishTraverse(self, request, name):
+ ... if name.startswith('f'):
+ ... raise Forbidden, name
+ ... if name.startswith('u'):
+ ... raise Unauthorized, name
+ ... return self.foo
+
+
+ Since the `BrowserMenuItem` is just a view, we can initiate it with an
+ object and a request.
+
+ >>> item = BrowserMenuItem(TestObject(), TestRequest())
+
+ Now we add a title and description and see whether we can then access the
+ value. Note that these assignments are always automatically done by the
+ framework.
+
+ >>> item.title = u'Item 1'
+ >>> item.title
+ u'Item 1'
+
+ >>> item.description = u'This is Item 1.'
+ >>> item.description
+ u'This is Item 1.'
+
+ >>> item.order
+ 0
+ >>> item.order = 1
+ >>> item.order
+ 1
+
+ >>> item.icon is None
+ True
+ >>> item.icon = u'/@@/icon.png'
+ >>> item.icon
+ u'/@@/icon.png'
+
+ Since there is no permission or view specified yet, the menu item should
+ be available and not selected.
+
+ >>> item.available()
+ True
+ >>> item.selected()
+ False
+
+ There are two ways to deny availability of a menu item: (1) the current
+ user does not have the correct permission to access the action or the menu
+ item itself, or (2) the filter returns `False`, in which case the menu
+ item should also not be shown.
+
+ >>> from zope.app.testing import ztapi
+ >>> from zope.app.security.interfaces import IPermission
+ >>> from zope.app.security.permission import Permission
+ >>> perm = Permission('perm', 'Permission')
+ >>> ztapi.provideUtility(IPermission, perm, 'perm')
+
+ >>> class ParticipationStub(object):
+ ... principal = 'principal'
+ ... interaction = None
+
+ >>> from zope.security.management import newInteraction, endInteraction
+
+ In the first case, the permission of the menu item was explicitely
+ specified. Make sure that the user needs this permission to make the menu
+ item available.
+
+ >>> item.permission = perm
+
+ Now, we are not setting any user. This means that the menu item should be
+ available.
+
+ >>> endInteraction()
+ >>> newInteraction()
+ >>> item.available()
+ True
+
+ Now we specify a principal that does not have the specified permission.
+
+ >>> endInteraction()
+ >>> newInteraction(ParticipationStub())
+ >>> item.available()
+ False
+
+ In the second case, the permission is not explicitely defined and the
+ availability is determined by the permission required to access the
+ action.
+
+ >>> item.permission = None
+
+ All views starting with 'f' are forbidden, the ones with 'u' are
+ unauthorized and all others are allowed.
+
+ >>> item.action = u'f'
+ >>> item.available()
+ False
+ >>> item.action = u'u'
+ >>> item.available()
+ False
+ >>> item.action = u'a'
+ >>> item.available()
+ True
+
+ Now let's test filtering. If the filter is specified, it is assumed to be
+ a TALES obejct.
+
+ >>> item.filter = Engine.compile('not:context')
+ >>> item.available()
+ False
+ >>> item.filter = Engine.compile('context')
+ >>> item.available()
+ True
+
+ Finally, make sure that the menu item can be selected.
+
+ >>> item.request = TestRequest(SERVER_URL='http://127.0.0.1/@@view.html',
+ ... PATH_INFO='/@@view.html')
+
+ >>> item.selected()
+ False
+ >>> item.action = u'view.html'
+ >>> item.selected()
+ True
+ >>> item.action = u'@@view.html'
+ >>> item.selected()
+ True
+ >>> item.request = TestRequest(
+ ... SERVER_URL='http://127.0.0.1/++view++view.html',
+ ... PATH_INFO='/++view++view.html')
+ >>> item.selected()
+ True
+ >>> item.action = u'otherview.html'
+ >>> item.selected()
+ False
+ """
+ implements(IBrowserMenuItem)
+
+ # See zope.app.publisher.interfaces.browser.IBrowserMenuItem
+ title = u''
+ description = u''
+ action = u''
+ extra = None
+ order = 0
+ permission = None
+ filter = None
+ icon = None
+ _for = Interface
+
+ def available(self):
+ """See zope.app.publisher.interfaces.browser.IBrowserMenuItem"""
+ # Make sure we have the permission needed to access the menu's action
+ if self.permission is not None:
+ # If we have an explicit permission, check that we
+ # can access it.
+ if not checkPermission(self.permission, self.context):
+ return False
+
+ elif self.action != u'':
+ # Otherwise, test access by attempting access
+ path = self.action
+ l = self.action.find('?')
+ if l >= 0:
+ path = self.action[:l]
+
+ traverser = PublicationTraverser()
+ try:
+ view = traverser.traverseRelativeURL(
+ self.request, self.context, path)
+ # TODO:
+ # tickle the security proxy's checker
+ # we're assuming that view pages are callable
+ # this is a pretty sound assumption
+ view.__call__
+ except (Unauthorized, Forbidden):
+ return False
+
+ # Make sure that we really want to see this menu item
+ if self.filter is not None:
+
+ try:
+ include = self.filter(Engine.getContext(
+ context = self.context,
+ nothing = None,
+ request = self.request,
+ modules = ProxyFactory(sys.modules),
+ ))
+ except Unauthorized:
+ return False
+ else:
+ if not include:
+ return False
+
+ return True
+
+
+ def selected(self):
+ """See zope.app.publisher.interfaces.browser.IBrowserMenuItem"""
+ request_url = self.request.getURL()
+
+ normalized_action = self.action
+ if self.action.startswith('@@'):
+ normalized_action = self.action[2:]
+
+ if request_url.endswith('/'+normalized_action):
+ return True
+ if request_url.endswith('/++view++'+normalized_action):
+ return True
+ if request_url.endswith('/@@'+normalized_action):
+ return True
+
+ return False
+
+
+def getMenu(menuItemType, object, request, max=999999):
+ """Return menu item entries in a TAL-friendly form.
+
+ >>> from zope.publisher.browser import TestRequest
+
+ >>> from zope.app.testing import ztapi
+ >>> def defineMenuItem(menuItemType, for_, title, action=u'', order=0):
+ ... newclass = type(title, (BrowserMenuItem,),
+ ... {'title':title, 'action':action, 'order':order})
+ ... classImplements(newclass, menuItemType)
+ ... ztapi.provideAdapter((for_, IBrowserRequest), menuItemType,
+ ... newclass, title)
+
+ >>> class IFoo(Interface): pass
+ >>> class IFooBar(IFoo): pass
+ >>> class IBlah(Interface): pass
+
+ >>> class FooBar(object):
+ ... implements(IFooBar)
+
+ >>> class Menu1(Interface): pass
+ >>> class Menu2(Interface): pass
+
+ >>> defineMenuItem(Menu1, IFoo, 'i1')
+ >>> defineMenuItem(Menu1, IFooBar, 'i2')
+ >>> defineMenuItem(Menu1, IBlah, 'i3')
+ >>> defineMenuItem(Menu2, IFoo, 'i4')
+ >>> defineMenuItem(Menu2, IFooBar, 'i5')
+ >>> defineMenuItem(Menu2, IBlah, 'i6')
+ >>> defineMenuItem(Menu1, IFoo, 'i7', order=-1)
+
+ >>> items = getMenu(Menu1, FooBar(), TestRequest())
+ >>> [item['title'] for item in items]
+ ['i7', 'i1', 'i2']
+ >>> items = getMenu(Menu2, FooBar(), TestRequest())
+ >>> [item['title'] for item in items]
+ ['i4', 'i5']
+ >>> items = getMenu(Menu2, FooBar(), TestRequest())
+ >>> [item['title'] for item in items]
+ ['i4', 'i5']
+ """
+ result = []
+ for name, item in zapi.getAdapters((object, request), menuItemType):
+ if item.available():
+ result.append(item)
+ if len(result) >= max:
+ break
+
+ # Now order the result. This is not as easy as it seems.
+ #
+ # (1) Look at the interfaces and put the more specific menu entries to the
+ # front.
+ # (2) Sort unabigious entries by order and then by title.
+ ifaces = list(providedBy(removeSecurityProxy(object)).__iro__)
+ result = [
+ (ifaces.index(item._for or Interface), item.order, item.title, item)
+ for item in result]
+ result.sort()
+
+ result = [{'title': item.title,
+ 'description': item.description,
+ 'action': item.action,
+ 'selected': (item.selected() and u'selected') or u'',
+ 'icon': item.icon,
+ 'extra': item.extra}
+ for index, order, title, item in result]
+ return result
+
+
+def getFirstMenuItem(menuItemType, object, request):
+ """Get the first item of a menu."""
+ items = getMenu(menuItemType, object, request)
+ if items:
+ return items[0]
+ return None
+
+class MenuAccessView(BrowserView):
+ """A view allowing easy access to menus."""
+ implements(IMenuAccessView)
+
+ def __getitem__(self, typeString):
+ # Convert the menu item type identifyer string to the type interface
+ menuItemType = zapi.getUtility(IMenuItemType, typeString)
+ return getMenu(menuItemType, self.context, self.request)
+
+
+def menuDirective(_context, id=None, interface=None,
+ title=u'', description=u''):
+ """Provides a new menu (item type).
+
+ >>> import pprint
+ >>> class Context(object):
+ ... info = u'doc'
+ ... def __init__(self): self.actions = []
+ ... def action(self, **kw): self.actions.append(kw)
+
+ Possibility 1: The Old Way
+ --------------------------
+
+ >>> context = Context()
+ >>> menuDirective(context, u'menu1', title=u'Menu 1')
+ >>> iface = context.actions[0]['args'][1]
+ >>> iface.getName()
+ u'menu1'
+ >>> iface.getTaggedValue('title')
+ u'Menu 1'
+ >>> iface.getTaggedValue('description')
+ u''
+
+ >>> hasattr(sys.modules['zope.app.menus'], 'menu1')
+ True
+
+ >>> del sys.modules['zope.app.menus'].menu1
+
+ Possibility 2: Just specify an interface
+ ----------------------------------------
+
+ >>> class menu1(Interface):
+ ... pass
+
+ >>> context = Context()
+ >>> menuDirective(context, interface=menu1)
+ >>> context.actions[0]['args'][1] is menu1
+ True
+
+ Possibility 3: Specify an interface and an id
+ ---------------------------------------------
+
+ >>> context = Context()
+ >>> menuDirective(context, id='menu1', interface=menu1)
+ >>> context.actions[0]['args'][1] is menu1
+ True
+ >>> import pprint
+ >>> pprint.pprint([action['discriminator'] for action in context.actions])
+ [('browser', 'MenuItemType', 'zope.app.publisher.browser.menu.menu1'),
+ ('interface', 'zope.app.publisher.browser.menu.menu1'),
+ ('browser', 'MenuItemType', 'menu1')]
+
+ Here are some disallowed configurations.
+
+ >>> context = Context()
+ >>> menuDirective(context)
+ Traceback (most recent call last):
+ ...
+ ConfigurationError: You must specify the 'id' or 'interface' attribute.
+ >>> menuDirective(context, title='Menu 1')
+ Traceback (most recent call last):
+ ...
+ ConfigurationError: You must specify the 'id' or 'interface' attribute.
+ """
+ if id is None and interface is None:
+ raise ConfigurationError(
+ "You must specify the 'id' or 'interface' attribute.")
+
+ if interface is None:
+ interface = InterfaceClass(id, (),
+ __doc__='Menu Item Type: %s' %id,
+ __module__='zope.app.menus')
+ # Add the menu item type to the `menus` module.
+ # Note: We have to do this immediately, so that directives using the
+ # MenuField can find the menu item type.
+ setattr(menus, id, interface)
+ path = 'zope.app.menus.' + id
+ else:
+ path = interface.__module__ + '.' + interface.getName()
+
+ # If an id was specified, make this menu available under this id.
+ # Note that the menu will be still available under its path, since it
+ # is an adapter, and the `MenuField` can resolve paths as well.
+ if id is None:
+ id = path
+ else:
+ # Make the interface available in the `zope.app.menus` module, so
+ # that other directives can find the interface under the name
+ # before the CA is setup.
+ _context.action(
+ discriminator = ('browser', 'MenuItemType', path),
+ callable = provideInterface,
+ args = (path, interface, IMenuItemType, _context.info)
+ )
+ setattr(menus, id, interface)
+
+ # Set the title and description of the menu item type
+ interface.setTaggedValue('title', title)
+ interface.setTaggedValue('description', description)
+
+ # Register the layer interface as an interface
+ _context.action(
+ discriminator = ('interface', path),
+ callable = provideInterface,
+ args = (path, interface),
+ kw = {'info': _context.info}
+ )
+
+ # Register the menu item type interface as an IMenuItemType
+ _context.action(
+ discriminator = ('browser', 'MenuItemType', id),
+ callable = provideInterface,
+ args = (id, interface, IMenuItemType, _context.info)
+ )
+
+
+def menuItemDirective(_context, menu, for_,
+ action, title, description=u'', icon=None, filter=None,
+ permission=None, extra=None, order=0):
+ """Register a single menu item.
+
+ See the `menuItemsDirective` class for tests.
+ """
+ return menuItemsDirective(_context, menu, for_).menuItem(
+ _context, action, title, description, icon, filter,
+ permission, extra, order)
+
+
+class menuItemsDirective(object):
+ """Register several menu items for a particular menu.
+
+ >>> class Context(object):
+ ... info = u'doc'
+ ... def __init__(self): self.actions = []
+ ... def action(self, **kw): self.actions.append(kw)
+
+ >>> class TestMenuItemType(Interface): pass
+ >>> class ITest(Interface): pass
+
+ >>> context = Context()
+ >>> items = menuItemsDirective(context, TestMenuItemType, ITest)
+ >>> context.actions
+ []
+ >>> items.menuItem(context, u'view.html', 'View')
+ >>> context.actions[0]['args'][0]
+ 'provideAdapter'
+ >>> len(context.actions)
+ 4
+ """
+ def __init__(self, _context, menu, for_):
+ self.for_ = for_
+ self.menuItemType = menu
+
+ def menuItem(self, _context, action, title, description=u'',
+ icon=None, filter=None, permission=None, extra=None, order=0):
+
+ if filter is not None:
+ filter = Engine.compile(filter)
+
+ if order == 0:
+ order = _order_counter.get(self.for_, 1)
+ _order_counter[self.for_] = order + 1
+
+ def MenuItemFactory(context, request):
+ item = BrowserMenuItem(context, request)
+ item.title = title
+ item.description = description
+ item.icon = icon
+ item.action = action
+ item.filter = filter
+ item.permission = permission
+ item.extra = extra
+ item.order = order
+ item._for = self.for_
+
+ if permission is not None:
+ if permission == 'zope.Public':
+ perm = CheckerPublic
+ else:
+ perm = permission
+ checker = InterfaceChecker(IBrowserMenuItem, perm)
+ item = proxify(item, checker)
+
+ return item
+ MenuItemFactory.factory = BrowserMenuItem
+
+ adapter(_context, (MenuItemFactory,), self.menuItemType,
+ (self.for_, IBrowserRequest), name=title)
+
+ def __call__(self, _context):
+ # Nothing to do.
+ pass
diff --git a/browser/metaconfigure.py b/browser/metaconfigure.py
new file mode 100644
index 0000000..d007c68
--- /dev/null
+++ b/browser/metaconfigure.py
@@ -0,0 +1,370 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser configuration code
+
+$Id$
+"""
+from zope.component.interfaces import IDefaultViewName
+from zope.configuration.exceptions import ConfigurationError
+from zope.interface.interface import InterfaceClass
+from zope.publisher.interfaces.browser import ILayer, ISkin, IDefaultSkin
+from zope.publisher.interfaces.browser import IBrowserRequest
+
+from zope.app import zapi
+from zope.app.component.metaconfigure import handler
+from zope.app.container.interfaces import IAdding
+from zope.app.publisher.browser.menu import menuItemDirective
+from zope.app.component.contentdirective import ContentDirective
+from zope.app.publisher.interfaces.browser import AddMenu
+
+# referred to through ZCML
+from zope.app.publisher.browser.resourcemeta import resource
+from zope.app.publisher.browser.resourcemeta import resourceDirectory
+from zope.app.publisher.browser.i18nresourcemeta import I18nResource
+from zope.app.publisher.browser.viewmeta import view
+from zope.app.component.interface import provideInterface
+
+# Create special modules that contain all layers and skins
+from types import ModuleType as module
+import sys
+layers = module('layers')
+sys.modules['zope.app.layers'] = layers
+
+skins = module('skins')
+sys.modules['zope.app.skins'] = skins
+
+
+def layer(_context, name=None, interface=None, base=ILayer):
+ """Provides a new layer.
+
+ >>> class Context(object):
+ ... info = u'doc'
+ ... def __init__(self): self.actions = []
+ ... def action(self, **kw): self.actions.append(kw)
+
+ Possibility 1: The Old Way
+ --------------------------
+
+ >>> context = Context()
+ >>> layer(context, u'layer1')
+ >>> iface = context.actions[0]['args'][1]
+ >>> iface.getName()
+ u'layer1'
+ >>> iface.__bases__
+ (,)
+ >>> hasattr(sys.modules['zope.app.layers'], 'layer1')
+ True
+
+ >>> del sys.modules['zope.app.layers'].layer1
+
+ Possibility 2: Providing a custom base interface
+ ------------------------------------------------
+
+ >>> class BaseLayer(ILayer):
+ ... pass
+ >>> context = Context()
+ >>> layer(context, u'layer1', base=BaseLayer)
+ >>> iface = context.actions[0]['args'][1]
+ >>> iface.getName()
+ u'layer1'
+ >>> iface.__bases__
+ (,)
+ >>> hasattr(sys.modules['zope.app.layers'], 'layer1')
+ True
+
+ >>> del sys.modules['zope.app.layers'].layer1
+
+ Possibility 3: Define a Layer just through an Interface
+ -------------------------------------------------------
+
+ >>> class layer1(ILayer):
+ ... pass
+ >>> context = Context()
+ >>> layer(context, interface=layer1)
+ >>> context.actions[0]['args'][1] is layer1
+ True
+ >>> hasattr(sys.modules['zope.app.layers'], 'layer1')
+ False
+
+ Possibility 4: Use an Interface and a Name
+ ------------------------------------------
+
+ >>> context = Context()
+ >>> layer(context, name='layer1', interface=layer1)
+ >>> context.actions[0]['args'][1] is layer1
+ True
+ >>> hasattr(sys.modules['zope.app.layers'], 'layer1')
+ True
+ >>> import pprint
+ >>> pprint.pprint([action['discriminator'] for action in context.actions])
+ [('interface', 'zope.app.publisher.browser.metaconfigure.layer1'),
+ ('layer', 'layer1')]
+
+ Here are some disallowed configurations.
+
+ >>> context = Context()
+ >>> layer(context, 'foo,bar')
+ Traceback (most recent call last):
+ ...
+ TypeError: Commas are not allowed in layer names.
+ >>> layer(context)
+ Traceback (most recent call last):
+ ...
+ ConfigurationError: You must specify the 'name' or 'interface' attribute.
+ >>> layer(context, base=BaseLayer)
+ Traceback (most recent call last):
+ ...
+ ConfigurationError: You must specify the 'name' or 'interface' attribute.
+
+ >>> layer(context, interface=layer1, base=BaseLayer)
+ Traceback (most recent call last):
+ ...
+ ConfigurationError: You cannot specify the 'interface' and 'base' together.
+ """
+ if name is not None and ',' in name:
+ raise TypeError("Commas are not allowed in layer names.")
+ if name is None and interface is None:
+ raise ConfigurationError(
+ "You must specify the 'name' or 'interface' attribute.")
+ if interface is not None and base is not ILayer:
+ raise ConfigurationError(
+ "You cannot specify the 'interface' and 'base' together.")
+
+ if interface is None:
+ interface = InterfaceClass(name, (base, ),
+ __doc__='Layer: %s' %name,
+ __module__='zope.app.layers')
+ # Add the layer to the layers module.
+ # Note: We have to do this immediately, so that directives using the
+ # InterfaceField can find the layer.
+ setattr(layers, name, interface)
+ path = 'zope.app.layers.'+name
+ else:
+ path = interface.__module__ + '.' + interface.getName()
+
+ # If a name was specified, make this layer available under this name.
+ # Note that the layer will be still available under its path, since it
+ # is an adapter, and the `LayerField` can resolve paths as well.
+ if name is None:
+ name = path
+ else:
+ # Make the interface available in the `zope.app.layers` module, so
+ # that other directives can find the interface under the name
+ # before the CA is setup.
+ setattr(layers, name, interface)
+
+ # Register the layer interface as an interface
+ _context.action(
+ discriminator = ('interface', path),
+ callable = provideInterface,
+ args = (path, interface),
+ kw = {'info': _context.info}
+ )
+
+ # Register the layer interface as a layer
+ _context.action(
+ discriminator = ('layer', name),
+ callable = provideInterface,
+ args = (name, interface, ILayer, _context.info)
+ )
+
+def skin(_context, name=None, interface=None, layers=None):
+ """Provides a new skin.
+
+ >>> import pprint
+ >>> class Context(object):
+ ... info = u'doc'
+ ... def __init__(self): self.actions = []
+ ... def action(self, **kw): self.actions.append(kw)
+
+ >>> class Layer1(ILayer): pass
+ >>> class Layer2(ILayer): pass
+
+ Possibility 1: The Old Way
+ --------------------------
+
+ >>> context = Context()
+ >>> skin(context, u'skin1', layers=[Layer1, Layer2])
+ >>> iface = context.actions[0]['args'][1]
+ >>> iface.getName()
+ u'skin1'
+ >>> pprint.pprint(iface.__bases__)
+ (,
+ )
+ >>> hasattr(sys.modules['zope.app.skins'], 'skin1')
+ True
+
+ >>> del sys.modules['zope.app.skins'].skin1
+
+ Possibility 2: Just specify an interface
+ ----------------------------------------
+
+ >>> class skin1(Layer1, Layer2):
+ ... pass
+
+ >>> context = Context()
+ >>> skin(context, interface=skin1)
+ >>> context.actions[0]['args'][1] is skin1
+ True
+
+ Possibility 3: Specify an interface and a Name
+ ----------------------------------------------
+
+ >>> context = Context()
+ >>> skin(context, name='skin1', interface=skin1)
+ >>> context.actions[0]['args'][1] is skin1
+ True
+ >>> import pprint
+ >>> pprint.pprint([action['discriminator'] for action in context.actions])
+ [('skin', 'skin1'),
+ ('interface', 'zope.app.publisher.browser.metaconfigure.skin1'),
+ ('skin', 'zope.app.publisher.browser.metaconfigure.skin1')]
+
+ Here are some disallowed configurations.
+
+ >>> context = Context()
+ >>> skin(context)
+ Traceback (most recent call last):
+ ...
+ ConfigurationError: You must specify the 'name' or 'interface' attribute.
+ >>> skin(context, layers=[Layer1])
+ Traceback (most recent call last):
+ ...
+ ConfigurationError: You must specify the 'name' or 'interface' attribute.
+ """
+ if name is None and interface is None:
+ raise ConfigurationError(
+ "You must specify the 'name' or 'interface' attribute.")
+
+ if name is not None and layers is not None:
+ interface = InterfaceClass(name, layers,
+ __doc__='Skin: %s' %name,
+ __module__='zope.app.skins')
+ # Add the layer to the skins module.
+ # Note: We have to do this immediately, so that directives using the
+ # InterfaceField can find the layer.
+ setattr(skins, name, interface)
+ path = 'zope.app.skins'+name
+ else:
+ path = interface.__module__ + '.' + interface.getName()
+
+ # Register the skin interface as a skin using the passed name.
+ if name is not None:
+ _context.action(
+ discriminator = ('skin', name),
+ callable = provideInterface,
+ args = (name, interface, ISkin, _context.info)
+ )
+
+ name = path
+
+ # Register the skin interface as an interface
+ _context.action(
+ discriminator = ('interface', path),
+ callable = provideInterface,
+ args = (path, interface),
+ kw = {'info': _context.info}
+ )
+
+ # Register the skin interface as a skin
+ _context.action(
+ discriminator = ('skin', name),
+ callable = provideInterface,
+ args = (name, interface, ISkin, _context.info)
+ )
+
+def setDefaultSkin(name, info=''):
+ """Set the default skin.
+
+ >>> from zope.interface import directlyProvides
+ >>> from zope.app.testing import ztapi
+
+ >>> class Skin1: pass
+ >>> directlyProvides(Skin1, ISkin)
+
+ >>> ztapi.provideUtility(ISkin, Skin1, 'Skin1')
+ >>> setDefaultSkin('Skin1')
+ >>> adapters = zapi.getSiteManager().adapters
+
+ Lookup the default skin for a request that has the
+
+ >>> adapters.lookup((IBrowserRequest,), IDefaultSkin, '') is Skin1
+ True
+ """
+ skin = zapi.getUtility(ISkin, name)
+ handler('provideAdapter',
+ (IBrowserRequest,), IDefaultSkin, '', skin, info),
+
+def defaultSkin(_context, name):
+
+ _context.action(
+ discriminator = 'defaultSkin',
+ callable = setDefaultSkin,
+ args = (name, _context.info)
+ )
+
+def defaultView(_context, name, for_=None):
+
+ type = IBrowserRequest
+
+ _context.action(
+ discriminator = ('defaultViewName', for_, type, name),
+ callable = handler,
+ args = ('provideAdapter',
+ (for_, type), IDefaultViewName, '', name, _context.info)
+ )
+
+ if for_ is not None:
+ _context.action(
+ discriminator = None,
+ callable = provideInterface,
+ args = ('', for_)
+ )
+
+
+def addMenuItem(_context, title, class_=None, factory=None, description='',
+ permission=None, filter=None, view=None):
+ """Create an add menu item for a given class or factory
+
+ As a convenience, a class can be provided, in which case, a
+ factory is automatically defined based on the class. In this
+ case, the factory id is based on the class name.
+
+ """
+
+ if class_ is None:
+ if factory is None:
+ raise ValueError("Must specify either class or factory")
+ else:
+ if factory is not None:
+ raise ValueError("Can't specify both class and factory")
+ if permission is None:
+ raise ValueError(
+ "A permission must be specified when a class is used")
+ factory = "BrowserAdd__%s.%s" % (
+ class_.__module__, class_.__name__)
+ ContentDirective(_context, class_).factory(
+ _context,
+ id = factory)
+
+ extra = {'factory': factory}
+
+ if view:
+ action = view
+ else:
+ action = factory
+
+ menuItemDirective(_context, AddMenu, IAdding,
+ action, title, description, None, filter,
+ permission, extra)
diff --git a/browser/resource.py b/browser/resource.py
new file mode 100644
index 0000000..dca6577
--- /dev/null
+++ b/browser/resource.py
@@ -0,0 +1,40 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser Resource
+
+$Id$
+"""
+from zope.component.interfaces import IResource
+from zope.interface import implements
+
+from zope.app import zapi
+from zope.app.component.hooks import getSite
+from zope.app.location import Location
+from zope.app.traversing.browser.interfaces import IAbsoluteURL
+
+
+class Resource(Location):
+ implements(IResource)
+
+ def __init__(self, request):
+ self.request = request
+
+ def __call__(self):
+ name = self.__name__
+ if name.startswith('++resource++'):
+ name = name[12:]
+
+ site = getSite()
+ url = str(zapi.getMultiAdapter((site, self.request), IAbsoluteURL))
+ return "%s/@@/%s" % (url, name)
diff --git a/browser/resourcemeta.py b/browser/resourcemeta.py
new file mode 100644
index 0000000..7a1b193
--- /dev/null
+++ b/browser/resourcemeta.py
@@ -0,0 +1,84 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser configuration code
+
+$Id$
+"""
+import os
+
+from zope.configuration.exceptions import ConfigurationError
+from zope.interface import Interface
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+from zope.security.checker import CheckerPublic, NamesChecker
+
+from zope.app import zapi
+from zope.app.component.metaconfigure import handler
+
+from fileresource import FileResourceFactory, ImageResourceFactory
+from pagetemplateresource import PageTemplateResourceFactory
+from directoryresource import DirectoryResourceFactory
+
+allowed_names = ('GET', 'HEAD', 'publishTraverse', 'browserDefault',
+ 'request', '__call__')
+
+def resource(_context, name, layer=IDefaultBrowserLayer,
+ permission='zope.Public', file=None, image=None, template=None):
+
+ if permission == 'zope.Public':
+ permission = CheckerPublic
+
+ checker = NamesChecker(allowed_names, permission)
+
+ if ((file and image) or (file and template) or
+ (image and template) or not (file or image or template)):
+ raise ConfigurationError(
+ "Must use exactly one of file or image or template"
+ " attributes for resource directives"
+ )
+
+ if file:
+ factory = FileResourceFactory(file, checker, name)
+ elif image:
+ factory = ImageResourceFactory(image, checker, name)
+ else:
+ factory = PageTemplateResourceFactory(template, checker, name)
+
+ _context.action(
+ discriminator = ('resource', name, IBrowserRequest, layer),
+ callable = handler,
+ args = ('provideAdapter',
+ (layer,), Interface, name, factory, _context.info),
+ )
+
+def resourceDirectory(_context, name, directory, layer=IDefaultBrowserLayer,
+ permission='zope.Public'):
+ if permission == 'zope.Public':
+ permission = CheckerPublic
+
+ checker = NamesChecker(allowed_names + ('__getitem__', 'get'),
+ permission)
+
+ if not os.path.isdir(directory):
+ raise ConfigurationError(
+ "Directory %s does not exist" % directory
+ )
+
+ factory = DirectoryResourceFactory(directory, checker, name)
+ _context.action(
+ discriminator = ('resource', name, IBrowserRequest, layer),
+ callable = handler,
+ args = ('provideAdapter',
+ (layer,), Interface, name, factory, _context.info),
+ )
diff --git a/browser/resources.py b/browser/resources.py
new file mode 100644
index 0000000..3040e4e
--- /dev/null
+++ b/browser/resources.py
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Resource URL acess
+
+$Id$
+"""
+from zope.app.publisher.browser import BrowserView
+from zope.publisher.interfaces.browser import IBrowserPublisher
+from zope.publisher.interfaces import NotFound
+from zope.interface import implements
+
+from zope.app import zapi
+from zope.app.location import locate
+
+class Resources(BrowserView):
+ """Provide a URL-accessible resource namespace
+ """
+
+ implements(IBrowserPublisher)
+
+ def publishTraverse(self, request, name):
+ '''See interface IBrowserPublisher'''
+
+ resource = zapi.queryAdapter(request, name=name)
+ if resource is None:
+ raise NotFound(self, name)
+
+ sm = zapi.getSiteManager()
+ locate(resource, sm, name)
+ return resource
+
+ def browserDefault(self, request):
+ '''See IBrowserPublisher'''
+ return empty, ()
+
+ def __getitem__(self, name):
+ return self.publishTraverse(self.request, name)
+
+
+def empty():
+ return ''
diff --git a/browser/tests/support.py b/browser/tests/support.py
new file mode 100644
index 0000000..c51b8f3
--- /dev/null
+++ b/browser/tests/support.py
@@ -0,0 +1,44 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Support for tests that need a simple site to be provided.
+
+$Id$
+"""
+from zope.interface import implements
+
+from zope.component.service import serviceManager
+
+from zope.app.component.hooks import setSite
+from zope.app.component.interfaces import ISite
+from zope.app.traversing.interfaces import IContainmentRoot
+
+
+class Site:
+ implements(ISite, IContainmentRoot)
+
+ def getSiteManager(self):
+ return serviceManager
+
+site = Site()
+
+
+class SiteHandler(object):
+
+ def setUp(self):
+ super(SiteHandler, self).setUp()
+ setSite(site)
+
+ def tearDown(self):
+ setSite()
+ super(SiteHandler, self).tearDown()
diff --git a/browser/tests/test_addMenuItem.py b/browser/tests/test_addMenuItem.py
new file mode 100644
index 0000000..3108016
--- /dev/null
+++ b/browser/tests/test_addMenuItem.py
@@ -0,0 +1,209 @@
+#############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test the addMenuItem directive
+
+>>> context = Context()
+>>> addMenuItem(context, class_=X, title="Add an X",
+... permission="zope.ManageContent")
+>>> context
+((('utility',
+ ,
+ 'BrowserAdd__zope.app.publisher.browser.tests.test_addMenuItem.X'),
+ ,
+ ('provideUtility',
+ ,
+ ,
+ 'BrowserAdd__zope.app.publisher.browser.tests.test_addMenuItem.X')),
+ (None,
+ ,
+ ('zope.component.interfaces.IFactory',
+ )),
+ (('adapter',
+ (,
+ ),
+ ,
+ 'Add an X'),
+ ,
+ ('provideAdapter',
+ (,
+ ),
+ ,
+ 'Add an X',
+ ,
+ '')),
+ (None,
+ ,
+ ('',
+ )),
+ (None,
+ ,
+ ('',
+ )),
+ (None,
+ ,
+ ('',
+ )))
+
+$Id$
+"""
+
+import unittest
+from zope.testing.doctestunit import DocTestSuite
+import re
+import pprint
+import cStringIO
+from zope.app.publisher.browser.metaconfigure import addMenuItem
+
+atre = re.compile(' at [0-9a-fA-Fx]+')
+
+class X(object):
+ pass
+
+class Context(object):
+ actions = ()
+ info = ''
+
+ def action(self, discriminator, callable, args):
+ self.actions += ((discriminator, callable, args), )
+
+ def __repr__(self):
+ stream = cStringIO.StringIO()
+ pprinter = pprint.PrettyPrinter(stream=stream, width=60)
+ pprinter.pprint(self.actions)
+ r = stream.getvalue()
+ return (''.join(atre.split(r))).strip()
+
+
+def test_w_factory():
+ """
+ >>> context = Context()
+ >>> addMenuItem(context, factory="x.y.z", title="Add an X",
+ ... permission="zope.ManageContent", description="blah blah",
+ ... filter="context/foo")
+ >>> context
+ ((('adapter',
+ (,
+ ),
+ ,
+ 'Add an X'),
+ ,
+ ('provideAdapter',
+ (,
+ ),
+ ,
+ 'Add an X',
+ ,
+ '')),
+ (None,
+ ,
+ ('',
+ )),
+ (None,
+ ,
+ ('',
+ )),
+ (None,
+ ,
+ ('',
+ )))
+ """
+
+def test_w_factory_and_view():
+ """
+ >>> context = Context()
+ >>> addMenuItem(context, factory="x.y.z", title="Add an X",
+ ... permission="zope.ManageContent", description="blah blah",
+ ... filter="context/foo", view="AddX")
+ >>> context
+ ((('adapter',
+ (,
+ ),
+ ,
+ 'Add an X'),
+ ,
+ ('provideAdapter',
+ (,
+ ),
+ ,
+ 'Add an X',
+ ,
+ '')),
+ (None,
+ ,
+ ('',
+ )),
+ (None,
+ ,
+ ('',
+ )),
+ (None,
+ ,
+ ('',
+ )))
+ """
+
+def test_w_factory_class_view():
+ """
+ >>> context = Context()
+ >>> addMenuItem(context, class_=X, title="Add an X",
+ ... permission="zope.ManageContent", description="blah blah",
+ ... filter="context/foo", view="AddX")
+ >>> import pprint
+ >>> context
+ ((('utility',
+ ,
+ 'BrowserAdd__zope.app.publisher.browser.tests.test_addMenuItem.X'),
+ ,
+ ('provideUtility',
+ ,
+ ,
+ 'BrowserAdd__zope.app.publisher.browser.tests.test_addMenuItem.X')),
+ (None,
+ ,
+ ('zope.component.interfaces.IFactory',
+ )),
+ (('adapter',
+ (,
+ ),
+ ,
+ 'Add an X'),
+ ,
+ ('provideAdapter',
+ (,
+ ),
+ ,
+ 'Add an X',
+ ,
+ '')),
+ (None,
+ ,
+ ('',
+ )),
+ (None,
+ ,
+ ('',
+ )),
+ (None,
+ ,
+ ('',
+ )))
+ """
+
+
+def test_suite():
+ return unittest.TestSuite((
+ DocTestSuite(),
+ ))
+
+if __name__ == '__main__': unittest.main()
diff --git a/browser/tests/test_directives.py b/browser/tests/test_directives.py
new file mode 100644
index 0000000..3c9bc7c
--- /dev/null
+++ b/browser/tests/test_directives.py
@@ -0,0 +1,1050 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""'browser' namespace directive tests
+
+$Id$
+"""
+import os
+import unittest
+from cStringIO import StringIO
+
+from zope.interface import Interface, implements, directlyProvides, providedBy
+
+import zope.security.management
+from zope.component.interfaces import IDefaultViewName
+from zope.configuration.xmlconfig import xmlconfig, XMLConfig
+from zope.configuration.exceptions import ConfigurationError
+from zope.publisher.browser import TestRequest
+from zope.publisher.interfaces.browser import IBrowserPublisher
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.publisher.interfaces.browser import ISkin, IDefaultSkin
+from zope.security.proxy import removeSecurityProxy, ProxyFactory
+from zope.testing.doctestunit import DocTestSuite
+
+import zope.app.publisher.browser
+from zope.app import zapi
+from zope.app.component.tests.views import IC, V1, VZMI, R1, IV
+from zope.app.publisher.browser.fileresource import FileResource
+from zope.app.publisher.browser.i18nfileresource import I18nFileResource
+from zope.app.publisher.browser.menu import getFirstMenuItem
+from zope.app.publisher.interfaces.browser import IMenuItemType
+from zope.app.security.permission import Permission
+from zope.app.security.interfaces import IPermission
+from zope.app.testing import placelesssetup, ztapi
+from zope.app.traversing.adapters import DefaultTraversable
+from zope.app.traversing.interfaces import ITraversable
+
+
+tests_path = os.path.join(
+ os.path.dirname(zope.app.publisher.browser.__file__),
+ 'tests')
+
+template = """
+ %s
+ """
+
+
+request = TestRequest()
+
+class VT(V1, object):
+ def publishTraverse(self, request, name):
+ try:
+ return int(name)
+ except:
+ return super(VT, self).publishTraverse(request, name)
+
+class Ob(object):
+ implements(IC)
+
+ob = Ob()
+
+class NCV(object):
+ "non callable view"
+
+ def __init__(self, context, request):
+ pass
+
+class CV(NCV):
+ "callable view"
+ def __call__(self):
+ pass
+
+
+class C_w_implements(NCV):
+ implements(Interface)
+
+ def index(self):
+ return self
+
+class Test(placelesssetup.PlacelessSetup, unittest.TestCase):
+
+ def setUp(self):
+ super(Test, self).setUp()
+ XMLConfig('meta.zcml', zope.app.publisher.browser)()
+ ztapi.provideAdapter(None, ITraversable, DefaultTraversable)
+
+
+ def testPage(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+
+ xmlconfig(StringIO(template % (
+ '''
+
+ '''
+ )))
+
+ v = zapi.queryMultiAdapter((ob, request), name='test')
+ self.assert_(issubclass(v.__class__, V1))
+
+ def testPageWithClassWithMenu(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+ testtemplate = os.path.join(tests_path, 'testfiles', 'test.pt')
+
+
+ xmlconfig(StringIO(template % (
+ '''
+
+
+ ''' % testtemplate
+ )))
+ test_menu = zapi.getUtility(IMenuItemType, 'test_menu')
+ menuItem = getFirstMenuItem(test_menu, ob, TestRequest())
+ self.assertEqual(menuItem["title"], "Test View")
+ self.assertEqual(menuItem["action"], "@@test")
+ v = zapi.queryMultiAdapter((ob, request), name='test')
+ self.assertEqual(v(), "test
\n")
+
+
+ def testPageWithTemplateWithMenu(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+ testtemplate = os.path.join(tests_path, 'testfiles', 'test.pt')
+
+ xmlconfig(StringIO(template % (
+ '''
+
+
+ ''' % testtemplate
+ )))
+
+ test_menu = zapi.getUtility(IMenuItemType, 'test_menu')
+ menuItem = getFirstMenuItem(test_menu, ob, TestRequest())
+ self.assertEqual(menuItem["title"], "Test View")
+ self.assertEqual(menuItem["action"], "@@test")
+ v = zapi.queryMultiAdapter((ob, request), name='test')
+ self.assertEqual(v(), "test
\n")
+
+
+ def testPageInPagesWithTemplateWithMenu(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+ testtemplate = os.path.join(tests_path, 'testfiles', 'test.pt')
+
+ xmlconfig(StringIO(template % (
+ '''
+
+
+
+
+ ''' % testtemplate
+ )))
+
+ test_menu = zapi.getUtility(IMenuItemType, 'test_menu')
+ menuItem = getFirstMenuItem(test_menu, ob, TestRequest())
+ self.assertEqual(menuItem["title"], "Test View")
+ self.assertEqual(menuItem["action"], "@@test")
+ v = zapi.queryMultiAdapter((ob, request), name='test')
+ self.assertEqual(v(), "test
\n")
+
+
+ def testPageInPagesWithClassWithMenu(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+ testtemplate = os.path.join(tests_path, 'testfiles', 'test.pt')
+
+
+ xmlconfig(StringIO(template % (
+ '''
+
+
+
+
+ ''' % testtemplate
+ )))
+
+ test_menu = zapi.getUtility(IMenuItemType, 'test_menu')
+ menuItem = getFirstMenuItem(test_menu, ob, TestRequest())
+ self.assertEqual(menuItem["title"], "Test View")
+ self.assertEqual(menuItem["action"], "@@test")
+ v = zapi.queryMultiAdapter((ob, request), name='test')
+ self.assertEqual(v(), "test
\n")
+
+ def testDefaultView(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+
+ xmlconfig(StringIO(template % (
+ '''
+
+ '''
+ )))
+
+ self.assertEqual(
+ zapi.getSiteManager().adapters.lookup(
+ map(providedBy, (ob, request)), IDefaultViewName), 'test')
+
+ def testSkinResource(self):
+ self.assertEqual(
+ zapi.queryAdapter(Request(IV), name='test'), None)
+
+ xmlconfig(StringIO(template % (
+ '''
+
+
+
+
+ '''
+ )))
+
+ self.assertEqual(
+ zapi.queryAdapter(request, name='test').__class__, R1)
+ zmi = zapi.getUtility(ISkin, 'zmi')
+ self.assertEqual(
+ zapi.queryAdapter(TestRequest(skin=zmi), name='test').__class__,
+ RZMI)
+
+ def testDefaultSkin(self):
+ request = TestRequest()
+
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+ xmlconfig(StringIO(template % (
+ '''
+
+
+
+
+
+ '''
+ )))
+
+ # Simulate Zope Publication behavior in beforeTraversal()
+ adapters = zapi.getSiteManager().adapters
+ skin = adapters.lookup((providedBy(request),), IDefaultSkin, '')
+ directlyProvides(request, skin)
+
+ v = zapi.queryMultiAdapter((ob, request), name='test')
+ self.assert_(issubclass(v.__class__, VZMI))
+
+ def testSkinPage(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+
+ xmlconfig(StringIO(template % (
+ '''
+
+
+
+
+ '''
+ )))
+
+ v = zapi.queryMultiAdapter((ob, request), name='test')
+ self.assert_(issubclass(v.__class__, V1))
+ zmi = zapi.getUtility(ISkin, 'zmi')
+ v = zapi.queryMultiAdapter((ob, TestRequest(skin=zmi)), name='test')
+ self.assert_(issubclass(v.__class__, VZMI))
+
+ def testI18nResource(self):
+ self.assertEqual(zapi.queryAdapter(request, name='test'), None)
+
+ path1 = os.path.join(tests_path, 'testfiles', 'test.pt')
+ path2 = os.path.join(tests_path, 'testfiles', 'test2.pt')
+
+ xmlconfig(StringIO(template % (
+ '''
+
+
+
+
+ ''' % (path1, path2)
+ )))
+
+ v = zapi.getAdapter(request, name='test')
+ self.assertEqual(
+ zapi.queryAdapter(request, name='test').__class__,
+ I18nFileResource)
+ self.assertEqual(v._testData('en'), open(path1, 'rb').read())
+ self.assertEqual(v._testData('fr'), open(path2, 'rb').read())
+
+ # translation must be provided for the default language
+ config = StringIO(template % (
+ '''
+
+
+
+
+ ''' % (path1, path2)
+ ))
+ self.assertRaises(ConfigurationError, xmlconfig, config)
+
+ # files and images can't be mixed
+ config = StringIO(template % (
+ '''
+
+
+
+
+ ''' % (path1, path2)
+ ))
+ self.assertRaises(ConfigurationError, xmlconfig, config)
+
+ def testInterfaceProtectedPage(self):
+ xmlconfig(StringIO(template %
+ '''
+
+ '''
+ ))
+
+ v = zapi.getMultiAdapter((ob, request), name='test')
+ v = ProxyFactory(v)
+ self.assertEqual(v.index(), 'V1 here')
+ self.assertRaises(Exception, getattr, v, 'action')
+
+ def testAttributeProtectedPage(self):
+ xmlconfig(StringIO(template %
+ '''
+
+ '''
+ ))
+
+ v = zapi.getMultiAdapter((ob, request), name='test')
+ v = ProxyFactory(v)
+ self.assertEqual(v.action(), 'done')
+ self.assertRaises(Exception, getattr, v, 'index')
+
+ def testInterfaceAndAttributeProtectedPage(self):
+ xmlconfig(StringIO(template %
+ '''
+
+ '''
+ ))
+
+ v = zapi.getMultiAdapter((ob, request), name='test')
+ self.assertEqual(v.index(), 'V1 here')
+ self.assertEqual(v.action(), 'done')
+
+ def testDuplicatedInterfaceAndAttributeProtectedPage(self):
+ xmlconfig(StringIO(template %
+ '''
+
+ '''
+ ))
+
+ v = zapi.getMultiAdapter((ob, request), name='test')
+ self.assertEqual(v.index(), 'V1 here')
+ self.assertEqual(v.action(), 'done')
+
+ def test_class_w_implements(self):
+ xmlconfig(StringIO(template %
+ '''
+
+ '''
+ ))
+
+ v = zapi.getMultiAdapter((ob, request), name='test')
+ self.assertEqual(v.index(), v)
+ self.assert_(IBrowserPublisher.providedBy(v))
+
+ def testIncompleteProtectedPageNoPermission(self):
+ self.assertRaises(
+ ConfigurationError,
+ xmlconfig,
+ StringIO(template %
+ '''
+
+ '''
+ ))
+
+
+ def testPageViews(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+ test3 = os.path.join(tests_path, 'testfiles', 'test3.pt')
+
+ xmlconfig(StringIO(template %
+ '''
+
+
+
+
+
+
+ ''' % test3
+ ))
+
+ v = zapi.getMultiAdapter((ob, request), name='index.html')
+ self.assertEqual(v(), 'V1 here')
+ v = zapi.getMultiAdapter((ob, request), name='action.html')
+ self.assertEqual(v(), 'done')
+ v = zapi.getMultiAdapter((ob, request), name='test.html')
+ self.assertEqual(str(v()), 'done
\n')
+
+ def testNamedViewPageViewsCustomTraversr(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+
+
+
+
+ '''
+ ))
+
+ view = zapi.getMultiAdapter((ob, request), name='test')
+ view = removeSecurityProxy(view)
+ self.assertEqual(view.browserDefault(request)[1], (u'index.html', ))
+
+
+ v = view.publishTraverse(request, 'index.html')
+ v = removeSecurityProxy(v)
+ self.assertEqual(v(), 'V1 here')
+ v = view.publishTraverse(request, 'action.html')
+ v = removeSecurityProxy(v)
+ self.assertEqual(v(), 'done')
+
+
+ def testNamedViewNoPagesForCallable(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+ '''
+ ))
+
+ view = zapi.getMultiAdapter((ob, request), name='test')
+ view = removeSecurityProxy(view)
+ self.assertEqual(view.browserDefault(request), (view, ()))
+
+ def testNamedViewNoPagesForNonCallable(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+ '''
+ ))
+
+ view = zapi.getMultiAdapter((ob, request), name='test')
+ view = removeSecurityProxy(view)
+ self.assertEqual(getattr(view, 'browserDefault', None), None)
+
+ def testNamedViewPageViewsNoDefault(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+ test3 = os.path.join(tests_path, 'testfiles', 'test3.pt')
+
+ xmlconfig(StringIO(template %
+ '''
+
+
+
+
+
+
+ ''' % test3
+ ))
+
+ view = zapi.getMultiAdapter((ob, request), name='test')
+ view = removeSecurityProxy(view)
+ self.assertEqual(view.browserDefault(request)[1], (u'index.html', ))
+
+
+ v = view.publishTraverse(request, 'index.html')
+ v = removeSecurityProxy(v)
+ self.assertEqual(v(), 'V1 here')
+ v = view.publishTraverse(request, 'action.html')
+ v = removeSecurityProxy(v)
+ self.assertEqual(v(), 'done')
+ v = view.publishTraverse(request, 'test.html')
+ v = removeSecurityProxy(v)
+ self.assertEqual(str(v()), 'done
\n')
+
+ def testNamedViewPageViewsWithDefault(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+ test3 = os.path.join(tests_path, 'testfiles', 'test3.pt')
+
+ xmlconfig(StringIO(template %
+ '''
+
+
+
+
+
+
+
+ ''' % test3
+ ))
+
+ view = zapi.getMultiAdapter((ob, request), name='test')
+ view = removeSecurityProxy(view)
+ self.assertEqual(view.browserDefault(request)[1], (u'test.html', ))
+
+
+ v = view.publishTraverse(request, 'index.html')
+ v = removeSecurityProxy(v)
+ self.assertEqual(v(), 'V1 here')
+ v = view.publishTraverse(request, 'action.html')
+ v = removeSecurityProxy(v)
+ self.assertEqual(v(), 'done')
+ v = view.publishTraverse(request, 'test.html')
+ v = removeSecurityProxy(v)
+ self.assertEqual(str(v()), 'done
\n')
+
+ def testTraversalOfPageForView(self):
+ """Tests proper traversal of a page defined for a view."""
+
+ xmlconfig(StringIO(template %
+ '''
+
+
+
+ '''
+ ))
+
+ view = zapi.getMultiAdapter((ob, request), name='test')
+ view = removeSecurityProxy(view)
+ view.publishTraverse(request, 'index.html')
+
+ def testTraversalOfPageForViewWithPublishTraverse(self):
+ """Tests proper traversal of a page defined for a view.
+
+ This test is different from testTraversalOfPageForView in that it
+ tests the behavior on a view that has a publishTraverse method --
+ the implementation of the lookup is slightly different in such a
+ case.
+ """
+ xmlconfig(StringIO(template %
+ '''
+
+
+
+ '''
+ ))
+
+ view = zapi.getMultiAdapter((ob, request), name='test')
+ view = removeSecurityProxy(view)
+ view.publishTraverse(request, 'index.html')
+
+ def testProtectedPageViews(self):
+ ztapi.provideUtility(IPermission, Permission('p', 'P'), 'p')
+
+ request = TestRequest()
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+
+
+
+
+
+
+
+
+ '''
+ ))
+
+ v = zapi.getMultiAdapter((ob, request), name='index.html')
+ v = ProxyFactory(v)
+ zope.security.management.getInteraction().add(request)
+ self.assertRaises(Exception, v)
+ v = zapi.getMultiAdapter((ob, request), name='action.html')
+ v = ProxyFactory(v)
+ self.assertRaises(Exception, v)
+
+ def testProtectedNamedViewPageViews(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+
+
+
+
+
+
+
+
+ '''
+ ))
+
+ view = zapi.getMultiAdapter((ob, request), name='test')
+ self.assertEqual(view.browserDefault(request)[1], (u'index.html', ))
+
+ v = view.publishTraverse(request, 'index.html')
+ self.assertEqual(v(), 'V1 here')
+
+ def testSkinnedPageView(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+
+
+
+
+
+
+
+
+
+ '''
+ ))
+
+ v = zapi.getMultiAdapter((ob, request), name='index.html')
+ self.assertEqual(v(), 'V1 here')
+ skinny = zapi.getUtility(ISkin, 'skinny')
+ v = zapi.getMultiAdapter((ob, TestRequest(skin=skinny)),
+ name='index.html')
+ self.assertEqual(v(), 'done')
+
+ def testFile(self):
+ path = os.path.join(tests_path, 'testfiles', 'test.pt')
+
+ self.assertEqual(zapi.queryAdapter(request, name='test'), None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+ ''' % path
+ ))
+
+ r = zapi.getAdapter(request, name='index.html')
+ self.assertEquals(r.__class__, FileResource)
+ r = ProxyFactory(r)
+ self.assertEqual(r.__name__, "index.html")
+
+ # Make sure we can access available attrs and not others
+ for n in ('GET', 'HEAD', 'publishTraverse', 'request', '__call__'):
+ getattr(r, n)
+ self.assertEqual(r.__name__, "index.html")
+
+ self.assertRaises(Exception, getattr, r, '_testData')
+
+ r = removeSecurityProxy(r)
+ self.assert_(r.__class__ is FileResource)
+ self.assertEqual(r._testData(), open(path, 'rb').read())
+
+
+ def testSkinResource(self):
+ self.assertEqual(zapi.queryAdapter(request, name='test'), None)
+
+ path = os.path.join(tests_path, 'testfiles', 'test.pt')
+
+ xmlconfig(StringIO(template % (
+ '''
+
+
+
+ ''' % path
+ )))
+
+ self.assertEqual(zapi.queryAdapter(request, name='test'), None)
+
+ zmi = zapi.getUtility(ISkin, 'zmi')
+ r = zapi.getAdapter(TestRequest(skin=zmi), name='test')
+ r = removeSecurityProxy(r)
+ self.assertEqual(r._testData(), open(path, 'rb').read())
+
+ def test_template_page(self):
+ path = os.path.join(tests_path, 'testfiles', 'test.pt')
+
+ self.assertEqual(
+ zapi.queryMultiAdapter((ob, request), name='index.html'), None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+ ''' % path
+ ))
+
+ v = zapi.getMultiAdapter((ob, request), name='index.html')
+ self.assertEqual(v().strip(), 'test
')
+
+ def testtemplateWClass(self):
+ path = os.path.join(tests_path, 'testfiles', 'test2.pt')
+
+ self.assertEqual(
+ zapi.queryMultiAdapter((ob, request), name='index.html'), None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+ ''' % path
+ ))
+
+ v = zapi.getMultiAdapter((ob, request), name='index.html')
+ self.assertEqual(v().strip(), '42
')
+
+ def testProtectedtemplate(self):
+
+ path = os.path.join(tests_path, 'testfiles', 'test.pt')
+
+ request = TestRequest()
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='test'),
+ None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+
+
+
+
+ ''' % path
+ ))
+
+ xmlconfig(StringIO(template %
+ '''
+
+ ''' % path
+ ))
+
+ v = zapi.getMultiAdapter((ob, request), name='xxx.html')
+ v = ProxyFactory(v)
+ zope.security.management.getInteraction().add(request)
+ self.assertRaises(Exception, v)
+
+ v = zapi.getMultiAdapter((ob, request), name='index.html')
+ v = ProxyFactory(v)
+ self.assertEqual(v().strip(), 'test
')
+
+
+ def testtemplateNoName(self):
+ path = os.path.join(tests_path, 'testfiles', 'test.pt')
+ self.assertRaises(
+ ConfigurationError,
+ xmlconfig,
+ StringIO(template %
+ '''
+
+ ''' % path
+ ))
+
+ def testtemplateAndPage(self):
+ path = os.path.join(tests_path, 'testfiles', 'test.pt')
+ self.assertRaises(
+ ConfigurationError,
+ xmlconfig,
+ StringIO(template %
+ '''
+
+
+
+ ''' % path
+ ))
+
+ def testViewThatProvidesAnInterface(self):
+ request = TestRequest()
+ self.assertEqual(
+ zapi.queryMultiAdapter((ob, request), IV, name='test'), None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+ '''
+ ))
+
+ v = zapi.queryMultiAdapter((ob, request), IV, name='test')
+ self.assertEqual(v, None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+ '''
+ ))
+
+ v = zapi.queryMultiAdapter((ob, request), IV, name='test')
+ self.assert_(isinstance(v, V1))
+
+ def testUnnamedViewThatProvidesAnInterface(self):
+ request = TestRequest()
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), IV),
+ None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+ '''
+ ))
+
+ v = zapi.queryMultiAdapter((ob, request), IV)
+ self.assertEqual(v, None)
+
+ xmlconfig(StringIO(template %
+ '''
+
+ '''
+ ))
+
+ v = zapi.queryMultiAdapter((ob, request), IV)
+
+ self.assert_(isinstance(v, V1))
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(Test),
+ DocTestSuite('zope.app.publisher.browser.metaconfigure',
+ setUp=placelesssetup.setUp,
+ tearDown=placelesssetup.tearDown)
+ ))
+
+if __name__=='__main__':
+ unittest.main(defaultTest="test_suite")
diff --git a/browser/tests/test_directoryresource.py b/browser/tests/test_directoryresource.py
new file mode 100644
index 0000000..b5b8309
--- /dev/null
+++ b/browser/tests/test_directoryresource.py
@@ -0,0 +1,114 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Directory-based resources test
+
+$Id$
+"""
+import os
+from unittest import TestCase, main, makeSuite
+
+from zope.publisher.interfaces import NotFound
+from zope.proxy import isProxy
+from zope.security.proxy import removeSecurityProxy
+from zope.publisher.browser import TestRequest
+from zope.security.checker import NamesChecker, ProxyFactory
+from zope.interface import implements
+
+from zope.app import zapi
+from zope.app.testing.placelesssetup import PlacelessSetup
+from zope.app.publisher.browser.directoryresource import \
+ DirectoryResourceFactory
+from zope.app.container.contained import Contained
+from zope.app.publisher.browser.fileresource import FileResource
+from zope.app.publisher.browser.pagetemplateresource import \
+ PageTemplateResource
+import zope.app.publisher.browser.tests as p
+from zope.app.publisher.browser.tests import support
+
+test_directory = os.path.dirname(p.__file__)
+
+checker = NamesChecker(
+ ('get', '__getitem__', 'request', 'publishTraverse')
+ )
+
+class Ob(Contained): pass
+
+ob = Ob()
+
+class Test(support.SiteHandler, PlacelessSetup, TestCase):
+
+ def testNotFound(self):
+ path = os.path.join(test_directory, 'testfiles')
+ request = TestRequest()
+ factory = DirectoryResourceFactory(path, checker, 'testfiles')
+ resource = factory(request)
+ self.assertRaises(NotFound, resource.publishTraverse,
+ resource.request, 'doesnotexist')
+ self.assertRaises(NotFound, resource.get, 'doesnotexist')
+
+ def testGetitem(self):
+ path = os.path.join(test_directory, 'testfiles')
+ request = TestRequest()
+ factory = DirectoryResourceFactory(path, checker, 'testfiles')
+ resource = factory(request)
+ self.assertRaises(KeyError, resource.__getitem__, 'doesnotexist')
+ file = resource['test.txt']
+
+ def testProxy(self):
+ path = os.path.join(test_directory, 'testfiles')
+ request = TestRequest()
+ factory = DirectoryResourceFactory(path, checker, 'testfiles')
+ resource = factory(request)
+ file = ProxyFactory(resource['test.txt'])
+ self.assert_(isProxy(file))
+
+ def testURL(self):
+ request = TestRequest()
+ request._vh_root = support.site
+ path = os.path.join(test_directory, 'testfiles')
+ files = DirectoryResourceFactory(path, checker, 'test_files')(request)
+ files.__parent__ = support.site
+ file = files['test.gif']
+ self.assertEquals(file(), 'http://127.0.0.1/@@/test_files/test.gif')
+
+ def testURL2Level(self):
+ request = TestRequest()
+ request._vh_root = support.site
+ ob.__parent__ = support.site
+ ob.__name__ = 'ob'
+ path = os.path.join(test_directory, 'testfiles')
+ files = DirectoryResourceFactory(path, checker, 'test_files')(request)
+ files.__parent__ = ob
+ file = files['test.gif']
+ self.assertEquals(file(), 'http://127.0.0.1/@@/test_files/test.gif')
+
+ def testCorrectFactories(self):
+ path = os.path.join(test_directory, 'testfiles')
+ request = TestRequest()
+ resource = DirectoryResourceFactory(path, checker, 'files')(request)
+
+ image = resource['test.gif']
+ self.assert_(zapi.isinstance(image, FileResource))
+ template = resource['test.pt']
+ self.assert_(zapi.isinstance(template, PageTemplateResource))
+ file = resource['test.txt']
+ self.assert_(zapi.isinstance(file, FileResource))
+ file = resource['png']
+ self.assert_(zapi.isinstance(file, FileResource))
+
+def test_suite():
+ return makeSuite(Test)
+
+if __name__ == '__main__':
+ main(defaultTest='test_suite')
diff --git a/browser/tests/test_fields.py b/browser/tests/test_fields.py
new file mode 100644
index 0000000..8aa3077
--- /dev/null
+++ b/browser/tests/test_fields.py
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test fields.
+
+$Id$
+"""
+import unittest
+from zope.testing.doctestunit import DocTestSuite
+from zope.app.testing import placelesssetup
+
+def test_suite():
+ return unittest.TestSuite((
+ DocTestSuite('zope.app.publisher.browser.fields',
+ setUp=placelesssetup.setUp,
+ tearDown=placelesssetup.tearDown),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/browser/tests/test_fileresource.py b/browser/tests/test_fileresource.py
new file mode 100644
index 0000000..40e968f
--- /dev/null
+++ b/browser/tests/test_fileresource.py
@@ -0,0 +1,111 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""File-based browser resource tests.
+
+$Id$
+"""
+import os
+from unittest import TestCase, main, makeSuite
+
+from zope.publisher.interfaces import NotFound
+from zope.i18n.interfaces import IUserPreferredCharsets
+from zope.security.proxy import removeSecurityProxy
+from zope.security.checker import NamesChecker
+
+from zope.app.testing.placelesssetup import PlacelessSetup
+from zope.app.testing import ztapi
+
+from zope.publisher.http import IHTTPRequest
+from zope.publisher.http import HTTPCharsets
+from zope.publisher.browser import TestRequest
+
+from zope.app.publisher.browser.fileresource import FileResourceFactory
+from zope.app.publisher.browser.fileresource import ImageResourceFactory
+import zope.app.publisher.browser.tests as p
+
+checker = NamesChecker(
+ ('__call__', 'HEAD', 'request', 'publishTraverse', 'GET')
+ )
+
+test_directory = os.path.dirname(p.__file__)
+
+class Test(PlacelessSetup, TestCase):
+
+ def setUp(self):
+ super(Test, self).setUp()
+ ztapi.provideAdapter(IHTTPRequest, IUserPreferredCharsets,
+ HTTPCharsets)
+
+ def testNoTraversal(self):
+
+ path = os.path.join(test_directory, 'testfiles', 'test.txt')
+ factory = FileResourceFactory(path, checker, 'test.txt')
+ resource = factory(TestRequest())
+ self.assertRaises(NotFound,
+ resource.publishTraverse,
+ resource.request,
+ '_testData')
+
+ def testFileGET(self):
+
+ path = os.path.join(test_directory, 'testfiles', 'test.txt')
+
+ factory = FileResourceFactory(path, checker, 'test.txt')
+ resource = factory(TestRequest())
+ self.assertEqual(resource.GET(), open(path, 'rb').read())
+
+ response = removeSecurityProxy(resource.request).response
+ self.assertEqual(response.getHeader('Content-Type'), 'text/plain')
+
+ def testFileHEAD(self):
+
+ path = os.path.join(test_directory, 'testfiles', 'test.txt')
+ factory = FileResourceFactory(path, checker, 'test.txt')
+ resource = factory(TestRequest())
+
+ self.assertEqual(resource.HEAD(), '')
+
+ response = removeSecurityProxy(resource.request).response
+ self.assertEqual(response.getHeader('Content-Type'), 'text/plain')
+
+ def testImageGET(self):
+
+ path = os.path.join(test_directory, 'testfiles', 'test.gif')
+
+ factory = ImageResourceFactory(path, checker, 'test.gif')
+ resource = factory(TestRequest())
+
+ self.assertEqual(resource.GET(), open(path, 'rb').read())
+
+ response = removeSecurityProxy(resource.request).response
+ self.assertEqual(response.getHeader('Content-Type'), 'image/gif')
+
+ def testImageHEAD(self):
+
+ path = os.path.join(test_directory, 'testfiles', 'test.gif')
+ factory = ImageResourceFactory(path, checker, 'test.gif')
+ resource = factory(TestRequest())
+
+ self.assertEqual(resource.HEAD(), '')
+
+ response = removeSecurityProxy(resource.request).response
+ self.assertEqual(response.getHeader('Content-Type'), 'image/gif')
+
+
+
+def test_suite():
+ return makeSuite(Test)
+
+if __name__=='__main__':
+ main(defaultTest='test_suite')
diff --git a/browser/tests/test_icondirective.py b/browser/tests/test_icondirective.py
new file mode 100644
index 0000000..6372681
--- /dev/null
+++ b/browser/tests/test_icondirective.py
@@ -0,0 +1,165 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test Icon-Directive
+
+$Id$
+"""
+import os
+from StringIO import StringIO
+from unittest import TestCase, main, makeSuite
+
+from zope.configuration.exceptions import ConfigurationError
+from zope.configuration.xmlconfig import xmlconfig, XMLConfig
+from zope.interface import implements
+from zope.publisher.browser import TestRequest
+from zope.security.checker import ProxyFactory, CheckerPublic
+from zope.security.interfaces import Forbidden
+from zope.security.proxy import removeSecurityProxy
+
+import zope.app.publisher.browser
+from zope.app import zapi
+from zope.app.component.tests.views import IC
+from zope.app.component.interfaces import ISite
+from zope.app.publisher.browser.tests import support
+from zope.app.testing.placelesssetup import PlacelessSetup
+from zope.app.traversing.interfaces import IContainmentRoot
+
+
+template = """
+ %s
+ """
+
+
+request = TestRequest()
+
+class Ob(object):
+ implements(IC)
+
+ob = Ob()
+request._vh_root = support.site
+
+def defineCheckers():
+ # define the appropriate checker for a FileResource for these tests
+ from zope.app.security.protectclass import protectName
+ from zope.app.publisher.browser.fileresource import FileResource
+ protectName(FileResource, '__call__', 'zope.Public')
+
+
+class Test(support.SiteHandler, PlacelessSetup, TestCase):
+
+ def setUp(self):
+ super(Test, self).setUp()
+ XMLConfig('meta.zcml', zope.app.publisher.browser)()
+ defineCheckers()
+
+ def test(self):
+ self.assertEqual(zapi.queryMultiAdapter((ob, request), name='zmi_icon'),
+ None)
+
+ import zope.app.publisher.browser.tests as p
+ path = os.path.dirname(p.__file__)
+ path = os.path.join(path, 'testfiles', 'test.gif')
+
+ xmlconfig(StringIO(template % (
+ '''
+
+ ''' % path
+ )))
+
+ view = zapi.getMultiAdapter((ob, request), name='zmi_icon')
+ rname = 'zope-app-component-tests-views-IC-zmi_icon.gif'
+ self.assertEqual(
+ view(),
+ ''
+ % rname)
+
+ resource = ProxyFactory(zapi.getAdapter(request, name=rname))
+ self.assertRaises(Forbidden, getattr, resource, '_testData')
+ resource = removeSecurityProxy(resource)
+ self.assertEqual(resource._testData(), open(path, 'rb').read())
+
+ def testResource(self):
+ self.assertEqual(
+ zapi.queryMultiAdapter((ob, request), name='zmi_icon'), None)
+
+ import zope.app.publisher.browser.tests as p
+ path = os.path.dirname(p.__file__)
+ path = os.path.join(path, 'testfiles', 'test.gif')
+
+ xmlconfig(StringIO(template % (
+ '''
+
+
+ ''' % path
+ )))
+
+ view = zapi.getMultiAdapter((ob, request), name='zmi_icon')
+ rname = "zmi_icon_res"
+ self.assertEqual(
+ view(),
+ ''
+ % rname)
+
+ resource = ProxyFactory(zapi.getAdapter(request, name=rname))
+
+ self.assertRaises(Forbidden, getattr, resource, '_testData')
+ resource = removeSecurityProxy(resource)
+ self.assertEqual(resource._testData(), open(path, 'rb').read())
+
+ def testResourceErrors(self):
+ self.assertEqual(
+ zapi.queryMultiAdapter((ob, request), name='zmi_icon'), None)
+
+ import zope.app.publisher.browser.tests as p
+ path = os.path.dirname(p.__file__)
+ path = os.path.join(path, 'testfiles', 'test.gif')
+
+ config = StringIO(template % (
+ '''
+
+
+ ''' % (path, path)
+ ))
+ self.assertRaises(ConfigurationError, xmlconfig, config)
+
+ config = StringIO(template % (
+ """
+
+ """
+ ))
+ self.assertRaises(ConfigurationError, xmlconfig, config)
+
+
+def test_suite():
+ return makeSuite(Test)
+
+if __name__=='__main__':
+ main(defaultTest='test_suite')
diff --git a/browser/tests/test_menu.py b/browser/tests/test_menu.py
new file mode 100644
index 0000000..2592ce9
--- /dev/null
+++ b/browser/tests/test_menu.py
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser Menu Item Tests
+
+$Id$
+"""
+import unittest
+from zope.testing.doctestunit import DocTestSuite
+
+from zope.app.testing import placelesssetup
+
+
+def test_suite():
+ return DocTestSuite('zope.app.publisher.browser.menu',
+ setUp=placelesssetup.setUp,
+ tearDown=placelesssetup.tearDown)
+
+if __name__=='__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/browser/tests/test_menudirectives.py b/browser/tests/test_menudirectives.py
new file mode 100644
index 0000000..bc1e7a9
--- /dev/null
+++ b/browser/tests/test_menudirectives.py
@@ -0,0 +1,98 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser Menu Directives Tests
+
+$Id$
+"""
+import unittest
+
+from zope.configuration.xmlconfig import XMLConfig
+from zope.interface import Interface, implements
+from zope.publisher.browser import TestRequest
+from zope.publisher.interfaces.browser import IBrowserPublisher
+from zope.security.interfaces import Unauthorized, Forbidden
+
+from zope.app.testing.placelesssetup import PlacelessSetup
+
+import zope.app.publisher.browser
+
+template = """
+ %s
+ """
+
+class I1(Interface): pass
+class I11(I1): pass
+class I12(I1): pass
+class I111(I11): pass
+
+class C1(object):
+ implements(I1)
+
+class TestObject(object):
+ implements(IBrowserPublisher, I111)
+
+ def f(self):
+ pass
+
+ def browserDefault(self, r):
+ return self, ()
+
+ def publishTraverse(self, request, name):
+ if name[:1] == 'f':
+ raise Forbidden, name
+ if name[:1] == 'u':
+ raise Unauthorized, name
+ return self.f
+
+
+class Test(PlacelessSetup, unittest.TestCase):
+
+ def setUp(self):
+ super(Test, self).setUp()
+ XMLConfig('meta.zcml', zope.app.publisher.browser)()
+
+ def test(self):
+ XMLConfig('tests/menus.zcml', zope.app.publisher.browser)()
+
+ from zope.app.menus import test_id
+
+ menu = zope.app.publisher.browser.menu.getMenu(
+ test_id, TestObject(), TestRequest())
+
+ def d(n):
+ return {'action': "a%s" % n,
+ 'title': "t%s" % n,
+ 'description': "",
+ 'selected': '',
+ 'icon': None,
+ 'extra': None}
+
+ self.assertEqual(list(menu), [d(5), d(6), d(3), d(2), d(1)])
+
+ first = zope.app.publisher.browser.menu.getFirstMenuItem(
+ test_id, TestObject(), TestRequest())
+
+ self.assertEqual(first, d(5))
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(Test),
+ ))
+
+if __name__=='__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/browser/tests/test_pagetemplateresource.py b/browser/tests/test_pagetemplateresource.py
new file mode 100644
index 0000000..1708545
--- /dev/null
+++ b/browser/tests/test_pagetemplateresource.py
@@ -0,0 +1,65 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Page Template based Resources Test
+
+$Id$
+"""
+import os
+from unittest import TestCase, main, makeSuite
+
+from zope.publisher.interfaces import NotFound
+from zope.app.testing import ztapi
+from zope.security.checker import NamesChecker
+from zope.publisher.browser import TestRequest
+
+from zope.app.testing.placelesssetup import PlacelessSetup
+from zope.app.publisher.browser.pagetemplateresource import \
+ PageTemplateResourceFactory
+from zope.app.traversing.interfaces import ITraversable
+from zope.app.traversing.adapters import DefaultTraversable
+import zope.app.publisher.browser.tests as p
+
+test_directory = os.path.dirname(p.__file__)
+
+checker = NamesChecker(
+ ('__call__', 'request', 'publishTraverse')
+ )
+
+class Test(PlacelessSetup, TestCase):
+
+ def setUp(self):
+ super(Test, self).setUp()
+ ztapi.provideAdapter(None, ITraversable, DefaultTraversable)
+
+ def testNoTraversal(self):
+ path = os.path.join(test_directory, 'testfiles', 'test.pt')
+ request = TestRequest()
+ factory = PageTemplateResourceFactory(path, checker, 'test.pt')
+ resource = factory(request)
+ self.assertRaises(NotFound, resource.publishTraverse,
+ resource.request, ())
+
+ def testCall(self):
+ path = os.path.join(test_directory, 'testfiles', 'testresource.pt')
+ test_data = "Foobar"
+ request = TestRequest(test_data=test_data)
+ factory = PageTemplateResourceFactory(path, checker, 'testresource.pt')
+ resource = factory(request)
+ self.assert_(resource(), test_data)
+
+def test_suite():
+ return makeSuite(Test)
+
+if __name__=='__main__':
+ main(defaultTest='test_suite')
diff --git a/browser/tests/test_resource.py b/browser/tests/test_resource.py
new file mode 100644
index 0000000..c1dbea7
--- /dev/null
+++ b/browser/tests/test_resource.py
@@ -0,0 +1,53 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Unit tests for Resource
+
+$Id$
+"""
+import unittest
+
+from zope.publisher.browser import TestRequest
+
+from zope.app.publisher.browser.resource import Resource
+from zope.app.publisher.browser.tests import support
+from zope.app.testing.placelesssetup import PlacelessSetup
+
+
+class TestResource(support.SiteHandler, PlacelessSetup, unittest.TestCase):
+
+ def testGlobal(self):
+ req = TestRequest()
+ r = Resource(req)
+ req._vh_root = support.site
+ r.__parent__ = support.site
+ r.__name__ = 'foo'
+ self.assertEquals(r(), 'http://127.0.0.1/@@/foo')
+ r.__name__ = '++resource++foo'
+ self.assertEquals(r(), 'http://127.0.0.1/@@/foo')
+
+ def testGlobalInVirtualHost(self):
+ req = TestRequest()
+ req.setVirtualHostRoot(['x', 'y'])
+ r = Resource(req)
+ req._vh_root = support.site
+ r.__parent__ = support.site
+ r.__name__ = 'foo'
+ self.assertEquals(r(), 'http://127.0.0.1/x/y/@@/foo')
+
+
+def test_suite():
+ return unittest.makeSuite(TestResource)
+
+if __name__ == '__main__':
+ unittest.main(defaultTest="test_suite")
diff --git a/browser/tests/test_resources.py b/browser/tests/test_resources.py
new file mode 100644
index 0000000..131d855
--- /dev/null
+++ b/browser/tests/test_resources.py
@@ -0,0 +1,78 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test Browser Resources
+
+$Id$
+"""
+from unittest import TestCase, main, makeSuite
+from zope.app.testing import ztapi
+from zope.app.testing.placelesssetup import PlacelessSetup
+
+from zope.i18n.interfaces import IUserPreferredCharsets
+
+from zope.publisher.http import IHTTPRequest
+from zope.publisher.http import HTTPCharsets
+from zope.publisher.browser import TestRequest
+from zope.app.publisher.interfaces.browser import IBrowserView
+
+class Test(PlacelessSetup, TestCase):
+
+ def setUp(self):
+ super(Test, self).setUp()
+ ztapi.provideAdapter(IHTTPRequest, IUserPreferredCharsets,
+ HTTPCharsets)
+
+ def test_publishTraverse(self):
+ from zope.app.publisher.browser.resources import Resources
+ request = TestRequest()
+
+ class Resource(object):
+ def __init__(self, request): pass
+ def __call__(self): return 42
+
+ ztapi.browserResource('test', Resource)
+ view = Resources(None, request)
+ resource = view.publishTraverse(request, 'test')
+ self.assertEqual(resource(), 42)
+
+ def test_getitem(self):
+ from zope.app.publisher.browser.resources import Resources
+ request = TestRequest()
+
+ class Resource(object):
+ def __init__(self, request): pass
+ def __call__(self): return 42
+
+ ztapi.browserResource('test', Resource)
+ view = Resources(None, request)
+ resource = view['test']
+ self.assertEqual(resource(), 42)
+
+ def testNotFound(self):
+ from zope.app.publisher.browser.resources import Resources
+ from zope.publisher.interfaces import NotFound
+ request = TestRequest()
+ view = Resources(None, request)
+ self.assertRaises(NotFound,
+ view.publishTraverse,
+ request, 'test'
+ )
+
+
+
+def test_suite():
+ return makeSuite(Test)
+
+if __name__=='__main__':
+ main(defaultTest='test_suite')
diff --git a/browser/tests/testi18nfileresource.py b/browser/tests/testi18nfileresource.py
new file mode 100644
index 0000000..98fe223
--- /dev/null
+++ b/browser/tests/testi18nfileresource.py
@@ -0,0 +1,152 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""I18n File-Resource Tests
+
+$Id$
+"""
+from unittest import main, makeSuite
+import os
+
+from zope.publisher.interfaces import NotFound
+
+from zope.app.testing.placelesssetup import PlacelessSetup
+from zope.app.testing import ztapi
+
+from zope.i18n.interfaces import IUserPreferredCharsets, IUserPreferredLanguages
+
+from zope.publisher.http import IHTTPRequest, HTTPCharsets
+from zope.publisher.browser import BrowserLanguages, TestRequest
+
+from zope.app.publisher.browser.i18nfileresource import I18nFileResource
+from zope.app.publisher.browser.i18nfileresource import I18nFileResourceFactory
+from zope.app.publisher.fileresource import File
+import zope.app.publisher.browser.tests as p
+
+from zope.i18n.interfaces import INegotiator
+from zope.i18n.negotiator import negotiator
+
+from zope.i18n.tests.testii18naware import TestII18nAware
+
+test_directory = os.path.dirname(p.__file__)
+
+
+class Test(PlacelessSetup, TestII18nAware):
+
+ def setUp(self):
+ super(Test, self).setUp()
+ TestII18nAware.setUp(self)
+ ztapi.provideAdapter(IHTTPRequest, IUserPreferredCharsets,
+ HTTPCharsets)
+ ztapi.provideAdapter(IHTTPRequest, IUserPreferredLanguages,
+ BrowserLanguages)
+ # Setup the negotiator utility
+ ztapi.provideUtility(INegotiator, negotiator)
+
+
+ def _createObject(self):
+ obj = I18nFileResource({'en':None, 'lt':None, 'fr':None},
+ TestRequest(), 'fr')
+ return obj
+
+
+ def _createDict(self, filename1='test.pt', filename2='test2.pt'):
+ path1 = os.path.join(test_directory, 'testfiles', filename1)
+ path2 = os.path.join(test_directory, 'testfiles', filename2)
+ return { 'en': File(path1, filename1),
+ 'fr': File(path2, filename2) }
+
+
+ def testNoTraversal(self):
+
+ resource = I18nFileResourceFactory(self._createDict(), 'en')\
+ (TestRequest())
+
+ self.assertRaises(NotFound,
+ resource.publishTraverse,
+ resource.request,
+ '_testData')
+
+ def testFileGET(self):
+
+ # case 1: no language preference, should get en
+ path = os.path.join(test_directory, 'testfiles', 'test.txt')
+
+ resource = I18nFileResourceFactory(self._createDict('test.txt'), 'en')\
+ (TestRequest())
+
+
+ self.assertEqual(resource.GET(), open(path, 'rb').read())
+
+ response = resource.request.response
+ self.assertEqual(response.getHeader('Content-Type'), 'text/plain')
+
+ # case 2: prefer lt, have only en and fr, should get en
+ resource = I18nFileResourceFactory(
+ self._createDict('test.txt'), 'en')\
+ (TestRequest(HTTP_ACCEPT_LANGUAGE='lt'))
+
+ self.assertEqual(resource.GET(), open(path, 'rb').read())
+
+ response = resource.request.response
+ self.assertEqual(response.getHeader('Content-Type'), 'text/plain')
+
+ # case 3: prefer fr, have it, should get fr
+ path = os.path.join(test_directory, 'testfiles', 'test2.pt')
+ resource = I18nFileResourceFactory(
+ self._createDict('test.pt', 'test2.pt'), 'en')\
+ (TestRequest(HTTP_ACCEPT_LANGUAGE='fr'))
+
+ self.assertEqual(resource.GET(), open(path, 'rb').read())
+
+ response = resource.request.response
+ self.assertEqual(response.getHeader('Content-Type'), 'text/html')
+
+
+ def testFileHEAD(self):
+
+ # case 1: no language preference, should get en
+ resource = I18nFileResourceFactory(self._createDict('test.txt'), 'en')\
+ (TestRequest())
+
+ self.assertEqual(resource.HEAD(), '')
+
+ response = resource.request.response
+ self.assertEqual(response.getHeader('Content-Type'), 'text/plain')
+
+ # case 2: prefer lt, have only en and fr, should get en
+ resource = I18nFileResourceFactory(
+ self._createDict('test.txt'), 'en')\
+ (TestRequest(HTTP_ACCEPT_LANGUAGE='lt'))
+
+ self.assertEqual(resource.HEAD(), '')
+
+ response = resource.request.response
+ self.assertEqual(response.getHeader('Content-Type'), 'text/plain')
+
+ # case 3: prefer fr, have it, should get fr
+ resource = I18nFileResourceFactory(
+ self._createDict('test.pt', 'test2.pt'), 'en')\
+ (TestRequest(HTTP_ACCEPT_LANGUAGE='fr'))
+
+ self.assertEqual(resource.HEAD(), '')
+
+ response = resource.request.response
+ self.assertEqual(response.getHeader('Content-Type'), 'text/html')
+
+
+def test_suite():
+ return makeSuite(Test)
+
+if __name__=='__main__':
+ main(defaultTest='test_suite')
diff --git a/browser/viewmeta.py b/browser/viewmeta.py
new file mode 100644
index 0000000..63e385f
--- /dev/null
+++ b/browser/viewmeta.py
@@ -0,0 +1,452 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser configuration code
+
+$Id$
+"""
+import os
+
+from zope.component.exceptions import ComponentLookupError
+from zope.component.interfaces import IDefaultViewName
+from zope.interface import implements, classImplements, Interface
+from zope.publisher.interfaces import NotFound
+from zope.security.checker import CheckerPublic, Checker
+from zope.security.checker import defineChecker
+from zope.configuration.exceptions import ConfigurationError
+from zope.app.component.interface import provideInterface
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+from zope.publisher.interfaces.browser import IBrowserPublisher
+from zope.app import zapi
+from zope.app.component.metaconfigure import handler
+from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
+from zope.app.publisher.browser import BrowserView
+from zope.app.publisher.browser.menu import menuItemDirective
+
+
+
+# There are three cases we want to suport:
+#
+# Named view without pages (single-page view)
+#
+#
+#
+# Unnamed view with pages (multi-page view)
+#
+#
+#
+#
+#
+#
+#
+# Named view with pages (add view is a special case of this)
+#
+#
+#
+#
+#
+#
+
+# We'll also provide a convenience directive for add views:
+#
+#
+#
+#
+#
+#
+
+# page
+
+def page(_context, name, permission, for_,
+ layer=IDefaultBrowserLayer, template=None, class_=None,
+ allowed_interface=None, allowed_attributes=None,
+ attribute='__call__', menu=None, title=None,
+ ):
+
+ _handle_menu(_context, menu, title, [for_], name, permission)
+ required = {}
+
+ permission = _handle_permission(_context, permission)
+
+ if not (class_ or template):
+ raise ConfigurationError("Must specify a class or template")
+
+ if attribute != '__call__':
+ if template:
+ raise ConfigurationError(
+ "Attribute and template cannot be used together.")
+
+ if not class_:
+ raise ConfigurationError(
+ "A class must be provided if attribute is used")
+
+ if template:
+ template = os.path.abspath(str(_context.path(template)))
+ if not os.path.isfile(template):
+ raise ConfigurationError("No such file", template)
+ required['__getitem__'] = permission
+
+ if class_:
+ if attribute != '__call__':
+ if not hasattr(class_, attribute):
+ raise ConfigurationError(
+ "The provided class doesn't have the specified attribute "
+ )
+ if template:
+ # class and template
+ new_class = SimpleViewClass(
+ template, bases=(class_, ))
+ else:
+ if not hasattr(class_, 'browserDefault'):
+ cdict = {
+ 'browserDefault':
+ lambda self, request: (getattr(self, attribute), ())
+ }
+ else:
+ cdict = {}
+
+ cdict['__page_attribute__'] = attribute
+ new_class = type(class_.__name__,
+ (class_, simple,),
+ cdict)
+
+ if hasattr(class_, '__implements__'):
+ classImplements(new_class, IBrowserPublisher)
+
+ else:
+ # template
+ new_class = SimpleViewClass(template)
+
+ for n in (attribute, 'browserDefault', '__call__', 'publishTraverse'):
+ required[n] = permission
+
+ _handle_allowed_interface(_context, allowed_interface, permission,
+ required)
+ _handle_allowed_attributes(_context, allowed_interface, permission,
+ required)
+
+ _handle_for(_context, for_)
+
+ defineChecker(new_class, Checker(required))
+
+ _context.action(
+ discriminator = ('view', for_, name, IBrowserRequest, layer),
+ callable = handler,
+ args = ('provideAdapter',
+ (for_, layer), Interface, name, new_class, _context.info),
+ )
+
+
+# pages, which are just a short-hand for multiple page directives.
+
+# Note that a class might want to access one of the defined
+# templates. If it does though, it should use getMultiAdapter.
+
+class pages(object):
+
+ def __init__(self, _context, for_, permission,
+ layer=IDefaultBrowserLayer, class_=None,
+ allowed_interface=None, allowed_attributes=None,
+ ):
+ self.opts = dict(for_=for_, permission=permission,
+ layer=layer, class_=class_,
+ allowed_interface=allowed_interface,
+ allowed_attributes=allowed_attributes,
+ )
+
+ def page(self, _context, name, attribute='__call__', template=None,
+ menu=None, title=None):
+ return page(_context,
+ name=name,
+ attribute=attribute,
+ template=template,
+ menu=menu, title=title,
+ **(self.opts))
+
+ def __call__(self):
+ return ()
+
+# view (named view with pages)
+
+# This is a different case. We actually build a class with attributes
+# for all of the given pages.
+
+class view(object):
+
+ default = None
+
+ def __init__(self, _context, for_, permission,
+ name='', layer=IDefaultBrowserLayer, class_=None,
+ allowed_interface=None, allowed_attributes=None,
+ menu=None, title=None, provides=Interface,
+ ):
+
+ _handle_menu(_context, menu, title, [for_], name, permission)
+
+ permission = _handle_permission(_context, permission)
+
+ self.args = (_context, name, for_, permission, layer, class_,
+ allowed_interface, allowed_attributes)
+
+ self.pages = []
+ self.menu = menu
+ self.provides = provides
+
+ def page(self, _context, name, attribute=None, template=None):
+ if template:
+ template = os.path.abspath(_context.path(template))
+ if not os.path.isfile(template):
+ raise ConfigurationError("No such file", template)
+ else:
+ if not attribute:
+ raise ConfigurationError(
+ "Must specify either a template or an attribute name")
+
+ self.pages.append((name, attribute, template))
+ return ()
+
+ def defaultPage(self, _context, name):
+ self.default = name
+ return ()
+
+ def __call__(self):
+ (_context, name, for_, permission, layer, class_,
+ allowed_interface, allowed_attributes) = self.args
+
+ required = {}
+
+ cdict = {}
+ pages = {}
+
+ for pname, attribute, template in self.pages:
+
+ if template:
+ cdict[pname] = ViewPageTemplateFile(template)
+ if attribute and attribute != name:
+ cdict[attribute] = cdict[pname]
+ else:
+ if not hasattr(class_, attribute):
+ raise ConfigurationError("Undefined attribute",
+ attribute)
+
+ attribute = attribute or pname
+ required[pname] = permission
+
+ pages[pname] = attribute
+
+ # This should go away, but noone seems to remember what to do. :-(
+ if hasattr(class_, 'publishTraverse'):
+
+ def publishTraverse(self, request, name,
+ pages=pages, getattr=getattr):
+
+ if name in pages:
+ return getattr(self, pages[name])
+ view = zapi.queryMultiAdapter((self, request), name=name)
+ if view is not None:
+ return view
+
+ m = class_.publishTraverse.__get__(self)
+ return m(request, name)
+
+ else:
+ def publishTraverse(self, request, name,
+ pages=pages, getattr=getattr):
+
+ if name in pages:
+ return getattr(self, pages[name])
+ view = zapi.queryMultiAdapter((self, request), name=name)
+ if view is not None:
+ return view
+
+ raise NotFound(self, name, request)
+
+ cdict['publishTraverse'] = publishTraverse
+
+ if not hasattr(class_, 'browserDefault'):
+ if self.default or self.pages:
+ default = self.default or self.pages[0][0]
+ cdict['browserDefault'] = (
+ lambda self, request, default=default:
+ (self, (default, ))
+ )
+ elif providesCallable(class_):
+ cdict['browserDefault'] = (
+ lambda self, request: (self, ())
+ )
+
+ if class_ is not None:
+ bases = (class_, simple)
+ else:
+ bases = (simple,)
+
+ try:
+ cname = str(name)
+ except:
+ cname = "GeneratedClass"
+
+ newclass = type(cname, bases, cdict)
+
+ for n in ('publishTraverse', 'browserDefault', '__call__'):
+ required[n] = permission
+
+ _handle_allowed_interface(_context, allowed_interface, permission,
+ required)
+ _handle_allowed_attributes(_context, allowed_interface, permission,
+ required)
+ _handle_for(_context, for_)
+
+ defineChecker(newclass, Checker(required))
+
+ if self.provides is not None:
+ _context.action(
+ discriminator = None,
+ callable = provideInterface,
+ args = ('', self.provides)
+ )
+
+ _context.action(
+ discriminator = ('view', (for_, layer), name, self.provides),
+ callable = handler,
+ args = ('provideAdapter',
+ (for_, layer), self.provides, name, newclass,
+ _context.info),
+ )
+
+def addview(_context, name, permission,
+ layer=IDefaultBrowserLayer, class_=None,
+ allowed_interface=None, allowed_attributes=None,
+ menu=None, title=None
+ ):
+ return view(_context, name,
+ 'zope.app.container.interfaces.IAdding',
+ permission,
+ layer, class_,
+ allowed_interface, allowed_attributes,
+ menu, title
+ )
+
+def defaultView(_context, name, for_=None):
+
+ _context.action(
+ discriminator = ('defaultViewName', for_, IBrowserRequest, name),
+ callable = handler,
+ args = ('provideAdapter',
+ (for_, IBrowserRequest), IDefaultViewName, '', name,
+ _context.info)
+ )
+
+ if for_ is not None:
+ _context.action(
+ discriminator = None,
+ callable = provideInterface,
+ args = ('', for_)
+ )
+
+
+def _handle_menu(_context, menu, title, for_, name, permission):
+ if menu or title:
+ if not (menu and title):
+ raise ConfigurationError(
+ "If either menu or title are specified, they must "
+ "both be specified.")
+ if len(for_) != 1:
+ raise ConfigurationError(
+ "Menus can be specified only for single-view, not for "
+ "multi-views.")
+ return menuItemDirective(
+ _context, menu, for_[0], '@@' + name, title,
+ permission=permission)
+
+ return []
+
+
+def _handle_permission(_context, permission):
+ if permission == 'zope.Public':
+ permission = CheckerPublic
+
+ return permission
+
+def _handle_allowed_interface(_context, allowed_interface, permission,
+ required):
+ # Allow access for all names defined by named interfaces
+ if allowed_interface:
+ for i in allowed_interface:
+ _context.action(
+ discriminator = None,
+ callable = provideInterface,
+ args = (None, i)
+ )
+
+ for name in i:
+ required[name] = permission
+
+def _handle_allowed_attributes(_context, allowed_attributes, permission,
+ required):
+ # Allow access for all named attributes
+ if allowed_attributes:
+ for name in allowed_attributes:
+ required[name] = permission
+
+def _handle_for(_context, for_):
+ if for_ is not None:
+ _context.action(
+ discriminator = None,
+ callable = provideInterface,
+ args = ('', for_)
+ )
+
+class simple(BrowserView):
+ implements(IBrowserPublisher)
+
+ def publishTraverse(self, request, name):
+ raise NotFound(self, name, request)
+
+ def __call__(self, *a, **k):
+ # If a class doesn't provide it's own call, then get the attribute
+ # given by the browser default.
+
+ attr = self.__page_attribute__
+ if attr == '__call__':
+ raise AttributeError("__call__")
+
+ meth = getattr(self, attr)
+ return meth(*a, **k)
+
+def providesCallable(class_):
+ if hasattr(class_, '__call__'):
+ for c in class_.__mro__:
+ if '__call__' in c.__dict__:
+ return True
+ return False
diff --git a/xmlrpc/metaconfigure.py b/xmlrpc/metaconfigure.py
new file mode 100644
index 0000000..e59f38a
--- /dev/null
+++ b/xmlrpc/metaconfigure.py
@@ -0,0 +1,103 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""XMLRPC configuration code
+
+$Id$
+"""
+import zope.interface
+from zope.interface import Interface
+from zope.security.checker import CheckerPublic, Checker
+from zope.configuration.exceptions import ConfigurationError
+from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
+
+from zope.app import zapi
+from zope.app.location import Location
+from zope.app.component.interface import provideInterface
+from zope.app.component.metaconfigure import handler
+
+def view(_context, for_=None, interface=None, methods=None,
+ class_=None, permission=None, name=None):
+
+ interface = interface or []
+ methods = methods or []
+
+ # If there were special permission settings provided, then use them
+ if permission == 'zope.Public':
+ permission = CheckerPublic
+
+ require = {}
+ for attr_name in methods:
+ require[attr_name] = permission
+
+ if interface:
+ for iface in interface:
+ for field_name in iface:
+ require[field_name] = permission
+ _context.action(
+ discriminator = None,
+ callable = provideInterface,
+ args = ('', for_)
+ )
+
+ if name:
+ # Register a single view
+
+ if permission:
+ checker = Checker(require)
+
+ def proxyView(context, request, class_=class_, checker=checker):
+ view = class_(context, request)
+ # We need this in case the resource gets unwrapped and
+ # needs to be rewrapped
+ view.__Security_checker__ = checker
+ return view
+
+ class_ = proxyView
+
+ # Register the new view.
+ _context.action(
+ discriminator = ('view', for_, name, IXMLRPCRequest),
+ callable = handler,
+ args = ('provideAdapter',
+ (for_, IXMLRPCRequest), Interface, name, class_,
+ _context.info)
+ )
+ else:
+ if permission:
+ checker = Checker({'__call__': permission})
+ else:
+ checker = None
+
+ for name in require:
+ # create a new callable class with a security checker; mix
+ # in zope.app.location.Location so that the view inherits
+ # a security context
+ cdict = {'__Security_checker__': checker,
+ '__call__': getattr(class_, name)}
+ new_class = type(class_.__name__, (class_, Location), cdict)
+ _context.action(
+ discriminator = ('view', for_, name, IXMLRPCRequest),
+ callable = handler,
+ args = ('provideAdapter',
+ (for_, IXMLRPCRequest), Interface, name, new_class,
+ _context.info)
+ )
+
+ # Register the used interfaces with the interface service
+ if for_ is not None:
+ _context.action(
+ discriminator = None,
+ callable = provideInterface,
+ args = ('', for_)
+ )
diff --git a/xmlrpc/tests/test_directives.py b/xmlrpc/tests/test_directives.py
new file mode 100644
index 0000000..1f20bf9
--- /dev/null
+++ b/xmlrpc/tests/test_directives.py
@@ -0,0 +1,97 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test 'xmlrpc' ZCML Namespace directives.
+
+$Id$
+"""
+import unittest
+
+from zope.configuration import xmlconfig
+from zope.configuration.exceptions import ConfigurationError
+from zope.app.component.tests.views import IC, V1
+from zope.app.testing.placelesssetup import PlacelessSetup
+from zope.security.proxy import ProxyFactory
+
+from zope.component.tests.request import Request
+
+from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
+
+from zope.app import zapi
+from zope.app.publisher import xmlrpc
+from zope.interface import implements
+
+
+request = Request(IXMLRPCRequest)
+
+class Ob(object):
+ implements(IC)
+
+ob = Ob()
+
+class DirectivesTest(PlacelessSetup, unittest.TestCase):
+
+ def testView(self):
+ self.assertEqual(
+ zapi.queryMultiAdapter((ob, request), name='test'), None)
+ xmlconfig.file("xmlrpc.zcml", xmlrpc.tests)
+ self.assertEqual(
+ zapi.queryMultiAdapter((ob, request), name='test').__class__, V1)
+
+ def testInterfaceProtectedView(self):
+ xmlconfig.file("xmlrpc.zcml", xmlrpc.tests)
+ v = zapi.getMultiAdapter((ob, request), name='test2')
+ v = ProxyFactory(v)
+ self.assertEqual(v.index(), 'V1 here')
+ self.assertRaises(Exception, getattr, v, 'action')
+
+ def testAttributeProtectedView(self):
+ xmlconfig.file("xmlrpc.zcml", xmlrpc.tests)
+ v = zapi.getMultiAdapter((ob, request), name='test3')
+ v = ProxyFactory(v)
+ self.assertEqual(v.action(), 'done')
+ self.assertRaises(Exception, getattr, v, 'index')
+
+ def testInterfaceAndAttributeProtectedView(self):
+ xmlconfig.file("xmlrpc.zcml", xmlrpc.tests)
+ v = zapi.getMultiAdapter((ob, request), name='test4')
+ self.assertEqual(v.index(), 'V1 here')
+ self.assertEqual(v.action(), 'done')
+
+ def testDuplicatedInterfaceAndAttributeProtectedView(self):
+ xmlconfig.file("xmlrpc.zcml", xmlrpc.tests)
+ v = zapi.getMultiAdapter((ob, request), name='test5')
+ self.assertEqual(v.index(), 'V1 here')
+ self.assertEqual(v.action(), 'done')
+
+ def testIncompleteProtectedViewNoPermission(self):
+ self.assertRaises(ConfigurationError, xmlconfig.file,
+ "xmlrpc_error.zcml", xmlrpc.tests)
+
+ def test_no_name(self):
+ xmlconfig.file("xmlrpc.zcml", xmlrpc.tests)
+ v = zapi.getMultiAdapter((ob, request), name='index')
+ self.assertEqual(v(), 'V1 here')
+ v = zapi.getMultiAdapter((ob, request), name='action')
+ self.assertEqual(v(), 'done')
+
+
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(DirectivesTest),
+ ))
+
+if __name__ == '__main__':
+ unittest.main()