Integrate Five 1.3b.

parent ce126e73
...@@ -2,6 +2,95 @@ ...@@ -2,6 +2,95 @@
Five Changes Five Changes
============ ============
Five 1.3b (2005-11-02)
======================
This version is also included in Zope 2.9b1.
Restructuring
-------------
* Support for Zope 3.2 was added. Five now requires Zope 2.9 (which
ships with Zope 3.2).
* As scheduled, the temporary fork of the new test runner
(``zope.testing``) at ``Five.testing`` was removed. So was the
``runtests.py`` script. Use the regular Zope test runner
(``test.py`` or ``bin/zopectl test``) to run tests.
* To reflect the Component Architecture simplification in Zope 3 since
the X3 3.0 release, ``IFiveUtilityService`` was renamed to
``IFiveUtilityRegistry`` and ``SimpleLocalUtilityService`` was
renamed to ``SimpleLocalUtilityRegistry``. The old names are still
available for a short period of time.
* Event support: ``<five:containerEvents/>`` is the default.
* Due to an incompatability with Zope 3.2's ObjectWidget and Zope 2's
Page Templates, Five now ships with its own ObjectWidget
implementation (which is just a thin wrapper around Zope's one to
make it work in Zope 2). If you use the ObjectWidget, please change
your imports to ``Products.Five.form.objectwidget.ObjectWidget``.
* Backwards compatability for Zope 3-style interfaces of Zope 2
components has been removed as that functionality is now in the Zope
2 core as of Zope 2.9.
Five 1.2b (2005-11-02)
======================
Features
--------
* Added IMarkerInterfaces adapter: This adapter provides methods for
inspecting and assigning marker interfaces. 'edit-markers.html' (or
'manage_interfaces' in the ZMI) allows to change the behavior of specific
objects by adding or removing marker interfaces TTW.
* Added the five:registerClass directive: This does the necessary Zope 2
registration for Five-based content. It is no longer necessary to add an
``initialize()`` function to the product's __init__ in order to register
a meta type to be addable through the ZMI. See doc/products/ViewsTutorial
for an example how to use the directive.
* Local site support: Five has now support for creating local sites
and thereby local utilities. This is mostly needed for allowing CMF
to convert it's portal tools into local utilities. See
doc/localsite.txt for more information
* Event support: When ``<five:containerEvents/>`` is specified, Five
makes the standard Zope 2 containers send events instead of using
manage_afterAdd, manage_beforeDelete and manage_afterClone. These
methods are still called for a class declared
``<five:deprecatedManageAddDelete class=.../>``, and are called in
compatibility mode with a deprecation warning for classes that don't
use this directive.
Restructuring
-------------
* Removed backwards compatibility for Five 1.0 Zope core interfaces.
* Removed backwards compatibility for Zope 2.7 and 2.8.0.
* Added a (temporarily) forked copy of the "new-and-improved" test
runner and supporting 'zope.testing' package, lifted from
http://svn.zope.org/zope.testing. This code should be removed for
Five 1.3, which will use the updated version of 'zope.testing' in
the Zope 2.9 / Zope 3.2 tree.
There is a test runner invoking script in the ``Five`` package. For
example, to run the Five tests with the new test runner, simply
execute the following command line from your instance home::
$ bin/zopectl run Products/Five/runtests.py -v -s Products.Five
* Moved the 'Five.testing' package down to 'Five.tests.testing', in
order to make room for the 'zope.testing' code.
* Removed backwards compatibility for some moved classes (AddForm,
EditForm, ContentAdding)
Five 1.1 (2005-10-04) Five 1.1 (2005-10-04)
===================== =====================
...@@ -100,7 +189,7 @@ Restructuring ...@@ -100,7 +189,7 @@ Restructuring
* The former test product, ``FiveTest``, was converted into separate * The former test product, ``FiveTest``, was converted into separate
modules that provide the mock objects for the corresponding tests modules that provide the mock objects for the corresponding tests
and are located right next to them. Common test helpers have been and are located right next to them. Common test helpers have been
moved to the Five.testing package. Overall, the testing framework moved to the Five.tests.testing package. Overall, the testing framework
was much simplified and the individual tests clean up after was much simplified and the individual tests clean up after
themselves much like they do in Zope 3. themselves much like they do in Zope 3.
......
...@@ -9,7 +9,7 @@ Five contributors ...@@ -9,7 +9,7 @@ Five contributors
- Lennart Regebro (regebro@nuxeo.com) - Lennart Regebro (regebro@nuxeo.com)
- Tres Seaver (tres@zope.com) - Tres Seaver (tseaver@palladion.com)
- Jan-Wijbrand Kolman (jw@infrae.com) - Jan-Wijbrand Kolman (jw@infrae.com)
...@@ -33,6 +33,9 @@ Five contributors ...@@ -33,6 +33,9 @@ Five contributors
- Tarek Ziad (tziade@nuxeo.com) - Tarek Ziad (tziade@nuxeo.com)
- Whit Morriss (whit@longnow.org)
Thank you Thank you
--------- ---------
......
...@@ -70,7 +70,7 @@ def trustedTraverse(ob, path, ignored,): ...@@ -70,7 +70,7 @@ def trustedTraverse(ob, path, ignored,):
o = get(object, name, M) o = get(object, name, M)
if o is M: if o is M:
try: o = object[name] try: o = object[name]
except AttributeError: # better exception except (AttributeError, TypeError): # better exception
raise AttributeError(name) raise AttributeError(name)
object = o object = o
......
...@@ -13,20 +13,13 @@ ...@@ -13,20 +13,13 @@
############################################################################## ##############################################################################
"""Provide basic browser functionality """Provide basic browser functionality
$Id: __init__.py 18841 2005-10-23 09:57:38Z philikon $ $Id: __init__.py 19283 2005-10-31 17:43:51Z philikon $
""" """
import Acquisition import Acquisition
from AccessControl import ClassSecurityInfo import zope.app.publisher.browser
from Globals import InitializeClass
class BrowserView(Acquisition.Explicit): class BrowserView(Acquisition.Explicit, zope.app.publisher.browser.BrowserView):
security = ClassSecurityInfo() """Five browser view
def __init__(self, context, request): Mixes in explicit acquisition so that security can be acquired for
self.context = context views"""
self.request = request
# XXX do not create any methods on the subclass called index_html,
# as this makes Zope 2 traverse into that first!
InitializeClass(BrowserView)
...@@ -52,7 +52,7 @@ class AbsoluteURL(BrowserView): ...@@ -52,7 +52,7 @@ class AbsoluteURL(BrowserView):
return ( return (
{'name': name, 'url': context.absolute_url()},) {'name': name, 'url': context.absolute_url()},)
view = zapi.getViewProviding(container, IAbsoluteURL, request) view = zapi.getMultiAdapter((container, request), IAbsoluteURL)
base = tuple(view.breadcrumbs()) base = tuple(view.breadcrumbs())
base += ( base += (
{'name': name, 'url': ("%s/%s" % (base[-1]['url'], name))},) {'name': name, 'url': ("%s/%s" % (base[-1]['url'], name))},)
......
...@@ -28,6 +28,8 @@ from zope.app.container.interfaces import IAdding, INameChooser ...@@ -28,6 +28,8 @@ from zope.app.container.interfaces import IAdding, INameChooser
from zope.app.container.interfaces import IContainerNamesContainer from zope.app.container.interfaces import IContainerNamesContainer
from zope.app.container.constraints import checkFactory, checkObject from zope.app.container.constraints import checkFactory, checkObject
from zope.app.publisher.browser.menu import getMenu
from zope.app import zapi from zope.app import zapi
from zope.app.event.objectevent import ObjectCreatedEvent from zope.app.event.objectevent import ObjectCreatedEvent
from zope.event import notify from zope.event import notify
...@@ -84,8 +86,8 @@ class BasicAdding(Implicit, BrowserView): ...@@ -84,8 +86,8 @@ class BasicAdding(Implicit, BrowserView):
# XXX this is definitely not right for all or even most uses # XXX this is definitely not right for all or even most uses
# of Five, but can be overridden by an AddView subclass, using # of Five, but can be overridden by an AddView subclass, using
# the class attribute of a zcml:addform directive # the class attribute of a zcml:addform directive
return (str(zapi.getView(self.context, "absolute_url", self.request)) return str(zapi.getMultiAdapter((self.context, self.request),
+ '/manage_main') name=u"absolute_url")) + '/manage_main'
# set in BrowserView.__init__ # set in BrowserView.__init__
request = None request = None
...@@ -104,7 +106,7 @@ class BasicAdding(Implicit, BrowserView): ...@@ -104,7 +106,7 @@ class BasicAdding(Implicit, BrowserView):
if view_name.startswith('@@'): if view_name.startswith('@@'):
view_name = view_name[2:] view_name = view_name[2:]
return zapi.getView(self, view_name, request) return zapi.getMultiAdapter((self, request), name=view_name)
if name.startswith('@@'): if name.startswith('@@'):
view_name = name[2:] view_name = name[2:]
...@@ -135,7 +137,7 @@ class BasicAdding(Implicit, BrowserView): ...@@ -135,7 +137,7 @@ class BasicAdding(Implicit, BrowserView):
if zapi.queryView(self, view_name, self.request) is not None: if zapi.queryView(self, view_name, self.request) is not None:
url = "%s/%s=%s" % ( url = "%s/%s=%s" % (
zapi.getView(self, "absolute_url", self.request), zapi.getMultiAdapter((self, self.request), name=u"absolute_url"),
type_name, id) type_name, id)
self.request.response.redirect(url) self.request.response.redirect(url)
return return
...@@ -169,12 +171,11 @@ class Adding(BasicAdding): ...@@ -169,12 +171,11 @@ class Adding(BasicAdding):
This is sorted by title. This is sorted by title.
""" """
container = self.context container = self.context
menu_service = zapi.getService("BrowserMenu")
result = [] result = []
for menu_id in (self.menu_id, 'zope.app.container.add'): for menu_id in (self.menu_id, 'zope.app.container.add'):
if not menu_id: if not menu_id:
continue continue
for item in menu_service.getMenu(menu_id, self, self.request): for item in getMenu(menu_id, self, self.request):
extra = item.get('extra') extra = item.get('extra')
if extra: if extra:
factory = extra.get('factory') factory = extra.get('factory')
......
<configure xmlns="http://namespaces.zope.org/zope" <configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"> xmlns:browser="http://namespaces.zope.org/browser">
<serviceType
id="BrowserMenu"
interface="zope.app.publisher.interfaces.browser.IBrowserMenuService"
/>
<service
serviceType="BrowserMenu"
permission="zope.Public"
component="zope.app.publisher.browser.globalbrowsermenuservice.globalBrowserMenuService"
/>
<browser:defaultView name="index.html" /> <browser:defaultView name="index.html" />
<browser:page <browser:page
......
...@@ -13,17 +13,15 @@ ...@@ -13,17 +13,15 @@
############################################################################## ##############################################################################
"""Some menu code """Some menu code
$Id: menu.py 14512 2005-07-11 18:40:51Z philikon $ $Id: menu.py 19283 2005-10-31 17:43:51Z philikon $
""" """
from zope.interface import implements from zope.interface import implements
from zope.app import zapi
from zope.app.publisher.interfaces.browser import IMenuAccessView from zope.app.publisher.interfaces.browser import IMenuAccessView
from zope.app.servicenames import BrowserMenu from zope.app.publisher.browser.menu import getMenu
from Products.Five import BrowserView from Products.Five import BrowserView
class MenuAccessView(BrowserView): class MenuAccessView(BrowserView):
implements(IMenuAccessView) implements(IMenuAccessView)
def __getitem__(self, menu_id): def __getitem__(self, menu_id):
browser_menu_service = zapi.getService(BrowserMenu) return getMenu(menu_id, self.context, self.request)
return browser_menu_service.getMenu(menu_id, self.context, self.request)
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<meta:directive <meta:directive
name="defaultView" name="defaultView"
schema="zope.app.publisher.browser.metadirectives.IDefaultViewDirective" schema="zope.app.publisher.browser.metadirectives.IDefaultViewDirective"
handler=".metaconfigure.defaultView" handler="zope.app.publisher.browser.metaconfigure.defaultView"
/> />
<meta:directive <meta:directive
...@@ -62,19 +62,19 @@ ...@@ -62,19 +62,19 @@
<meta:directive <meta:directive
name="menu" name="menu"
schema="zope.app.publisher.browser.metadirectives.IMenuDirective" schema="zope.app.publisher.browser.metadirectives.IMenuDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuDirective" handler="zope.app.publisher.browser.menumeta.menuDirective"
/> />
<meta:directive <meta:directive
name="menuItem" name="menuItem"
schema="zope.app.publisher.browser.metadirectives.IMenuItemDirective" schema="zope.app.publisher.browser.metadirectives.IMenuItemDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuItemDirective" handler="zope.app.publisher.browser.menumeta.menuItemDirective"
/> />
<meta:complexDirective <meta:complexDirective
name="menuItems" name="menuItems"
schema="zope.app.publisher.browser.metadirectives.IMenuItemsDirective" schema="zope.app.publisher.browser.metadirectives.IMenuItemsDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuItemsDirective" handler="zope.app.publisher.browser.menumeta.menuItemsDirective"
> >
<meta:subdirective <meta:subdirective
......
...@@ -21,18 +21,16 @@ $Id: metaconfigure.py 13257 2005-06-09 21:56:39Z philikon $ ...@@ -21,18 +21,16 @@ $Id: metaconfigure.py 13257 2005-06-09 21:56:39Z philikon $
import os import os
from zope.interface import Interface from zope.interface import Interface
from zope.component import getGlobalService, ComponentLookupError
from zope.configuration.exceptions import ConfigurationError from zope.configuration.exceptions import ConfigurationError
from zope.component.servicenames import Presentation from zope.publisher.interfaces.browser import IBrowserRequest, \
from zope.publisher.interfaces.browser import IBrowserRequest IDefaultBrowserLayer
from zope.app.publisher.browser.viewmeta import pages as zope_app_pages from zope.app.publisher.browser.viewmeta import pages as zope_app_pages
from zope.app.publisher.browser.viewmeta import view as zope_app_view from zope.app.publisher.browser.viewmeta import view as zope_app_view
from zope.app.publisher.browser.viewmeta import providesCallable from zope.app.publisher.browser.viewmeta import providesCallable, \
from zope.app.publisher.browser.globalbrowsermenuservice import\ _handle_menu, _handle_for
menuItemDirective
from zope.app.component.metaconfigure import handler from zope.app.component.metaconfigure import handler
from zope.app.component.interface import provideInterface from zope.app.component.interface import provideInterface
from zope.app.container.interfaces import IAdding
from Products.Five.browser import BrowserView from Products.Five.browser import BrowserView
from Products.Five.browser.resource import FileResourceFactory, ImageResourceFactory from Products.Five.browser.resource import FileResourceFactory, ImageResourceFactory
...@@ -40,22 +38,16 @@ from Products.Five.browser.resource import PageTemplateResourceFactory ...@@ -40,22 +38,16 @@ from Products.Five.browser.resource import PageTemplateResourceFactory
from Products.Five.browser.resource import DirectoryResourceFactory from Products.Five.browser.resource import DirectoryResourceFactory
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Products.Five.metaclass import makeClass from Products.Five.metaclass import makeClass
from Products.Five.security import getSecurityInfo, protectClass, \ from Products.Five.security import getSecurityInfo, protectClass, protectName
protectName, initializeClass
import ExtensionClass from Globals import InitializeClass as initializeClass
def page(_context, name, permission, for_, def page(_context, name, permission, for_,
layer='default', template=None, class_=None, layer=IDefaultBrowserLayer, template=None, class_=None,
allowed_interface=None, allowed_attributes=None, allowed_interface=None, allowed_attributes=None,
attribute='__call__', menu=None, title=None, attribute='__call__', menu=None, title=None,
): ):
try:
s = getGlobalService(Presentation)
except ComponentLookupError, err:
pass
_handle_menu(_context, menu, title, [for_], name, permission) _handle_menu(_context, menu, title, [for_], name, permission)
if not (class_ or template): if not (class_ or template):
...@@ -64,8 +56,7 @@ def page(_context, name, permission, for_, ...@@ -64,8 +56,7 @@ def page(_context, name, permission, for_,
allowed_attributes = [] allowed_attributes = []
if allowed_interface is not None: if allowed_interface is not None:
for interface in allowed_interface: for interface in allowed_interface:
attrs = [n for n, d in interface.namesAndDescriptions(1)] allowed_attributes.extend(interface.names())
allowed_attributes.extend(attrs)
if attribute != '__call__': if attribute != '__call__':
if template: if template:
...@@ -92,9 +83,10 @@ def page(_context, name, permission, for_, ...@@ -92,9 +83,10 @@ def page(_context, name, permission, for_,
"The provided class doesn't have the specified attribute " "The provided class doesn't have the specified attribute "
) )
cdict = getSecurityInfo(class_) cdict = getSecurityInfo(class_)
cdict['__name__'] = name
if template: if template:
new_class = makeClassForTemplate(template, bases=(class_, ), new_class = makeClassForTemplate(template, bases=(class_, ),
cdict=cdict) cdict=cdict, name=name)
elif attribute != "__call__": elif attribute != "__call__":
# we're supposed to make a page for an attribute (read: # we're supposed to make a page for an attribute (read:
# method) and it's not __call__. We thus need to create a # method) and it's not __call__. We thus need to create a
...@@ -125,16 +117,15 @@ def page(_context, name, permission, for_, ...@@ -125,16 +117,15 @@ def page(_context, name, permission, for_,
else: else:
# template # template
new_class = makeClassForTemplate(template) new_class = makeClassForTemplate(template, name=name)
_handle_for(_context, for_) _handle_for(_context, for_)
_context.action( _context.action(
discriminator = ('view', for_, name, IBrowserRequest, layer), discriminator = ('view', for_, name, IBrowserRequest, layer),
callable = handler, callable = handler,
args = (Presentation, 'provideAdapter', args = ('provideAdapter',
IBrowserRequest, new_class, name, [for_], Interface, layer, (for_, layer), Interface, name, new_class, _context.info),
_context.info),
) )
_context.action( _context.action(
discriminator = ('five:protectClass', new_class), discriminator = ('five:protectClass', new_class),
...@@ -165,19 +156,6 @@ class pages(zope_app_pages): ...@@ -165,19 +156,6 @@ class pages(zope_app_pages):
menu=menu, title=title, menu=menu, title=title,
**(self.opts)) **(self.opts))
def defaultView(_context, name, for_=None):
type = IBrowserRequest
_context.action(
discriminator = ('defaultViewName', for_, type, name),
callable = handler,
args = (Presentation,
'setDefaultViewName', for_, type, name),
)
_handle_for(_context, for_)
# view (named view with pages) # view (named view with pages)
class view(zope_app_view): class view(zope_app_view):
...@@ -192,11 +170,6 @@ class view(zope_app_view): ...@@ -192,11 +170,6 @@ class view(zope_app_view):
pages = {} pages = {}
for pname, attribute, template in self.pages: for pname, attribute, template in self.pages:
try:
s = getGlobalService(Presentation)
except ComponentLookupError, err:
pass
if template: if template:
cdict[pname] = ZopeTwoPageTemplateFile(template) cdict[pname] = ZopeTwoPageTemplateFile(template)
if attribute and attribute != name: if attribute and attribute != name:
...@@ -255,7 +228,7 @@ class view(zope_app_view): ...@@ -255,7 +228,7 @@ class view(zope_app_view):
if class_ is not None: if class_ is not None:
bases = (class_, ViewMixinForTemplates) bases = (class_, ViewMixinForTemplates)
else: else:
bases = (ViewMixinForTemplates) bases = (ViewMixinForTemplates,)
try: try:
cname = str(name) cname = str(name)
...@@ -277,37 +250,11 @@ class view(zope_app_view): ...@@ -277,37 +250,11 @@ class view(zope_app_view):
discriminator = ('view', for_, name, IBrowserRequest, layer, discriminator = ('view', for_, name, IBrowserRequest, layer,
self.provides), self.provides),
callable = handler, callable = handler,
args = (Presentation, 'provideAdapter', args = ('provideAdapter',
IBrowserRequest, newclass, name, [for_], self.provides, (for_, layer), self.provides, name, newclass,
layer, _context.info), _context.info),
)
def _handle_for(_context, for_):
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], '@@' + str(name), title,
permission=permission)
return []
_factory_map = {'image':{'prefix':'ImageResource', _factory_map = {'image':{'prefix':'ImageResource',
'count':0, 'count':0,
'factory':ImageResourceFactory}, 'factory':ImageResourceFactory},
...@@ -319,7 +266,7 @@ _factory_map = {'image':{'prefix':'ImageResource', ...@@ -319,7 +266,7 @@ _factory_map = {'image':{'prefix':'ImageResource',
'factory':PageTemplateResourceFactory} 'factory':PageTemplateResourceFactory}
} }
def resource(_context, name, layer='default', permission='zope.Public', def resource(_context, name, layer=IDefaultBrowserLayer, permission='zope.Public',
file=None, image=None, template=None): file=None, image=None, template=None):
if ((file and image) or (file and template) or if ((file and image) or (file and template) or
...@@ -343,8 +290,8 @@ def resource(_context, name, layer='default', permission='zope.Public', ...@@ -343,8 +290,8 @@ def resource(_context, name, layer='default', permission='zope.Public',
_context.action( _context.action(
discriminator = ('resource', name, IBrowserRequest, layer), discriminator = ('resource', name, IBrowserRequest, layer),
callable = handler, callable = handler,
args = (Presentation, 'provideResource', args = ('provideAdapter',
name, IBrowserRequest, factory, layer), (layer,), Interface, name, factory, _context.info),
) )
_context.action( _context.action(
discriminator = ('five:protectClass', new_class), discriminator = ('five:protectClass', new_class),
...@@ -367,7 +314,7 @@ _rd_map = {ImageResourceFactory:{'prefix':'DirContainedImageResource', ...@@ -367,7 +314,7 @@ _rd_map = {ImageResourceFactory:{'prefix':'DirContainedImageResource',
'count':0} 'count':0}
} }
def resourceDirectory(_context, name, directory, layer='default', def resourceDirectory(_context, name, directory, layer=IDefaultBrowserLayer,
permission='zope.Public'): permission='zope.Public'):
if not os.path.isdir(directory): if not os.path.isdir(directory):
...@@ -410,8 +357,8 @@ def resourceDirectory(_context, name, directory, layer='default', ...@@ -410,8 +357,8 @@ def resourceDirectory(_context, name, directory, layer='default',
_context.action( _context.action(
discriminator = ('resource', name, IBrowserRequest, layer), discriminator = ('resource', name, IBrowserRequest, layer),
callable = handler, callable = handler,
args = (Presentation, 'provideResource', args = ('provideAdapter',
name, IBrowserRequest, factory, layer), (layer,), Interface, name, factory, _context.info),
) )
for new_class in new_classes: for new_class in new_classes:
_context.action( _context.action(
...@@ -456,11 +403,12 @@ class ViewMixinForTemplates(BrowserView): ...@@ -456,11 +403,12 @@ class ViewMixinForTemplates(BrowserView):
return self.index(self, *args, **kw) return self.index(self, *args, **kw)
def makeClassForTemplate(filename, globals=None, used_for=None, def makeClassForTemplate(filename, globals=None, used_for=None,
bases=(), cdict=None): bases=(), cdict=None, name=u''):
# XXX needs to deal with security from the bases? # XXX needs to deal with security from the bases?
if cdict is None: if cdict is None:
cdict = {} cdict = {}
cdict.update({'index': ZopeTwoPageTemplateFile(filename, globals)}) cdict.update({'index': ZopeTwoPageTemplateFile(filename, globals),
'__name__': name})
bases += (ViewMixinForTemplates,) bases += (ViewMixinForTemplates,)
class_ = makeClass("SimpleViewClass from %s" % filename, bases, cdict) class_ = makeClass("SimpleViewClass from %s" % filename, bases, cdict)
......
...@@ -18,15 +18,15 @@ $Id: resource.py 13268 2005-06-10 14:18:23Z philikon $ ...@@ -18,15 +18,15 @@ $Id: resource.py 13268 2005-06-10 14:18:23Z philikon $
import os import os
import urllib import urllib
from Acquisition import Explicit import Acquisition
from ComputedAttribute import ComputedAttribute from ComputedAttribute import ComputedAttribute
from OFS.Traversable import Traversable as OFSTraversable from OFS.Traversable import Traversable as OFSTraversable
from zope.exceptions import NotFoundError
from zope.interface import implements from zope.interface import implements
from zope.component.interfaces import IResource from zope.component.interfaces import IResource
from zope.component import getViewProviding
from zope.publisher.interfaces.browser import IBrowserPublisher from zope.publisher.interfaces.browser import IBrowserPublisher
from zope.app import zapi
from zope.app.traversing.browser.interfaces import IAbsoluteURL from zope.app.traversing.browser.interfaces import IAbsoluteURL
from zope.app.datetimeutils import time as timeFromDateTimeString from zope.app.datetimeutils import time as timeFromDateTimeString
from zope.app.publisher.fileresource import File, Image from zope.app.publisher.fileresource import File, Image
...@@ -37,7 +37,7 @@ from Products.Five.browser import BrowserView ...@@ -37,7 +37,7 @@ from Products.Five.browser import BrowserView
_marker = [] _marker = []
class Resource(Explicit): class Resource(Acquisition.Explicit):
"""A publishable resource """A publishable resource
""" """
implements(IResource) implements(IResource)
...@@ -49,7 +49,9 @@ class Resource(Explicit): ...@@ -49,7 +49,9 @@ class Resource(Explicit):
name = self.__name__ name = self.__name__
container = self.__parent__ container = self.__parent__
url = str(getViewProviding(container, IAbsoluteURL, self.request)) # TODO Zope 3 uses site = getSite() instead of container here
# and the @@ resource access view
url = str(zapi.getMultiAdapter((container, self.request), IAbsoluteURL))
url = urllib.unquote(url) url = urllib.unquote(url)
if not isinstance(container, DirectoryResource): if not isinstance(container, DirectoryResource):
name = '++resource++%s' % name name = '++resource++%s' % name
...@@ -213,7 +215,7 @@ class DirectoryResource(BrowserView, Resource, OFSTraversable): ...@@ -213,7 +215,7 @@ class DirectoryResource(BrowserView, Resource, OFSTraversable):
filename = os.path.join(path, name) filename = os.path.join(path, name)
if not os.path.isfile(filename): if not os.path.isfile(filename):
if default is _marker: if default is _marker:
raise NotFoundError(name) raise KeyError(name)
return default return default
ext = name.split('.')[-1] ext = name.split('.')[-1]
factory = self.resource_factories.get(ext, self.default_factory) factory = self.resource_factories.get(ext, self.default_factory)
......
...@@ -7,7 +7,7 @@ ObjectManagerNameChooser ...@@ -7,7 +7,7 @@ ObjectManagerNameChooser
First we need to import and setup some prerequisites: First we need to import and setup some prerequisites:
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> from Products.Five.browser.adding import ObjectManagerNameChooser >>> from Products.Five.browser.adding import ObjectManagerNameChooser
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
......
...@@ -3,15 +3,15 @@ ...@@ -3,15 +3,15 @@
xmlns:five="http://namespaces.zope.org/five"> xmlns:five="http://namespaces.zope.org/five">
<five:defaultViewable <five:defaultViewable
class="Products.Five.testing.simplecontent.SimpleContent" /> class="Products.Five.tests.testing.simplecontent.SimpleContent" />
<browser:defaultView <browser:defaultView
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="eagledefaultview.txt" name="eagledefaultview.txt"
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="eagledefaultview.txt" name="eagledefaultview.txt"
class=".pages.SimpleView" class=".pages.SimpleView"
attribute="eagle" attribute="eagle"
...@@ -22,16 +22,16 @@ ...@@ -22,16 +22,16 @@
already provides __call__, such as our CallableSimpleContent --> already provides __call__, such as our CallableSimpleContent -->
<five:defaultViewable <five:defaultViewable
class="Products.Five.testing.simplecontent.CallableSimpleContent" /> class="Products.Five.tests.testing.simplecontent.CallableSimpleContent" />
<!-- this tests whether five:defaultViewable can be called on a class that <!-- this tests whether five:defaultViewable can be called on a class that
already provides index_html, such as our IndexSimpleContent --> already provides index_html, such as our IndexSimpleContent -->
<five:defaultViewable <five:defaultViewable
class="Products.Five.testing.simplecontent.IndexSimpleContent" /> class="Products.Five.tests.testing.simplecontent.IndexSimpleContent" />
<browser:defaultView <browser:defaultView
for="Products.Five.testing.simplecontent.IIndexSimpleContent" for="Products.Five.tests.testing.simplecontent.IIndexSimpleContent"
name="index_html" name="index_html"
/> />
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<!-- mouse instead of eagle --> <!-- mouse instead of eagle -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
attribute="mouse" attribute="mouse"
name="overridden_view" name="overridden_view"
......
...@@ -34,6 +34,11 @@ class FancyView(BrowserView): ...@@ -34,6 +34,11 @@ class FancyView(BrowserView):
def view(self): def view(self):
return "Fancy, fancy" return "Fancy, fancy"
class CallView(BrowserView):
def __call__(self):
return "I was __call__()'ed"
class CallableNoDocstring: class CallableNoDocstring:
def __call__(self): def __call__(self):
......
...@@ -10,7 +10,7 @@ Let's register a quite large amount of test pages: ...@@ -10,7 +10,7 @@ Let's register a quite large amount of test pages:
Let's add a test object that we view most of the pages off of: Let's add a test object that we view most of the pages off of:
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
We also need to create a stub user account and login; otherwise we We also need to create a stub user account and login; otherwise we
...@@ -29,7 +29,7 @@ Simple pages ...@@ -29,7 +29,7 @@ Simple pages
A browser page that is a view class's attribute (method): A browser page that is a view class's attribute (method):
>>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt') >>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
>>> view != None >>> view is not None
True True
>>> from Products.Five.browser.tests.pages import SimpleView >>> from Products.Five.browser.tests.pages import SimpleView
>>> isinstance(view, SimpleView) >>> isinstance(view, SimpleView)
...@@ -196,8 +196,10 @@ high-level security tests). Let's manually look up a protected view: ...@@ -196,8 +196,10 @@ high-level security tests). Let's manually look up a protected view:
>>> from Products.Five.traversable import FakeRequest >>> from Products.Five.traversable import FakeRequest
>>> from zope.app import zapi >>> from zope.app import zapi
>>> from zope.app.publication.browser import setDefaultSkin
>>> request = FakeRequest() >>> request = FakeRequest()
>>> view = zapi.getView(self.folder.testoid, 'eagle.txt', request) >>> setDefaultSkin(request)
>>> view = zapi.getMultiAdapter((self.folder.testoid, request), name=u'eagle.txt')
It's protecting the object with the permission, and not the attribute, It's protecting the object with the permission, and not the attribute,
so we get ('',) instead of ('eagle',): so we get ('',) instead of ('eagle',):
...@@ -213,7 +215,7 @@ evaluated. __roles__ is a imPermissionRole object: ...@@ -213,7 +215,7 @@ evaluated. __roles__ is a imPermissionRole object:
>>> view_roles >>> view_roles
('Manager',) ('Manager',)
Check to see if view's context properly acquires it's true Check to see if view's context properly acquires its true
parent parent
>>> from Acquisition import aq_parent, aq_base, aq_inner >>> from Acquisition import aq_parent, aq_base, aq_inner
...@@ -255,8 +257,8 @@ High-level security ...@@ -255,8 +257,8 @@ High-level security
... 'nodoc-method', 'nodoc-function', 'nodoc-object', ... 'nodoc-method', 'nodoc-function', 'nodoc-object',
... 'dirpage1', 'dirpage2'] ... 'dirpage1', 'dirpage2']
>>> from Products.Five.testing.restricted import checkRestricted >>> from Products.Five.tests.testing.restricted import checkRestricted
>>> from Products.Five.testing.restricted import checkUnauthorized >>> from Products.Five.tests.testing.restricted import checkUnauthorized
As long as we're not authenticated, we should get Unauthorized for As long as we're not authenticated, we should get Unauthorized for
protected views, but we should be able to view the public ones: protected views, but we should be able to view the public ones:
...@@ -312,5 +314,5 @@ Test traversal to resources from within ZPT pages: ...@@ -312,5 +314,5 @@ Test traversal to resources from within ZPT pages:
Clean up Clean up
-------- --------
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<!-- attribute page --> <!-- attribute page -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
attribute="eagle" attribute="eagle"
name="eagle.txt" name="eagle.txt"
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
name="eagle.method" name="eagle.method"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<!-- attribute page --> <!-- attribute page -->
<browser:pages <browser:pages
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
> >
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
<!-- template/class page --> <!-- template/class page -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
template="falcon.pt" template="falcon.pt"
name="falcon.html" name="falcon.html"
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
<!-- template page (with simple python expression) --> <!-- template page (with simple python expression) -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="owl.pt" template="owl.pt"
name="owl.html" name="owl.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
<!-- template page which calls on context using python and path <!-- template page which calls on context using python and path
expressions --> expressions -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="flamingo.pt" template="flamingo.pt"
name="flamingo.html" name="flamingo.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
<!-- template/class page which calls on context, view, views --> <!-- template/class page which calls on context, view, views -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
template="condor.pt" template="condor.pt"
name="condor.html" name="condor.html"
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
<!-- template page that defines a macro page --> <!-- template page that defines a macro page -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="birdmacro.pt" template="birdmacro.pt"
name="bird.html" name="bird.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
<!-- template page that uses macro page --> <!-- template page that uses macro page -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="seagull.pt" template="seagull.pt"
name="seagull.html" name="seagull.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
...@@ -92,21 +92,21 @@ ...@@ -92,21 +92,21 @@
<!-- test TALES --> <!-- test TALES -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="ostrich.pt" template="ostrich.pt"
name="ostrich.html" name="ostrich.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="tales_traversal.pt" template="tales_traversal.pt"
name="tales_traversal.html" name="tales_traversal.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="template_variables.pt" template="template_variables.pt"
name="template_variables.html" name="template_variables.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
...@@ -115,7 +115,7 @@ ...@@ -115,7 +115,7 @@
<!-- template security --> <!-- template security -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="security.pt" template="security.pt"
name="security.html" name="security.html"
permission="zope2.View" permission="zope2.View"
...@@ -124,7 +124,7 @@ ...@@ -124,7 +124,7 @@
<!-- a publicly accessible page, attribute, template, template/class --> <!-- a publicly accessible page, attribute, template, template/class -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
attribute="eagle" attribute="eagle"
name="public_attribute_page" name="public_attribute_page"
...@@ -132,14 +132,14 @@ ...@@ -132,14 +132,14 @@
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="owl.pt" template="owl.pt"
name="public_template_page" name="public_template_page"
permission="zope2.Public" permission="zope2.Public"
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
template="falcon.pt" template="falcon.pt"
name="public_template_class_page" name="public_template_class_page"
...@@ -147,16 +147,23 @@ ...@@ -147,16 +147,23 @@
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
template="parakeet.pt" template="parakeet.pt"
name="parakeet.html" name="parakeet.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
/> />
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.CallView"
name="callview.html"
permission="zope2.Public"
/>
<!-- pages from methods/functions/callables that don't have docstrings --> <!-- pages from methods/functions/callables that don't have docstrings -->
<browser:pages <browser:pages
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class="Products.Five.browser.tests.pages.NoDocstringView" class="Products.Five.browser.tests.pages.NoDocstringView"
permission="zope2.Public"> permission="zope2.Public">
<browser:page <browser:page
...@@ -177,7 +184,7 @@ ...@@ -177,7 +184,7 @@
This is mainly used to load Zope2 skin templates so they can be used This is mainly used to load Zope2 skin templates so they can be used
in five skins and layers. --> in five skins and layers. -->
<five:pagesFromDirectory <five:pagesFromDirectory
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
module="Products.Five.browser.tests" module="Products.Five.browser.tests"
directory="pages" directory="pages"
permission="zope2.Public" permission="zope2.Public"
...@@ -186,7 +193,7 @@ ...@@ -186,7 +193,7 @@
<!-- browser:page directives with new style classes are ignored --> <!-- browser:page directives with new style classes are ignored -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.NewStyleClass" class=".pages.NewStyleClass"
name="new_style_class" name="new_style_class"
attribute="method" attribute="method"
...@@ -198,7 +205,7 @@ ...@@ -198,7 +205,7 @@
<browser:view <browser:view
name="" name=""
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
permission="zope2.Public" permission="zope2.Public"
/> />
...@@ -207,14 +214,14 @@ ...@@ -207,14 +214,14 @@
<!-- protected edit form for permission check --> <!-- protected edit form for permission check -->
<browser:editform <browser:editform
schema="Products.Five.testing.simplecontent.ISimpleContent" schema="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="protectededitform.html" name="protectededitform.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
/> />
<!-- stuff that we'll override in overrides.zcml --> <!-- stuff that we'll override in overrides.zcml -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
attribute="eagle" attribute="eagle"
name="overridden_view" name="overridden_view"
......
...@@ -11,7 +11,7 @@ some: ...@@ -11,7 +11,7 @@ some:
Let's also add one of our stub objects to play with: Let's also add one of our stub objects to play with:
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
...@@ -122,8 +122,23 @@ All public views should always be accessible by anyone: ...@@ -122,8 +122,23 @@ All public views should always be accessible by anyone:
... self.failUnless(status == 200, (status, 200, view_name)) ... self.failUnless(status == 200, (status, 200, view_name))
Miscellaneous
-------------
Zope 2 always wants objects in the traversal graph to have a __name__.
That is also true for views, e.g. a view constructed from a simple
class bearing only a __call__ method:
>>> print http(r'''
... GET /test_folder_1_/testoid/callview.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
I was __call__()'ed
Clean up Clean up
-------- --------
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
...@@ -42,7 +42,7 @@ the PTS languagees adapter and 3) register our test page: ...@@ -42,7 +42,7 @@ the PTS languagees adapter and 3) register our test page:
Finally, we need a traversable folder so that the test page we Finally, we need a traversable folder so that the test page we
registered is found: registered is found:
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'ftf') >>> manage_addFiveTraversableFolder(self.folder, 'ftf')
Now for some actual testing... Our test page is a simple ZPT Now for some actual testing... Our test page is a simple ZPT
......
...@@ -8,7 +8,7 @@ Set up the test fixtures: ...@@ -8,7 +8,7 @@ Set up the test fixtures:
>>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests) >>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> import os, glob >>> import os, glob
...@@ -73,8 +73,8 @@ PageTemplateResource's __call__ renders the template ...@@ -73,8 +73,8 @@ PageTemplateResource's __call__ renders the template
Security Security
-------- --------
>>> from Products.Five.testing.restricted import checkRestricted >>> from Products.Five.tests.testing.restricted import checkRestricted
>>> from Products.Five.testing.restricted import checkUnauthorized >>> from Products.Five.tests.testing.restricted import checkUnauthorized
>>> resource_names = ['cockatiel.html', 'style.css', 'pattern.png'] >>> resource_names = ['cockatiel.html', 'style.css', 'pattern.png']
...@@ -112,5 +112,5 @@ We can now view them all: ...@@ -112,5 +112,5 @@ We can now view them all:
Clean up Clean up
-------- --------
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
...@@ -8,7 +8,7 @@ Set up the test fixtures: ...@@ -8,7 +8,7 @@ Set up the test fixtures:
>>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests) >>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> import os, glob >>> import os, glob
...@@ -69,5 +69,5 @@ Page templates aren't guaranteed to render, so exclude them from the test: ...@@ -69,5 +69,5 @@ Page templates aren't guaranteed to render, so exclude them from the test:
Clean up Clean up
-------- --------
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
...@@ -29,7 +29,7 @@ def test_absoluteurl(): ...@@ -29,7 +29,7 @@ def test_absoluteurl():
>>> from Products.Five import zcml >>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config("configure.zcml", Products.Five)
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
A simple traversal will yield us the @@absolute_url view: A simple traversal will yield us the @@absolute_url view:
...@@ -86,7 +86,7 @@ def test_absoluteurl(): ...@@ -86,7 +86,7 @@ def test_absoluteurl():
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
......
...@@ -33,9 +33,9 @@ def test_default_view(): ...@@ -33,9 +33,9 @@ def test_default_view():
Now let's add a couple of stub objects: Now let's add a couple of stub objects:
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.testing.simplecontent import manage_addCallableSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addCallableSimpleContent
>>> from Products.Five.testing.simplecontent import manage_addIndexSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addIndexSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> manage_addCallableSimpleContent(self.folder, 'testcall', 'TestCall') >>> manage_addCallableSimpleContent(self.folder, 'testcall', 'TestCall')
...@@ -79,7 +79,7 @@ def test_default_view(): ...@@ -79,7 +79,7 @@ def test_default_view():
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
......
...@@ -33,7 +33,7 @@ def test_zpt_i18n(): ...@@ -33,7 +33,7 @@ def test_zpt_i18n():
... </configure> ... </configure>
... <configure package="Products.Five.browser.tests"> ... <configure package="Products.Five.browser.tests">
... <browser:page ... <browser:page
... for="Products.Five.interfaces.IFolder" ... for="OFS.interfaces.IFolder"
... template="i18n.pt" ... template="i18n.pt"
... name="i18n.html" ... name="i18n.html"
... permission="zope2.View" ... permission="zope2.View"
...@@ -49,7 +49,7 @@ def test_zpt_i18n(): ...@@ -49,7 +49,7 @@ def test_zpt_i18n():
In order to be able to traverse to the PageTemplate view, we need In order to be able to traverse to the PageTemplate view, we need
a traversable object: a traversable object:
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
We tell Zope to translate the messages by passing the We tell Zope to translate the messages by passing the
...@@ -80,7 +80,7 @@ def test_zpt_i18n(): ...@@ -80,7 +80,7 @@ def test_zpt_i18n():
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
......
...@@ -28,7 +28,8 @@ def test_menu(): ...@@ -28,7 +28,8 @@ def test_menu():
>>> import Products.Five.browser.tests >>> import Products.Five.browser.tests
>>> from Products.Five import zcml >>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config("meta.zcml", Products.Five)
>>> zcml.load_config("permissions.zcml", Products.Five)
>>> zcml.load_config('menu.zcml', package=Products.Five.browser.tests) >>> zcml.load_config('menu.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.security import newInteraction >>> from Products.Five.security import newInteraction
...@@ -37,12 +38,12 @@ def test_menu(): ...@@ -37,12 +38,12 @@ def test_menu():
Now for some actual testing... Let's look up the menu we registered: Now for some actual testing... Let's look up the menu we registered:
>>> from Products.Five.traversable import FakeRequest >>> from Products.Five.traversable import FakeRequest
>>> from zope.app.publisher.browser.globalbrowsermenuservice import \\ >>> from zope.app.publication.browser import setDefaultSkin
... globalBrowserMenuService >>> from zope.app.publisher.browser.menu import getMenu
>>> request = FakeRequest() >>> request = FakeRequest()
>>> menu = globalBrowserMenuService.getMenu( >>> setDefaultSkin(request)
... 'testmenu', self.folder, request) >>> menu = getMenu('testmenu', self.folder, request)
It should have It should have
...@@ -53,27 +54,41 @@ def test_menu(): ...@@ -53,27 +54,41 @@ def test_menu():
>>> menu.sort(lambda x, y: cmp(x['title'], y['title'])) >>> menu.sort(lambda x, y: cmp(x['title'], y['title']))
>>> from pprint import pprint >>> from pprint import pprint
>>> pprint(menu) >>> pprint(menu[0])
[{'action': '@@cockatiel_menu_public.html', {'action': u'@@cockatiel_menu_public.html',
'description': '', 'description': u'',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Page in a menu (public)'}, 'selected': u'',
'submenu': None,
'title': u'Page in a menu (public)'}
>>> pprint(menu[1])
{'action': u'seagull.html', {'action': u'seagull.html',
'description': u'This is a test menu item', 'description': u'This is a test menu item',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Test Menu Item'}, 'selected': u'',
'submenu': None,
'title': u'Test Menu Item'}
>>> pprint(menu[2])
{'action': u'parakeet.html', {'action': u'parakeet.html',
'description': u'This is a test menu item', 'description': u'This is a test menu item',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Test Menu Item 2'}, 'selected': u'',
'submenu': None,
'title': u'Test Menu Item 2'}
>>> pprint(menu[3])
{'action': u'falcon.html', {'action': u'falcon.html',
'description': u'This is a test menu item', 'description': u'This is a test menu item',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Test Menu Item 3'}] 'selected': u'',
'submenu': None,
'title': u'Test Menu Item 3'}
Let's create a manager user account and log in. Let's create a manager user account and log in.
...@@ -82,8 +97,7 @@ def test_menu(): ...@@ -82,8 +97,7 @@ def test_menu():
>>> self.login('manager') >>> self.login('manager')
>>> newInteraction() >>> newInteraction()
>>> menu = globalBrowserMenuService.getMenu( >>> menu = getMenu('testmenu', self.folder, request)
... 'testmenu', self.folder, request)
We should get the protected menu items now: We should get the protected menu items now:
...@@ -91,47 +105,73 @@ def test_menu(): ...@@ -91,47 +105,73 @@ def test_menu():
7 7
>>> menu.sort(lambda x, y: cmp(x['title'], y['title'])) >>> menu.sort(lambda x, y: cmp(x['title'], y['title']))
>>> pprint(menu) >>> pprint(menu[0])
[{'action': '@@cockatiel_menu_protected.html', {'action': u'@@cockatiel_menu_protected.html',
'description': '', 'description': u'',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Page in a menu (protected)'}, 'selected': u'',
{'action': '@@cockatiel_menu_public.html', 'submenu': None,
'description': '', 'title': u'Page in a menu (protected)'}
>>> pprint(menu[1])
{'action': u'@@cockatiel_menu_public.html',
'description': u'',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Page in a menu (public)'}, 'selected': u'',
'submenu': None,
'title': u'Page in a menu (public)'}
>>> pprint(menu[2])
{'action': u'seagull.html', {'action': u'seagull.html',
'description': u'This is a protected test menu item', 'description': u'This is a protected test menu item',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Protected Test Menu Item'}, 'selected': u'',
'submenu': None,
'title': u'Protected Test Menu Item'}
>>> pprint(menu[3])
{'action': u'falcon.html', {'action': u'falcon.html',
'description': u'This is a protected test menu item', 'description': u'This is a protected test menu item',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Protected Test Menu Item 2'}, 'selected': u'',
'submenu': None,
'title': u'Protected Test Menu Item 2'}
>>> pprint(menu[4])
{'action': u'seagull.html', {'action': u'seagull.html',
'description': u'This is a test menu item', 'description': u'This is a test menu item',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Test Menu Item'}, 'selected': u'',
'submenu': None,
'title': u'Test Menu Item'}
>>> pprint(menu[5])
{'action': u'parakeet.html', {'action': u'parakeet.html',
'description': u'This is a test menu item', 'description': u'This is a test menu item',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Test Menu Item 2'}, 'selected': u'',
'submenu': None,
'title': u'Test Menu Item 2'}
>>> pprint(menu[6])
{'action': u'falcon.html', {'action': u'falcon.html',
'description': u'This is a test menu item', 'description': u'This is a test menu item',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Test Menu Item 3'}] 'selected': u'',
'submenu': None,
'title': u'Test Menu Item 3'}
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
......
...@@ -26,7 +26,7 @@ def test_ViewAcquisitionWrapping(): ...@@ -26,7 +26,7 @@ def test_ViewAcquisitionWrapping():
>>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests) >>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> uf = self.folder.acl_users >>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], []) >>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
...@@ -56,7 +56,7 @@ def test_ViewAcquisitionWrapping(): ...@@ -56,7 +56,7 @@ def test_ViewAcquisitionWrapping():
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
...@@ -65,11 +65,10 @@ def test_suite(): ...@@ -65,11 +65,10 @@ def test_suite():
from Testing.ZopeTestCase import installProduct, ZopeDocTestSuite from Testing.ZopeTestCase import installProduct, ZopeDocTestSuite
from Testing.ZopeTestCase import ZopeDocFileSuite from Testing.ZopeTestCase import ZopeDocFileSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite from Testing.ZopeTestCase import FunctionalDocFileSuite
installProduct('PythonScripts') # for Five.testing.restricted installProduct('PythonScripts') # for Five.tests.testing.restricted
return unittest.TestSuite(( return unittest.TestSuite((
ZopeDocTestSuite(), ZopeDocTestSuite(),
ZopeDocFileSuite('pages.txt', ZopeDocFileSuite('pages.txt', package='Products.Five.browser.tests'),
package='Products.Five.browser.tests'),
FunctionalDocFileSuite('pages_ftest.txt', FunctionalDocFileSuite('pages_ftest.txt',
package='Products.Five.browser.tests') package='Products.Five.browser.tests')
)) ))
......
...@@ -23,9 +23,6 @@ def test_recursion(): ...@@ -23,9 +23,6 @@ def test_recursion():
""" """
Test recursion Test recursion
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> setUp()
This test makes sure that recursion is avoided for view lookup. This test makes sure that recursion is avoided for view lookup.
First, we need to set up a stub interface... First, we need to set up a stub interface...
...@@ -51,10 +48,10 @@ def test_recursion(): ...@@ -51,10 +48,10 @@ def test_recursion():
>>> from Products.Five.fiveconfigure import classDefaultViewable >>> from Products.Five.fiveconfigure import classDefaultViewable
>>> classDefaultViewable(Recurse) >>> classDefaultViewable(Recurse)
>>> from zope.app import zapi >>> from zope.component import provideAdapter
>>> from zope.publisher.interfaces.browser import IBrowserRequest >>> from zope.publisher.interfaces.browser import IBrowserRequest
>>> pres = zapi.getGlobalService('Presentation') >>> from zope.component.interfaces import IDefaultViewName
>>> pres.setDefaultViewName(IRecurse, IBrowserRequest, 'view') >>> provideAdapter(u'view', (IRecurse, IBrowserRequest), IDefaultViewName)
Here comes the actual test: Here comes the actual test:
...@@ -65,9 +62,10 @@ def test_recursion(): ...@@ -65,9 +62,10 @@ def test_recursion():
'foo' 'foo'
Clean up: Clean up adapter registry and monkey patches to classes:
>>> tearDown() >>> from zope.testing.cleanup import cleanUp
>>> cleanUp()
""" """
def test_suite(): def test_suite():
......
...@@ -23,7 +23,7 @@ def test_suite(): ...@@ -23,7 +23,7 @@ def test_suite():
import unittest import unittest
from Testing.ZopeTestCase import installProduct, ZopeDocFileSuite from Testing.ZopeTestCase import installProduct, ZopeDocFileSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite from Testing.ZopeTestCase import FunctionalDocFileSuite
installProduct('PythonScripts') # for Five.testing.restricted installProduct('PythonScripts') # for Five.tests.testing.restricted
return unittest.TestSuite(( return unittest.TestSuite((
ZopeDocFileSuite('resource.txt', ZopeDocFileSuite('resource.txt',
package='Products.Five.browser.tests'), package='Products.Five.browser.tests'),
......
...@@ -33,7 +33,7 @@ def test_traversable(): ...@@ -33,7 +33,7 @@ def test_traversable():
the wrong reason: None doesn't have a docstring so BaseRequest the wrong reason: None doesn't have a docstring so BaseRequest
raises NotFoundError.) raises NotFoundError.)
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> print http(r''' >>> print http(r'''
... GET /test_folder_1_/testoid/doesntexist HTTP/1.1 ... GET /test_folder_1_/testoid/doesntexist HTTP/1.1
...@@ -54,21 +54,21 @@ def test_traversable(): ...@@ -54,21 +54,21 @@ def test_traversable():
... <meta:redefinePermission from="zope2.Public" to="zope.Public" /> ... <meta:redefinePermission from="zope2.Public" to="zope.Public" />
... ...
... <five:traversable ... <five:traversable
... class="Products.Five.testing.fancycontent.FancyContent" ... class="Products.Five.tests.testing.fancycontent.FancyContent"
... /> ... />
... ...
... <browser:page ... <browser:page
... for="Products.Five.testing.fancycontent.IFancyContent" ... for="Products.Five.tests.testing.fancycontent.IFancyContent"
... class="Products.Five.browser.tests.pages.FancyView" ... class="Products.Five.browser.tests.pages.FancyView"
... attribute="view" ... attribute="view"
... name="fancy" ... name="fancyview"
... permission="zope2.Public" ... permission="zope2.Public"
... /> ... />
... ...
... </configure>''' ... </configure>'''
>>> zcml.load_string(configure_zcml) >>> zcml.load_string(configure_zcml)
>>> from Products.Five.testing.fancycontent import manage_addFancyContent >>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent
>>> info = manage_addFancyContent(self.folder, 'fancy', '') >>> info = manage_addFancyContent(self.folder, 'fancy', '')
In the following test we let the original __bobo_traverse__ method In the following test we let the original __bobo_traverse__ method
...@@ -85,7 +85,7 @@ def test_traversable(): ...@@ -85,7 +85,7 @@ def test_traversable():
actually works: actually works:
>>> print http(r''' >>> print http(r'''
... GET /test_folder_1_/fancy/fancy HTTP/1.1 ... GET /test_folder_1_/fancy/fancyview HTTP/1.1
... ''') ... ''')
HTTP/1.1 200 OK HTTP/1.1 200 OK
... ...
...@@ -94,7 +94,7 @@ def test_traversable(): ...@@ -94,7 +94,7 @@ def test_traversable():
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
......
...@@ -2,13 +2,15 @@ ...@@ -2,13 +2,15 @@
xmlns:five="http://namespaces.zope.org/five"> xmlns:five="http://namespaces.zope.org/five">
<include file="meta.zcml" /> <include file="meta.zcml" />
<include file="services.zcml" />
<include file="interfaces.zcml" />
<include file="permissions.zcml" /> <include file="permissions.zcml" />
<include file="i18n.zcml" /> <include file="i18n.zcml" />
<include file="event.zcml"/>
<include file="deprecated.zcml"/>
<include package=".site" />
<include package=".browser" /> <include package=".browser" />
<include package=".form" /> <include package=".form" />
<include package=".skin" /> <include package=".skin" />
<include package=".utilities" />
<include package="zope.app.event" /> <include package="zope.app.event" />
<include package="zope.app.traversing" /> <include package="zope.app.traversing" />
......
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<!-- deprecated in core Zope, should be fixed there in Zope 2.9 -->
<five:deprecatedManageAddDelete
class="AccessControl.User.BasicUserFolder"/>
<five:deprecatedManageAddDelete
class="App.Factory.Factory"/>
<five:deprecatedManageAddDelete
class="App.Permission.Permission"/>
<five:deprecatedManageAddDelete
class="HelpSys.HelpTopic.HelpTopicBase"/>
<five:deprecatedManageAddDelete
class="OFS.Cache.CacheManager"/>
<five:deprecatedManageAddDelete
class="Products.OFSP.Draft.Draft"/>
<five:deprecatedManageAddDelete
class="Products.OFSP.Version.Version"/>
<five:deprecatedManageAddDelete
class="Products.PythonScripts.PythonScript.PythonScript"/>
<five:deprecatedManageAddDelete
class="Products.Sessions.BrowserIdManager.BrowserIdManager"/>
<five:deprecatedManageAddDelete
class="Products.Sessions.SessionDataManager.SessionDataManager"/>
<five:deprecatedManageAddDelete
class="Products.SiteAccess.VirtualHostMonster.VirtualHostMonster"/>
<five:deprecatedManageAddDelete
class="Products.SiteAccess.SiteRoot.Traverser"/>
<five:deprecatedManageAddDelete
class="Products.SiteErrorLog.SiteErrorLog.SiteErrorLog"/>
<five:deprecatedManageAddDelete
class="Products.ZCatalog.CatalogAwareness.CatalogAware"/>
<five:deprecatedManageAddDelete
class="Products.ZCatalog.CatalogPathAwareness.CatalogAware"/>
<five:deprecatedManageAddDelete
class="ZClasses.Property.ZCommonSheet"/>
<five:deprecatedManageAddDelete
class="ZClasses.ZClass.ZClass"/>
</configure>
...@@ -33,16 +33,6 @@ redefinePermission ...@@ -33,16 +33,6 @@ redefinePermission
Redefine a permission in included ZCML as another one. Redefine a permission in included ZCML as another one.
service
-------
Declare a global service
serviceType
-----------
Declare a type of service.
skin skin
---- ----
...@@ -55,8 +45,24 @@ Declare a global utility. ...@@ -55,8 +45,24 @@ Declare a global utility.
interface interface
--------- ---------
Register an interface in ZCML. Register an interface in ZCML.
factory
-------
Register an object factory.
modulealias
-----------
Provide a module under an alias name, e.g. for persistent backward
compatability.
hook
----
Install a hook on a hookable object.
browser ``http://namespaces.zope.org/browser`` browser ``http://namespaces.zope.org/browser``
============================================== ==============================================
...@@ -148,16 +154,34 @@ sizable ...@@ -148,16 +154,34 @@ sizable
Retrieve size information for a Zope 2 content class via a Zope 3 Retrieve size information for a Zope 2 content class via a Zope 3
style ``ISized`` adapter. style ``ISized`` adapter.
sendEvents containerEvents
---------- ---------------
Make events be sent for Zope 2 container objects, instead of calling old
methods like ``manage_afterAdd``. These old methods will still be called
for classes specified in a ``deprecatedManageAddDelete`` directive.
Lets a Zope 2 content class send out Zope 3 object events that deprecatedManageAddDelete
correspond to the Zope 2 methods ``manage_afterAdd`` and -------------------------
``manage_beforeDelete``.
Specify a class that needs its old deprecated methods like
``manage_afterAdd``, ``manage_beforeDelete`` and ``manage_afterClone``
to be called. Modern classes should use event subscribers instead.
pagesFromDirectory pagesFromDirectory
------------------ ------------------
Load all *.pt files in a directory as pages. Useful when you want to Loads all files with .pt extension in a directory as pages.
share templates between Five and CMF, so you can declare pages like
this is a similar way to setting up skin folders in portal_skins. registerClass
-------------
Registers Five content with Zope 2.
localsite
---------
Turns a class into an implementation of ``IPossibleSite`` so that its
instances can be serve as local sites. Unless otherwise specified, a
default implementation's methods will be used to make the class comply
with the ``IPossibleSite`` interface.
...@@ -8,7 +8,7 @@ and some limitations. ...@@ -8,7 +8,7 @@ and some limitations.
Zope 3 interfaces Zope 3 interfaces
================= =================
Everything in the ``zope.interface`` package should work. Zope 3 Everything from the ``zope.interface`` package works. Zope 3
interfaces are the foundation of the component architecture, and also interfaces are the foundation of the component architecture, and also
the foundation of schemas. the foundation of schemas.
...@@ -84,3 +84,18 @@ Zope 2 content classes. Note however that these permissions will be ...@@ -84,3 +84,18 @@ Zope 2 content classes. Note however that these permissions will be
ignored by views anyway, as they are trusted -- it only serves to ignored by views anyway, as they are trusted -- it only serves to
protect directly exposed methods on content classes (the python protect directly exposed methods on content classes (the python
scripts and the ZPublisher). scripts and the ZPublisher).
Local Sites
===========
Five supports the concept of a local sites and local site managers.
See localsite.txt_ for more information.
.. _localsite.txt: localsite.html
Object events
=============
Five supports sending Zope 3 object events when objects are added,
moved, renamed, copied and deleted. The use of ``manage_afterAdd`` & co
methods is deprecated.
===============================
Porting Five to Zope 3.1+ notes
===============================
Introduction
------------
Five needs to work in Zope 2.9. Zope 2.9 will ship with Zope 3.2. This
means Five will need to work with Zope 3.2. Since Zope 3.2 doesn't
truly exist yet we'll target Zope 3.1 for now.
A Five Roadmap
--------------
Here is a tentative Five roadmap:
Five 1.1 is to be released shortly, and its main feature is a
refactored directory structure and Zope 3 i18n for Zope 2. It's still
targeting the Zope X3.0 that's in Zope 2.8.
Five 1.2 is still targetting Zope 2.8, and its main expected feature
is support for local utilities.
Five 1.3 is targetting Zope 2.9 and thus Zope 3.2. We're talking about
this release of Five in this document.
Main problem
------------
Zope 3.1 has internal changes that Five needs to support. Five works
by reimplementing ZCML statements it supplies in the context of Zope
2. This reimplementation is hard to maintain, as for each Zope 3
upgrade we need to review all these ZCML statements and port them into
Five again.
The straightforward way to start supporting Zope 3.1+ with Five would
be to review all the ZCML statements in Five and update them to work
with Zope 3.1+.
A more ambitious but nicer solution would be if we could reuse the
Zope 3 ZCML statements directly. If we could accomplish this,
maintainability of Five would be improved by a lot. Far less review of
Five would be necessary for each Zope 3 upgrade. In the rest of this
document we'll be discussing this scenario.
Reasons for Five's modified ZCML statements
-------------------------------------------
Five ships with modified implementations of Zope 3 ZCML statements for
a number of reasons:
* could not use new-style classes that are in Zope 3 due to
ExtensionClass.
* Five views need to work with the Zope 2 publisher, and this expects
different things than the Zope 3 publisher.
* cannot use the Zope 3 security system, while the Zope 3 ZCML calls
into this to configure it.
* Five views need to work with the Zope 2 security system. This means
Five needs to issue Zope 2 style security declarations for views.
We'll go into more detail about each of these points below.
New-style ExtensionClass
========================
Five needed to be compatible with Zope 2.7, which uses old-style
ExtensionClass. This made life difficult for Five, as Zope 3 uses
new-style Python classes in many places. It's not easy to mix the two.
Zope 2.8 changed to allow new-style ExtensionClasses, which are
compatible with new-style Python classes. This means Five can
hopefully be simplified as we can forget about old-style
ExtensionClasses.
Five views need to work with the Zope 2 publisher
=================================================
The Zope 2 publisher expects something quite different than the Zope 3
publisher.
* does what is returned to the publisher need to inherit from
Acquisition.Explicit? (security reasons?)
* we may need something that calls the right methods on the Zope 3
view (such as browserDefault, __call__ and publishTraverse)
Cannot use the Zope 3 security system
=====================================
Do the Zope 3 security calls get in the way? Five currently removes
these calls, but perhaps doing the calls does not harm.
If they do interface, we could perhaps still trick things into
working harmlessly.
Five must issue Zope 2 security declarations for views
======================================================
This cannot be done by the ZCML implementation of Zope 3. We could
hopefully do this by following the following pattern::
def our_directive_implementation(...):
original_directive_implementation(...)
do_the_zope2_work(...)
Local sites in Five
===================
Intro
-----
Zope 3 has a concept of local sites and site managers. They allow one
to locally override component registrations and have components and
their configuration be persisted in the ZODB as well as managed
through the web interface.
By default, Zope 3 has a global site which is configured through ZCML.
It provides the fallback for all component look-up. Local sites are
typically set during traversal, when the traverser encounters an
``ISite`` object. The last encountered ``ISite`` wins. Component
look-up will cascade through all the sites in the hierarchy and fall
back to the global site where it can finally fail.
Five also supports local sites, however by default only local
utilities. Local adapters, such as ZODB-based views, could be
supported with a custom implementation of the local site manager and
local adapter registry. This is not the focus of local sites in Five,
though.
Turning possible sites into sites
---------------------------------
Five uses the same technique as Zope 3 for determining local sites:
sites are found during URL traversal. However, since the Zope 2
ZPublisher doesn't emit the necessary events by default, Five needs to
set a ``BeforeTraverse`` hook on site objects.
Setting this hook needs to be done an object-per-object basis and can
be performed through the ``manage_site.html`` browser page. This view
operates on ``IPossibleSite`` objects (in other words, objects that
*can* be sites but aren't yet). It sets the traversal hook on the
object and then marks it with the ``ISite`` interface to indicate that
it is a real site now, not just a possible site.
Note that unlike the Zope 3 equivalent of this view, it does not set
the site manager to site; it is assumed that the site already knows
how to get its site manager.
Also note that in order for the view to work, the object's class needs
to be Five-traversable, e.g. with the following ZCML statement:
<five:traversable class=".module.MyClass" />
Custom site implementations
---------------------------
Anything can be a site, there are no restrictions (sites don't have to
be folders, for examples). Sites can also be nested. For all the
Component Architecture cares, every object in your URL graph could be
a site.
The only requirement are two interfaces:
``IPossibleSite``
Objects that can potentially be turned into a site need to provide
this interface. That requires them to have a ``setSiteManager()``
and ``getSiteManager()`` method for setting and getting the local
site manager of that site. The site manager is the registry that
takes care of local component look-up.
``IFiveSiteManager``
This interface is a slight extension of the ``IServiceService`` or
``ISiteManager`` interface, respectively (the former in Zope X3
3.0, the latter in later versions). It defines the API of a local
site manager that is to be used in a Five environment. The site's
``getSiteManager()`` method should return an object providing this
interface.
Five's default site manager
----------------------------
If you want to instantly make your custom class an ``IPossibleSite``
implementation, you can use a default mix-in class from Five, e.g.::
class MySite(OFS.Folder, Products.Five.site.localsite.FiveSite):
pass
This default implementation of ``IPossibleSite`` features a site
manager implementation that knows how to register and look-up local
utilities. It does so by adapting the site to
``IFiveUtilityRegistry``.
The default adapter for this local utility registry simply stores the
utilities in a standard OFS Folder on called ``utilities`` on the site
object. You probably want to exchange that simple behaviour with
something that works better in your application. You can do so by
plugging in your own utility registry adapter, e.g.::
<adapter for=".interfaces.IMySite"
provides="Products.Five.site.interfaces.IFiveUtilityRegistry"
fatory=".module.MyUtilityRegistry" />
All this implementation needs to do is comply with the
``IFiveUtilityRegistry`` interface, which essentially means the
standard utility look-up methods like ``queryUtility()``,
``getUtilitiesFor()``, etc.
Turning existing classes into possible sites
--------------------------------------------
If you cannot or do not want to modify existing classes to mix in the
``FiveSite`` class, you can also use a structured monkey patch via
ZCML::
<five:localsite class=".module.MyClass" />
This makes ``MyClass`` an ``IPossibleSite`` and sticks ``FiveSite``'s
``getSiteManager()`` and ``setSiteManager()`` methods on the class as
well. You can also tell it to use a different site implementation's
methods for the monkey patch::
<five:localsite class=".module.MyClass"
site_class=".module.MySiteImpl" />
Just make sure that this class implements ``IPossibleSite``.
...@@ -5,23 +5,19 @@ What is Five? ...@@ -5,23 +5,19 @@ What is Five?
------------- -------------
Five is a Zope 2 product that allows you to integrate Zope 3 Five is a Zope 2 product that allows you to integrate Zope 3
technologies into Zope 2, today. Five right now allows you to use the technologies into Zope 2, today. Among others, it allows you to use
following Zope 3 technologies in Zope 2: Zope 3 interfaces, ZCML-based configuration, adapters, browser pages
(including skins, layers, and resources), automated add and edit forms
based on schemas, object events, as well as Zope 3-style i18n message
catalogs.
* Zope 3 interfaces We've tried to keep the Five experience as close to Zope 3 as
possible, so this means that what you learn while using Five should
also be applicable to Zope 3, and viceversa.
* adapters Five 1.0 and 1.1 work on a straight Zope 2.7 installation, as long as
Zope 3 has been installed. Five 1.2 requires Zope 2.8 which already
* pages (views), including skins and layers, and edit and add forms ships with Zope 3, Five 1.3 is included in Zope 2.9.
* ZCML
It is possible to add Zope 3 style views to your own Zope 2 objects,
or to existing ones, even normal Folders!
Five works with a straight Zope 2.7 installation, as long as Zope 3
has been installed. See Five's INSTALL.txt for more information on how
to set it up.
We're in the process of evaluating lots more Zope 3 technologies for We're in the process of evaluating lots more Zope 3 technologies for
integration into Zope 2. This is the right moment for interested Zope integration into Zope 2. This is the right moment for interested Zope
...@@ -32,6 +28,19 @@ system for us all. ...@@ -32,6 +28,19 @@ system for us all.
Download Download
-------- --------
2005-11-02 -- We have released Five 1.2b and 1.3b! Download Five 1.2b
here:
http://codespeak.net/z3/five/release/Five-1.2b.tgz
2005-10-04 -- We have released Five 1.1! Download it here:
http://codespeak.net/z3/five/release/Five-1.1.tgz
2005-07-13 -- We have released Five 1.1b! Download it here:
http://codespeak.net/z3/five/release/Five-1.1b.tgz
2005-07-12 -- We have released Five 1.0.2! This is also the version 2005-07-12 -- We have released Five 1.0.2! This is also the version
that will be included in Zope 2.8.1. Download it here: that will be included in Zope 2.8.1. Download it here:
......
############################################################################## # make this directory a package
#
# Copyright (c) 2005 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.
#
##############################################################################
import democontent
def initialize(context):
context.registerClass(
democontent.DemoContent,
constructors = (democontent.manage_addDemoContentForm,
democontent.manage_addDemoContent),
)
<h1 tal:replace="structure context/manage_page_header">Header</h1>
<h2 tal:define="form_title string:Add Demo Content"
tal:replace="structure context/manage_form_title">Form Title</h2>
<p class="form-help">
Add Demo Content
</p>
<form action="." method="post"
tal:attributes="action request/ACTUAL_URL">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
</td>
<td align="left" valign="top">
<input type="text" name="add_input_name" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Title
</div>
</td>
<td align="left" valign="top">
<input type="text" name="title" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit_add"
value=" Add " />
</div>
</td>
</tr>
</table>
</form>
<h1 tal:replace="structure context/manage_page_footer">Footer</h1>
##############################################################################
#
# Copyright (c) 2005 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.
#
##############################################################################
from Products.Five import BrowserView
import random import random
from democontent import DemoContent
class Overview:
"""View for overview.
"""
class Overview(BrowserView):
def reversedIds(self): def reversedIds(self):
result = [] result = []
for id in self.context.objectIds(): for id in self.context.objectIds():
...@@ -27,9 +19,28 @@ class Overview(BrowserView): ...@@ -27,9 +19,28 @@ class Overview(BrowserView):
def directlyPublished(self): def directlyPublished(self):
return "This is directly published" return "This is directly published"
class NewExample(BrowserView):
class NewExample:
"""View for new example.
"""
def helpsWithOne(self): def helpsWithOne(self):
return random.randrange(10) return random.randrange(10)
def two(self): def two(self):
return "Two got called" return "Two got called"
class DemoContentAddView:
"""Add view for demo content.
"""
def __call__(self, add_input_name='', title='', submit_add=''):
if submit_add:
obj = DemoContent(add_input_name, title)
self.context.add(obj)
self.request.response.redirect(self.context.nextURL())
return ''
return self.index()
...@@ -3,27 +3,29 @@ ...@@ -3,27 +3,29 @@
xmlns:browser="http://namespaces.zope.org/browser" xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five"> xmlns:five="http://namespaces.zope.org/five">
<five:traversable <five:traversable class="OFS.Application.Application"/>
class="OFS.Folder.Folder"
/> <!-- OFS.Folder.Folder views -->
<five:traversable class="OFS.Folder.Folder"/>
<browser:page <browser:page
for="Products.Five.interfaces.IFolder" for="OFS.interfaces.IFolder"
name="overview.html" name="overview.html"
template="overview.pt" template="overview.pt"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
/> />
<browser:page <browser:page
for="Products.Five.interfaces.IFolder" for="OFS.interfaces.IFolder"
name="overview2.html" name="overview2.html"
template="overview2.pt" template="overview2.pt"
permission="zope2.ViewManagementScreens"
class=".browser.Overview" class=".browser.Overview"
permission="zope2.ViewManagementScreens"
/> />
<browser:page <browser:page
for="Products.Five.interfaces.IFolder" for="OFS.interfaces.IFolder"
name="test.html" name="test.html"
class=".browser.Overview" class=".browser.Overview"
attribute="directlyPublished" attribute="directlyPublished"
...@@ -31,7 +33,7 @@ ...@@ -31,7 +33,7 @@
/> />
<browser:pages <browser:pages
for="Products.Five.interfaces.IFolder" for="OFS.interfaces.IFolder"
class=".browser.NewExample" class=".browser.NewExample"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
> >
...@@ -45,7 +47,30 @@ ...@@ -45,7 +47,30 @@
/> />
</browser:pages> </browser:pages>
<five:traversable class=".democontent.DemoContent" /> <!-- .democontent.IDemoContent views -->
<five:traversable class=".democontent.DemoContent"/>
<browser:page
for="zope.app.container.interfaces.IAdding"
name="addDemoContent.html"
template="addDemoContent.pt"
class=".browser.DemoContentAddView"
permission="zope2.ViewManagementScreens"
/>
<browser:resource
name="green5.png"
image="green5.png"
/>
<five:registerClass
class=".democontent.DemoContent"
meta_type="Five Demo Content"
addview="addDemoContent.html"
icon="green5.png"
permission="zope2.ViewManagementScreens"
/>
<browser:page <browser:page
for=".democontent.IDemoContent" for=".democontent.IDemoContent"
...@@ -54,10 +79,11 @@ ...@@ -54,10 +79,11 @@
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
/> />
<five:defaultViewable class=".democontent.DemoContent" /> <five:defaultViewable class=".democontent.DemoContent"/>
<browser:defaultView <browser:defaultView
for=".democontent.IDemoContent" for=".democontent.IDemoContent"
name="someview.html" /> name="someview.html"
/>
</configure> </configure>
##############################################################################
#
# Copyright (c) 2005 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.
#
##############################################################################
from zope.interface import Interface, implements from zope.interface import Interface, implements
from OFS.SimpleItem import SimpleItem from OFS.SimpleItem import SimpleItem
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
class IDemoContent(Interface): class IDemoContent(Interface):
def mymethod(): def mymethod():
"Return some text" """Return some text.
"""
class DemoContent(SimpleItem): class DemoContent(SimpleItem):
implements(IDemoContent)
meta_type = 'Five Demo Content' implements(IDemoContent)
def __init__(self, id, title): def __init__(self, id, title):
self.id = id self.id = id
...@@ -30,15 +19,3 @@ class DemoContent(SimpleItem): ...@@ -30,15 +19,3 @@ class DemoContent(SimpleItem):
def mymethod(self): def mymethod(self):
return "Hello world" return "Hello world"
manage_addDemoContentForm = PageTemplateFile(
"www/demoContentAdd", globals(),
__name__ = 'manage_addDemoContentForm')
def manage_addDemoContent(self, id, title, REQUEST=None):
"""Add the demo content."""
id = self._setObject(id, DemoContent(id, title))
if REQUEST is None:
return
REQUEST.RESPONSE.redirect(REQUEST['URL1'] + '/manage_main')
<configure xmlns="http://namespaces.zope.org/zope">
<!-- Adapter giving sublocations for ObjectManagers, used
by dispatchToSublocations -->
<adapter
for="OFS.interfaces.IObjectManager"
provides="zope.app.location.interfaces.ISublocations"
factory="OFS.subscribers.ObjectManagerSublocations"
/>
<!-- dispatch IObjectWillBeMovedEvent with "bottom-up" semantics -->
<subscriber
for="OFS.interfaces.IItem
OFS.interfaces.IObjectWillBeMovedEvent"
handler="OFS.subscribers.dispatchObjectWillBeMovedEvent"
/>
<!-- dispatch IObjectMovedEvent with "top-down" semantics -->
<subscriber
for="OFS.interfaces.IItem
zope.app.container.interfaces.IObjectMovedEvent"
handler="OFS.subscribers.dispatchObjectMovedEvent"
/>
<!-- dispatch IObjectClonedEvent with "top-down" semantics -->
<subscriber
for="OFS.interfaces.IItem
OFS.interfaces.IObjectClonedEvent"
handler="OFS.subscribers.dispatchObjectClonedEvent"
/>
</configure>
############################################################################## ##############################################################################
# #
# Copyright (c) 2004, 2005 Zope Corporation and Contributors. # Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved. # All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
...@@ -15,109 +15,38 @@ ...@@ -15,109 +15,38 @@
Use 'structured monkey patching' to enable zope.app.container event sending for Use 'structured monkey patching' to enable zope.app.container event sending for
Zope 2 objects. Zope 2 objects.
$Id: eventconfigure.py 17810 2005-09-24 09:12:59Z efge $ $Id: eventconfigure.py 19413 2005-11-02 14:37:52Z efge $
""" """
from Products.Five.fiveconfigure import isFiveMethod
from zope.event import notify
from zope.interface import implements
from zope.app.container.interfaces import IObjectAddedEvent,\
IObjectRemovedEvent
from zope.app.container.contained import ObjectMovedEvent
from zope.app.event.objectevent import ObjectCopiedEvent
# holds classes that were monkeyed with; for clean up import warnings
_monkied = [] from OFS.subscribers import deprecatedManageAddDeleteClasses
# ObjectAddedEvent and ObjectRemovedEvent are different in Zope 2 def setContainerEvents():
class ObjectAddedEvent(ObjectMovedEvent): warnings.warn("Using <five:containerEvents/> is deprecated (it is now "
implements(IObjectAddedEvent) "the default), it will be removed in Zope 2.11",
DeprecationWarning)
def __init__(self, object, newParent=None, newName=None): def setDeprecatedManageAddDelete(class_):
if newParent is None: """Instances of the class will still see their old methods called."""
newParent = object.aq_inner.aq_parent deprecatedManageAddDeleteClasses.append(class_)
if newName is None:
newName = object.id
ObjectMovedEvent.__init__(self, object, None, None, newParent, newName)
class ObjectRemovedEvent(ObjectMovedEvent): def cleanUp():
implements(IObjectRemovedEvent) deprecatedManageAddDeleteClasses[:] = []
def __init__(self, object, oldParent=None, oldName=None):
if oldParent is None:
oldParent = object.aq_inner.aq_parent
if oldName is None:
oldName = object.id
ObjectMovedEvent.__init__(self, object, oldParent, oldName, None, None)
def manage_afterAdd(self, item, container):
original_location_path = getattr(self, '__five_location_path__', None)
self.__five_location_path__ = self.getPhysicalPath()
# if there still is an object in the original location, we're copied
# we cannot rely on manage_afterClone, as this gets triggered only
# *after* a manage_afterAdd. This logic might fail in the case where
# something *is* somehow left in the original location that can
# be traversed to.
is_copied = original_location_path and (self.unrestrictedTraverse(
original_location_path, None) is not None)
if is_copied:
notify(ObjectCopiedEvent(self))
if original_location_path is None or is_copied:
notify(ObjectAddedEvent(self))
else:
original_location = self.unrestrictedTraverse(
original_location_path[:-1])
notify(ObjectMovedEvent(self,
original_location, original_location_path[-1],
container, self.id))
# call original
method = getattr(self, '__five_original_manage_afterAdd', None)
if method is not None:
self.__five_original_manage_afterAdd(item, container)
manage_afterAdd.__five_method__ = True
def manage_beforeDelete(self, item, container):
notify(ObjectRemovedEvent(self))
# call original
method = getattr(self, '__five_original_manage_beforeDelete', None)
if method is not None:
self.__five_original_manage_beforeDelete(item, container)
manage_beforeDelete.__five_method__ = True
def classSendEvents(class_):
"""Make instances of the class send Object*Event."""
# tuck away original methods if necessary
for name in ['manage_afterAdd', 'manage_beforeDelete']:
method = getattr(class_, name, None)
if not isFiveMethod(method):
# if we haven't alread overridden this, tuck away originals
setattr(class_, '__five_original_' + name, method)
class_.manage_afterAdd = manage_afterAdd def containerEvents(_context):
class_.manage_beforeDelete = manage_beforeDelete _context.action(
# remember class for clean up discriminator=None,
_monkied.append(class_) callable=setContainerEvents,
args=(),
)
def sendEvents(_context, class_): def deprecatedManageAddDelete(_context, class_):
_context.action( _context.action(
discriminator = ('five:sendEvents', class_), discriminator=('five:deprecatedManageAddDelete', class_),
callable = classSendEvents, callable=setDeprecatedManageAddDelete,
args=(class_,) args=(class_,),
) )
# clean up code
from Products.Five.fiveconfigure import killMonkey
from zope.testing.cleanup import addCleanUp from zope.testing.cleanup import addCleanUp
def unsendEvents(class_):
"""Restore class's initial state with respect to sending events"""
for name in ['manage_afterAdd', 'manage_beforeDelete']:
killMonkey(class_, name, '__five_original_'+name)
def cleanUp():
for class_ in _monkied:
unsendEvents(class_)
addCleanUp(cleanUp) addCleanUp(cleanUp)
del addCleanUp del addCleanUp
...@@ -22,12 +22,20 @@ import sys ...@@ -22,12 +22,20 @@ import sys
import glob import glob
import warnings import warnings
import App import App.config
import Products
from zLOG import LOG, ERROR from zLOG import LOG, ERROR
from zope.interface import classImplements from zope.interface import classImplements, classImplementsOnly, implementedBy
from zope.interface.interface import InterfaceClass
from zope.configuration import xmlconfig from zope.configuration import xmlconfig
from zope.configuration.exceptions import ConfigurationError
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
from zope.app import zapi
from zope.app.component.interface import provideInterface from zope.app.component.interface import provideInterface
from zope.app.component.metaconfigure import adapter
from zope.app.security.interfaces import IPermission
from viewable import Viewable from viewable import Viewable
from traversable import Traversable from traversable import Traversable
...@@ -55,7 +63,7 @@ def handleBrokenProduct(product): ...@@ -55,7 +63,7 @@ def handleBrokenProduct(product):
# in the control panel. However, all attempts to do so has failed from my # in the control panel. However, all attempts to do so has failed from my
# side. //regebro # side. //regebro
exc = sys.exc_info() exc = sys.exc_info()
LOG('Five', ERROR, 'Could not import Product %s' % name, error=exc) LOG('Five', ERROR, 'Could not import Product %s' % product.__name__, error=exc)
def loadProducts(_context): def loadProducts(_context):
products = findProducts() products = findProducts()
...@@ -177,20 +185,6 @@ def defaultViewable(_context, class_): ...@@ -177,20 +185,6 @@ def defaultViewable(_context, class_):
args = (class_,) args = (class_,)
) )
def viewable(_context, class_):
# XXX do not need to mark where this is used, as simple search
# should find all instances easily
warnings.warn(
'The five:viewable directive has been deprecated. '
'Please use the five:traversable directive instead.',
DeprecationWarning)
_context.action(
discriminator = None,
callable = classTraversable,
args=(class_,)
)
def createZope2Bridge(zope2, package, name): def createZope2Bridge(zope2, package, name):
# Map a Zope 2 interface into a Zope3 interface, seated within 'package' # Map a Zope 2 interface into a Zope3 interface, seated within 'package'
# as 'name'. # as 'name'.
...@@ -215,7 +209,7 @@ def bridge(_context, zope2, package, name=None): ...@@ -215,7 +209,7 @@ def bridge(_context, zope2, package, name=None):
) )
def pagesFromDirectory(_context, directory, module, for_=None, def pagesFromDirectory(_context, directory, module, for_=None,
layer='default', permission='zope.Public'): layer=IDefaultBrowserLayer, permission='zope.Public'):
if isinstance(module, basestring): if isinstance(module, basestring):
module = _context.resolve(module) module = _context.resolve(module)
...@@ -233,6 +227,41 @@ def pagesFromDirectory(_context, directory, module, for_=None, ...@@ -233,6 +227,41 @@ def pagesFromDirectory(_context, directory, module, for_=None,
page(_context, name=name, permission=permission, page(_context, name=name, permission=permission,
layer=layer, for_=for_, template=fname) layer=layer, for_=for_, template=fname)
_register_monkies = []
_meta_type_regs = []
def _registerClass(class_, meta_type, permission, addview, icon, global_):
setattr(class_, 'meta_type', meta_type)
permission_obj = zapi.getUtility(IPermission, permission)
if icon:
setattr(class_, 'icon', '++resource++%s' % icon)
interfaces = tuple(implementedBy(class_))
info = {'name': meta_type,
'action': addview and ('+/%s' % addview) or '',
'product': 'Five',
'permission': str(permission_obj.title),
'visibility': global_ and 'Global' or None,
'interfaces': interfaces,
'instance': class_,
'container_filter': None}
Products.meta_types += (info,)
_register_monkies.append(class_)
_meta_type_regs.append(meta_type)
def registerClass(_context, class_, meta_type, permission, addview=None,
icon=None, global_=True):
_context.action(
discriminator = ('registerClass', meta_type),
callable = _registerClass,
args = (class_, meta_type, permission, addview, icon, global_)
)
# clean up code # clean up code
def killMonkey(class_, name, fallback, attr=None): def killMonkey(class_, name, fallback, attr=None):
...@@ -265,11 +294,33 @@ def undefaultViewable(class_): ...@@ -265,11 +294,33 @@ def undefaultViewable(class_):
killMonkey(class_, '__browser_default__', '__fallback_default__', killMonkey(class_, '__browser_default__', '__fallback_default__',
'__five_viewable__') '__five_viewable__')
def unregisterClass(class_):
delattr(class_, 'meta_type')
try:
delattr(class_, 'icon')
except AttributeError:
pass
def cleanUp(): def cleanUp():
global _traversable_monkies
for class_ in _traversable_monkies: for class_ in _traversable_monkies:
untraversable(class_) untraversable(class_)
_traversable_monkies = []
global _defaultviewable_monkies
for class_ in _defaultviewable_monkies: for class_ in _defaultviewable_monkies:
undefaultViewable(class_) undefaultViewable(class_)
_defaultviewable_monkies = []
global _register_monkies
for class_ in _register_monkies:
unregisterClass(class_)
_register_monkies = []
global _meta_type_regs
Products.meta_types = tuple([ info for info in Products.meta_types
if info['name'] not in _meta_type_regs ])
_meta_type_regs = []
from zope.testing.cleanup import addCleanUp from zope.testing.cleanup import addCleanUp
addCleanUp(cleanUp) addCleanUp(cleanUp)
......
...@@ -17,7 +17,10 @@ $Id: fivedirectives.py 12884 2005-05-30 13:10:41Z philikon $ ...@@ -17,7 +17,10 @@ $Id: fivedirectives.py 12884 2005-05-30 13:10:41Z philikon $
""" """
from zope.interface import Interface from zope.interface import Interface
from zope.app.publisher.browser.metadirectives import IBasicResourceInformation from zope.app.publisher.browser.metadirectives import IBasicResourceInformation
from zope.app.security.fields import Permission
from zope.configuration.fields import GlobalObject, Tokens, PythonIdentifier from zope.configuration.fields import GlobalObject, Tokens, PythonIdentifier
from zope.configuration.fields import Bool
from zope.schema import ASCII
from zope.schema import TextLine from zope.schema import TextLine
class IImplementsDirective(Interface): class IImplementsDirective(Interface):
...@@ -56,7 +59,7 @@ class IDefaultViewableDirective(Interface): ...@@ -56,7 +59,7 @@ class IDefaultViewableDirective(Interface):
required=True required=True
) )
class ISendEventsDirective(Interface): class ISizableDirective(Interface):
"""Make instances of class send events. """Make instances of class send events.
""" """
...@@ -65,6 +68,19 @@ class ISendEventsDirective(Interface): ...@@ -65,6 +68,19 @@ class ISendEventsDirective(Interface):
required=True required=True
) )
class IContainerEventsDirective(Interface):
"""Global switch to enable container events
"""
class IDeprecatedManageAddDeleteDirective(Interface):
"""Call manage_afterAdd & co for these contained content classes.
"""
class_ = GlobalObject(
title=u"Class",
required=True,
)
class IBridgeDirective(Interface): class IBridgeDirective(Interface):
"""Bridge from a Zope 2 interface to an equivalent Zope3 interface. """Bridge from a Zope 2 interface to an equivalent Zope3 interface.
""" """
...@@ -104,3 +120,51 @@ class IPagesFromDirectoryDirective(IBasicResourceInformation): ...@@ -104,3 +120,51 @@ class IPagesFromDirectoryDirective(IBasicResourceInformation):
description=u"The directory containing the resource data.", description=u"The directory containing the resource data.",
required=True required=True
) )
class IRegisterClassDirective(Interface):
"""registerClass directive schema.
Register Five content with Zope 2.
"""
class_ = GlobalObject(
title=u'Instance Class',
description=u'Dotted name of the class that is registered.',
required=True
)
meta_type = ASCII(
title=u'Meta Type',
description=u'A human readable unique identifier for the class.',
required=True
)
permission = Permission(
title=u'Add Permission',
description=u'The permission for adding objects of this class.',
required=True
)
addview = ASCII(
title=u'Add View ID',
description=u'The ID of the add view used in the ZMI. Consider this '
u'required unless you know exactly what you do.',
default=None,
required=False
)
icon = ASCII(
title=u'Icon ID',
description=u'The ID of the icon used in the ZMI.',
default=None,
required=False
)
global_ = Bool(
title=u'Global scope?',
description=u'If "global" is False the class is only available in '
u'containers that explicitly allow one of its interfaces.',
default=True,
required=False
)
...@@ -33,7 +33,7 @@ from zope.app.form.interfaces import WidgetsError, MissingInputError ...@@ -33,7 +33,7 @@ from zope.app.form.interfaces import WidgetsError, MissingInputError
from zope.app.form.utility import setUpWidgets, getWidgetsData from zope.app.form.utility import setUpWidgets, getWidgetsData
from zope.app.form.interfaces import IInputWidget, WidgetsError from zope.app.form.interfaces import IInputWidget, WidgetsError
from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.app.i18n import ZopeMessageIDFactory as _ from zope.app.i18n import ZopeMessageFactory as _
from Products.Five.browser import BrowserView from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
...@@ -144,12 +144,13 @@ class EditView(BrowserView): ...@@ -144,12 +144,13 @@ class EditView(BrowserView):
if changed: if changed:
self.changed() self.changed()
# XXX: Needs locale support: # XXX: Needs locale support:
# formatter = self.request.locale.dates.getFormatter( #formatter = self.request.locale.dates.getFormatter(
# 'dateTime', 'medium') # 'dateTime', 'medium')
status = _("Updated on ${date_time}") #status = _("Updated on ${date_time}",
# status.mapping = {'date_time': formatter.format( # mapping={'date_time':
# datetime.utcnow())} # formatter.format(datetime.utcnow())})
status.mapping = {'date_time': str(datetime.utcnow())} status = _("Updated on ${date_time}",
mapping={'date_time': str(datetime.utcnow())})
self.update_status = status self.update_status = status
return status return status
...@@ -249,9 +250,3 @@ class AddView(EditView): ...@@ -249,9 +250,3 @@ class AddView(EditView):
def nextURL(self): def nextURL(self):
return self.context.nextURL() return self.context.nextURL()
# BBB: Will be removed in future versions
from Products.Five import browser
browser.AddView = AddView
browser.EditView = EditView
...@@ -13,28 +13,29 @@ ...@@ -13,28 +13,29 @@
############################################################################## ##############################################################################
"""Edit form directives """Edit form directives
$Id: metaconfigure.py 12884 2005-05-30 13:10:41Z philikon $ $Id: metaconfigure.py 19283 2005-10-31 17:43:51Z philikon $
""" """
import ExtensionClass import ExtensionClass
from Globals import InitializeClass as initializeClass
from zope.component import getGlobalService from zope.interface import Interface
from zope.component.servicenames import Presentation
from zope.publisher.interfaces.browser import IBrowserRequest from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.publisher.browser.globalbrowsermenuservice import \
menuItemDirective from zope.app import zapi
from zope.app.publisher.browser.menumeta import menuItemDirective
from zope.app.form.browser.metaconfigure import BaseFormDirective from zope.app.form.browser.metaconfigure import BaseFormDirective
from zope.app.container.interfaces import IAdding from zope.app.container.interfaces import IAdding
from zope.app.i18n import ZopeMessageFactory as _
from Products.Five.form import EditView, AddView from Products.Five.form import EditView, AddView
from Products.Five.metaclass import makeClass from Products.Five.metaclass import makeClass
from Products.Five.security import protectClass, initializeClass from Products.Five.security import protectClass
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Products.Five.browser.metaconfigure import makeClassForTemplate from Products.Five.browser.metaconfigure import makeClassForTemplate
def EditViewFactory(name, schema, label, permission, layer, def EditViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_, fields, template, default_template, bases, for_, fields,
fulledit_path=None, fulledit_label=None, menu=u''): fulledit_path=None, fulledit_label=None, menu=u''):
s = getGlobalService(Presentation)
class_ = makeClassForTemplate(template, globals(), used_for=schema, class_ = makeClassForTemplate(template, globals(), used_for=schema,
bases=bases) bases=bases)
class_.schema = schema class_.schema = schema
...@@ -49,8 +50,15 @@ def EditViewFactory(name, schema, label, permission, layer, ...@@ -49,8 +50,15 @@ def EditViewFactory(name, schema, label, permission, layer,
class_.generated_form = ZopeTwoPageTemplateFile(default_template) class_.generated_form = ZopeTwoPageTemplateFile(default_template)
if layer is None:
layer = IDefaultBrowserLayer
s = zapi.getGlobalSiteManager()
s.provideAdapter((for_, layer), Interface, name, class_)
s.provideView(for_, name, IBrowserRequest, class_, layer) # Reminder: the permission we got has already been processed by
# BaseFormDirective, that means that zope.Public has been
# translated to the CheckerPublic object
protectClass(class_, permission) protectClass(class_, permission)
initializeClass(class_) initializeClass(class_)
...@@ -58,20 +66,22 @@ class FiveFormDirective(BaseFormDirective): ...@@ -58,20 +66,22 @@ class FiveFormDirective(BaseFormDirective):
def _processWidgets(self): def _processWidgets(self):
if self._widgets: if self._widgets:
customWidgetsObject = makeClass('CustomWidgetsMixin', (ExtensionClass.Base,), self._widgets) customWidgetsObject = makeClass(
'CustomWidgetsMixin', (ExtensionClass.Base,), self._widgets)
self.bases = self.bases + (customWidgetsObject,) self.bases = self.bases + (customWidgetsObject,)
class EditFormDirective(FiveFormDirective): class EditFormDirective(FiveFormDirective):
view = EditView view = EditView
default_template = 'edit.pt' default_template = 'edit.pt'
title = 'Edit' title = _('Edit')
def _handle_menu(self): def _handle_menu(self):
if self.menu: if self.menu:
menuItemDirective( menuItemDirective(
self._context, self.menu, self.for_ or self.schema, self._context, self.menu, self.for_ or self.schema,
'@@' + self.name, self.title, permission=self.permission) '@@' + self.name, self.title, permission=self.permission,
layer=self.layer)
def __call__(self): def __call__(self):
self._processWidgets() self._processWidgets()
...@@ -89,8 +99,6 @@ def AddViewFactory(name, schema, label, permission, layer, ...@@ -89,8 +99,6 @@ def AddViewFactory(name, schema, label, permission, layer,
fields, content_factory, arguments, fields, content_factory, arguments,
keyword_arguments, set_before_add, set_after_add, keyword_arguments, set_before_add, set_after_add,
menu=u''): menu=u''):
s = getGlobalService(Presentation)
class_ = makeClassForTemplate(template, globals(), used_for=schema, class_ = makeClassForTemplate(template, globals(), used_for=schema,
bases=bases) bases=bases)
...@@ -105,7 +113,15 @@ def AddViewFactory(name, schema, label, permission, layer, ...@@ -105,7 +113,15 @@ def AddViewFactory(name, schema, label, permission, layer,
class_.generated_form = ZopeTwoPageTemplateFile(default_template) class_.generated_form = ZopeTwoPageTemplateFile(default_template)
s.provideView(for_, name, IBrowserRequest, class_, layer) if layer is None:
layer = IDefaultBrowserLayer
s = zapi.getGlobalSiteManager()
s.provideAdapter((for_, layer), Interface, name, class_)
# Reminder: the permission we got has already been processed by
# BaseFormDirective, that means that zope.Public has been
# translated to the CheckerPublic object
protectClass(class_, permission) protectClass(class_, permission)
initializeClass(class_) initializeClass(class_)
...@@ -132,7 +148,7 @@ class AddFormDirective(FiveFormDirective): ...@@ -132,7 +148,7 @@ class AddFormDirective(FiveFormDirective):
# for=self.schema. # for=self.schema.
menuItemDirective( menuItemDirective(
self._context, self.menu, self.for_, '@@' + self.name, self._context, self.menu, self.for_, '@@' + self.name,
self.title, permission=self.permission, self.title, permission=self.permission, layer=self.layer,
description=self.description) description=self.description)
def _handle_arguments(self, leftover=None): def _handle_arguments(self, leftover=None):
......
<fieldset>
<legend tal:content="context/legendTitle"
i18n:translate="">The Legend</legend>
<div class="row" tal:repeat="widget context/subwidgets">
<tal:comment condition="nothing">
This is why we have to duplicate this template: we want to look
up the @@form_macros browser page from something that's
definitely five:traversable (it doesn't really matter where we
look it up, just *that* we look it up); we know the object we're
editing is five:traversable, so we just use that. Yes, three
times context. Weird, eh?
</tal:comment>
<metal:block use-macro="context/context/context/@@form_macros/widget_row" />
</div>
</fieldset>
##############################################################################
#
# Copyright (c) 2005 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.
#
##############################################################################
"""Five-compatible version of ObjectWidget
This is needed because ObjectWidget uses ViewPageTemplateFile whose
macro definition is unfortunately incompatible with
ZopeTwoPageTemplateFile. So this subclass uses
ZopeTwoPageTemplateFile for the template that renders the widget's
sub-editform. Acquisition has to be mixed in to provide the
ZopeTwoPageTemplateFile with the proper acquisition context.
$Id$
"""
import os.path
import Acquisition
import zope.app.form.browser.objectwidget
from AccessControl import ClassSecurityInfo
from Globals import InitializeClass as initializeClass
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
class ObjectWidgetView(Acquisition.Explicit,
zope.app.form.browser.objectwidget.ObjectWidgetView):
security = ClassSecurityInfo()
security.declareObjectPublic()
template = ZopeTwoPageTemplateFile('objectwidget.pt')
initializeClass(ObjectWidgetView)
class ObjectWidgetClass(Acquisition.Explicit,
zope.app.form.browser.objectwidget.ObjectWidget):
def __init__(self, context, request, factory, **kw):
super(ObjectWidgetClass, self).__init__(context, request, factory, **kw)
self.view = ObjectWidgetView(self, request)
def setRenderedValue(self, value):
"""Slightly more robust re-implementation this method."""
# re-call setupwidgets with the content
self._setUpEditWidgets()
for name in self.names:
val = getattr(value, name, None)
if val is None:
# this is where we are more robust than Zope 3.2's
# object widget: we supply subwidgets with the default
# from the schema, not None (Zope 3.2's list widget
# breaks when the rendered value is None)
val = self.context.schema[name].default
self.getSubWidget(name).setRenderedValue(val)
def ObjectWidget(context, request, factory, **kw):
"""Return an ObjectWidget suitable in the Five environment, with
right acquisition context"""
return ObjectWidgetClass(context, request, factory, **kw
).__of__(context.context)
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
type="zope.publisher.interfaces.browser.IBrowserRequest" type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IObject" for="zope.schema.interfaces.IObject"
provides="zope.app.form.interfaces.IInputWidget" provides="zope.app.form.interfaces.IInputWidget"
factory="zope.app.form.browser.objectwidget.ObjectWidget" factory="Products.Five.form.objectwidget.ObjectWidget"
permission="zope.Public" permission="zope.Public"
/> />
......
...@@ -17,7 +17,7 @@ We need to configure all of Five for the functional test: ...@@ -17,7 +17,7 @@ We need to configure all of Five for the functional test:
Finally, we need to setup a traversable folder. Otherwise, Five won't Finally, we need to setup a traversable folder. Otherwise, Five won't
get to to do its view lookup: get to to do its view lookup:
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'ftf') >>> manage_addFiveTraversableFolder(self.folder, 'ftf')
...@@ -47,9 +47,10 @@ which incorrectly ignored its 'handle_errors' argument): ...@@ -47,9 +47,10 @@ which incorrectly ignored its 'handle_errors' argument):
>>> print http(r""" >>> print http(r"""
... GET /test_folder_1_/ftf/+/protectedaddform.html HTTP/1.1 ... GET /test_folder_1_/ftf/+/protectedaddform.html HTTP/1.1
... """, handle_errors=True) ... """, handle_errors=False)
HTTP/1.1 401 Unauthorized Traceback (most recent call last):
... ...
Unauthorized: ...
Now let's add a piece of our sample content object to test more things Now let's add a piece of our sample content object to test more things
on it: on it:
...@@ -372,7 +373,11 @@ element to the list: ...@@ -372,7 +373,11 @@ element to the list:
... -----------------------------968064918930967154199105236 ... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somelist.add" ... Content-Disposition: form-data; name="field.somelist.add"
... ...
... Add ... Add Some item
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somelist.count"
...
... 0
... -----------------------------968064918930967154199105236-- ... -----------------------------968064918930967154199105236--
... """ % (wo_hen_hao, ni_hao), handle_errors=False) ... """ % (wo_hen_hao, ni_hao), handle_errors=False)
HTTP/1.1 200 OK HTTP/1.1 200 OK
...@@ -408,6 +413,10 @@ Now, let's enter some more Chinese: ...@@ -408,6 +413,10 @@ Now, let's enter some more Chinese:
... ...
... %s ... %s
... -----------------------------968064918930967154199105236 ... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somelist.count"
...
... 1
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
... ...
... Change ... Change
...@@ -572,5 +581,5 @@ Clean up ...@@ -572,5 +581,5 @@ Clean up
Finally, we need to clean up: Finally, we need to clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
...@@ -18,13 +18,13 @@ $Id: schemacontent.py 15514 2005-08-02 16:37:34Z yuppie $ ...@@ -18,13 +18,13 @@ $Id: schemacontent.py 15514 2005-08-02 16:37:34Z yuppie $
from OFS.SimpleItem import SimpleItem from OFS.SimpleItem import SimpleItem
from Globals import InitializeClass from Globals import InitializeClass
from zope.i18nmessageid import MessageIDFactory from zope.i18nmessageid import MessageFactory
from zope.interface import implements, Interface from zope.interface import implements, Interface
from zope.schema import TextLine, Text, Object, Int, List from zope.schema import TextLine, Text, Object, Int, List
from zope.app.form import CustomWidgetFactory from zope.app.form import CustomWidgetFactory
from zope.app.form.browser import ObjectWidget from Products.Five.form.objectwidget import ObjectWidget
_ = MessageIDFactory('formtest') _ = MessageFactory('formtest')
class IFieldContent(Interface): class IFieldContent(Interface):
......
...@@ -46,25 +46,26 @@ def test_get_widgets_for_schema_fields(): ...@@ -46,25 +46,26 @@ def test_get_widgets_for_schema_fields():
>>> from zope.app.form.browser.textwidgets import TextWidget >>> from zope.app.form.browser.textwidgets import TextWidget
>>> from zope.app.form.browser.itemswidgets import DropdownWidget >>> from zope.app.form.browser.itemswidgets import DropdownWidget
>>> view1 = zapi.getViewProviding(contactname, IInputWidget, request) >>> view1 = zapi.getMultiAdapter((contactname, request), IInputWidget)
>>> view1.__class__ == TextWidget >>> view1.__class__ == TextWidget
True True
>>> view2 = zapi.getViewProviding(salutation, IInputWidget, request) >>> view2 = zapi.getMultiAdapter((salutation, request), IInputWidget)
>>> view2.__class__ == DropdownWidget >>> view2.__class__ == DropdownWidget
True True
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
def test_suite(): def test_suite():
import unittest import unittest
from Testing.ZopeTestCase import ZopeDocTestSuite, FunctionalDocFileSuite from zope.testing.doctest import DocTestSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
return unittest.TestSuite(( return unittest.TestSuite((
ZopeDocTestSuite(), DocTestSuite(),
FunctionalDocFileSuite('forms.txt', FunctionalDocFileSuite('forms.txt',
package="Products.Five.form.tests",), package="Products.Five.form.tests",),
)) ))
......
...@@ -13,15 +13,21 @@ ...@@ -13,15 +13,21 @@
############################################################################## ##############################################################################
"""Mimick Zope3 i18n machinery for Zope 2 """Mimick Zope3 i18n machinery for Zope 2
$Id: i18n.py 14400 2005-07-07 17:55:08Z philikon $ $Id: i18n.py 19435 2005-11-02 16:34:58Z philikon $
""" """
from Acquisition import aq_acquire
from zope.interface import implements from zope.interface import implements
from zope.i18n import interpolate from zope.i18n import interpolate
from zope.i18n.interfaces import ITranslationDomain, IUserPreferredLanguages from zope.i18n.interfaces import ITranslationDomain, IUserPreferredLanguages
from zope.i18nmessageid import MessageID
from zope.app import zapi from zope.app import zapi
from zope.publisher.browser import BrowserLanguages from zope.publisher.browser import BrowserLanguages
# BBB 2005/10/10 -- MessageIDs are to be removed for Zope 3.3
import zope.deprecation
zope.deprecation.__show__.off()
from zope.i18nmessageid import MessageID, Message
zope.deprecation.__show__.on()
class FiveTranslationService: class FiveTranslationService:
"""Translation service that delegates to ``zope.i18n`` machinery. """Translation service that delegates to ``zope.i18n`` machinery.
""" """
...@@ -29,7 +35,7 @@ class FiveTranslationService: ...@@ -29,7 +35,7 @@ class FiveTranslationService:
# regarding fallback and Zope 2 compatability # regarding fallback and Zope 2 compatability
def translate(self, domain, msgid, mapping=None, def translate(self, domain, msgid, mapping=None,
context=None, target_language=None, default=None): context=None, target_language=None, default=None):
if isinstance(msgid, MessageID): if isinstance(msgid, (Message, MessageID)):
domain = msgid.domain domain = msgid.domain
default = msgid.default default = msgid.default
mapping = msgid.mapping mapping = msgid.mapping
...@@ -46,7 +52,7 @@ class FiveTranslationService: ...@@ -46,7 +52,7 @@ class FiveTranslationService:
# in Zope3, context is adapted to IUserPreferredLanguages, # in Zope3, context is adapted to IUserPreferredLanguages,
# which means context should be the request in this case. # which means context should be the request in this case.
if context is not None: if context is not None:
context = context.REQUEST context = aq_acquire(context, 'REQUEST', None)
return util.translate(msgid, mapping=mapping, context=context, return util.translate(msgid, mapping=mapping, context=context,
target_language=target_language, default=default) target_language=target_language, default=default)
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Five interfaces """Five interfaces
$Id: interfaces.py 14613 2005-07-13 10:39:05Z philikon $ $Id: interfaces.py 19283 2005-10-31 17:43:51Z philikon $
""" """
from zope.interface import Interface from zope.interface import Interface
from zope.interface.interfaces import IInterface from zope.interface.interfaces import IInterface
...@@ -33,23 +33,3 @@ class IMenuItemType(IInterface): ...@@ -33,23 +33,3 @@ class IMenuItemType(IInterface):
Menu item types are interfaces that define classes of Menu item types are interfaces that define classes of
menu items. menu items.
""" """
#
# BBB: Zope core interfaces
#
# The interfaces here are only provided for backwards compatibility and will
# be removed in Five 1.2. Please import interfaces from the corresponding Zope
# package instead.
#
from persistent.interfaces import IPersistent
from AccessControl.interfaces import *
from Acquisition.interfaces import *
from App.interfaces import *
from OFS.interfaces import *
from webdav.interfaces import *
# BBB: for old names used in Five 1.0
IAcquisition = IAcquirer
IPermissionMapping = IPermissionMappingSupport
<configure xmlns="http://namespaces.zope.org/five">
<!-- IPersistent, IPersistentExtra -->
<!-- Acquisition -->
<implements
class="Acquisition.ImplicitAcquisitionWrapper"
interface="Acquisition.interfaces.IAcquisitionWrapper"
/>
<implements
class="Acquisition.ExplicitAcquisitionWrapper"
interface="Acquisition.interfaces.IAcquisitionWrapper"
/>
<implements
class="Acquisition.Implicit"
interface="Acquisition.interfaces.IAcquirer"
/>
<implements
class="Acquisition.Explicit"
interface="Acquisition.interfaces.IAcquirer"
/>
<!-- DAV -->
<implements
class="webdav.Lockable.LockableItem"
interface="webdav.interfaces.IWriteLock"
/>
<implements
class="webdav.Resource.Resource"
interface="webdav.interfaces.IDAVResource"
/>
<implements
class="webdav.Collection.Collection"
interface="webdav.interfaces.IDAVCollection"
/>
<!-- OFS -->
<implements
class="OFS.CopySupport.CopySource"
interface="OFS.interfaces.ICopySource"
/>
<implements
class="OFS.CopySupport.CopyContainer"
interface="OFS.interfaces.ICopyContainer"
/>
<implements
class="OFS.Traversable.Traversable"
interface="OFS.interfaces.ITraversable"
/>
<implements
class="OFS.SimpleItem.Item"
interface="OFS.interfaces.IItem"
/>
<implements
class="OFS.SimpleItem.Item_w__name__"
interface="OFS.interfaces.IItemWithName"
/>
<implements
class="OFS.SimpleItem.SimpleItem"
interface="OFS.interfaces.ISimpleItem"
/>
<implements
class="OFS.ObjectManager.ObjectManager"
interface="OFS.interfaces.IObjectManager"
/>
<implements
class="OFS.PropertyManager.PropertyManager"
interface="OFS.interfaces.IPropertyManager"
/>
<implements
class="OFS.FindSupport.FindSupport"
interface="OFS.interfaces.IFindSupport"
/>
<implements
class="OFS.Folder.Folder"
interface="OFS.interfaces.IFolder"
/>
<implements
class="OFS.OrderSupport.OrderSupport"
interface="OFS.interfaces.IOrderedContainer"
/>
<implements
class="OFS.OrderedFolder.OrderedFolder"
interface="OFS.interfaces.IOrderedFolder"
/>
<implements
class="OFS.Application.Application"
interface="OFS.interfaces.IApplication"
/>
<!-- App -->
<implements
class="App.Undo.UndoSupport"
interface="App.interfaces.IUndoSupport"
/>
<implements
class="App.Management.Navigation"
interface="App.interfaces.INavigation"
/>
<!-- AccessControl -->
<implements
class="AccessControl.Owned.Owned"
interface="AccessControl.interfaces.IOwned"
/>
<implements
class="AccessControl.PermissionMapping.RoleManager"
interface="AccessControl.interfaces.IPermissionMappingSupport"
/>
<implements
class="AccessControl.Role.RoleManager"
interface="AccessControl.interfaces.IRoleManager"
/>
</configure>
...@@ -2,13 +2,10 @@ ...@@ -2,13 +2,10 @@
xmlns="http://namespaces.zope.org/zope" xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta"> xmlns:meta="http://namespaces.zope.org/meta">
<include package=".site" file="meta.zcml" />
<include package=".browser" file="meta.zcml" /> <include package=".browser" file="meta.zcml" />
<include package=".form" file="meta.zcml" /> <include package=".form" file="meta.zcml" />
<!-- load the zope:modulealias and zope:hook directives -->
<include package="zope.modulealias" file="meta.zcml" />
<include package="zope.configuration" file="meta.zcml" />
<meta:directives namespace="http://namespaces.zope.org/zope"> <meta:directives namespace="http://namespaces.zope.org/zope">
<meta:directive <meta:directive
...@@ -53,18 +50,6 @@ ...@@ -53,18 +50,6 @@
handler="zope.app.component.metaconfigure.factory" handler="zope.app.component.metaconfigure.factory"
/> />
<meta:directive
name="serviceType"
schema="zope.app.component.metadirectives.IServiceTypeDirective"
handler="zope.app.component.metaconfigure.serviceType"
/>
<meta:directive
name="service"
schema="zope.app.component.metadirectives.IServiceDirective"
handler="zope.app.component.metaconfigure.service"
/>
<meta:complexDirective <meta:complexDirective
name="content" name="content"
schema="zope.app.component.metadirectives.IClassDirective" schema="zope.app.component.metadirectives.IClassDirective"
...@@ -131,14 +116,20 @@ ...@@ -131,14 +116,20 @@
/> />
<meta:directive <meta:directive
name="sendEvents" name="containerEvents"
schema=".fivedirectives.ISendEventsDirective" schema=".fivedirectives.IContainerEventsDirective"
handler=".eventconfigure.sendEvents" handler=".eventconfigure.containerEvents"
/>
<meta:directive
name="deprecatedManageAddDelete"
schema=".fivedirectives.IDeprecatedManageAddDeleteDirective"
handler=".eventconfigure.deprecatedManageAddDelete"
/> />
<meta:directive <meta:directive
name="sizable" name="sizable"
schema=".fivedirectives.ISendEventsDirective" schema=".fivedirectives.ISizableDirective"
handler=".sizeconfigure.sizable" handler=".sizeconfigure.sizable"
/> />
...@@ -148,20 +139,18 @@ ...@@ -148,20 +139,18 @@
handler=".fiveconfigure.pagesFromDirectory" handler=".fiveconfigure.pagesFromDirectory"
/> />
<!-- viewable is deprecated, use traversable instead -->
<meta:directive
name="viewable"
schema=".fivedirectives.ITraversableDirective"
handler=".fiveconfigure.viewable"
/>
<meta:directive <meta:directive
name="bridge" name="bridge"
schema=".fivedirectives.IBridgeDirective" schema=".fivedirectives.IBridgeDirective"
handler=".fiveconfigure.bridge" handler=".fiveconfigure.bridge"
/> />
<meta:directive
name="registerClass"
schema=".fivedirectives.IRegisterClassDirective"
handler=".fiveconfigure.registerClass"
/>
</meta:directives> </meta:directives>
<meta:directive <meta:directive
...@@ -171,6 +160,9 @@ ...@@ -171,6 +160,9 @@
handler="zope.app.security.metaconfigure.redefinePermission" handler="zope.app.security.metaconfigure.redefinePermission"
/> />
<!-- load the zope:modulealias directive -->
<include package="zope.modulealias" file="meta.zcml" />
<!-- load the i18n:registerTranslations directive --> <!-- load the i18n:registerTranslations directive -->
<include package="zope.app.i18n" file="meta.zcml" /> <include package="zope.app.i18n" file="meta.zcml" />
......
...@@ -13,73 +13,25 @@ ...@@ -13,73 +13,25 @@
############################################################################## ##############################################################################
"""Generic Components ZCML Handlers """Generic Components ZCML Handlers
$Id: metaconfigure.py 12884 2005-05-30 13:10:41Z philikon $ $Id: metaconfigure.py 19283 2005-10-31 17:43:51Z philikon $
""" """
from types import ModuleType from Products.Five.security import CheckerPublic, protectName
from Globals import InitializeClass as initializeClass
from zope.interface import classImplements from zope.app.component.contentdirective import ContentDirective as \
from zope.configuration.exceptions import ConfigurationError zope_app_ContentDirective
from security import CheckerPublic class ContentDirective(zope_app_ContentDirective):
from security import protectName, initializeClass
class ContentDirective:
def __init__(self, _context, class_):
self.__class = class_
if isinstance(self.__class, ModuleType):
raise ConfigurationError('Content class attribute must be a class')
self.__context = _context
def implements(self, _context, interface):
for interface in interface:
_context.action(
discriminator = (
'five::directive:content', self.__class, object()),
callable = classImplements,
args = (self.__class, interface),
)
interface(_context, interface)
def require(self, _context, permission=None,
attributes=None, interface=None):
"""Require a the permission to access a specific aspect"""
if not (interface or attributes):
raise ConfigurationError("Nothing required")
if interface:
for i in interface:
if i:
self.__protectByInterface(i, permission)
if attributes:
self.__protectNames(attributes, permission)
def allow(self, _context, attributes=None, interface=None):
"""Like require, but with permission_id zope.Public"""
return self.require(_context, CheckerPublic, attributes, interface)
def __protectByInterface(self, interface, permission_id):
"Set a permission on names in an interface."
for n, d in interface.namesAndDescriptions(1):
self.__protectName(n, permission_id)
interface(self.__context, interface)
def __protectName(self, name, permission_id): def __protectName(self, name, permission_id):
"Set a permission on a particular name."
self.__context.action( self.__context.action(
discriminator = ('five:protectName', self.__class, name), discriminator = ('five:protectName', self.__class, name),
callable = protectName, callable = protectName,
args = (self.__class, name, permission_id) args = (self.__class, name, permission_id)
) )
def __protectNames(self, names, permission_id):
"Set a permission on a bunch of names."
for name in names:
self.__protectName(name, permission_id)
def __call__(self): def __call__(self):
"Handle empty/simple declaration." """Handle empty/simple declaration."""
return self.__context.action( return self.__context.action(
discriminator = ('five:initialize:class', self.__class), discriminator = ('five:initialize:class', self.__class),
callable = initializeClass, callable = initializeClass,
......
<configure xmlns="http://namespaces.zope.org/zope" <configure xmlns="http://namespaces.zope.org/zope"
i18n_domain="Five"> i18n_domain="Five">
<permission
id="five.ManageSite"
title="Manage Five local sites"
/>
<!-- Give common Zope2 and CMF permissions a permission ID <!-- Give common Zope2 and CMF permissions a permission ID
The title of the permission is what Zope 2 knows it under --> The title of the permission is what Zope 2 knows it under -->
......
...@@ -24,7 +24,7 @@ from zope.app.security.interfaces import IPermission ...@@ -24,7 +24,7 @@ from zope.app.security.interfaces import IPermission
from zope.app import zapi from zope.app import zapi
from AccessControl import ClassSecurityInfo, getSecurityManager from AccessControl import ClassSecurityInfo, getSecurityManager
from Globals import InitializeClass from Globals import InitializeClass as initializeClass
from types import StringTypes from types import StringTypes
CheckerPublicId = 'zope.Public' CheckerPublicId = 'zope.Public'
...@@ -99,9 +99,6 @@ def newInteraction(): ...@@ -99,9 +99,6 @@ def newInteraction():
if getattr(thread_local, 'interaction', None) is None: if getattr(thread_local, 'interaction', None) is None:
thread_local.interaction = FiveSecurityPolicy() thread_local.interaction = FiveSecurityPolicy()
def initializeClass(klass):
InitializeClass(klass)
def _getSecurity(klass): def _getSecurity(klass):
# a Zope 2 class can contain some attribute that is an instance # a Zope 2 class can contain some attribute that is an instance
# of ClassSecurityInfo. Zope 2 scans through things looking for # of ClassSecurityInfo. Zope 2 scans through things looking for
...@@ -121,15 +118,12 @@ def protectName(klass, name, permission_id): ...@@ -121,15 +118,12 @@ def protectName(klass, name, permission_id):
"""Protect the attribute 'name' on 'klass' using the given """Protect the attribute 'name' on 'klass' using the given
permission""" permission"""
security = _getSecurity(klass) security = _getSecurity(klass)
# XXX: Sometimes, the object CheckerPublic is used instead of the
# string zope.Public. I haven't ben able to figure out why, or if
# it is correct, or a bug. So this is a workaround.
if permission_id is CheckerPublic:
security.declarePublic(name)
return
# Zope 2 uses string, not unicode yet # Zope 2 uses string, not unicode yet
name = str(name) name = str(name)
if permission_id == CheckerPublicId: if permission_id == CheckerPublicId or permission_id is CheckerPublic:
# Sometimes, we already get a processed permission id, which
# can mean that 'zope.Public' has been interchanged for the
# CheckerPublic object
security.declarePublic(name) security.declarePublic(name)
elif permission_id == CheckerPrivateId: elif permission_id == CheckerPrivateId:
security.declarePrivate(name) security.declarePrivate(name)
...@@ -142,7 +136,10 @@ def protectName(klass, name, permission_id): ...@@ -142,7 +136,10 @@ def protectName(klass, name, permission_id):
def protectClass(klass, permission_id): def protectClass(klass, permission_id):
"""Protect the whole class with the given permission""" """Protect the whole class with the given permission"""
security = _getSecurity(klass) security = _getSecurity(klass)
if permission_id == CheckerPublicId: if permission_id == CheckerPublicId or permission_id is CheckerPublic:
# Sometimes, we already get a processed permission id, which
# can mean that 'zope.Public' has been interchanged for the
# CheckerPublic object
security.declareObjectPublic() security.declareObjectPublic()
elif permission_id == CheckerPrivateId: elif permission_id == CheckerPrivateId:
security.declareObjectPrivate() security.declareObjectPrivate()
......
<configure xmlns="http://namespaces.zope.org/zope">
<serviceType
id="Utilities"
interface="zope.component.interfaces.IUtilityService" />
<service
serviceType="Utilities"
factory="zope.component.utility.GlobalUtilityService" />
<serviceType
id="Adapters"
interface="zope.component.interfaces.IAdapterService" />
<service
serviceType="Adapters"
factory="zope.component.adapter.GlobalAdapterService" />
<serviceType
id="Presentation"
interface="zope.component.interfaces.IPresentationService" />
<service
serviceType="Presentation"
factory="zope.component.presentation.GlobalPresentationService" />
</configure>
# make this directory a package
##############################################################################
#
# Copyright (c) 2005 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.
#
##############################################################################
"""Local sites browser views
$Id$
"""
from zope.app.component.interfaces import ISite
from zope.app.component.hooks import clearSite
from Products.Five.browser import BrowserView
from Products.Five.site.localsite import enableLocalSiteHook, \
disableLocalSiteHook
class LocalSiteView(BrowserView):
"""View for convering a possible site to a site
"""
def update(self):
form = self.request.form
if form.has_key('UPDATE_MAKESITE'):
self.makeSite()
elif form.has_key('UPDATE_UNMAKESITE'):
self.unmakeSite()
def isSite(self):
return ISite.providedBy(self.context)
def makeSite(self):
"""Convert a possible site to a site"""
if self.isSite():
raise ValueError('This is already a site')
enableLocalSiteHook(self.context)
return "This object is now a site"
def unmakeSite(self):
"""Convert a site to a possible site"""
if not self.isSite():
raise ValueError('This is not a site')
disableLocalSiteHook(self.context)
# disableLocalSiteHook circumcised our context so that it's
# not an ISite anymore. That can mean that certain things for
# it can't be found anymore. So, for the rest of this request
# (which will be over in about 20 CPU cycles), already clear
# the local site from the thread local.
clearSite()
return "This object is no longer a site"
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<adapter
for="*"
provides="zope.component.interfaces.ISiteManager"
factory=".localsite.siteManagerAdapter"
/>
<adapter
for="zope.app.site.interfaces.ISite"
provides=".interfaces.IFiveUtilityRegistry"
factory=".utility.SimpleLocalUtilityRegistry"
/>
<subscriber
for="zope.app.component.interfaces.ISite
zope.app.publication.interfaces.IBeforeTraverseEvent"
handler="zope.app.component.site.threadSiteSubscriber"
/>
<subscriber
for="zope.app.publication.interfaces.IEndRequestEvent"
handler="zope.app.component.site.clearThreadSiteSubscriber"
/>
<browser:page
for="zope.app.component.interfaces.IPossibleSite"
name="manage_site.html"
permission="five.ManageSite"
class=".browser.LocalSiteView"
template="managesite.pt"
/>
</configure>
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Five interfaces
$Id: interfaces.py 18584 2005-10-14 17:13:27Z regebro $
"""
from zope.interface import Interface, Attribute
from zope.component.interfaces import ISiteManager
class IRegisterUtilitySimply(Interface):
"""Register utilities simply
Allow local registrations of utilities, in a much simpler
manner than Zope 3 does it currently.
Note: The name of this interface is expressed as a verb
(describing the action it expresses, namely registering
utilities). The reason for that is that the names *utility
registry* (successor of the Zope 3 utility service) and *utility
registration* (object in a registration stack, part of the
complicated registration framework in Zope 3) have different
connotations in Zope 3 than we want to express here.
"""
def registerUtility(self, interface, utility, name=''):
"""Registers a utility in the local context"""
# TODO Define an exception than is to be thrown when a local
# utility of that interface and name is already registered.
next = Attribute("The next local registry in the tree. This attribute "
"represents the parent of this registry node. If the "
"value is ``None``, then this registry represents the "
"root of the tree")
class IFiveUtilityRegistry(IRegisterUtilitySimply):
"""Look up and register utilities"""
def getUtility(interface, name='', context=None):
"""Get the utility that provides interface
Returns the nearest utility to the context that implements the
specified interface. If one is not found, raises
ComponentLookupError.
"""
def queryUtility(interface, name='', default=None, context=None):
"""Look for the utility that provides interface
Returns the nearest utility to the context that implements
the specified interface. If one is not found, returns default.
"""
def getUtilitiesFor(interface, context=None):
"""Return the utilities that provide an interface
An iterable of utility name-value pairs is returned.
"""
def getAllUtilitiesRegisteredFor(interface, context=None):
"""Return all registered utilities for an interface
This includes overridden utilities.
An iterable of utility instances is returned. No names are
returned.
"""
class IFiveSiteManager(ISiteManager, IRegisterUtilitySimply):
"""Five site manager
For the sake of forward-portability, registering utilities can be
done directly on the site manager to cut out the middle man called
utility service (this corresponds to Zope 3.1's understanding of
site managers). An implementation of this interface will probably
delegate the work to an IFiveUtilityService component, though."""
# BBB 2005/11/01 -- gone in Five 1.5.
IFiveUtilityService = IFiveUtilityRegistry
import zope.deprecation
zope.deprecation.deprecated(
'IFiveUtilityService', "'IFiveUtilityService' has been renamed to "
"'IFiveUtilityRegistry' and will disappear in Five 1.5."
)
##############################################################################
#
# Copyright (c) 2005 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.
#
##############################################################################
"""Local sites
$Id$
"""
from zope.event import notify
from zope.interface import directlyProvides, directlyProvidedBy
from zope.interface import implements
from zope.component import getGlobalSiteManager
from zope.component.exceptions import ComponentLookupError
from zope.app.component.interfaces import ISite, IPossibleSite
from zope.app.publication.zopepublication import BeforeTraverseEvent
from ExtensionClass import Base
from Acquisition import aq_base, aq_inner, aq_parent
from Products.SiteAccess.AccessRule import AccessRule
from ZPublisher.BeforeTraverse import registerBeforeTraverse
from ZPublisher.BeforeTraverse import unregisterBeforeTraverse
from Products.Five.site.interfaces import IFiveSiteManager, IFiveUtilityRegistry
# Hook up custom component architecture calls
import zope.app.component.hooks
zope.app.component.hooks.setHooks()
def siteManagerAdapter(ob):
"""An adapter * -> ISiteManager.
This is registered in place of the one in Zope 3 so that we lookup
using acquisition instead of ILocation.
"""
current = ob
while True:
if ISite.providedBy(current):
return current.getSiteManager()
current = getattr(current, '__parent__', aq_parent(aq_inner(current)))
if current is None:
# It does not support acquisition or has no parent, so we
# return the global site
return getGlobalSiteManager()
HOOK_NAME = '__local_site_hook__'
class LocalSiteHook(Base):
def __call__(self, container, request):
notify(BeforeTraverseEvent(container, request))
def enableLocalSiteHook(obj):
"""Install __before_traverse__ hook for Local Site
"""
# We want the original object, not stuff in between, and no acquisition
obj = aq_base(obj)
if not IPossibleSite.providedBy(obj):
raise TypeError, 'Must provide IPossibleSite'
hook = AccessRule(HOOK_NAME)
registerBeforeTraverse(obj, hook, HOOK_NAME, 1)
if not hasattr(obj, HOOK_NAME):
setattr(obj, HOOK_NAME, LocalSiteHook())
directlyProvides(obj, ISite, directlyProvidedBy(obj))
def disableLocalSiteHook(obj):
"""Remove __before_traverse__ hook for Local Site
"""
# We want the original object, not stuff in between, and no acquisition
obj = aq_base(obj)
if not ISite.providedBy(obj):
raise TypeError, 'Must provide ISite'
unregisterBeforeTraverse(obj, HOOK_NAME)
if hasattr(obj, HOOK_NAME):
delattr(obj, HOOK_NAME)
directlyProvides(obj, directlyProvidedBy(obj) - ISite)
class FiveSiteManager(object):
implements(IFiveSiteManager)
def __init__(self, context):
# make {get|query}NextSiteManager() work without having to
# resort to Zope 2 acquisition
self.context = self.__parent__ = context
@property
def next(self):
obj = self.context
while obj is not None:
obj = aq_parent(aq_inner(obj))
if ISite.providedBy(obj):
return obj.getSiteManager()
# In Zope 3.1+, returning None here is understood by
# getNextSiteManager as that our next site manager is the
# global one. If we returned the global one, it would be
# understood as a lookup error. Yeah, it's weird, tell me
# about it.
return None
@property
def adapters(self):
return getGlobalSiteManager().adapters #XXX wrong
@property
def utilities(self):
return IFiveUtilityRegistry(self.context)
def queryAdapter(self, object, interface, name, default=None):
return self.adapters.queryAdapter(object, interface, name, default)
def queryMultiAdapter(self, objects, interface, name, default=None):
return self.adapters.queryMultiAdapter(objects, interface, name, default)
def getAdapters(self, objects, provided):
return self.adapters.getAdapters(objects, provided)
def subscribers(self, required, provided):
return self.adapters.subscribers(required, provided)
def queryUtility(self, interface, name='', default=None):
return self.utilities.queryUtility(interface, name, default)
def getUtilitiesFor(self, interface):
return self.utilities.getUtilitiesFor(interface)
def getAllUtilitiesRegisteredFor(self, interface):
return self.utilities.getAllUtilitiesRegisteredFor(interface)
def registerUtility(self, interface, utility, name=''):
return self.utilities.registerUtility(interface, utility, name)
class FiveSite:
implements(IPossibleSite)
def getSiteManager(self):
return FiveSiteManager(self)
def setSiteManager(self, sm):
raise NotImplementedError('This class has a fixed site manager')
<tal:tag condition="view/update"/>
<html metal:use-macro="context/@@standard_macros/view"
i18n:domain="zope">
<body>
<div metal:fill-slot="body">
<form action="." tal:attributes="action request/URL" method="POST"
enctype="multipart/form-data">
<div class="row">
<div class="controls">
<input type="submit" value="Make site" name="UPDATE_MAKESITE"
i18n:attributes="value"
tal:attributes="disabled view/isSite"/>
<input type="submit" value="Unmake site" name="UPDATE_UNMAKESITE"
i18n:attributes="value"
tal:attributes="disabled not:view/isSite"/>
</div>
</div>
</form>
</div>
</body>
</html>
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta">
<meta:directives namespace="http://namespaces.zope.org/five">
<meta:directive
name="localsite"
schema=".metadirectives.ILocalSiteDirective"
handler=".metaconfigure.installSiteHook"
/>
</meta:directives>
</configure>
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Five-specific directive handlers
These directives are specific to Five and have no equivalents in Zope 3.
$Id: fiveconfigure.py 18581 2005-10-14 16:54:25Z regebro $
"""
from zope.interface import classImplements, classImplementsOnly, implementedBy
from zope.interface.interface import InterfaceClass
from zope.configuration.exceptions import ConfigurationError
from zope.app.component.metaconfigure import adapter
from zope.app.component.interfaces import IPossibleSite
from Products.Five.site.localsite import FiveSite
def classSiteHook(class_, site_class):
setattr(class_, 'getSiteManager',
site_class.getSiteManager.im_func)
setattr(class_, 'setSiteManager',
site_class.setSiteManager.im_func)
_localsite_monkies = []
def installSiteHook(_context, class_, site_class=None):
if site_class is None:
if not IPossibleSite.implementedBy(class_):
# This is not a possible site, we need to monkey-patch it so that
# it is.
site_class = FiveSite
else:
if not IPossibleSite.implementedBy(site_class):
raise ConfigurationError('Site class does not implement '
'IPossibleClass: %s' % site_class)
if site_class is not None:
_context.action(
discriminator = (class_,),
callable = classSiteHook,
args=(class_, site_class)
)
_context.action(
discriminator = (class_, IPossibleSite),
callable = classImplements,
args=(class_, IPossibleSite)
)
_localsite_monkies.append(class_)
# clean up code
def uninstallSiteHooks():
for class_ in _localsite_monkies:
delattr(class_, 'getSiteManager')
delattr(class_, 'setSiteManager')
classImplementsOnly(class_, implementedBy(class_)-IPossibleSite)
_localsite_monkies.remove(class_)
from zope.testing.cleanup import addCleanUp
addCleanUp(uninstallSiteHooks)
del addCleanUp
...@@ -11,30 +11,26 @@ ...@@ -11,30 +11,26 @@
# FOR A PARTICULAR PURPOSE. # FOR A PARTICULAR PURPOSE.
# #
############################################################################## ##############################################################################
"""Test import conflicts """Site support ZCML directive schemas
$Id: test_import_conflicts.py 18150 2005-10-04 16:19:49Z philikon $ $Id: fivedirectives.py 18581 2005-10-14 16:54:25Z regebro $
""" """
import os, sys from zope.interface import Interface
if __name__ == '__main__': from zope.configuration.fields import GlobalObject
execfile(os.path.join(sys.path[0], 'framework.py'))
def testImportConflicts(): class ILocalSiteDirective(Interface):
""" """Make instances of class hookable for Site.
In a Five environment, importing Zope 3 packages that would use
interfaces from the Zope 3 transaction module would lead to an
error, because of the monkey patching. The zope.app.mail package
makes use of transaction interfaces, for example the following
class:
>>> from zope.app.mail.delivery import QueueProcessorThread
Note that this only concerns Zope 2.7 and Zope X3 3.0. site_class is an implementation of ISite, which will have it's methods
monkey_patched into the the class. If not given a default implementation
will be used.
""" """
class_ = GlobalObject(
title=u"Class",
required=True
)
def test_suite(): site_class = GlobalObject(
from Testing.ZopeTestCase import ZopeDocTestSuite title=u"Site Class",
return ZopeDocTestSuite() required=False
)
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2005 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.
#
##############################################################################
"""Dummy test fixtures
$Id$
"""
from zope.interface import implements, Interface
from OFS.SimpleItem import SimpleItem
from Products.Five.tests.testing import FiveTraversableFolder
class IDummySite(Interface):
pass
class DummySite(FiveTraversableFolder):
"""A very dummy Site
"""
implements(IDummySite)
def manage_addDummySite(self, id, REQUEST=None):
"""Add the dummy site."""
id = self._setObject(id, DummySite(id))
return ''
class IDummyUtility(Interface):
pass
class ISuperDummyUtility(IDummyUtility):
pass
class DummyUtility(SimpleItem):
implements(IDummyUtility)
##############################################################################
#
# ZopeTestCase
#
# COPY THIS FILE TO YOUR 'tests' DIRECTORY.
#
# This version of framework.py will use the SOFTWARE_HOME
# environment variable to locate Zope and the Testing package.
#
# If the tests are run in an INSTANCE_HOME installation of Zope,
# Products.__path__ and sys.path with be adjusted to include the
# instance's Products and lib/python directories respectively.
#
# If you explicitly set INSTANCE_HOME prior to running the tests,
# auto-detection is disabled and the specified path will be used
# instead.
#
# If the 'tests' directory contains a custom_zodb.py file, INSTANCE_HOME
# will be adjusted to use it.
#
# If you set the ZEO_INSTANCE_HOME environment variable a ZEO setup
# is assumed, and you can attach to a running ZEO server (via the
# instance's custom_zodb.py).
#
##############################################################################
#
# The following code should be at the top of every test module:
#
# import os, sys
# if __name__ == '__main__':
# execfile(os.path.join(sys.path[0], 'framework.py'))
#
# ...and the following at the bottom:
#
# if __name__ == '__main__':
# framework()
#
##############################################################################
__version__ = '0.2.3'
# Save start state
#
__SOFTWARE_HOME = os.environ.get('SOFTWARE_HOME', '')
__INSTANCE_HOME = os.environ.get('INSTANCE_HOME', '')
if __SOFTWARE_HOME.endswith(os.sep):
__SOFTWARE_HOME = os.path.dirname(__SOFTWARE_HOME)
if __INSTANCE_HOME.endswith(os.sep):
__INSTANCE_HOME = os.path.dirname(__INSTANCE_HOME)
# Find and import the Testing package
#
if not sys.modules.has_key('Testing'):
p0 = sys.path[0]
if p0 and __name__ == '__main__':
os.chdir(p0)
p0 = ''
s = __SOFTWARE_HOME
p = d = s and s or os.getcwd()
while d:
if os.path.isdir(os.path.join(p, 'Testing')):
zope_home = os.path.dirname(os.path.dirname(p))
sys.path[:1] = [p0, p, zope_home]
break
p, d = s and ('','') or os.path.split(p)
else:
print 'Unable to locate Testing package.',
print 'You might need to set SOFTWARE_HOME.'
sys.exit(1)
import Testing, unittest
execfile(os.path.join(os.path.dirname(Testing.__file__), 'common.py'))
# Include ZopeTestCase support
#
if 1: # Create a new scope
p = os.path.join(os.path.dirname(Testing.__file__), 'ZopeTestCase')
if not os.path.isdir(p):
print 'Unable to locate ZopeTestCase package.',
print 'You might need to install ZopeTestCase.'
sys.exit(1)
ztc_common = 'ztc_common.py'
ztc_common_global = os.path.join(p, ztc_common)
f = 0
if os.path.exists(ztc_common_global):
execfile(ztc_common_global)
f = 1
if os.path.exists(ztc_common):
execfile(ztc_common)
f = 1
if not f:
print 'Unable to locate %s.' % ztc_common
sys.exit(1)
# Debug
#
print 'SOFTWARE_HOME: %s' % os.environ.get('SOFTWARE_HOME', 'Not set')
print 'INSTANCE_HOME: %s' % os.environ.get('INSTANCE_HOME', 'Not set')
sys.stdout.flush()
Functional test for local sites
===============================
Set up all of Five:
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
First we turn our DummySite class into a site in ZCML (and register
some views that will provide us with some test info),
>>> zcml_text = """
... <configure xmlns="http://namespaces.zope.org/zope"
... xmlns:meta="http://namespaces.zope.org/meta"
... xmlns:five="http://namespaces.zope.org/five"
... xmlns:browser="http://namespaces.zope.org/browser">
...
... <!-- make the zope2.Public permission work -->
... <meta:redefinePermission from="zope2.Public" to="zope.Public" />
...
... <five:localsite class="Products.Five.site.tests.dummy.DummySite" />
...
... <browser:page
... for="Products.Five.site.tests.dummy.IDummySite"
... name="checkSiteManager.html"
... class="Products.Five.site.tests.test_functional.CheckSiteManagerView"
... permission="zope2.Public"
... />
...
... <browser:page
... for="Products.Five.site.tests.dummy.IDummySite"
... name="lookupUtilities.html"
... class="Products.Five.site.tests.test_functional.LookupUtilitiesView"
... permission="zope2.Public"
... />
...
... </configure>"""
>>> zcml.load_string(zcml_text)
then we add an instance to our folder:
>>> from Products.Five.site.tests.dummy import manage_addDummySite
>>> nothing = manage_addDummySite(self.folder, 'site')
Now we check what the info view tells us about local component lookup:
>>> print http(r'''
... GET /test_folder_1_/site/@@checkSiteManager.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
{'IFiveUtilityRegistry.providedBy(utility_service)': False,
'isinstance(zapi.getSiteManager(), FiveSiteManager)': False,
'zapi.getSiteManager() is zapi.getGlobalSiteManager()': True}
We see that we have no local component lookup yet, because we haven't
set the site. Therefore, enable the traversal hook by using the view
that's provided for this task (we first need to create a manager
account in order to be able to access it):
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> print http(r'''
... POST /test_folder_1_/site/@@manage_site.html HTTP/1.1
... Authorization: Basic manager:r00t
... Content-Length: 25
...
... UPDATE_MAKESITE=Make site''')
HTTP/1.1 200 OK
...
Now we call the info view again and find that local component lookup
is working:
>>> print http(r'''
... GET /test_folder_1_/site/@@checkSiteManager.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
{'IFiveUtilityRegistry.providedBy(utility_service)': True,
'isinstance(zapi.getSiteManager(), FiveSiteManager)': True,
'zapi.getSiteManager() is zapi.getGlobalSiteManager()': False}
Of course, sites are only active *during* traversal; after traversal
they're gone:
>>> from zope.app.component.hooks import getSite
>>> getSite() is None
True
We can also register utilities now:
>>> from zope.app import zapi
>>> sm = self.folder.site.getSiteManager()
>>> from Products.Five.site.tests.dummy import IDummyUtility, DummyUtility
>>> dummy = DummyUtility()
>>> sm.registerUtility(IDummyUtility, dummy)
and find them being looked up just fine:
>>> print http(r'''
... GET /test_folder_1_/site/@@lookupUtilities.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
zapi.getUtility(IDummyUtility) == dummy: True
Of course, we can't look it up once the request has ended, because we
lose the local site setup:
>>> zapi.getUtility(IDummyUtility)
Traceback (most recent call last):
...
ComponentLookupError: (<InterfaceClass Products.Five.site.tests.dummy.IDummyUtility>, '')
At last we can "unmake" the site using the browser view provided by
Five:
>>> print http(r'''
... POST /test_folder_1_/site/@@manage_site.html HTTP/1.1
... Authorization: Basic manager:r00t
... Content-Length: 29
...
... UPDATE_UNMAKESITE=Unmake site''')
HTTP/1.1 200 OK
...
And everything is back to normal with respect to local component
lookup:
>>> print http(r'''
... GET /test_folder_1_/site/@@checkSiteManager.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
{'IFiveUtilityRegistry.providedBy(utility_service)': False,
'isinstance(zapi.getSiteManager(), FiveSiteManager)': False,
'zapi.getSiteManager() is zapi.getGlobalSiteManager()': True}
Finally, global services and the monkeys:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
Five Site Manager
=================
In this test we want to test Five's implementation of a site manager.
First, we need to set a few things up...
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("meta.zcml", Products.Five)
>>> zcml.load_config("permissions.zcml", Products.Five)
>>> zcml.load_config("configure.zcml", Products.Five.site)
>>> zcml_text = """\
... <five:localsite
... xmlns:five="http://namespaces.zope.org/five"
... class="Products.Five.site.tests.dummy.DummySite" />"""
>>> zcml.load_string(zcml_text)
...for example some sort of site object:
>>> from Products.Five.site.tests.dummy import manage_addDummySite
>>> nothing = manage_addDummySite(self.folder, 'dummysite')
>>> dummysite = self.folder.dummysite
Local vs. global sites
----------------------
Let's make the possible site a real site:
>>> from Products.Five.site.localsite import enableLocalSiteHook
>>> enableLocalSiteHook(dummysite)
and tell Zope 3 about it:
>>> from zope.app.component.hooks import setSite, setHooks
>>> setSite(dummysite)
Also hook up custom component architecture calls; we need to do this
here because zope.app.component.hooks registers a cleanup with the
testing cleanup framework, so the hooks get torn down by
placelesssetup each time.
>>> setHooks()
That seems to have worked (we test this by using the context property
of FiveSiteManager):
>>> from zope.app import zapi
>>> zapi.getSiteManager().context == dummysite
True
Since there's no other local site in between this one and the global
one, the next one should be the global one. FiveSiteManager indicates
that to us by return ``None``:
>>> from zope.app import zapi
>>> zapi.getSiteManager().next is None
True
To the the Zope 3 API, this means the next site manager should be the
global one:
>>> from zope.app.component import getNextSiteManager
>>> getNextSiteManager(dummysite.getSiteManager()) is zapi.getGlobalSiteManager()
True
ISiteManager API
----------------
Site managers are supposed to have an ``adapters`` and a ``utilities``
attribute. Five's site manager simply passes through the global
adapter registry:
>>> zapi.getSiteManager().adapters is zapi.getGlobalSiteManager().adapters
True
The utility registry, however, is an ``IFiveUtilityRegistry``:
>>> from Products.Five.site.interfaces import IFiveUtilityRegistry
>>> IFiveUtilityRegistry.providedBy(zapi.getSiteManager().utilities)
True
The methods on registering and looking up utilities are covered by the
utility tests in depth. The methods on adapter look up are indirectly
covered in the functional test; view look up, for example, is adapter
look up.
Nesting sites
-------------
Let's set up another site to test nested sites:
>>> nothing = manage_addDummySite(self.folder.dummysite, 'subsite')
>>> subsite = self.folder.dummysite.subsite
Now we set the current site to the ``subsite``:
>>> enableLocalSiteHook(subsite)
>>> setSite(subsite)
When we call getServices() now, we get the correct site manager:
>>> zapi.getSiteManager().context == subsite
True
The "next" site is the less local one:
>>> zapi.getSiteManager().next.context == dummysite
True
The Zope 3 API for this agrees with that:
>>> getNextSiteManager(subsite.getSiteManager()).context == dummysite
True
Finally, some clean up:
>>> tearDown()
##############################################################################
#
# Copyright (c) 2005 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 local sites
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
import pprint
from zope.app import zapi
from Products.Five import BrowserView
from Products.Five.site.interfaces import IFiveUtilityRegistry
from Products.Five.site.localsite import FiveSiteManager
from Products.Five.site.tests.dummy import IDummyUtility
class CheckSiteManagerView(BrowserView):
def __call__(self):
sm = zapi.getSiteManager()
result = {
'zapi.getSiteManager() is zapi.getGlobalSiteManager()':
sm is zapi.getGlobalSiteManager(),
'IFiveUtilityRegistry.providedBy(utility_service)':
IFiveUtilityRegistry.providedBy(sm.utilities),
'isinstance(zapi.getSiteManager(), FiveSiteManager)':
isinstance(sm, FiveSiteManager),
}
return pprint.pformat(result)
class LookupUtilitiesView(BrowserView):
def __call__(self):
dummy = getattr(self.context.utilities, IDummyUtility.getName())
return "zapi.getUtility(IDummyUtility) == dummy: %s" % \
(zapi.getUtility(IDummyUtility) == dummy)
def test_suite():
from Testing.ZopeTestCase import FunctionalDocFileSuite
suite = FunctionalDocFileSuite('functional.txt',
package='Products.Five.site.tests')
suite.level = 2
return suite
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2005 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 local sites
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
import unittest
from Testing import ZopeTestCase
from zope.interface import implements
from zope.interface import directlyProvides, directlyProvidedBy
from zope.component import getGlobalSiteManager, getSiteManager
from zope.component.exceptions import ComponentLookupError
from zope.component.interfaces import ISiteManager
from zope.app.component.hooks import setSite, getSite, setHooks
from zope.app.component.interfaces import IPossibleSite, ISite
from zope.app.traversing.interfaces import IContainmentRoot
from zope.app.testing.placelesssetup import PlacelessSetup
from Acquisition import Implicit
from OFS.ObjectManager import ObjectManager
import Products.Five
from Products.Five import zcml
class SiteManager(Implicit):
implements(ISiteManager)
class Folder(ObjectManager):
implements(IPossibleSite)
sm = None
def getId(self):
return self.id
def getSiteManager(self, default=None):
return self.sm
def setSiteManager(self, sm):
self.sm = sm
directlyProvides(self, ISite, directlyProvidedBy(self))
class Package(Implicit):
pass
class Root(Folder):
implements(IContainmentRoot, ISite)
def getSiteManager(self):
return getGlobalSiteManager()
class SiteManagerStub(object):
implements(ISiteManager)
class SiteManagerTest(PlacelessSetup, unittest.TestCase):
def setUp(self):
super(SiteManagerTest, self).setUp()
self.root = root = Root()
self.f1 = f1 = Folder().__of__(root)
self.sm1 = sm1 = SiteManager()
f1.setSiteManager(sm1)
self.p1 = p1 = Package().__of__(sm1)
self.f2 = f2 = Folder().__of__(f1)
self.sm2 = sm2 = SiteManager()
f2.setSiteManager(sm2)
self.p2 = p2 = Package().__of__(sm2)
sm1.next = getGlobalSiteManager()
sm2.next = sm1
self.unparented_folder = Folder()
self.unrooted_subfolder = Folder().__of__(self.unparented_folder)
zcml.load_config("meta.zcml", Products.Five)
zcml.load_config("permissions.zcml", Products.Five)
zcml.load_config("configure.zcml", Products.Five.site)
zcml_text = """\
<five:localsite
xmlns:five="http://namespaces.zope.org/five"
class="Products.Five.site.tests.dummy.DummySite" />"""
zcml.load_string(zcml_text)
# Hook up custom component architecture calls; we need to do
# this here because zope.app.component.hooks registers a
# cleanup with the testing cleanup framework, so the hooks get
# torn down by placelesssetup each time.
setHooks()
def test_getSiteManager(self):
self.assertEqual(getSiteManager(None), getGlobalSiteManager())
self.assertEqual(getSiteManager(self.root), getGlobalSiteManager())
self.assertEqual(getSiteManager(self.f1), self.sm1)
self.assertEqual(getSiteManager(self.f2), self.sm2)
setSite(self.f2)
self.assertEqual(getSiteManager(None), self.sm2)
def test_queryNextSiteManager(self):
from zope.app.component import queryNextSiteManager
marker = object()
self.assert_(queryNextSiteManager(self.root, marker) is marker)
self.assert_(queryNextSiteManager(self.f1, marker) is getGlobalSiteManager())
#XXX the following used to be
#self.assertEqual(queryNextSiteManager(self.f2, marker), marker)
self.assertEqual(queryNextSiteManager(self.f2, marker), self.sm1)
self.assertEqual(queryNextSiteManager(self.sm1), getGlobalSiteManager())
self.assertEqual(queryNextSiteManager(self.sm2), self.sm1)
#XXX the following used to be
#self.assert_(queryNextSiteManager(self.p1) is getGlobalSiteManager())
self.assert_(queryNextSiteManager(self.p1, marker) is marker)
#XXX the following used to be
#self.assertEqual(queryNextSiteManager(self.p2), self.sm1)
self.assert_(queryNextSiteManager(self.p2, marker) is marker)
self.assert_(queryNextSiteManager(self.unparented_folder, marker)
is marker)
self.assert_(queryNextSiteManager(self.unrooted_subfolder, marker)
is marker)
def test_getNextSiteManager(self):
from zope.app.component import getNextSiteManager
self.assertRaises(ComponentLookupError, getNextSiteManager, self.root)
self.assertEqual(getNextSiteManager(self.f1), getGlobalSiteManager())
#XXX the following used to be
#self.assertRaises(ComponentLookupError, getNextSiteManager, self.f2)
self.assertEqual(getNextSiteManager(self.f2), self.sm1)
self.assertEqual(getNextSiteManager(self.sm1), getGlobalSiteManager())
self.assertEqual(getNextSiteManager(self.sm2), self.sm1)
#XXX the following used to be
#self.assert_(getNextSiteManager(self.p1) is getGlobalSiteManager())
self.assertRaises(ComponentLookupError, getNextSiteManager, self.p1)
#XXX the following used to be
#self.assertEqual(getNextSiteManager(self.p2), self.sm1)
self.assertRaises(ComponentLookupError, getNextSiteManager, self.p2)
self.assertRaises(ComponentLookupError,
getNextSiteManager, self.unparented_folder)
self.assertRaises(ComponentLookupError,
getNextSiteManager, self.unrooted_subfolder)
# XXX Maybe we need to test this with RestrictedPython in the context
# of Zope2? Maybe we just don't care.
#
# def test_getNextSiteManager_security(self):
# from zope.app.component import getNextSiteManager
# from zope.security.checker import ProxyFactory, NamesChecker
# sm = ProxyFactory(self.sm1, NamesChecker(('next',)))
# # Check that getGlobalSiteManager() is not proxied
# self.assert_(getNextSiteManager(sm) is getGlobalSiteManager())
def test_siteManagerAdapter(self):
from Products.Five.site.localsite import siteManagerAdapter
# If it is a site, return the service service.
sm = SiteManagerStub()
site = Folder()
site.setSiteManager(sm)
self.assertEqual(siteManagerAdapter(site), sm)
# If it has an acquisition context, "acquire" the site
# and return the service service
ob = Folder()
ob = ob.__of__(site)
self.assertEqual(siteManagerAdapter(ob), sm)
ob2 = Folder()
ob2 = ob2.__of__(ob)
self.assertEqual(siteManagerAdapter(ob2), sm)
# If it does we are unable to find a service service, raise
# ComponentLookupError
orphan = Folder()
self.failUnless(siteManagerAdapter(orphan) is getGlobalSiteManager())
def test_setThreadSite_clearThreadSite(self):
from zope.app.component.site import threadSiteSubscriber, clearSite
from zope.app.publication.zopepublication import BeforeTraverseEvent
self.assertEqual(getSite(), None)
# A site is traversed
sm = SiteManagerStub()
site = Folder()
site.setSiteManager(sm)
ev = BeforeTraverseEvent(site, object())
threadSiteSubscriber(site, ev)
self.assertEqual(getSite(), site)
clearSite()
self.assertEqual(getSite(), None)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(SiteManagerTest))
return suite
if __name__ == '__main__':
framework()
############################################################################## ##############################################################################
# #
# Copyright (c) 2004, 2005 Zope Corporation and Contributors. # Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved. # All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
...@@ -11,16 +11,18 @@ ...@@ -11,16 +11,18 @@
# FOR A PARTICULAR PURPOSE. # FOR A PARTICULAR PURPOSE.
# #
############################################################################## ##############################################################################
""" Z2 -> Z3 bridge utilities. """Test Five site manager
$Id: adding.py 15515 2005-08-02 17:42:08Z yuppie $ $Id$
""" """
import os, sys
# BBB: This file will be removed in future versions of Five. if __name__ == '__main__':
from browser.adding import ContentAdding execfile(os.path.join(sys.path[0], 'framework.py'))
import warnings def test_suite():
warnings.warn("\nThe Products.Five.adding module has been renamed to " from Testing.ZopeTestCase import ZopeDocFileSuite
"Products.Five.browser.adding \n" return ZopeDocFileSuite('sitemanager.txt', package="Products.Five.site.tests")
"and will be disabled starting in Five 1.2.\n",
DeprecationWarning, stacklevel=2) if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2005 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 local sites
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
import unittest
import sets
from Testing import ZopeTestCase
from zope.interface import directlyProvides
from zope.component import provideUtility
from zope.component.exceptions import ComponentLookupError
from zope.app import zapi
from zope.app.testing.placelesssetup import setUp, tearDown
from zope.app.component import getNextUtility
from zope.app.component.hooks import setSite, clearSite, setHooks
import Products.Five
from Products.Five import zcml
from Products.Five.site.interfaces import IRegisterUtilitySimply
from Products.Five.site.localsite import enableLocalSiteHook
from Products.Five.site.tests.dummy import manage_addDummySite, \
IDummyUtility, ISuperDummyUtility, DummyUtility
class LocalUtilityServiceTest(ZopeTestCase.ZopeTestCase):
def afterSetUp(self):
setUp()
zcml.load_config("meta.zcml", Products.Five)
zcml.load_config("permissions.zcml", Products.Five)
zcml.load_config("configure.zcml", Products.Five.site)
zcml_text = """\
<five:localsite
xmlns:five="http://namespaces.zope.org/five"
class="Products.Five.site.tests.dummy.DummySite" />"""
zcml.load_string(zcml_text)
manage_addDummySite(self.folder, 'site')
enableLocalSiteHook(self.folder.site)
setSite(self.folder.site)
# Hook up custom component architecture calls; we need to do
# this here because zope.app.component.hooks registers a
# cleanup with the testing cleanup framework, so the hooks get
# torn down by placelesssetup each time.
setHooks()
def beforeTearDown(self):
tearDown()
def test_getSiteManagerHook(self):
from Products.Five.site.localsite import FiveSiteManager
from Products.Five.site.utility import SimpleLocalUtilityRegistry
local_sm = zapi.getSiteManager(None)
self.failIf(local_sm is zapi.getGlobalSiteManager())
self.failUnless(isinstance(local_sm, FiveSiteManager))
local_sm = zapi.getSiteManager(self.folder.site)
self.failIf(local_sm is zapi.getGlobalSiteManager())
self.failUnless(isinstance(local_sm, FiveSiteManager))
sm = zapi.getSiteManager()
self.failUnless(isinstance(sm.utilities, SimpleLocalUtilityRegistry))
def test_getUtilitiesNoUtilitiesFolder(self):
sm = zapi.getSiteManager()
#XXX test whether sm really is a local site...
self.failUnless(sm.queryUtility(IDummyUtility) is None)
self.assertEquals(list(sm.getUtilitiesFor(IDummyUtility)), [])
self.assertEquals(list(sm.getAllUtilitiesRegisteredFor(IDummyUtility)), [])
def test_registerUtilityOnUtilityRegistry(self):
utils = zapi.getSiteManager().utilities
dummy = DummyUtility()
utils.registerUtility(IDummyUtility, dummy, 'dummy')
self.assertEquals(zapi.getUtility(IDummyUtility, name='dummy'), dummy)
self.assertEquals(list(zapi.getUtilitiesFor(IDummyUtility)),
[('dummy', dummy)])
self.assertEquals(list(zapi.getAllUtilitiesRegisteredFor(
IDummyUtility)), [dummy])
def test_registerUtilityOnSiteManager(self):
sm = zapi.getSiteManager()
self.failUnless(IRegisterUtilitySimply.providedBy(sm))
dummy = DummyUtility()
sm.registerUtility(IDummyUtility, dummy, 'dummy')
self.assertEquals(zapi.getUtility(IDummyUtility, name='dummy'), dummy)
self.assertEquals(list(zapi.getUtilitiesFor(IDummyUtility)),
[('dummy', dummy)])
self.assertEquals(list(zapi.getAllUtilitiesRegisteredFor(
IDummyUtility)), [dummy])
def test_registerTwoUtilitiesWithSameNameDifferentInterface(self):
sm = zapi.getSiteManager()
self.failUnless(IRegisterUtilitySimply.providedBy(sm))
dummy = DummyUtility()
superdummy = DummyUtility()
directlyProvides(superdummy, ISuperDummyUtility)
sm.registerUtility(IDummyUtility, dummy, 'dummy')
sm.registerUtility(ISuperDummyUtility, superdummy, 'dummy')
self.assertEquals(zapi.getUtility(IDummyUtility, 'dummy'), dummy)
self.assertEquals(zapi.getUtility(ISuperDummyUtility, 'dummy'),
superdummy)
def test_nestedSitesDontConflictButStillAcquire(self):
# let's register a dummy utility in the dummy site
dummy = DummyUtility()
sm = zapi.getSiteManager()
sm.registerUtility(IDummyUtility, dummy)
# let's also create a subsite and make that our site
manage_addDummySite(self.folder.site, 'subsite')
enableLocalSiteHook(self.folder.site.subsite)
setSite(self.folder.site.subsite)
# we should still be able to lookup the original utility from
# the site one level above
self.assertEqual(zapi.getUtility(IDummyUtility), dummy)
# now we register a dummy utility in the subsite and see that
# its registration doesn't conflict
subdummy = DummyUtility()
sm = zapi.getSiteManager()
sm.registerUtility(IDummyUtility, subdummy)
# when we look it up we get the more local one now because the
# more local one shadows the less local one
self.assertEqual(zapi.getUtility(IDummyUtility), subdummy)
# getAllUtilitiesFor gives us both the more local and the less
# local utility (XXX not sure if this is the right semantics
# for getAllUtilitiesFor)
self.assertEqual(sets.Set(zapi.getAllUtilitiesRegisteredFor(IDummyUtility)),
sets.Set([subdummy, dummy]))
# getUtilitiesFor will only find one, because the more local
# one shadows the less local one
self.assertEqual(list(zapi.getUtilitiesFor(IDummyUtility)),
[('', subdummy)])
def test_registeringTwiceIsConflict(self):
dummy1 = DummyUtility()
dummy2 = DummyUtility()
sm = zapi.getSiteManager()
sm.registerUtility(IDummyUtility, dummy1)
self.assertRaises(ValueError, sm.registerUtility,
IDummyUtility, dummy2)
sm.registerUtility(IDummyUtility, dummy1, 'dummy')
self.assertRaises(ValueError, sm.registerUtility,
IDummyUtility, dummy2, 'dummy')
def test_utilitiesHaveProperAcquisitionContext(self):
dummy = DummyUtility()
sm = zapi.getSiteManager()
sm.registerUtility(IDummyUtility, dummy)
# let's see if we can acquire something all the way from the
# root (Application) object; we need to be careful to choose
# something that's only available from the root object
from Acquisition import aq_acquire
dummy = zapi.getUtility(IDummyUtility)
acquired = aq_acquire(dummy, 'ZopeAttributionButton', None)
self.failUnless(acquired is not None)
name, dummy = zapi.getUtilitiesFor(IDummyUtility).next()
acquired = aq_acquire(dummy, 'ZopeAttributionButton', None)
self.failUnless(acquired is not None)
dummy = zapi.getAllUtilitiesRegisteredFor(IDummyUtility).next()
acquired = aq_acquire(dummy, 'ZopeAttributionButton', None)
self.failUnless(acquired is not None)
def test_getNextUtility(self):
# test local site vs. global site
global_dummy = DummyUtility()
provideUtility(global_dummy, IDummyUtility)
local_dummy = DummyUtility()
sm = zapi.getSiteManager()
sm.registerUtility(IDummyUtility, local_dummy)
self.assertEquals(zapi.getUtility(IDummyUtility), local_dummy)
self.assertEquals(getNextUtility(self.folder.site, IDummyUtility),
global_dummy)
# test local site vs. nested local site
manage_addDummySite(self.folder.site, 'subsite')
enableLocalSiteHook(self.folder.site.subsite)
setSite(self.folder.site.subsite)
sublocal_dummy = DummyUtility()
sm = zapi.getSiteManager()
sm.registerUtility(IDummyUtility, sublocal_dummy)
self.assertEquals(zapi.getUtility(IDummyUtility), sublocal_dummy)
self.assertEquals(getNextUtility(self.folder.site.subsite, IDummyUtility),
local_dummy)
self.assertEquals(getNextUtility(self.folder.site, IDummyUtility),
global_dummy)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(LocalUtilityServiceTest))
return suite
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2005 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.
#
##############################################################################
"""Local utility registration
$Id$
"""
from zope.interface import implements
from zope.component import getGlobalSiteManager
from zope.component.exceptions import ComponentLookupError
from zope.app.component import getNextSiteManager
from Acquisition import aq_base
from OFS.Folder import Folder
from Products.Five.site.interfaces import IFiveUtilityRegistry
class SimpleLocalUtilityRegistry(object):
implements(IFiveUtilityRegistry)
def __init__(self, context):
self.context = context
# make {get|query}NextSiteManager() work without having to
# resort to Zope 2 acquisition
self.__parent__ = self.context.getSiteManager()
@property
def next(self):
try:
return getNextSiteManager(self)
except ComponentLookupError:
return getGlobalSiteManager()
def getUtility(self, interface, name=''):
"""See IFiveUtilityRegistry interface
"""
c = self.queryUtility(interface, name)
if c is not None:
return c
raise ComponentLookupError(interface, name)
def queryUtility(self, interface, name='', default=None):
"""See IFiveUtilityRegistry interface
"""
if name == '':
# Singletons. Only one per interface allowed, so, let's call it
# by the interface.
id = interface.getName()
else:
id = interface.getName() + '-' + name
if getattr(aq_base(self.context), 'utilities', None) is not None:
utility = self.context.utilities._getOb(id, None)
if utility is not None:
return utility
return self.next.queryUtility(interface, name, default)
def getUtilitiesFor(self, interface):
names = []
prefix = interface.getName() + '-'
if getattr(aq_base(self.context), 'utilities', None) is not None:
for name, utility in self.context.utilities.objectItems():
if name == interface.getName():
names.append('')
yield '', utility
elif name.startswith(prefix):
name = name[len(prefix):]
names.append(name)
yield (name, utility)
for name, utility in self.next.getUtilitiesFor(interface):
if name not in names:
yield name, utility
def getAllUtilitiesRegisteredFor(self, interface):
# This also supposedly returns "overridden" utilities, but we don't
# keep them around. It also does not return the name-value pair that
# getUtilitiesFor returns.
if getattr(aq_base(self.context), 'utilities', None) is not None:
for utility in self.context.utilities.objectValues():
if interface.providedBy(utility):
yield utility
for utility in self.next.getAllUtilitiesRegisteredFor(interface):
yield utility
def registerUtility(self, interface, utility, name=''):
# I think you are *really* supposed to:
# 1. Check if there is a "registrations" object for utilities.
# 2. If not create one.
# 3. Get it.
# 4. Create a registration object for the utility.
# 5. Rgister the registration object in the registrations.
# But that is quite complex, and Jim sais he wants to change that
# anyway, and in any case the way you would normally do this in Zope3
# and Five would probably differ anyway, so, here is this new
# Five-only, easy to use method!
if getattr(aq_base(self.context), 'utilities', None) is None:
self.context._setObject('utilities', Folder('utilities'))
utilities = self.context.utilities
if name == '':
# Singletons. Only one per interface allowed, so, let's call it
# by the interface.
id = interface.getName()
else:
id = interface.getName() + '-' + name
if id in utilities.objectIds():
raise ValueError("There is already a utility registered for "
"%s with the name '%s'" % (interface.getName(),
name))
utilities._setObject(id, utility)
# BBB 2005/11/01 -- gone in Five 1.5.
SimpleLocalUtilityService = SimpleLocalUtilityRegistry
import zope.deprecation
zope.deprecation.deprecated(
'SimpleLocalUtilityService', "'SimpleLocalUtilityService' has been renamed to "
"'SimpleLocalUtilityRegistry' and will disappear in Five 1.5."
)
...@@ -13,17 +13,17 @@ ...@@ -13,17 +13,17 @@
############################################################################## ##############################################################################
"""Mimick the Zope 3 skinning system in Five. """Mimick the Zope 3 skinning system in Five.
$Id: standardmacros.py 12884 2005-05-30 13:10:41Z philikon $ $Id: standardmacros.py 19283 2005-10-31 17:43:51Z philikon $
""" """
from zope.interface.common.mapping import IItemMapping import zope.interface
from zope.interface import implements
from zope.component import getView from zope.app import zapi
from Products.Five.browser import BrowserView from Products.Five.browser import BrowserView
# this is a verbatim copy of zope.app.basicskin except that it doesn't # this is a verbatim copy of zope.app.basicskin except that it doesn't
# derive from ``object`` # derive from ``object``
class Macros: class Macros:
implements(IItemMapping) zope.interface.implements(zope.interface.common.mapping.IItemMapping)
macro_pages = () macro_pages = ()
aliases = { aliases = {
...@@ -37,7 +37,7 @@ class Macros: ...@@ -37,7 +37,7 @@ class Macros:
context = self.context context = self.context
request = self.request request = self.request
for name in self.macro_pages: for name in self.macro_pages:
page = getView(context, name, request) page = zapi.getMultiAdapter((context, request), name=name)
try: try:
v = page[key] v = page[key]
except KeyError: except KeyError:
......
...@@ -26,7 +26,7 @@ def test_standard_macros(): ...@@ -26,7 +26,7 @@ def test_standard_macros():
>>> uf._doAddUser('manager', 'r00t', ['Manager'], []) >>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.login('manager') >>> self.login('manager')
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> import Products.Five.skin.tests >>> import Products.Five.skin.tests
...@@ -72,7 +72,7 @@ def test_standard_macros(): ...@@ -72,7 +72,7 @@ def test_standard_macros():
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
......
Five tests Five tests
========== ==========
The tests require ZopeTestCase to be installed. ZopeTestCase can be All you have to do is type::
downloaded from here:
http://zope.org/Members/shh/ZopeTestCase $ bin/zopectl test -s Products.Five
it needs to be installed in your Zope software's lib/python/Testing
directory.
Then, if you have Zope 2.7.3 or better all you have to do is type::
./bin/zopectl test --dir Products/Five
to run the Five tests. to run the Five tests.
...@@ -21,16 +21,16 @@ if __name__ == '__main__': ...@@ -21,16 +21,16 @@ if __name__ == '__main__':
def test_boilerplate(): def test_boilerplate():
""" """
>>> from zope.app.tests.placelesssetup import setUp, tearDown >>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp() >>> setUp()
>>> import Products.Five.tests >>> import Products.Five.tests
>>> from Products.Five import zcml >>> from Products.Five import zcml
>>> zcml.load_config('boilerplate.zcml', Products.Five.tests) >>> zcml.load_config('boilerplate.zcml', Products.Five.tests)
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.testing.fancycontent import manage_addFancyContent >>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent
>>> tearDown() >>> tearDown()
""" """
......
...@@ -11,17 +11,17 @@ ...@@ -11,17 +11,17 @@
once on a class; SimpleContent inherits from Traversable, so once on a class; SimpleContent inherits from Traversable, so
one directive suffices here --> one directive suffices here -->
<five:traversable class="Products.Five.testing.simplecontent.SimpleContent" /> <five:traversable class="Products.Five.tests.testing.simplecontent.SimpleContent" />
<!-- this is a test whether the *directive* can be called more than <!-- this is a test whether the *directive* can be called more than
once without raising a conflicting configuration exception --> once without raising a conflicting configuration exception -->
<five:traversable class="Products.Five.testing.simplecontent.SimpleContent" /> <five:traversable class="Products.Five.tests.testing.simplecontent.SimpleContent" />
<!-- this tests whether five:traversable can be called on a class that <!-- this tests whether five:traversable can be called on a class that
already provides __bobo_traverse__, such as our FancyContent --> already provides __bobo_traverse__, such as our FancyContent -->
<five:traversable class="Products.Five.testing.fancycontent.FancyContent" /> <five:traversable class="Products.Five.tests.testing.fancycontent.FancyContent" />
<!-- Testing the vocabulary directive --> <!-- Testing the vocabulary directive -->
......
Test events ================
=========== Container events
================
Before we can start, we need to set up an event subscriber that allows Zope 3 container events are used to inform subscribers that an object is
us to inspect events that will be thrown during the test: about to be added/removed from a container, and also after it has been
done. This is used for bookkeeping and cleaning up in subobjects.
>>> from zope.app.tests.placelesssetup import setUp, tearDown These events replace the old Zope 2 manage_afterAdd, manage_beforeDelete
>>> setUp() and manage_afterClone methods.
Add a folder that doesn't verify objects on paste. We use it as a
test sandbox:
>>> from Products.Five.testing import manage_addNoVerifyPasteFolder
>>> manage_addNoVerifyPasteFolder(self.folder, 'npvf')
>>> folder = self.folder.npvf
Finally add a manager user login, give it the right permissions and
log in using it:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.setPermissions(standard_permissions + ['Copy or Move'], 'Manager')
>>> self.login('manager')
>>> from zope.app.event.tests.placelesssetup import getEvents, clearEvents
Sending events
--------------
Zope 2 classes need to be modified so that they send Zope 3 style
events. Our stub class here is such a case. We can add it to a
folder, for example, ...
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(folder, 'foo', 'Foo')
and no event will have been triggered:
>>> len(getEvents())
0
Clean up:
>>> folder.manage_delObjects(['foo'])
Now make the class send events:
>>> from Products.Five.eventconfigure import classSendEvents
>>> from Products.Five.testing.simplecontent import SimpleContent
>>> classSendEvents(SimpleContent)
Added event
------------
Let's add an object to a folder:
>>> manage_addSimpleContent(folder, 'foo', 'Foo')
One object event should have been sent with the event's object being All standard Zope containers will only call manage_afterAdd & co on
our foo object: classes specified with the directive::
>>> events = getEvents() <five:deprecatedManageAddDelete class="some.content.class"/>
>>> len(events)
1
>>> foo = folder.foo
>>> events[0].object == foo
True
That object event should have been an object added event: Classes that don't have this directive but still have manage_afterAdd &
co methods will trigger a warning when they are called (and this is
strictly a compatibility call, behavior may not be strictly equivalent
to the original one).
>>> from zope.app.container.interfaces import IObjectAddedEvent Test setup
>>> events = getEvents(IObjectAddedEvent) ==========
>>> len(events)
1
>>> events[0].object == foo
True
>>> events[0].newParent == foo.aq_parent
True
Check that the object's original manage_afterAdd method was also called: A bit of setup for the tests. Because we'll test copy/paste, we need to
work inside a database::
>>> foo.afterAdd_called >>> from zope.app.testing.placelesssetup import setUp, tearDown
True >>> setUp()
Now clean up:
>>> clearEvents()
Moved event (I) -- Renaming
--------------------------
Somehow we need to at least commit a subtransaction to make renaming >>> import ZODB.tests.util
succeed: >>> db = ZODB.tests.util.DB()
>>> connection = db.open()
>>> root = connection.root()
We'll use a few simple classes (defined in python code for picklability)
for our tests.
>>> from Products.Five.tests.test_event import MyApp, MyContent
>>> from Products.Five.tests.test_event import MyFolder, MyBTreeFolder
>>> from Products.Five.tests.test_event import MyOrderedFolder
>>> app = MyApp('')
>>> root['app'] = app
>>> folder = MyFolder('folder')
>>> app._setObject('folder', folder) # doctest: +NORMALIZE_WHITESPACE
old manage_afterAdd folder folder
'folder'
>>> folder = app.folder
>>> btfolder = MyBTreeFolder('btfolder')
>>> app._setObject('btfolder', btfolder) # doctest: +NORMALIZE_WHITESPACE
old manage_afterAdd btfolder btfolder
'btfolder'
To observe what object events are dispatched, we'll have some
subscribers print them. We'll actually do that for a specific interface,
not for (None, IObjectEvent), and register our subscribers before the
framework's ones, so ours will be called first. This has the effect that
printed events will be in their "natural" order::
>>> from zope.app.event.interfaces import IObjectEvent
>>> from zope.app.container.interfaces import IObjectMovedEvent
>>> from OFS.interfaces import IObjectWillBeMovedEvent
>>> from OFS.interfaces import IObjectClonedEvent
>>> from OFS.interfaces import IItem
>>> def printObjectEvent(object, event):
... print event.__class__.__name__, object.getId()
>>> def printObjectEventExceptSome(object, event):
... if (IObjectMovedEvent.providedBy(event) or
... IObjectWillBeMovedEvent.providedBy(event) or
... IObjectClonedEvent.providedBy(event)):
... return
... print event.__class__.__name__, object.getId()
>>> from zope.component import provideHandler
>>> provideHandler(printObjectEvent, (IItem, IObjectMovedEvent))
>>> provideHandler(printObjectEvent, (IItem, IObjectWillBeMovedEvent))
>>> provideHandler(printObjectEvent, (IItem, IObjectClonedEvent))
>>> provideHandler(printObjectEventExceptSome, (None, IObjectEvent))
Finally we need to load the subscribers configuration::
>>> from Products.Five import zcml
>>> import Products.Five
>>> import zope.app.component
>>> zcml.load_config('meta.zcml', zope.app.component)
>>> zcml.load_config('event.zcml', Products.Five)
Old class
=========
If we use an instance of an old class for which we haven't specified
anything, events are sent and the manage_afterAdd & co methods are
called but in a "compatibility" way.
Because the bases classes of Zope have been changed to not recurse
except through the event framework, unexpected behavior may happen
(however a warning will be sent)::
>>> ob = MyContent('dog')
>>> folder._setObject('dog', ob)
ObjectWillBeAddedEvent dog
ObjectAddedEvent dog
old manage_afterAdd dog dog folder
'dog'
And when we delete the object, manage_beforeDelete is also called and
events are sent::
>>> folder.manage_delObjects('dog')
old manage_beforeDelete dog dog folder
ObjectWillBeRemovedEvent dog
ObjectRemovedEvent dog
Old class with deprecatedManageAddDelete
========================================
We specifiy that our class is deprecated (using zcml in real life)::
>>> from Products.Five.eventconfigure import setDeprecatedManageAddDelete
>>> setDeprecatedManageAddDelete(MyContent)
>>> setDeprecatedManageAddDelete(MyFolder)
>>> setDeprecatedManageAddDelete(MyOrderedFolder)
Now some events are sent but the old manage_afterAdd method is also
called correctly::
>>> ob = MyContent('lassie')
>>> folder._setObject('lassie', ob)
ObjectWillBeAddedEvent lassie
ObjectAddedEvent lassie
old manage_afterAdd lassie lassie folder
'lassie'
And when we delete the object, manage_beforeDelete is also called and
events are sent::
>>> folder.manage_delObjects('lassie')
ObjectWillBeRemovedEvent lassie
old manage_beforeDelete lassie lassie folder
ObjectRemovedEvent lassie
The old behavior happens for a move or a copy, with events too.
For a move::
>>> ob = MyContent('blueberry')
>>> folder._setObject('blueberry', ob)
ObjectWillBeAddedEvent blueberry
ObjectAddedEvent blueberry
old manage_afterAdd blueberry blueberry folder
'blueberry'
>>> cp = folder.manage_cutObjects('blueberry')
>>> folder.manage_pasteObjects(cp)
ObjectWillBeMovedEvent blueberry
old manage_beforeDelete blueberry blueberry folder
ObjectMovedEvent blueberry
old manage_afterAdd blueberry blueberry folder
[{'new_id': 'blueberry', 'id': 'blueberry'}]
Old behavior with events for a copy::
>>> cp = folder.manage_copyObjects('blueberry')
>>> folder.manage_pasteObjects(cp)
ObjectCopiedEvent copy_of_blueberry
ObjectWillBeAddedEvent copy_of_blueberry
ObjectAddedEvent copy_of_blueberry
old manage_afterAdd copy_of_blueberry copy_of_blueberry folder
ObjectClonedEvent copy_of_blueberry
old manage_afterClone copy_of_blueberry copy_of_blueberry
[{'new_id': 'copy_of_blueberry', 'id': 'blueberry'}]
Old behavior with events for a renaming::
>>> folder.manage_renameObject('copy_of_blueberry', 'myrtille')
ObjectWillBeMovedEvent copy_of_blueberry
old manage_beforeDelete copy_of_blueberry copy_of_blueberry folder
ObjectMovedEvent myrtille
old manage_afterAdd myrtille myrtille folder
Old behavior with events for a clone::
>>> res = folder.manage_clone(folder.blueberry, 'strawberry')
ObjectCopiedEvent strawberry
ObjectWillBeAddedEvent strawberry
ObjectAddedEvent strawberry
old manage_afterAdd strawberry strawberry folder
ObjectClonedEvent strawberry
old manage_afterClone strawberry strawberry
>>> res.getId()
'strawberry'
Events are also sent when we work with a BTreeFolder::
>>> ob = MyContent('luckyluke')
>>> btfolder._setObject('luckyluke', ob)
ObjectWillBeAddedEvent luckyluke
ObjectAddedEvent luckyluke
old manage_afterAdd luckyluke luckyluke btfolder
'luckyluke'
>>> btfolder.manage_delObjects('luckyluke')
ObjectWillBeRemovedEvent luckyluke
old manage_beforeDelete luckyluke luckyluke btfolder
ObjectRemovedEvent luckyluke
Here is what happens for a tree of objects. Let's create a simple one::
>>> subfolder = MyFolder('subfolder')
>>> folder._setObject('subfolder', subfolder)
ObjectWillBeAddedEvent subfolder
ObjectAddedEvent subfolder
old manage_afterAdd subfolder subfolder folder
'subfolder'
>>> subfolder = folder.subfolder
>>> ob = MyContent('donald')
>>> subfolder._setObject('donald', ob)
ObjectWillBeAddedEvent donald
ObjectAddedEvent donald
old manage_afterAdd donald donald subfolder
'donald'
Renaming a tree of objects. Note that manage_beforeDelete is called
bottom-up::
>>> folder.manage_renameObject('subfolder', 'pluto')
ObjectWillBeMovedEvent subfolder
ObjectWillBeMovedEvent donald
old manage_beforeDelete donald subfolder folder
old manage_beforeDelete subfolder subfolder folder
ObjectMovedEvent pluto
old manage_afterAdd pluto pluto folder
ObjectMovedEvent donald
old manage_afterAdd donald pluto folder
Cloning a tree of objects::
>>> res = folder.manage_clone(folder.pluto, 'mickey')
ObjectCopiedEvent mickey
ObjectWillBeAddedEvent mickey
ObjectWillBeAddedEvent donald
ObjectAddedEvent mickey
old manage_afterAdd mickey mickey folder
ObjectAddedEvent donald
old manage_afterAdd donald mickey folder
ObjectClonedEvent mickey
old manage_afterClone mickey mickey
ObjectClonedEvent donald
old manage_afterClone donald mickey
>>> res.getId()
'mickey'
New class
=========
If we use classes that don't have any manage_afterAdd & co method,
everything happens correctly::
>>> from Products.Five.tests.test_event import MyNewFolder, MyNewContent
>>> app = MyApp('')
>>> root['app'] = app
>>> folder = MyNewFolder('folder')
>>> app._setObject('folder', folder)
ObjectWillBeAddedEvent folder
ObjectAddedEvent folder
'folder'
>>> folder = app.folder
>>> ob = MyNewContent('dogbert')
>>> folder._setObject('dogbert', ob)
ObjectWillBeAddedEvent dogbert
ObjectAddedEvent dogbert
'dogbert'
>>> folder.manage_delObjects('dogbert')
ObjectWillBeRemovedEvent dogbert
ObjectRemovedEvent dogbert
Now move::
>>> ob = MyNewContent('dilbert')
>>> folder._setObject('dilbert', ob)
ObjectWillBeAddedEvent dilbert
ObjectAddedEvent dilbert
'dilbert'
>>> cp = folder.manage_cutObjects('dilbert')
>>> folder.manage_pasteObjects(cp)
ObjectWillBeMovedEvent dilbert
ObjectMovedEvent dilbert
[{'new_id': 'dilbert', 'id': 'dilbert'}]
And copy::
>>> cp = folder.manage_copyObjects('dilbert')
>>> folder.manage_pasteObjects(cp)
ObjectCopiedEvent copy_of_dilbert
ObjectWillBeAddedEvent copy_of_dilbert
ObjectAddedEvent copy_of_dilbert
ObjectClonedEvent copy_of_dilbert
[{'new_id': 'copy_of_dilbert', 'id': 'dilbert'}]
Then rename::
>>> folder.manage_renameObject('copy_of_dilbert', 'wally')
ObjectWillBeMovedEvent copy_of_dilbert
ObjectMovedEvent wally
Or copy using manage_clone::
>>> res = folder.manage_clone(folder.dilbert, 'phb')
ObjectCopiedEvent phb
ObjectWillBeAddedEvent phb
ObjectAddedEvent phb
ObjectClonedEvent phb
>>> res.getId()
'phb'
Also on a BTreeFolder::
>>> ob = MyNewContent('alice')
>>> btfolder._setObject('alice', ob)
ObjectWillBeAddedEvent alice
ObjectAddedEvent alice
'alice'
>>> btfolder.manage_renameObject('alice', 'rabbit')
ObjectWillBeMovedEvent alice
ObjectMovedEvent rabbit
>>> btfolder.manage_delObjects('rabbit')
ObjectWillBeRemovedEvent rabbit
ObjectRemovedEvent rabbit
Now for a tree of objects. Let's create a simple one::
>>> subfolder = MyNewFolder('subfolder')
>>> folder._setObject('subfolder', subfolder)
ObjectWillBeAddedEvent subfolder
ObjectAddedEvent subfolder
'subfolder'
>>> subfolder = folder.subfolder
>>> ob = MyNewContent('mel')
>>> subfolder._setObject('mel', ob)
ObjectWillBeAddedEvent mel
ObjectAddedEvent mel
'mel'
Renaming a tree of objects::
>>> folder.manage_renameObject('subfolder', 'firefly')
ObjectWillBeMovedEvent subfolder
ObjectWillBeMovedEvent mel
ObjectMovedEvent firefly
ObjectMovedEvent mel
Cloning a tree of objects::
>>> res = folder.manage_clone(folder.firefly, 'serenity')
ObjectCopiedEvent serenity
ObjectWillBeAddedEvent serenity
ObjectWillBeAddedEvent mel
ObjectAddedEvent serenity
ObjectAddedEvent mel
ObjectClonedEvent serenity
ObjectClonedEvent mel
>>> res.getId()
'serenity'
OrderedFolder has the same renaming behavior than before::
>>> ofolder = MyOrderedFolder('ofolder')
>>> app._setObject('ofolder', ofolder) # doctest: +NORMALIZE_WHITESPACE
ObjectWillBeAddedEvent ofolder
ObjectAddedEvent ofolder
old manage_afterAdd ofolder ofolder
'ofolder'
>>> ob1 = MyNewContent('ob1')
>>> ofolder._setObject('ob1', ob1)
ObjectWillBeAddedEvent ob1
ObjectAddedEvent ob1
'ob1'
>>> ob2 = MyNewContent('ob2')
>>> ofolder._setObject('ob2', ob2)
ObjectWillBeAddedEvent ob2
ObjectAddedEvent ob2
'ob2'
>>> ofolder.manage_renameObject('ob1', 'ob4')
ObjectWillBeMovedEvent ob1
ObjectMovedEvent ob4
>>> ofolder.objectIds()
['ob4', 'ob2']
Now cleanup::
>>> import transaction >>> import transaction
>>> transaction.commit(1) >>> transaction.abort()
Let's rename the object we created before:
>>> folder.manage_renameObject('foo', 'bar')
We should get two events...
>>> events = getEvents()
>>> len(events)
2
the removed event...
>>> event = events[0]
>>> from zope.app.container.interfaces import IObjectRemovedEvent
>>> IObjectRemovedEvent.providedBy(event)
True
>>> event.object == foo
True
>>> event.oldName, event.newName
('foo', None)
>>> event.oldParent == folder
True
>>> event.newParent is None
True
and the moved event:
>>> event = events[1]
>>> event.object == foo
True
>>> event.oldName, event.newName
('foo', 'bar')
>>> event.oldParent == folder
True
>>> event.newParent == folder
True
Now clean up:
>>> folder.manage_delObjects(['bar'])
>>> clearEvents()
We don't delete the stub object just yet because it's being used in
the next part of the test.
Moved event (II) -- Cut and paste
---------------------------------
Let's move from one folder to another:
>>> manage_addNoVerifyPasteFolder(folder, 'folder1', 'Folder1')
>>> folder1 = folder.folder1
>>> manage_addNoVerifyPasteFolder(folder, 'folder2', 'Folder2')
>>> folder2 = folder.folder2
>>> manage_addSimpleContent(folder1, 'foo', 'Foo')
>>> foo = folder1.foo
We need to trigger a subtransaction before cut/paste can work:
>>> transaction.commit(1)
>>> cb = folder1.manage_cutObjects(['foo'])
>>> info = folder2.manage_pasteObjects(cb)
Apart from the added event we triggerred when we added the stub object
to the folder, we expect two events...
>>> events = getEvents()
>>> len(events)
3
>>> len(getEvents(IObjectAddedEvent))
1
a removed event...
>>> event = events[1]
>>> from zope.app.container.interfaces import IObjectRemovedEvent
>>> IObjectRemovedEvent.providedBy(event)
True
>>> event.oldParent == folder1
True
>>> event.newParent is None
True
and a moved event:
>>> event = events[2]
>>> event.object == foo
True
>>> event.oldName, event.newName
('foo', 'foo')
>>> event.oldParent == folder1
True
>>> event.newParent == folder2
True
Now clean up:
>>> folder.manage_delObjects(['folder1'])
>>> folder.manage_delObjects(['folder2'])
>>> clearEvents()
Copied event
------------
>>> manage_addSimpleContent(folder, 'foo', 'Foo')
>>> manage_addNoVerifyPasteFolder(folder, 'folder1')
>>> folder1 = folder.folder1
We need to trigger subtransaction before copy/paste can work
>>> transaction.commit(1)
>>> cb = folder.manage_copyObjects(['foo'])
>>> info = folder1.manage_pasteObjects(cb)
>>> foo = folder1.foo
Apart from the added event we triggerred when we added the stub object
to the folder, we expect two events...
>>> events = getEvents()
>>> len(events)
3
a copied event...
>>> event = events[1]
>>> from zope.app.event.interfaces import IObjectCopiedEvent
>>> IObjectCopiedEvent.providedBy(event)
True
>>> events[1].object == foo
True
and an added event:
>>> event = events[2]
>>> IObjectAddedEvent.providedBy(event)
True
>>> event.object == foo
True
>>> event.newName
'foo'
>>> event.newParent == folder1
True
Now clean up:
>>> folder.manage_delObjects(['folder1'])
>>> folder.manage_delObjects(['foo'])
>>> clearEvents()
Removed event
-------------
>>> manage_addSimpleContent(folder, 'foo', 'Foo')
>>> foo = folder.foo
>>> foo.beforeDelete_called
False
>>> folder.manage_delObjects(['foo'])
>>> events = getEvents()
>>> len(events)
2
>>> events[1].object.id
'foo'
Check that the object's original manage_beforeDelete method was also called:
>>> foo.beforeDelete_called
True
>>> clearEvents()
Clean up
--------
Finally, we need to put our stub class back the way it was before we
monkeyed with it:
>>> from Products.Five.eventconfigure import cleanUp
>>> cleanUp()
Now adding an object won't trigger an event anymore:
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(folder, 'foo', 'Foo')
>>> len(getEvents())
0
Finally, we need to tear down everything else (services, etc.)
>>> tearDown() >>> tearDown()
...@@ -23,9 +23,6 @@ def test_directives(): ...@@ -23,9 +23,6 @@ def test_directives():
""" """
Test ZCML directives Test ZCML directives
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> setUp()
There isn't much to test here since the actual directive handlers There isn't much to test here since the actual directive handlers
are either tested in other, more specific tests, or they're are either tested in other, more specific tests, or they're
already tested in Zope 3. We'll just do a symbolic test of already tested in Zope 3. We'll just do a symbolic test of
...@@ -62,9 +59,10 @@ def test_directives(): ...@@ -62,9 +59,10 @@ def test_directives():
>>> dest.method() >>> dest.method()
'Overridden' 'Overridden'
Clean up: Clean up adapter registry and others:
>>> tearDown() >>> from zope.testing.cleanup import cleanUp
>>> cleanUp()
""" """
def test_suite(): def test_suite():
......
...@@ -19,9 +19,64 @@ import os, sys ...@@ -19,9 +19,64 @@ import os, sys
if __name__ == '__main__': if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py')) execfile(os.path.join(sys.path[0], 'framework.py'))
# These classes aren't defined in the doctest because otherwise
# they wouldn't be picklable, and we need that to test copy/paste.
from OFS.SimpleItem import SimpleItem
from OFS.Folder import Folder
from OFS.OrderedFolder import OrderedFolder
from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2
class DontComplain(object):
def _verifyObjectPaste(self, object, validate_src=1):
pass
def cb_isMoveable(self):
return True
def cb_isCopyable(self):
return True
class NotifyBase(DontComplain):
def manage_afterAdd(self, item, container):
print 'old manage_afterAdd %s %s %s' % (self.getId(), item.getId(),
container.getId())
def manage_beforeDelete(self, item, container):
print 'old manage_beforeDelete %s %s %s' % (self.getId(), item.getId(),
container.getId())
def manage_afterClone(self, item):
print 'old manage_afterClone %s %s' % (self.getId(), item.getId())
class MyApp(Folder):
def getPhysicalRoot(self):
return self
class MyFolder(NotifyBase, Folder):
pass
class MyOrderedFolder(NotifyBase, OrderedFolder):
pass
class MyBTreeFolder(NotifyBase, BTreeFolder2):
def _verifyObjectPaste(self, object, validate_src=1):
pass
class MyContent(NotifyBase, SimpleItem):
def __init__(self, id):
self._setId(id)
# These don't have manage_beforeDelete & co methods
class MyNewContent(DontComplain, SimpleItem):
def __init__(self, id):
self._setId(id)
class MyNewFolder(DontComplain, Folder):
pass
def test_suite(): def test_suite():
from Testing.ZopeTestCase import ZopeDocFileSuite from zope.testing.doctest import DocFileSuite
return ZopeDocFileSuite('event.txt', package="Products.Five.tests") return DocFileSuite('event.txt', package="Products.Five.tests")
if __name__ == '__main__': if __name__ == '__main__':
framework() framework()
...@@ -23,7 +23,7 @@ def test_directive(): ...@@ -23,7 +23,7 @@ def test_directive():
""" """
Test the i18n directive Test the i18n directive
>>> from zope.app.tests.placelesssetup import setUp, tearDown >>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp() >>> setUp()
First, we need to register the ZCML directive: First, we need to register the ZCML directive:
...@@ -44,9 +44,9 @@ def test_directive(): ...@@ -44,9 +44,9 @@ def test_directive():
Now, take an arbitrary message id from that domain: Now, take an arbitrary message id from that domain:
>>> from zope.i18nmessageid import MessageIDFactory >>> from zope.i18nmessageid import MessageFactory
>>> from zope.i18n import translate >>> from zope.i18n import translate
>>> _ = MessageIDFactory('fivetest') >>> _ = MessageFactory('fivetest')
>>> msg = _(u'explicit-msg', u'This is an explicit message') >>> msg = _(u'explicit-msg', u'This is an explicit message')
As you can see, both the default functionality and translation to As you can see, both the default functionality and translation to
......
##############################################################################
#
# Copyright (c) 2004, 2005 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 the registerClass directive.
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_registerClass():
"""
Testing registerClass
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
>>> import Products
>>> import Products.Five
>>> from Products.Five import zcml
>>> from Products.Five.tests.testing.simplecontent import SimpleContent
>>> from Products.Five.tests.testing.simplecontent import ISimpleContent
>>> from persistent.interfaces import IPersistent
Use the five:registerClass directive::
>>> configure_zcml = '''
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:five="http://namespaces.zope.org/five"
... i18n_domain="foo">
... <permission id="foo.add" title="Add Foo"/>
... <five:registerClass
... class="Products.Five.tests.testing.simplecontent.SimpleContent"
... meta_type="Foo Type"
... permission="foo.add"
... addview="addfoo.html"
... icon="foo_icon.png"
... global="false"
... />
... </configure>'''
>>> zcml.load_config('meta.zcml', Products.Five)
>>> zcml.load_string(configure_zcml)
Make sure that the class attributes are set correctly::
>>> SimpleContent.meta_type
'Foo Type'
>>> SimpleContent.icon
'++resource++foo_icon.png'
And the meta_type is registered correctly::
>>> for info in Products.meta_types:
... if info['name'] == 'Foo Type':
... break
>>> info['product']
'Five'
>>> info['permission']
'Add Foo'
>>> ISimpleContent in info['interfaces']
True
>>> IPersistent in info['interfaces']
True
>>> info['visibility'] is None
True
>>> info['instance'] is SimpleContent
True
>>> info['action']
'+/addfoo.html'
>>> info['container_filter'] is None
True
Now reset everything and see what happens without optional parameters::
>>> tearDown()
>>> setUp()
Use the five:registerClass directive again::
>>> configure_zcml = '''
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:five="http://namespaces.zope.org/five"
... i18n_domain="bar">
... <permission id="bar.add" title="Add Bar"/>
... <five:registerClass
... class="Products.Five.tests.testing.simplecontent.SimpleContent"
... meta_type="Bar Type"
... permission="bar.add"
... />
... </configure>'''
>>> zcml.load_config('meta.zcml', Products.Five)
>>> zcml.load_string(configure_zcml)
Make sure that the class attributes are set correctly::
>>> SimpleContent.meta_type
'Bar Type'
>>> SimpleContent.icon
''
And the meta_type is registered correctly::
>>> for info in Products.meta_types:
... if info['name'] == 'Bar Type':
... break
>>> info['product']
'Five'
>>> info['permission']
'Add Bar'
>>> ISimpleContent in info['interfaces']
True
>>> IPersistent in info['interfaces']
True
>>> info['visibility']
'Global'
>>> info['instance'] is SimpleContent
True
>>> info['action']
''
>>> info['container_filter'] is None
True
Clean up:
>>> tearDown()
>>> SimpleContent.meta_type
'simple item'
>>> SimpleContent.icon
''
>>> [info for info in Products.meta_types if info['name'] == 'Bar Type']
[]
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
...@@ -45,7 +45,7 @@ def test_security_equivalence(): ...@@ -45,7 +45,7 @@ def test_security_equivalence():
Zope 2 can be replaced by ZCML statements without any loss of Zope 2 can be replaced by ZCML statements without any loss of
information. information.
>>> from zope.app.tests.placelesssetup import setUp, tearDown >>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp() >>> setUp()
We start out with two classes, ``Dummy1`` and ``Dummy2``. They We start out with two classes, ``Dummy1`` and ``Dummy2``. They
...@@ -141,7 +141,7 @@ def test_checkPermission(): ...@@ -141,7 +141,7 @@ def test_checkPermission():
""" """
Test checkPermission Test checkPermission
>>> from zope.app.tests.placelesssetup import setUp, tearDown >>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp() >>> setUp()
Zope 3 has a function zope.security.checkPermission which provides Zope 3 has a function zope.security.checkPermission which provides
......
...@@ -54,21 +54,21 @@ def test_size(): ...@@ -54,21 +54,21 @@ def test_size():
Set up: Set up:
>>> from zope.app.tests.placelesssetup import setUp, tearDown >>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp() >>> setUp()
>>> configure_zcml = ''' >>> configure_zcml = '''
... <configure xmlns="http://namespaces.zope.org/zope" ... <configure xmlns="http://namespaces.zope.org/zope"
... xmlns:five="http://namespaces.zope.org/five"> ... xmlns:five="http://namespaces.zope.org/five">
... <five:sizable class="Products.Five.testing.simplecontent.SimpleContent" /> ... <five:sizable class="Products.Five.tests.testing.simplecontent.SimpleContent" />
... <five:sizable class="Products.Five.testing.fancycontent.FancyContent" /> ... <five:sizable class="Products.Five.tests.testing.fancycontent.FancyContent" />
... <adapter ... <adapter
... for="Products.Five.testing.simplecontent.ISimpleContent" ... for="Products.Five.tests.testing.simplecontent.ISimpleContent"
... provides="zope.app.size.interfaces.ISized" ... provides="zope.app.size.interfaces.ISized"
... factory="Products.Five.tests.test_size.SimpleContentSize" ... factory="Products.Five.tests.test_size.SimpleContentSize"
... /> ... />
... <adapter ... <adapter
... for="Products.Five.testing.fancycontent.IFancyContent" ... for="Products.Five.tests.testing.fancycontent.IFancyContent"
... provides="zope.app.size.interfaces.ISized" ... provides="zope.app.size.interfaces.ISized"
... factory="Products.Five.tests.test_size.FancyContentSize" ... factory="Products.Five.tests.test_size.FancyContentSize"
... /> ... />
...@@ -79,8 +79,8 @@ def test_size(): ...@@ -79,8 +79,8 @@ def test_size():
>>> zcml.load_config('meta.zcml', Products.Five) >>> zcml.load_config('meta.zcml', Products.Five)
>>> zcml.load_string(configure_zcml) >>> zcml.load_string(configure_zcml)
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.testing.fancycontent import manage_addFancyContent >>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent
We have registered an ``ISized`` adapter for SimpleContent: We have registered an ``ISized`` adapter for SimpleContent:
......
...@@ -23,9 +23,6 @@ def test_defaultView(): ...@@ -23,9 +23,6 @@ def test_defaultView():
""" """
Testing default view functionality Testing default view functionality
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> setUp()
Take a class Foo and an interface IFoo: Take a class Foo and an interface IFoo:
>>> class Foo: >>> class Foo:
...@@ -37,14 +34,14 @@ def test_defaultView(): ...@@ -37,14 +34,14 @@ def test_defaultView():
Set up a default view for IFoo: Set up a default view for IFoo:
>>> from zope.app import zapi >>> from zope.component import provideAdapter
>>> pres = zapi.getGlobalService('Presentation') >>> from zope.component.interfaces import IDefaultViewName
>>> from zope.publisher.interfaces.browser import IBrowserRequest >>> from zope.publisher.interfaces.browser import IBrowserRequest
and default view names for everything and IFoo objects in particular: and default view names for everything and IFoo objects in particular:
>>> pres.setDefaultViewName(None, IBrowserRequest, u'index.html') >>> provideAdapter(u'index.html', (None, IBrowserRequest), IDefaultViewName)
>>> pres.setDefaultViewName(IFoo, IBrowserRequest, u'foo.html') >>> provideAdapter(u'foo.html', (IFoo, IBrowserRequest), IDefaultViewName)
Now take a BrowserDefault for an instance of Foo:: Now take a BrowserDefault for an instance of Foo::
...@@ -73,10 +70,10 @@ def test_defaultView(): ...@@ -73,10 +70,10 @@ def test_defaultView():
>>> path >>> path
[u'foo.html'] [u'foo.html']
Clean up adapter registry:
Clean up: >>> from zope.testing.cleanup import cleanUp
>>> cleanUp()
>>> tearDown()
""" """
def test_suite(): def test_suite():
......
...@@ -13,11 +13,11 @@ ...@@ -13,11 +13,11 @@
############################################################################## ##############################################################################
"""Test helpers """Test helpers
$Id: __init__.py 14470 2005-07-10 11:57:49Z philikon $ $Id$
""" """
from Products.Five.testing.restricted import RestrictedPythonTestCase from Products.Five.tests.testing.restricted import RestrictedPythonTestCase
from Products.Five.testing.folder import FiveTraversableFolder from Products.Five.tests.testing.folder import FiveTraversableFolder
from Products.Five.testing.folder import manage_addFiveTraversableFolder from Products.Five.tests.testing.folder import manage_addFiveTraversableFolder
from Products.Five.testing.folder import NoVerifyPasteFolder from Products.Five.tests.testing.folder import NoVerifyPasteFolder
from Products.Five.testing.folder import manage_addNoVerifyPasteFolder from Products.Five.tests.testing.folder import manage_addNoVerifyPasteFolder
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Test content objects. """Test content objects.
$Id: fancycontent.py 14470 2005-07-10 11:57:49Z philikon $ $Id$
""" """
import Acquisition import Acquisition
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Test folders """Test folders
$Id: folder.py 14468 2005-07-10 11:39:32Z philikon $ $Id$
""" """
from OFS.Folder import Folder from OFS.Folder import Folder
from OFS.interfaces import IFolder from OFS.interfaces import IFolder
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
Based on Plone's RestrictedPythonTestCase, with kind permission by the Based on Plone's RestrictedPythonTestCase, with kind permission by the
Plone developers. Plone developers.
$Id: restricted.py 14473 2005-07-10 13:02:21Z philikon $ $Id$
""" """
from AccessControl import Unauthorized from AccessControl import Unauthorized
from Testing.ZopeTestCase import ZopeTestCase from Testing.ZopeTestCase import ZopeTestCase
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Simple content class(es) for browser tests """Simple content class(es) for browser tests
$Id: simplecontent.py 17810 2005-09-24 09:12:59Z efge $ $Id$
""" """
from OFS.SimpleItem import SimpleItem from OFS.SimpleItem import SimpleItem
from Globals import InitializeClass from Globals import InitializeClass
...@@ -37,19 +37,10 @@ class SimpleContent(Traversable, SimpleItem): ...@@ -37,19 +37,10 @@ class SimpleContent(Traversable, SimpleItem):
meta_type = 'Five SimpleContent' meta_type = 'Five SimpleContent'
security = ClassSecurityInfo() security = ClassSecurityInfo()
afterAdd_called = False
beforeDelete_called = False
def __init__(self, id, title): def __init__(self, id, title):
self.id = id self.id = id
self.title = title self.title = title
def manage_afterAdd(self, item, container):
self.afterAdd_called = True
def manage_beforeDelete(self, item, container):
self.beforeDelete_called = True
security.declarePublic('mymethod') security.declarePublic('mymethod')
def mymethod(self): def mymethod(self):
return "Hello world" return "Hello world"
......
...@@ -13,28 +13,29 @@ ...@@ -13,28 +13,29 @@
############################################################################## ##############################################################################
"""Machinery for making things traversable through adaptation """Machinery for making things traversable through adaptation
$Id: traversable.py 18841 2005-10-23 09:57:38Z philikon $ $Id: traversable.py 19283 2005-10-31 17:43:51Z philikon $
""" """
from zExceptions import NotFound from zExceptions import NotFound
from zope.exceptions import NotFoundError
from zope.component import getView, ComponentLookupError from zope.component import getMultiAdapter, ComponentLookupError
from zope.interface import implements from zope.interface import implements, Interface
from zope.publisher.interfaces import ILayer
from zope.publisher.interfaces.browser import IBrowserRequest from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.traversing.interfaces import ITraverser, ITraversable from zope.app.traversing.interfaces import ITraverser, ITraversable
from zope.app.traversing.adapters import DefaultTraversable from zope.app.traversing.adapters import DefaultTraversable
from zope.app.traversing.adapters import traversePathElement from zope.app.traversing.adapters import traversePathElement
from zope.app.publication.browser import setDefaultSkin
from zope.app.interface import queryType
from AccessControl import getSecurityManager from AccessControl import getSecurityManager
from Products.Five.security import newInteraction from Products.Five.security import newInteraction
_marker = object _marker = object
class FakeRequest: class FakeRequest(dict):
implements(IBrowserRequest) implements(IBrowserRequest)
def getPresentationSkin(self):
return None
def has_key(self, key): def has_key(self, key):
return False return False
...@@ -69,16 +70,20 @@ class Traversable: ...@@ -69,16 +70,20 @@ class Traversable:
REQUEST = getattr(self, 'REQUEST', None) REQUEST = getattr(self, 'REQUEST', None)
if not IBrowserRequest.providedBy(REQUEST): if not IBrowserRequest.providedBy(REQUEST):
REQUEST = FakeRequest() REQUEST = FakeRequest()
# set the default skin on the request if it doesn't have any
# layers set on it yet
if queryType(REQUEST, ILayer) is None:
setDefaultSkin(REQUEST)
# con Zope 3 into using Zope 2's checkPermission # con Zope 3 into using Zope 2's checkPermission
newInteraction() newInteraction()
try: try:
return ITraverser(self).traverse( return ITraverser(self).traverse(
path=[name], request=REQUEST).__of__(self) path=[name], request=REQUEST).__of__(self)
except (ComponentLookupError, NotFoundError, except (ComponentLookupError, LookupError,
AttributeError, KeyError, NotFound): AttributeError, KeyError, NotFound):
pass pass
try: try:
return getattr(self, name) return getattr(self, name)
except AttributeError: except AttributeError:
...@@ -100,8 +105,9 @@ class FiveTraversable(DefaultTraversable): ...@@ -100,8 +105,9 @@ class FiveTraversable(DefaultTraversable):
REQUEST = getattr(context, 'REQUEST', None) REQUEST = getattr(context, 'REQUEST', None)
if not IBrowserRequest.providedBy(REQUEST): if not IBrowserRequest.providedBy(REQUEST):
REQUEST = FakeRequest() REQUEST = FakeRequest()
# Try to lookup a view first setDefaultSkin(REQUEST)
# Try to lookup a view
try: try:
return getView(context, name, REQUEST) return getMultiAdapter((context, REQUEST), Interface, name)
except ComponentLookupError: except ComponentLookupError:
pass pass
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<browser:page
for="*"
name="edit-markers.html"
template="edit_markers.pt"
class="Products.Five.utilities.browser.marker.EditView"
permission="zope2.ManageProperties"
/>
<browser:page
for="*"
name="manage_interfaces"
template="manage_interfaces.pt"
class="Products.Five.utilities.browser.marker.EditView"
permission="zope2.ManageProperties"
/>
</configure>
<html metal:use-macro="context/@@standard_macros/page">
<body>
<metal:slot metal:fill-slot="body">
<metal:macro metal:define-macro="heading">
<h1 i18n:translate="heading_edit_marker">Assign Marker Interfaces</h1>
</metal:macro>
<metal:macro metal:define-macro="main">
<p class="form-help formHelp" i18n:translate="">Change the behavior of this
object by adding or removing marker interfaces. You can choose one or more
interfaces to be added to the list of provided interfaces for this
object.</p>
<p class="form-help formHelp" i18n:translate="">A marker interface is used to
identify an instance of a piece of content. When in conjunction with Five,
this allows you to enable and disable views based on marker interfaces for
example.</p>
<form action="." method="post"
tal:attributes="action request/ACTUAL_URL">
<fieldset>
<legend i18n:translate="legend_provided">Provided Interfaces</legend>
<tal:loop tal:repeat="interface view/getInterfaceNames">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<label class="form-mono"
tal:content="interface/name" i18n:translate="">INTERFACE</label><br />
</tal:loop>
<tal:loop tal:repeat="interface view/getDirectlyProvidedNames">
<input type="checkbox" id="INTERFACE" name="remove:list"
tal:attributes="id interface/name; value interface/name" />
<label class="form-mono" for="INTERFACE" tal:attributes="for interface/name"
tal:content="interface/name" i18n:translate="">INTERFACE</label><br />
</tal:loop>
<tal:case tal:condition="view/getDirectlyProvidedNames">
<div class="formControls FormButtons">
<input class="form-element" type="submit" name="SAVE" value="Remove"
i18n:attributes="value" />
</div>
</tal:case>
</fieldset>
<fieldset>
<legend i18n:translate="legend_available_marker">Available Marker
Interfaces</legend>
<tal:loop tal:repeat="interface view/getAvailableInterfaceNames">
<input type="checkbox" id="INTERFACE" name="add:list"
tal:attributes="id interface/name; value interface/name" />
<label class="form-mono" for="INTERFACE" tal:attributes="for interface/name"
tal:content="interface/name" i18n:translate="">INTERFACE</label><br />
</tal:loop>
<div class="formControls FormButtons">
<input class="form-element" type="submit" name="SAVE" value="Add"
i18n:attributes="value" />
</div>
</fieldset>
</form>
</metal:macro>
</metal:slot>
</body>
</html>
<h1 tal:replace="structure context/manage_page_header">PAGE HEADER</h1>
<h2 tal:replace="structure context/manage_tabs">TABS</h2>
<style type="text/css">
fieldset {width:auto; float:left}
</style>
<metal:macro metal:use-macro="context/@@edit-markers.html/main" />
<h1 tal:replace="structure context/manage_page_footer">PAGE FOOTER</h1>
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Marker interfaces adapter views.
$Id$
"""
from Products.Five.utilities.interfaces import IMarkerInterfaces
class EditView:
"""Marker interface edit view.
"""
def __init__(self, context, request):
self.context = context
self.request = request
self.adapted = IMarkerInterfaces(context)
self.context_url = self.context.absolute_url()
def __call__(self, SAVE=None, add=(), remove=()):
if SAVE:
self.update(add, remove)
self.request.response.redirect(self.request.ACTUAL_URL)
return ''
return self.index()
def _getLinkToInterfaceDetailsView(self, interfaceName):
return (self.context_url +
'/views-details.html?iface=%s&type=zope.publisher.interfaces.browser.IBrowserRequest' % interfaceName)
def _getNameLinkDicts(self, interfaceNames):
return [dict(name=name,
link=self._getLinkToInterfaceDetailsView(name))
for name in interfaceNames]
def getAvailableInterfaceNames(self):
return self._getNameLinkDicts(
self.adapted.getAvailableInterfaceNames())
def getDirectlyProvidedNames(self):
return self._getNameLinkDicts(self.adapted.getDirectlyProvidedNames())
def getInterfaceNames(self):
return self._getNameLinkDicts(self.adapted.getInterfaceNames())
def update(self, add, remove):
# this could return errors
add = self.adapted.dottedToInterfaces(add)
remove = self.adapted.dottedToInterfaces(remove)
self.adapted.update(add=add, remove=remove)
##############################################################################
#
# ZopeTestCase
#
# COPY THIS FILE TO YOUR 'tests' DIRECTORY.
#
# This version of framework.py will use the SOFTWARE_HOME
# environment variable to locate Zope and the Testing package.
#
# If the tests are run in an INSTANCE_HOME installation of Zope,
# Products.__path__ and sys.path with be adjusted to include the
# instance's Products and lib/python directories respectively.
#
# If you explicitly set INSTANCE_HOME prior to running the tests,
# auto-detection is disabled and the specified path will be used
# instead.
#
# If the 'tests' directory contains a custom_zodb.py file, INSTANCE_HOME
# will be adjusted to use it.
#
# If you set the ZEO_INSTANCE_HOME environment variable a ZEO setup
# is assumed, and you can attach to a running ZEO server (via the
# instance's custom_zodb.py).
#
##############################################################################
#
# The following code should be at the top of every test module:
#
# import os, sys
# if __name__ == '__main__':
# execfile(os.path.join(sys.path[0], 'framework.py'))
#
# ...and the following at the bottom:
#
# if __name__ == '__main__':
# framework()
#
##############################################################################
__version__ = '0.2.3'
# Save start state
#
__SOFTWARE_HOME = os.environ.get('SOFTWARE_HOME', '')
__INSTANCE_HOME = os.environ.get('INSTANCE_HOME', '')
if __SOFTWARE_HOME.endswith(os.sep):
__SOFTWARE_HOME = os.path.dirname(__SOFTWARE_HOME)
if __INSTANCE_HOME.endswith(os.sep):
__INSTANCE_HOME = os.path.dirname(__INSTANCE_HOME)
# Find and import the Testing package
#
if not sys.modules.has_key('Testing'):
p0 = sys.path[0]
if p0 and __name__ == '__main__':
os.chdir(p0)
p0 = ''
s = __SOFTWARE_HOME
p = d = s and s or os.getcwd()
while d:
if os.path.isdir(os.path.join(p, 'Testing')):
zope_home = os.path.dirname(os.path.dirname(p))
sys.path[:1] = [p0, p, zope_home]
break
p, d = s and ('','') or os.path.split(p)
else:
print 'Unable to locate Testing package.',
print 'You might need to set SOFTWARE_HOME.'
sys.exit(1)
import Testing, unittest
execfile(os.path.join(os.path.dirname(Testing.__file__), 'common.py'))
# Include ZopeTestCase support
#
if 1: # Create a new scope
p = os.path.join(os.path.dirname(Testing.__file__), 'ZopeTestCase')
if not os.path.isdir(p):
print 'Unable to locate ZopeTestCase package.',
print 'You might need to install ZopeTestCase.'
sys.exit(1)
ztc_common = 'ztc_common.py'
ztc_common_global = os.path.join(p, ztc_common)
f = 0
if os.path.exists(ztc_common_global):
execfile(ztc_common_global)
f = 1
if os.path.exists(ztc_common):
execfile(ztc_common)
f = 1
if not f:
print 'Unable to locate %s.' % ztc_common
sys.exit(1)
# Debug
#
print 'SOFTWARE_HOME: %s' % os.environ.get('SOFTWARE_HOME', 'Not set')
print 'INSTANCE_HOME: %s' % os.environ.get('INSTANCE_HOME', 'Not set')
sys.stdout.flush()
##############################################################################
#
# Copyright (c) 2004, 2005 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 marker interface views.
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_editview():
"""
Set everything up:
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
>>> import Products.Five
>>> import Products.Five.utilities
>>> from Products.Five import zcml
>>> zcml.load_config('meta.zcml', Products.Five)
>>> zcml.load_config('permissions.zcml', Products.Five)
>>> zcml.load_config('configure.zcml', Products.Five.utilities)
>>> from Products.Five.utilities.browser.marker import EditView
>>> from Products.Five.tests.testing.simplecontent import SimpleContent
>>> obj = SimpleContent('foo', 'Foo').__of__(self.folder)
Create an EditView:
>>> view = EditView(obj, {})
>>> view.context.aq_inner is obj
True
>>> view.request
{}
>>> view.getAvailableInterfaceNames()
[]
>>> view.getDirectlyProvidedNames()
[]
>>> view.getInterfaceNames()
[...ISimpleContent...]
Try to add a marker interface that doesn't exist:
>>> view.update(('__builtin__.IFooMarker',), ())
Traceback (most recent call last):
...
ComponentLookupError...
Now create the marker interface:
>>> from Products.Five.tests.testing.simplecontent import ISimpleContent
>>> class IFooMarker(ISimpleContent): pass
>>> from zope.app.component.interface import provideInterface
>>> provideInterface('', IFooMarker)
>>> view.getAvailableInterfaceNames()
[...IFooMarker...]
>>> view.getDirectlyProvidedNames()
[]
And try again to add it to the object:
>>> view.update(('__builtin__.IFooMarker',), ())
>>> view.getAvailableInterfaceNames()
[]
>>> view.getDirectlyProvidedNames()
[...IFooMarker...]
And remove it again:
>>> view.update((), ('__builtin__.IFooMarker',))
>>> view.getAvailableInterfaceNames()
[...IFooMarker...]
>>> view.getDirectlyProvidedNames()
[]
Finally tear down:
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
<configure xmlns="http://namespaces.zope.org/zope">
<include package=".browser"/>
<adapter
for="*"
provides=".interfaces.IMarkerInterfaces"
factory=".marker.MarkerInterfacesAdapter"
permission="zope2.ManageProperties"
/>
</configure>
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Utility Interface Definitions.
$Id$
"""
from zope.interface import Interface
class IReadInterface(Interface):
def getDirectlyProvided():
"""List the interfaces directly implemented by the object.
"""
def getDirectlyProvidedNames():
"""List the names of interfaces directly implemented by the object.
"""
def getAvailableInterfaces():
"""List the marker interfaces available for the object.
"""
def getAvailableInterfaceNames():
"""List the names of marker interfaces available for the object.
"""
def getInterfaces():
"""List interfaces provided by the class of the object.
"""
def getInterfaceNames():
"""List the names of interfaces provided by the class of the object.
"""
def getProvided():
"""List interfaces provided by the object.
"""
def getDirectlyProvidedNames():
"""List the names of interfaces provided by the object.
"""
class IWriteInterface(Interface):
def update(add=(), remove=()):
"""Update directly provided interfaces of the object.
"""
def mark(interface):
"""Add interface to interfaces the object directly provides.
"""
def erase(interface):
"""Remove interfaces from interfaces the object directly provides.
"""
class IMarkerInterfaces(IReadInterface, IWriteInterface):
"""Provides methods for inspecting and assigning marker interfaces.
"""
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Marker interfaces adapter.
Allows for arbitrary application of marker interfaces to objects.
$Id$
"""
from sets import Set
from zope.interface import implements
from zope.interface import implementedBy
from zope.interface import directlyProvidedBy
from zope.interface import directlyProvides
from zope.interface import providedBy
from zope.interface.interfaces import IInterface
from zope.app.component.interface import getInterface, interfaceToName
from zope.app.component.interface import searchInterface
from interfaces import IMarkerInterfaces
def interfaceStringCheck(f):
def wrapper(ob, interface):
if isinstance(interface, str):
interface = getInterface(ob, interface)
return f(ob, interface)
return wrapper
def mark(ob, interface):
directlyProvides(ob, directlyProvidedBy(ob), interface)
def erase(ob, interface):
directlyProvides(ob, directlyProvidedBy(ob)-interface)
mark = interfaceStringCheck(mark)
erase = interfaceStringCheck(erase)
class MarkerInterfacesAdapter(object):
implements(IMarkerInterfaces)
mark = staticmethod(mark)
erase = staticmethod(erase)
def __init__(self, context):
self.context = context
def dottedToInterfaces(self, seq):
return [getInterface(self.context, dotted) for dotted in seq]
def getDirectlyProvided(self):
return directlyProvidedBy(self.context)
def getDirectlyProvidedNames(self):
return self._getInterfaceNames(self.getDirectlyProvided())
def getAvailableInterfaces(self):
results = []
todo = list(providedBy(self.context))
done = []
while todo:
interface = todo.pop()
done.append(interface)
for base in interface.__bases__:
if base not in todo and base not in done:
todo.append(base)
markers = self._getDirectMarkersOf(interface)
for interface in markers:
if (interface not in results
and not interface.providedBy(self.context)):
results.append(interface)
todo += markers
return tuple(results)
def getAvailableInterfaceNames(self):
names = self._getInterfaceNames(self.getAvailableInterfaces())
names.sort()
return names
def getInterfaces(self):
return tuple(implementedBy(self.context.__class__))
def getInterfaceNames(self):
return self._getInterfaceNames(self.getInterfaces())
def getProvided(self):
return providedBy(self.context)
def getProvidedNames(self):
return self._getInterfaceNames(self.getProvided())
def update(self, add=(), remove=()):
"""Currently update adds and then removes, rendering duplicate null.
"""
marker_ifaces = self.getAvailableInterfaces()
if len(add):
[mark(self.context, interface)
for interface in Set(marker_ifaces) & Set(add)]
direct_ifaces = self.getDirectlyProvided()
if len(remove):
[erase(self.context, interface)
for interface in Set(direct_ifaces) & Set(remove)]
def _getInterfaceNames(self, interfaces):
return [interfaceToName(self, iface) for iface in interfaces]
def _getDirectMarkersOf(self, base):
"""Get empty interfaces directly inheriting from the given one.
"""
results = []
interfaces = searchInterface(None, base=base)
for interface in interfaces:
# There are things registered with the interface service
# that are not interfaces. Yay!
if not IInterface.providedBy(interface):
continue
if base in interface.__bases__ and not interface.names():
results.append(interface)
results.sort()
return tuple(results)
...@@ -17,10 +17,10 @@ $Id: viewable.py 14595 2005-07-12 21:26:12Z philikon $ ...@@ -17,10 +17,10 @@ $Id: viewable.py 14595 2005-07-12 21:26:12Z philikon $
""" """
import inspect import inspect
from zExceptions import NotFound from zExceptions import NotFound
from zope.exceptions import NotFoundError from zope.component import ComponentLookupError
from zope.component import getView, getDefaultViewName, ComponentLookupError
from zope.interface import implements from zope.interface import implements
from zope.publisher.interfaces.browser import IBrowserRequest from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.zapi import getDefaultViewName
from Products.Five.traversable import FakeRequest from Products.Five.traversable import FakeRequest
from Products.Five.interfaces import IBrowserDefault from Products.Five.interfaces import IBrowserDefault
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment