Prepare to stitch in Five via svn:externals.

parent 7123d3e4
============
Five Changes
============
Five 1.3.1 (2006-01-08)
=======================
Bugfixes
--------
* Fix an adapter look-up bug in the local site implementation that was
due to an oversight during the port to Zope 3.2.
Five 1.3 (2006-01-07)
=====================
This version is also included in Zope 2.9.0.
Bugfixes
--------
* Fix functional test for local sites and re-enable it for standard
test runs.
* If one class was set to have a localsite hook twice, removing the hook
would be attempted twice during the cleanup of unit tests, and the
tests would fail.
* Fix cleanup of five:traversable.
Five 1.3c (2005-12-06)
======================
This version is also included in Zope 2.9b1.
Restructuring
-------------
* (b6) No longer use the ``defaultLayer`` directive, it's been deprecated.
* (b4) Cleaned up security test.
* (b4) Made Five send a ContainerModifiedEvent when appropriate.
Bugfixes
--------
* (b6) Fixed the form i18n messages to work with Zope 3.2 beta 1.
* (b3) Made the creation of custom skins work again. It was broken in
the port to Zope 3.2.
* (b2) Fixed bug that broke WebDAV access for five:defaultViewable
objects. The __browser_default__ now modifies only GET and POST
requests.
* (b2) Fixed some event recursion compatibility modes.
Five 1.3b (2005-11-02)
======================
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.2 (2006-01-07)
=====================
Bugfixes
--------
* Fixed bug that broke WebDAV access for five:defaultViewable objects. The
__browser_default__ now modifies only GET and POST requests.
* Fixed some event recursion compatibility modes.
* Fixed loops in zcml loading due to events in some cases.
* Made Five send a ContainerModifiedEvent when appropriate.
* Fix cleanup of five:traversable.
* If one class was set to have a localsite hook twice, removing the
hook would be attempted twice during the cleanup of unit tests, and
the tests would fail.
Restructuring
-------------
* Cleaned up security test.
* Added monkey so that ++skin++ works with Zope <= 2.8.4.
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)
=====================
Features
--------
* When Zope was not in debug mode, an error in a ZCML file would cause Five to
stop loading ZCML completely, making all subsequent products "dead". The
effect would typically be that objects appeared to have no views at all.
Now a ZCML error will only stop the ZCML loading for that product, but the
rest of the products will load as usual. A traceback will still be printed
in the event log.
In debug mode the behaviour has not changed; a ZCML error will stop Zope
startup completely, and print a traceback if running in foreground mode.
Restructuring
-------------
* The deprecated FivePageTemplateFile was removed, and the erroneous use of
this by EditView was changed.
Bugfixes
--------
* Repaired 'forms.txt' test which expected an error page when passing
'handle_errors' as False; it now expects an Unauthorized traceback.
Note that this test fails on Zope 2.8.1, which incorrectly ignored
'handle_errors'.
* FiveTraversable should only do a view lookup and not call the traverse
method of its superclass.
* Fixed manage_beforeDelete triggering for classes using five:sendEvents.
* The redefinePermission directive was falsely registered under the
``zope`` namespace, not the ``meta`` namespace as it is in Zope 3.
* Some parts of add.pt and edit.pt were not translated correctly or not
translated at all. The fix depends on TAL changes in Zope 2.8.1 and changes
in Zope X3-3.0.1 (shipped with 2.8.1). Form i18n is still broken with older
Zope versions.
* 'zope' domain translations are now set up by default. Form i18n needs them.
* Added backwards compatibility for some moved classes (AddForm, EditForm,
ContentAdding)
* The ZPT variable 'container' makes little sense in Zope3/Five, but is now
always set to be the same as 'here' which is normal Zope2 behaviour.
It is in Five 1.0.x set to be the same as 'view' which breaks some templates.
* In some hard to replicate cases, using the "modules" variable in ZPT cause
an AuthenticationError. Using the secure module importer fixes this.
* If you used some parts of Zope 3 (for example the mail delivery) Five 1.1
transaction backport would conflict with Zope 3s transaction module.
This is now solved.
Five 1.1b (2005-07-13)
======================
Features
--------
* Zope 3-style i18n support has been provided. Apart from being able
to register translations through ZCML, Five now lets Zope 2 ZPTs
automatically use Zope 3 translation domains. Fallback to an
old-style translation service (e.g. Localizer or PTS) is supported.
This also includes the detection of preferred languages. See
``doc/i18n.txt`` for more information.
* Added support for Zope 3 -> Zope 2 interface bridging. This
functionality will be part of Zope 2.9, with Five you can already
use it in Zope 2.7/2.8. Since Zope 2 interfaces are rarely used and
their Zope 3 equivalents are more meaningful (for the Component
Architecture), the preferred way of dealing with interface migration
is to write Zope 3 interfaces and bridge them to Zope 2 ones as
needed. To bridge, use the ``Interface.bridge.fromZ3Interface()``
function.
* Support for the standard <factory />, <modulealias /> and <hook />
ZCML directives was added.
* The default browser view name for all objects is now 'index.html',
just as it is in Zope 3. This means that a view by that name will
be looked up if no specific view name is given in the URL.
Restructuring
-------------
* Restructured the Five source code to be easier to navigate in.
Three subpackages were created, Five.browser, Five.form and
Five.skin.
* The former test product, ``FiveTest``, was converted into separate
modules that provide the mock objects for the corresponding tests
and are located right next to them. Common test helpers have been
moved to the Five.testing package. Overall, the testing framework
was much simplified and the individual tests clean up after
themselves much like they do in Zope 3.
* Relocated Zope core interfaces. Future Zope versions will ship with their
own z3 interfaces. Five now patches the older Zope versions to make sure
you can always find the interfaces in 'AccessControl.interfaces',
'Acquisition.interfaces', 'App.interfaces', 'OFS.interfaces' and
'webdav.interfaces'. Please don't use the aliases in 'Five.interfaces' or
'Five.bbb.*interfaces' - they are only provided for backwards
compatibility.
* Zope 2.8 HTTPRequest is no longer patched. It has the required methods.
Bugfixes
--------
* The ZPT variable 'container' did not always contain the parent object
of the context.
* The deprecated get_transaction method is no longer used in Zope 2.8.
Five 1.0.2 (2005-07-12)
=======================
This version is also included in Zope 2.8.1
* Fixed some issues with bridged interfaces: Bases and Methods were not
bridged correctly. extends() was never True.
* zope.security.checkPermission now behaves exactly like
Five.security.checkPermission (in fact, the former now calls the
latter through the indirection of Zope 3 security policies).
* Fixed a bug with resource directories. Resources within those were
not rendering their absolute URL correctly.
Five 1.0.1 (2005-05-31)
=======================
This version is also included in Zope 2.8.0
* Changed license headers to the ones used in the Zope.org repository.
This makes merging between the main development line of Five (hosted
on codespeak.net) and the version integrated into Zope 2.8 much
easier. The actual copyright ownership isn't affected because Five
had been contributed to the Zope project anyway (which was blessed
by all Five contributors).
* Made automatically generated add and edit forms unicode-aware.
ZPublisher does not automatically decode incoming form values to
unicode, so AddView and EditView emulate this behaviour themselves
now. They also take care of setting the right charset on the
outgoing form so that ZPublisher will encode it accordingly when
sending the response to the client. (In Zope 3, all charset
negotation between the client and the server takes place in the
publisher.)
* Added ``IHTTPCharset`` adapter for ``IHTTPRequest`` so that
application can find out the preferred character set of the HTTP
client (Zope 2 applications needs to take care of their own charset
header). The adapter is used for the automatically-generated forms
when determining encodings for unicode field content.
* Modified edit.pt to make sure editforms have only one body tag.
* Fixed the ``INameChooser`` adapter for ObjectManagers (e.g. Zope 2
folders) and added unit tests.
* Fixed small bug in BrowserDefault which caused an error if the class is
defaultViewable but the object's interfaces have no defaultView.
Five 1.0 (2005-04-27)
=====================
Features
--------
* Zope 3 style ``ISized`` adapters for objects are now exposed to the
ZMI and other Zope 2 frameworks via the known ``get_size`` method,
provided this is turned for the class in question via the
five:sizable ZCML directive.
* There is now a standard standard_macros. Five page templates can use
context/@@standard_macros/view to get the default site layout, and
people can register their own standard_macros in a skin.
* The addform and editform directive now supports the widget ZCML
subdirective, which previously was ignored.
* Five now supports the vocabulary ZCML directive.
Bugfixes
--------
* Add and edit forms are now protected properly.
* The checkbox widget did not work correctly in its off state, this
has been fixed.
Five 0.3 (2005-03-11)
=====================
* Five now uses the Zope 2 page template engine, not the Zope 3
engine. This allows better integration with Zope 2-based page
templates, such as macros.
It uses TrustedExecutables technology (thanks to Dieter Maurer) to
turn off Zope 2 security in page templates, so Five's security
behavior is very similar to what it was before.
* Five now supports the browser:menu, menuItem and menuItems
directives.
* A new Five-specific directive has been added:
five:pagesFromDirectory. This adds one page for each .pt file in a
directory to the specified interface. This is useful for Five
integration with CMF and other systems that have Page Templates
macros that need to be shared between Zope2 and Five.
* Five.security.checkPermission has been changed from a (unused)
method for checking the existence of permissions. Use
zope.app.security.permission.checkPermission if you need that
functionality.
Instead Five.security.checkPermission is now a Five version of
zope.security.checkPermission, which checks if the current user has
a permission on an object.
* Support for browser:editform. You can now use schemas for editing.
* Support for browser:addform; add forms using '+'. You can now browse
to 'container/+/addsomething.html' to get to a schema-driven add
form.
* Fixed a traversal bug which caused Zope to give the wrong error when
a page could not be found (missing docstring instead of not
found). Zope 2.7.4 (or higher) is required for this fix.
Five 0.2b (2004-09-24)
======================
* Added utility module, 'bridge', allowing reuse of Zope 2 interfaces
(by introspecting them to create equivalent Zope 3 interfaces).
* five:viewable was renamed to five:traversable, five:viewable still
works but is deprecated; a deprecation warning is emitted when it is
used.
* like in Zope3, an ITraverser adapter is looked up to determine what
happens when traversing into a Five traversable object.
* added five:defaultViewable to make instances of a class directly
viewable using browser:defaultView. This is hookable by the use of a
IBrowserDefault adapter
* deprecated use of Products.Five.api as public API for other products
to use, instead import directly from Products.Five. Retired
Traversable and Viewable from the public API; use ZCML directives
(five:traversable, five:defaultView) instead of mixins to make
instances of classes work with Five.
* classes that Five monkeypatches now have a __five_method__
attribute, making it easier for Five not to stomp on existing methods.
* registered absolute_url view and IAbsoluteURL adapter for *
* zope.app.traversing is registered by default, to make special
namespaces available (eg: @@, ++resource++)
* we now have resources (FileResource, ImageResource,
PageTemplateResource) and directory resources.
* Zope 3 'StandardMacros' now works with Five as well.
* browser:page now correctly handles the allow_attributes and protects
the named attributes on the view with the same permission used for
the view.
* zopeconf.py will try to find etc/zope.conf on INSTANCE_HOME. This
requires Zope 2.7.2, as earlier Zope versions have a bug in this
area which causes them to look in lib/python/Testing.
* Exposed the Zope 3 event system to Five. A class can be made to send
out event notifications using the five:sendEvents directive. Events can
be subscribed to using the subscriber directive.
* Change in findProducts so that non-filesystem products are skipped.
Five 0.1 (2004-07-30)
=====================
Initial public release (mainly Martijn's work)
Five is distributed under the provisions of the Zope Public License
(ZPL) v2.1. See doc/ZopePublicLicense.txt for the license text.
Copyright (C) 2005 Five Contributors. See CREDITS.txt for a list of
Five contributors.
Five contains source code derived from:
- Zope 3, copyright (C) 2001-2005 by Zope Corporation.
- metaclass.py is derived from PEAK, copyright (C) 1996-2004 by
Phillip J. Eby and Tyler C. Sarna. PEAK may be used under the same
terms as Zope.
- TrustedExecutables. Dieter Mauer kindly allow licensing this under the
ZPL 2.1.
Five contributors
-----------------
- Martijn Faassen (faassen@infrae.com)
- Sidnei da Silva (sidnei@awkly.org)
- Philipp von Weitershausen (philikon@philikon.de)
- Lennart Regebro (regebro@nuxeo.com)
- Tres Seaver (tseaver@palladion.com)
- Jan-Wijbrand Kolman (jw@infrae.com)
- Stefan Holek (ssh@epy.co.at)
- Florent Guillaume (fg@nuxeo.com)
- Godefroid Chapelle (gotcha@bubblenet.be)
- Andy Adiwidjaja (mail@adiwidjaja.com)
- Stuart Bishop (stuart@stuartbishop.net)
- Simon Eisenmann (simon@struktur.de)
- Dieter Maurer (dieter@handshake.de)
- Yvo Schubbe (y.2005-@wcm-solutions.de)
- Malcolm Cleaton (malcolm@jamkit.com)
- Tarek Ziad (tziade@nuxeo.com)
- Whit Morriss (whit@longnow.org)
Thank you
---------
Infrae for the initial development and continuing support.
Martijn Faassen would like to thank ETH Zurich for their support and
encouragement during the initial development of Five.
Nuxeo for significant contributions to making Five usable in the real
world.
Dieter Maurer for use of code from TrustedExecutables within Five
under the ZPL.
The Five developers would like to thank the Zope 3 developers, in
particular Jim Fulton, for the mountain to stand on.
How to install Five
===================
Requirements for Five 1.3
-------------------------
* Zope 2.9+ with Python 2.4.1+
Note that Five 1.3 is already part of Zope 2.9. You can still install
a newer Five version in your instance, if you like. It will override
the Five product inside the Zope tree.
Compatability matrix
--------------------
The following table shows which Five version can and should be used
with which Zope 2 and Zope 3 versions.
============ ======================= =========== ========
. Zope 2.7 Zope 2.8 Zope 2.9
------------ ----------------------- ----------- --------
. Zope X3 3.0 (not incl.) Zope X3 3.0 Zope 3.2
============ ======================= =========== ========
Five 1.0 X included
Five 1.1[#]_ X X
Five 1.2 X
Five 1.3 included
============ ======================= =========== ========
.. [#] This branch is no longer actively maintained.
Running the tests
-----------------
For information on how to install the automatic Five tests, please see
``tests/README.txt``.
Introduction
------------
"It was the dawn of the third age of Zope. The Five project was a dream
given form. Its goal: to use Zope 3 technologies in Zope 2.7 by
creating a Zope 2 product where Zope 3 and Zope 2 could work out their
differences peacefully." -- Babylon 5, creatively quoted
"The Law of Fives states simply that: ALL THINGS HAPPEN IN FIVES, OR
ARE DIVISIBLE BY OR ARE MULTIPLES OF FIVE, OR ARE SOMEHOW DIRECTLY OR
INDIRECTLY RELATED TO FIVE.
THE LAW OF FIVES IS NEVER WRONG." -- Principia Discordia
What is Five?
-------------
The goal of five is to allow Zope 2 developers to use Zope 3
technology right now, inside of Zope 2. Additionally, this allows a
gradual evolution of Zope 2 code to Zope 3.
Five already makes the following Zope 3 technologies available in Zope
2:
* Zope 3 interfaces
* ZCML (Zope Configuration Markup Language)
* Adapters
* Zope 3 views, even for standard Zope objects
* layers & skins
* schema/forms machinery, including edit and add forms.
* Zope 2 security declarations in ZCML instead of in Python code.
Together with another product, CMFonFive, Five can integrate into CMF.
For more information, see ``doc/features.txt``.
How to install Five
-------------------
See ``INSTALL.txt``.
How to use Five
---------------
Please see ``doc/manual.txt``.
##############################################################################
#
# 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.
#
##############################################################################
"""Initialize the Five product
$Id: __init__.py 20254 2005-11-25 18:45:08Z efge $
"""
import Acquisition
from Globals import INSTANCE_HOME
import zcml
# public API provided by Five
# usage: from Products.Five import <something>
from browser import BrowserView
from skin.standardmacros import StandardMacros
def initialize(context):
zcml.load_site()
##############################################################################
#
# 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.
#
##############################################################################
""" Z2 -> Z3 bridge utilities.
$Id: bridge.py 14504 2005-07-11 15:49:59Z yuppie $
"""
from Interface._InterfaceClass import Interface as Z2_InterfaceClass
from Interface import Interface as Z2_Interface
from Interface import Attribute as Z2_Attribute
from Interface.Method import Method as Z2_Method
from zope.interface.interface import InterfaceClass as Z3_InterfaceClass
from zope.interface.interface import Interface as Z3_Interface
from zope.interface.interface import Attribute as Z3_Attribute
from zope.interface.interface import Method as Z3_Method
_bridges = {Z2_Interface: Z3_Interface}
def fromZ2Interface(z2i):
""" Return a Zope 3 interface corresponding to 'z2i'.
o 'z2i' must be a Zope 2 interface.
"""
if not isinstance(z2i, Z2_InterfaceClass):
raise ValueError, 'Not a Zope 2 interface!'
if z2i in _bridges:
return _bridges[z2i]
name = z2i.getName()
bases = [ fromZ2Interface(x) for x in z2i.getBases() ]
attrs = {}
for k, v in z2i.namesAndDescriptions():
if isinstance(v, Z2_Method):
v = fromZ2Method(v)
elif isinstance(v, Z2_Attribute):
v = fromZ2Attribute(v)
attrs[k] = v
# XXX: Note that we pass the original interface's __module__;
# we may live to regret that.
z3i = Z3_InterfaceClass(name=name,
bases=tuple(bases),
attrs=attrs,
__doc__=z2i.getDoc(),
__module__=z2i.__module__)
_bridges[z2i] = z3i
return z3i
def fromZ2Attribute(z2a):
""" Return a Zope 3 interface attribute corresponding to 'z2a'.
o 'z2a' must be a Zope 2 interface attribute.
"""
if not isinstance(z2a, Z2_Attribute):
raise ValueError, 'Not a Zope 2 interface attribute!'
return Z3_Attribute(z2a.getName(), z2a.getDoc())
def fromZ2Method(z2m):
""" Return a Zope 3 interface method corresponding to 'z2m'.
o 'z2m' must be a Zope 2 interface method.
"""
if not isinstance(z2m, Z2_Method):
raise ValueError, 'Not a Zope 2 interface method!'
z3m = Z3_Method(z2m.getName(), z2m.getDoc())
sig = z2m.getSignatureInfo()
z3m.positional = sig['positional']
z3m.required = sig['required']
z3m.optional = sig['optional']
z3m.varargs = sig['varargs']
z3m.kwargs = sig['kwargs']
return z3m
##############################################################################
#
# 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.
#
##############################################################################
"""Utils to be reused
$Id: ReuseUtils.py 12907 2005-05-31 06:26:15Z philikon $
"""
from new import function
def rebindFunction(f,rebindDir=None,**rebinds):
'''return *f* with some globals rebound.'''
d= {}
if rebindDir : d.update(rebindDir)
if rebinds: d.update(rebinds)
if not d: return f
f= getattr(f,'im_func',f)
fd= f.func_globals.copy()
fd.update(d)
nf= function(f.func_code,fd,f.func_name,f.func_defaults or ())
nf.__doc__= f.__doc__
if f.__dict__ is not None: nf.__dict__= f.__dict__.copy()
return nf
##############################################################################
#
# 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.
#
##############################################################################
"""Trusted expression
$Id: TrustedExpression.py 17373 2005-09-08 14:48:27Z regebro $
"""
from sys import modules
from Products.PageTemplates.PythonExpr import PythonExpr
from Products.PageTemplates.Expressions import \
SubPathExpr, PathExpr, \
StringExpr, \
getEngine, installHandlers,\
SecureModuleImporter
from ReuseUtils import rebindFunction
ModuleImporter = SecureModuleImporter
def trustedTraverse(ob, path, ignored,):
if not path: return self
get = getattr
has = hasattr
N = None
M = rebindFunction # artifical marker
if isinstance(path, str): path = path.split('/')
else: path=list(path)
REQUEST={'TraversalRequestNameStack': path}
path.reverse()
pop=path.pop
if len(path) > 1 and not path[0]:
# Remove trailing slash
path.pop(0)
if not path[-1]:
# If the path starts with an empty string, go to the root first.
pop()
self=ob.getPhysicalRoot()
object = ob
while path:
name=pop()
__traceback_info__ = path, name
if name == '..':
o=getattr(object, 'aq_parent', M)
if o is not M:
object=o
continue
t=get(object, '__bobo_traverse__', M)
if t is not M: o=t(REQUEST, name)
else:
o = get(object, name, M)
if o is M:
try: o = object[name]
except (AttributeError, TypeError): # better exception
raise AttributeError(name)
object = o
return object
class SubPathExpr(SubPathExpr):
_eval = rebindFunction(SubPathExpr._eval.im_func,
restrictedTraverse=trustedTraverse,
)
class PathExpr(PathExpr):
__init__ = rebindFunction(PathExpr.__init__.im_func,
SubPathExpr=SubPathExpr,
)
class StringExpr(StringExpr):
__init__ = rebindFunction(StringExpr.__init__.im_func,
PathExpr=PathExpr,
)
installHandlers = rebindFunction(installHandlers,
PathExpr=PathExpr,
StringExpr=StringExpr,
PythonExpr=PythonExpr,
)
_engine=None
getEngine = rebindFunction(getEngine,
_engine=_engine,
installHandlers=installHandlers
)
##############################################################################
#
# 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.
#
##############################################################################
"""Provide basic browser functionality
$Id: __init__.py 19283 2005-10-31 17:43:51Z philikon $
"""
import Acquisition
import zope.app.publisher.browser
class BrowserView(Acquisition.Explicit, zope.app.publisher.browser.BrowserView):
"""Five browser view
Mixes in explicit acquisition so that security can be acquired for
views"""
##############################################################################
#
# 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.
#
##############################################################################
"""Absolute URL
$Id: absoluteurl.py 13254 2005-06-09 21:40:41Z philikon $
"""
from Acquisition import aq_inner, aq_parent
from OFS.interfaces import ITraversable
from zope.interface import implements
from zope.app import zapi
from zope.app.traversing.browser.interfaces import IAbsoluteURL
from Products.Five.browser import BrowserView
class AbsoluteURL(BrowserView):
"""An adapter for Zope3-style absolute_url using Zope2 methods
(original: zope.app.traversing.browser.absoluteurl)
"""
implements(IAbsoluteURL)
def __init__(self, context, request):
self.context, self.request = context, request
def __str__(self):
context = aq_inner(self.context)
return context.absolute_url()
__call__ = __str__
def breadcrumbs(self):
context = aq_inner(self.context)
container = aq_parent(context)
request = self.request
name = context.getId()
if container is None or self._isVirtualHostRoot() \
or not ITraversable.providedBy(container):
return (
{'name': name, 'url': context.absolute_url()},)
view = zapi.getMultiAdapter((container, request), IAbsoluteURL)
base = tuple(view.breadcrumbs())
base += (
{'name': name, 'url': ("%s/%s" % (base[-1]['url'], name))},)
return base
def _isVirtualHostRoot(self):
virtualrootpath = self.request.get('VirtualRootPhysicalPath', None)
if virtualrootpath is None:
return False
context = aq_inner(self.context)
return context.restrictedTraverse(virtualrootpath) == context
class SiteAbsoluteURL(AbsoluteURL):
"""An adapter for Zope3-style absolute_url using Zope2 methods
This one is just used to stop breadcrumbs from crumbing up
to the Zope root.
(original: zope.app.traversing.browser.absoluteurl)
"""
def breadcrumbs(self):
context = self.context
request = self.request
return ({'name': context.getId(),
'url': context.absolute_url()
},)
<html metal:use-macro="context/@@standard_macros/page">
<body>
<div metal:fill-slot="main">
<p>+ screen not yet supported by Five</p>
</div>
</body>
</html>
##############################################################################
#
# Copyright (c) 2002-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.
#
##############################################################################
"""Adding View
The Adding View is used to add new objects to a container. It is sort of a
factory screen.
"""
__docformat__ = 'restructuredtext'
from warnings import warn
from zope.interface import implements
from zope.publisher.interfaces import IPublishTraverse
from zope.component.interfaces import IFactory
from zope.app.exception.interfaces import UserError
from zope.app.container.interfaces import IAdding, INameChooser
from zope.app.container.interfaces import IContainerNamesContainer
from zope.app.container.constraints import checkFactory, checkObject
from zope.app.publisher.browser.menu import getMenu
from zope.app import zapi
from zope.app.event.objectevent import ObjectCreatedEvent
from zope.event import notify
from zExceptions import BadRequest
from Products.Five import BrowserView
from Products.Five.traversable import Traversable
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Acquisition import Implicit
from OFS.SimpleItem import SimpleItem
class BasicAdding(Implicit, BrowserView):
implements(IAdding, IPublishTraverse)
def add(self, content):
"""See zope.app.container.interfaces.IAdding
"""
container = self.context
name = self.contentName
chooser = INameChooser(container)
# check precondition
checkObject(container, name, content)
if IContainerNamesContainer.providedBy(container):
# The container picks it's own names.
# We need to ask it to pick one.
name = chooser.chooseName(self.contentName or '', content)
else:
request = self.request
name = request.get('add_input_name', name)
if name is None:
name = chooser.chooseName(self.contentName or '', content)
elif name == '':
name = chooser.chooseName('', content)
else:
# Invoke the name chooser even when we have a
# name. It'll do useful things with it like converting
# the incoming unicode to an ASCII string.
name = chooser.chooseName(name, container)
content.id = name
container._setObject(name, content)
self.contentName = name # Set the added object Name
return container._getOb(name)
contentName = None # usually set by Adding traverser
def nextURL(self):
"""See zope.app.container.interfaces.IAdding"""
# XXX this is definitely not right for all or even most uses
# of Five, but can be overridden by an AddView subclass, using
# the class attribute of a zcml:addform directive
return str(zapi.getMultiAdapter((self.context, self.request),
name=u"absolute_url")) + '/manage_main'
# set in BrowserView.__init__
request = None
context = None
def renderAddButton(self):
warn("The renderAddButton method is deprecated, use nameAllowed",
DeprecationWarning, 2)
def publishTraverse(self, request, name):
"""See zope.app.container.interfaces.IAdding"""
if '=' in name:
view_name, content_name = name.split("=", 1)
self.contentName = content_name
if view_name.startswith('@@'):
view_name = view_name[2:]
return zapi.getMultiAdapter((self, request), name=view_name)
if name.startswith('@@'):
view_name = name[2:]
else:
view_name = name
view = zapi.queryView(self, view_name, request)
if view is not None:
return view
factory = zapi.queryUtility(IFactory, name)
if factory is None:
return super(BasicAdding, self).publishTraverse(request, name)
return factory
def action(self, type_name='', id=''):
if not type_name:
raise UserError("You must select the type of object to add.")
if type_name.startswith('@@'):
type_name = type_name[2:]
if '/' in type_name:
view_name = type_name.split('/', 1)[0]
else:
view_name = type_name
if zapi.queryView(self, view_name, self.request) is not None:
url = "%s/%s=%s" % (
zapi.getMultiAdapter((self, self.request), name=u"absolute_url"),
type_name, id)
self.request.response.redirect(url)
return
if not self.contentName:
self.contentName = id
factory = zapi.getUtility(IFactory, type_name)
content = factory()
notify(ObjectCreatedEvent(content))
self.add(content)
self.request.response.redirect(self.nextURL())
def namesAccepted(self):
return not IContainerNamesContainer.providedBy(self.context)
def nameAllowed(self):
"""Return whether names can be input by the user."""
return not IContainerNamesContainer.providedBy(self.context)
class Adding(BasicAdding):
menu_id = None
index = ZopeTwoPageTemplateFile("adding.pt")
def addingInfo(self):
"""Return menu data.
This is sorted by title.
"""
container = self.context
result = []
for menu_id in (self.menu_id, 'zope.app.container.add'):
if not menu_id:
continue
for item in getMenu(menu_id, self, self.request):
extra = item.get('extra')
if extra:
factory = extra.get('factory')
if factory:
factory = zapi.getUtility(IFactory, factory)
if not checkFactory(container, None, factory):
continue
elif item['extra']['factory'] != item['action']:
item['has_custom_add_view']=True
result.append(item)
result.sort(lambda a, b: cmp(a['title'], b['title']))
return result
def isSingleMenuItem(self):
"Return whether there is single menu item or not."
return len(self.addingInfo()) == 1
def hasCustomAddView(self):
"This should be called only if there is `singleMenuItem` else return 0"
if self.isSingleMenuItem():
menu_item = self.addingInfo()[0]
if 'has_custom_add_view' in menu_item:
return True
return False
class ContentAdding(Adding, Traversable, SimpleItem):
menu_id = "add_content"
class ObjectManagerNameChooser:
"""A name chooser for a Zope object manager.
"""
implements(INameChooser)
def __init__(self, context):
self.context = context
def checkName(self, name, object):
# ObjectManager can only deal with ASCII names. Specially
# ObjectManager._checkId can only deal with strings.
try:
name = name.encode('ascii')
except UnicodeDecodeError:
raise UserError, "Id must contain only ASCII characters."
try:
self.context._checkId(name, allow_dup=False)
except BadRequest, e:
msg = ' '.join(e.args) or "Id is in use or invalid"
raise UserError, msg
def chooseName(self, name, object):
if not name:
name = object.__class__.__name__
else:
try:
name = name.encode('ascii')
except UnicodeDecodeError:
raise UserError, "Id must contain only ASCII characters."
dot = name.rfind('.')
if dot >= 0:
suffix = name[dot:]
name = name[:dot]
else:
suffix = ''
n = name + suffix
i = 0
while True:
i += 1
try:
self.context._getOb(n)
except AttributeError:
break
n = name + '-' + str(i) + suffix
# Make sure the name is valid. We may have started with
# something bad.
self.checkName(n, object)
return n
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<browser:defaultView name="index.html" />
<interface
interface="zope.publisher.interfaces.browser.ILayer"
/>
<interface
interface="zope.publisher.interfaces.browser.ISkin"
/>
<interface
interface="zope.app.publisher.interfaces.browser.IMenuItemType"
/>
<browser:layer
name="default"
interface="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
/>
<browser:page
for="*"
name="absolute_url"
class=".absoluteurl.AbsoluteURL"
permission="zope.Public"
allowed_interface="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<view
for="*"
factory=".absoluteurl.AbsoluteURL"
type="zope.publisher.interfaces.http.IHTTPRequest"
permission="zope.Public"
provides="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<browser:page
for="zope.app.traversing.interfaces.IContainmentRoot"
name="absolute_url"
class=".absoluteurl.SiteAbsoluteURL"
permission="zope.Public"
allowed_interface="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<view
for="zope.app.traversing.interfaces.IContainmentRoot"
factory=".absoluteurl.SiteAbsoluteURL"
type="zope.publisher.interfaces.http.IHTTPRequest"
permission="zope.Public"
provides="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<browser:view
for="OFS.interfaces.IObjectManager"
name="+"
class=".adding.ContentAdding"
permission="zope2.ViewManagementScreens"
>
<browser:page name="index.html" template="adding.pt" />
<browser:page name="action.html" attribute="action" />
</browser:view>
<adapter
for="OFS.interfaces.IObjectManager"
factory=".adding.ObjectManagerNameChooser"
provides="zope.app.container.interfaces.INameChooser"
/>
<!-- Menu access -->
<browser:page
for="*"
name="view_get_menu"
permission="zope.Public"
class=".menu.MenuAccessView"
allowed_interface="zope.app.publisher.interfaces.browser.IMenuAccessView"
/>
</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.
#
##############################################################################
"""Some menu code
$Id: menu.py 19283 2005-10-31 17:43:51Z philikon $
"""
from zope.interface import implements
from zope.app.publisher.interfaces.browser import IMenuAccessView
from zope.app.publisher.browser.menu import getMenu
from Products.Five import BrowserView
class MenuAccessView(BrowserView):
implements(IMenuAccessView)
def __getitem__(self, menu_id):
return getMenu(menu_id, self.context, self.request)
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta">
<meta:directives namespace="http://namespaces.zope.org/browser">
<meta:directive
name="layer"
schema="zope.app.publisher.browser.metadirectives.ILayerDirective"
handler="zope.app.publisher.browser.metaconfigure.layer"
/>
<meta:directive
name="skin"
schema="zope.app.publisher.browser.metadirectives.ISkinDirective"
handler="zope.app.publisher.browser.metaconfigure.skin"
/>
<meta:directive
name="defaultSkin"
schema="zope.app.publisher.browser.metadirectives.IDefaultSkinDirective"
handler="zope.app.publisher.browser.metaconfigure.defaultSkin"
/>
<meta:directive
name="defaultView"
schema="zope.app.publisher.browser.metadirectives.IDefaultViewDirective"
handler="zope.app.publisher.browser.metaconfigure.defaultView"
/>
<meta:directive
name="page"
schema="zope.app.publisher.browser.metadirectives.IPageDirective"
handler=".metaconfigure.page"
/>
<meta:complexDirective
name="pages"
schema="zope.app.publisher.browser.metadirectives.IPagesDirective"
handler=".metaconfigure.pages"
>
<meta:subdirective
name="page"
schema="zope.app.publisher.browser.metadirectives.IPagesPageSubdirective"
/>
</meta:complexDirective>
<meta:directive
name="resource"
schema="zope.app.publisher.browser.metadirectives.IResourceDirective"
handler=".metaconfigure.resource"
/>
<meta:directive
name="resourceDirectory"
schema="zope.app.publisher.browser.metadirectives.IResourceDirectoryDirective"
handler=".metaconfigure.resourceDirectory"
/>
<meta:directive
name="menu"
schema="zope.app.publisher.browser.metadirectives.IMenuDirective"
handler="zope.app.publisher.browser.menumeta.menuDirective"
/>
<meta:directive
name="menuItem"
schema="zope.app.publisher.browser.metadirectives.IMenuItemDirective"
handler="zope.app.publisher.browser.menumeta.menuItemDirective"
/>
<meta:complexDirective
name="menuItems"
schema="zope.app.publisher.browser.metadirectives.IMenuItemsDirective"
handler="zope.app.publisher.browser.menumeta.menuItemsDirective"
>
<meta:subdirective
name="menuItem"
schema="zope.app.publisher.browser.metadirectives.IMenuItemSubdirective"
/>
</meta:complexDirective>
<meta:complexDirective
name="view"
schema="zope.app.publisher.browser.metadirectives.IViewDirective"
handler=".metaconfigure.view"
>
<meta:subdirective
name="page"
schema="zope.app.publisher.browser.metadirectives.IViewPageSubdirective"
/>
<meta:subdirective
name="defaultPage"
schema="zope.app.publisher.browser.metadirectives.IViewDefaultPageSubdirective"
/>
</meta:complexDirective>
</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.
#
##############################################################################
"""Browser directives
Directives to emulate the 'http://namespaces.zope.org/browser'
namespace in ZCML known from zope.app.
$Id: metaconfigure.py 13257 2005-06-09 21:56:39Z philikon $
"""
import os
from zope.interface import Interface
from zope.configuration.exceptions import ConfigurationError
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 view as zope_app_view
from zope.app.publisher.browser.viewmeta import providesCallable, \
_handle_menu, _handle_for
from zope.app.component.metaconfigure import handler
from zope.app.component.interface import provideInterface
from Products.Five.browser import BrowserView
from Products.Five.browser.resource import FileResourceFactory, ImageResourceFactory
from Products.Five.browser.resource import PageTemplateResourceFactory
from Products.Five.browser.resource import DirectoryResourceFactory
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Products.Five.metaclass import makeClass
from Products.Five.security import getSecurityInfo, protectClass, protectName
from Globals import InitializeClass as initializeClass
def page(_context, name, permission, for_,
layer=IDefaultBrowserLayer, template=None, class_=None,
allowed_interface=None, allowed_attributes=None,
attribute='__call__', menu=None, title=None,
):
_handle_menu(_context, menu, title, [for_], name, permission)
if not (class_ or template):
raise ConfigurationError("Must specify a class or template")
if allowed_attributes is None:
allowed_attributes = []
if allowed_interface is not None:
for interface in allowed_interface:
allowed_attributes.extend(interface.names())
if attribute != '__call__':
if template:
raise ConfigurationError(
"Attribute and template cannot be used together.")
if not class_:
raise ConfigurationError(
"A class must be provided if attribute is used")
if template:
template = os.path.abspath(str(_context.path(template)))
if not os.path.isfile(template):
raise ConfigurationError("No such file", template)
if class_:
# new-style classes do not work with Five. As we want to import
# packages from z3 directly, we ignore new-style classes for now.
if type(class_) == type:
return
if attribute != '__call__':
if not hasattr(class_, attribute):
raise ConfigurationError(
"The provided class doesn't have the specified attribute "
)
cdict = getSecurityInfo(class_)
cdict['__name__'] = name
if template:
new_class = makeClassForTemplate(template, bases=(class_, ),
cdict=cdict, name=name)
elif attribute != "__call__":
# we're supposed to make a page for an attribute (read:
# method) and it's not __call__. We thus need to create a
# new class using our mixin for attributes.
cdict.update({'__page_attribute__': attribute})
new_class = makeClass(class_.__name__,
(class_, ViewMixinForAttributes),
cdict)
# in case the attribute does not provide a docstring,
# ZPublisher refuses to publish it. So, as a workaround,
# we provide a stub docstring
func = getattr(new_class, attribute)
if not func.__doc__:
# cannot test for MethodType/UnboundMethod here
# because of ExtensionClass
if hasattr(func, 'im_func'):
# you can only set a docstring on functions, not
# on method objects
func = func.im_func
func.__doc__ = "Stub docstring to make ZPublisher work"
else:
# we could use the class verbatim here, but we'll execute
# some security declarations on it so we really shouldn't
# modify the original. So, instead we make a new class
# with just one base class -- the original
new_class = makeClass(class_.__name__, (class_,), cdict)
else:
# template
new_class = makeClassForTemplate(template, name=name)
_handle_for(_context, for_)
_context.action(
discriminator = ('view', for_, name, IBrowserRequest, layer),
callable = handler,
args = ('provideAdapter',
(for_, layer), Interface, name, new_class, _context.info),
)
_context.action(
discriminator = ('five:protectClass', new_class),
callable = protectClass,
args = (new_class, permission)
)
if allowed_attributes:
for attr in allowed_attributes:
_context.action(
discriminator = ('five:protectName', new_class, attr),
callable = protectName,
args = (new_class, attr, permission)
)
_context.action(
discriminator = ('five:initialize:class', new_class),
callable = initializeClass,
args = (new_class,)
)
class pages(zope_app_pages):
def page(self, _context, name, attribute='__call__', template=None,
menu=None, title=None):
return page(_context,
name=name,
attribute=attribute,
template=template,
menu=menu, title=title,
**(self.opts))
# view (named view with pages)
class view(zope_app_view):
def __call__(self):
(_context, name, for_, permission, layer, class_,
allowed_interface, allowed_attributes) = self.args
required = {}
cdict = {}
pages = {}
for pname, attribute, template in self.pages:
if template:
cdict[pname] = ZopeTwoPageTemplateFile(template)
if attribute and attribute != name:
cdict[attribute] = cdict[pname]
else:
if not hasattr(class_, attribute):
raise ConfigurationError("Undefined attribute",
attribute)
attribute = attribute or pname
required[pname] = permission
pages[pname] = attribute
# This should go away, but noone seems to remember what to do. :-(
if hasattr(class_, 'publishTraverse'):
def publishTraverse(self, request, name,
pages=pages, getattr=getattr):
if name in pages:
return getattr(self, pages[name])
view = zapi.queryView(self, name, request)
if view is not None:
return view
m = class_.publishTraverse.__get__(self)
return m(request, name)
else:
def publishTraverse(self, request, name,
pages=pages, getattr=getattr):
if name in pages:
return getattr(self, pages[name])
view = zapi.queryView(self, name, request)
if view is not None:
return view
raise NotFoundError(self, name, request)
cdict['publishTraverse'] = publishTraverse
if not hasattr(class_, 'browserDefault'):
if self.default or self.pages:
default = self.default or self.pages[0][0]
cdict['browserDefault'] = (
lambda self, request, default=default:
(self, (default, ))
)
elif providesCallable(class_):
cdict['browserDefault'] = (
lambda self, request: (self, ())
)
if class_ is not None:
bases = (class_, ViewMixinForTemplates)
else:
bases = (ViewMixinForTemplates,)
try:
cname = str(name)
except:
cname = "GeneratedClass"
newclass = makeClass(cname, bases, cdict)
_handle_for(_context, for_)
if self.provides is not None:
_context.action(
discriminator = None,
callable = provideInterface,
args = ('', self.provides)
)
_context.action(
discriminator = ('view', for_, name, IBrowserRequest, layer,
self.provides),
callable = handler,
args = ('provideAdapter',
(for_, layer), self.provides, name, newclass,
_context.info),
)
_factory_map = {'image':{'prefix':'ImageResource',
'count':0,
'factory':ImageResourceFactory},
'file':{'prefix':'FileResource',
'count':0,
'factory':FileResourceFactory},
'template':{'prefix':'PageTemplateResource',
'count':0,
'factory':PageTemplateResourceFactory}
}
def resource(_context, name, layer=IDefaultBrowserLayer, permission='zope.Public',
file=None, image=None, template=None):
if ((file and image) or (file and template) or
(image and template) or not (file or image or template)):
raise ConfigurationError(
"Must use exactly one of file or image or template"
"attributes for resource directives"
)
res = file or image or template
res_type = ((file and 'file') or
(image and 'image') or
(template and 'template'))
factory_info = _factory_map.get(res_type)
factory_info['count'] += 1
res_factory = factory_info['factory']
class_name = '%s%s' % (factory_info['prefix'], factory_info['count'])
new_class = makeClass(class_name, (res_factory.resource,), {})
factory = res_factory(name, res, resource_factory=new_class)
_context.action(
discriminator = ('resource', name, IBrowserRequest, layer),
callable = handler,
args = ('provideAdapter',
(layer,), Interface, name, factory, _context.info),
)
_context.action(
discriminator = ('five:protectClass', new_class),
callable = protectClass,
args = (new_class, permission)
)
_context.action(
discriminator = ('five:initialize:class', new_class),
callable = initializeClass,
args = (new_class,)
)
_rd_map = {ImageResourceFactory:{'prefix':'DirContainedImageResource',
'count':0},
FileResourceFactory:{'prefix':'DirContainedFileResource',
'count':0},
PageTemplateResourceFactory:{'prefix':'DirContainedPTResource',
'count':0},
DirectoryResourceFactory:{'prefix':'DirectoryResource',
'count':0}
}
def resourceDirectory(_context, name, directory, layer=IDefaultBrowserLayer,
permission='zope.Public'):
if not os.path.isdir(directory):
raise ConfigurationError(
"Directory %s does not exist" % directory
)
resource = DirectoryResourceFactory.resource
f_cache = {}
resource_factories = dict(resource.resource_factories)
resource_factories['default'] = resource.default_factory
for ext, factory in resource_factories.items():
if f_cache.get(factory) is not None:
continue
factory_info = _rd_map.get(factory)
factory_info['count'] += 1
class_name = '%s%s' % (factory_info['prefix'], factory_info['count'])
factory_name = '%s%s' % (factory.__name__, factory_info['count'])
f_resource = makeClass(class_name, (factory.resource,), {})
f_cache[factory] = makeClass(factory_name, (factory,),
{'resource':f_resource})
for ext, factory in resource_factories.items():
resource_factories[ext] = f_cache[factory]
default_factory = resource_factories['default']
del resource_factories['default']
cdict = {'resource_factories':resource_factories,
'default_factory':default_factory}
factory_info = _rd_map.get(DirectoryResourceFactory)
factory_info['count'] += 1
class_name = '%s%s' % (factory_info['prefix'], factory_info['count'])
dir_factory = makeClass(class_name, (resource,), cdict)
factory = DirectoryResourceFactory(name, directory,
resource_factory=dir_factory)
new_classes = [dir_factory,
] + [f.resource for f in f_cache.values()]
_context.action(
discriminator = ('resource', name, IBrowserRequest, layer),
callable = handler,
args = ('provideAdapter',
(layer,), Interface, name, factory, _context.info),
)
for new_class in new_classes:
_context.action(
discriminator = ('five:protectClass', new_class),
callable = protectClass,
args = (new_class, permission)
)
_context.action(
discriminator = ('five:initialize:class', new_class),
callable = initializeClass,
args = (new_class,)
)
#
# mixin classes / class factories
#
class ViewMixinForAttributes(BrowserView):
# we have an attribute that we can simply tell ZPublisher to go to
def __browser_default__(self, request):
return self, (self.__page_attribute__,)
# this is technically not needed because ZPublisher finds our
# attribute through __browser_default__; but we also want to be
# able to call pages from python modules, PythonScripts or ZPT
def __call__(self, *args, **kw):
attr = self.__page_attribute__
meth = getattr(self, attr)
return meth(*args, **kw)
class ViewMixinForTemplates(BrowserView):
# short cut to get to macros more easily
def __getitem__(self, name):
if name == 'macros':
return self.index.macros
return self.index.macros[name]
# make the template publishable
def __call__(self, *args, **kw):
return self.index(self, *args, **kw)
def makeClassForTemplate(filename, globals=None, used_for=None,
bases=(), cdict=None, name=u''):
# XXX needs to deal with security from the bases?
if cdict is None:
cdict = {}
cdict.update({'index': ZopeTwoPageTemplateFile(filename, globals),
'__name__': name})
bases += (ViewMixinForTemplates,)
class_ = makeClass("SimpleViewClass from %s" % filename, bases, cdict)
if used_for is not None:
class_.__used_for__ = used_for
return class_
##############################################################################
#
# 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.
#
##############################################################################
"""A 'PageTemplateFile' without security restrictions.
$Id: pagetemplatefile.py 15193 2005-07-27 13:27:04Z regebro $
"""
import os, sys
from Globals import package_home
from AccessControl import getSecurityManager
from Shared.DC.Scripts.Bindings import Unauthorized, UnauthorizedBinding
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from zope.app.pagetemplate.viewpagetemplatefile import ViewMapper
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from Products.Five.browser.ReuseUtils import rebindFunction
from Products.Five.browser.TrustedExpression import getEngine, ModuleImporter
class ZopeTwoPageTemplateFile(PageTemplateFile):
"""A strange hybrid between Zope 2 and Zope 3 page template.
Uses Zope 2's engine, but with security disabled and with some
initialization and API from Zope 3.
"""
def __init__(self, filename, _prefix=None, content_type=None):
# XXX doesn't use content_type yet
self.ZBindings_edit(self._default_bindings)
path = self.get_path_from_prefix(_prefix)
self.filename = os.path.join(path, filename)
if not os.path.isfile(self.filename):
raise ValueError("No such file", self.filename)
basepath, ext = os.path.splitext(self.filename)
self.__name__ = os.path.basename(basepath)
def get_path_from_prefix(self, _prefix):
if isinstance(_prefix, str):
path = _prefix
else:
if _prefix is None:
_prefix = sys._getframe(2).f_globals
path = package_home(_prefix)
return path
_cook = rebindFunction(PageTemplateFile._cook,
getEngine=getEngine)
pt_render = rebindFunction(PageTemplateFile.pt_render,
getEngine=getEngine)
def _pt_getContext(self):
try:
root = self.getPhysicalRoot()
view = self._getContext()
except AttributeError:
# self has no attribute getPhysicalRoot. This typically happens
# when the template has no proper acquisition context.
# That also means it has no view. /regebro
root = self.context.getPhysicalRoot()
view = None
here = self.context.aq_inner
request = getattr(root, 'REQUEST', None)
c = {'template': self,
'here': here,
'context': here,
'container': here,
'nothing': None,
'options': {},
'root': root,
'request': request,
'modules': ModuleImporter,
}
if view:
c['view'] = view
c['views'] = ViewMapper(here, request)
return c
pt_getContext = rebindFunction(_pt_getContext,
SecureModuleImporter=ModuleImporter)
##############################################################################
#
# 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.
#
##############################################################################
"""Provide basic resource functionality
$Id: resource.py 13268 2005-06-10 14:18:23Z philikon $
"""
import os
import urllib
import Acquisition
from ComputedAttribute import ComputedAttribute
from OFS.Traversable import Traversable as OFSTraversable
from zope.interface import implements
from zope.component.interfaces import IResource
from zope.publisher.interfaces.browser import IBrowserPublisher
from zope.app import zapi
from zope.app.traversing.browser.interfaces import IAbsoluteURL
from zope.app.datetimeutils import time as timeFromDateTimeString
from zope.app.publisher.fileresource import File, Image
from zope.app.publisher.pagetemplateresource import PageTemplate
from zope.app.publisher.browser.resources import empty
from Products.Five.browser import BrowserView
_marker = []
class Resource(Acquisition.Explicit):
"""A publishable resource
"""
implements(IResource)
def __init__(self, request):
self.request = request
def __call__(self):
name = self.__name__
container = self.__parent__
# 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)
if not isinstance(container, DirectoryResource):
name = '++resource++%s' % name
return "%s/%s" % (url, name)
class PageTemplateResource(BrowserView, Resource):
#implements(IBrowserPublisher)
def __browser_default__(self, request):
return self, ('render',)
def render(self):
"""Rendered content"""
pt = self.context
return pt(self.request)
class FileResource(BrowserView, Resource):
"""A publishable file-based resource"""
#implements(IBrowserPublisher)
def __browser_default__(self, request):
return self, (request.REQUEST_METHOD,)
def GET(self):
"""Default content"""
file = self.context
request = self.request
response = request.response
# HTTP If-Modified-Since header handling. This is duplicated
# from OFS.Image.Image - it really should be consolidated
# somewhere...
header = request.environ.get('If-Modified-Since', None)
if header is not None:
header = header.split(';')[0]
# Some proxies seem to send invalid date strings for this
# header. If the date string is not valid, we ignore it
# rather than raise an error to be generally consistent
# with common servers such as Apache (which can usually
# understand the screwy date string as a lucky side effect
# of the way they parse it).
try: mod_since=long(timeFromDateTimeString(header))
except: mod_since=None
if mod_since is not None:
if getattr(file, 'lmt', None):
last_mod = long(file.lmt)
else:
last_mod = long(0)
if last_mod > 0 and last_mod <= mod_since:
response.setStatus(304)
return ''
response.setHeader('Content-Type', file.content_type)
response.setHeader('Last-Modified', file.lmh)
# Cache for one day
response.setHeader('Cache-Control', 'public,max-age=86400')
f = open(file.path, 'rb')
data = f.read()
f.close()
return data
def HEAD(self):
file = self.context
response = self.request.response
response = self.request.response
response.setHeader('Content-Type', file.content_type)
response.setHeader('Last-Modified', file.lmh)
# Cache for one day
response.setHeader('Cache-Control', 'public,max-age=86400')
return ''
class ResourceFactory:
factory = None
resource = None
def __init__(self, name, path, resource_factory=None):
self.__name = name
self.__rsrc = self.factory(path, name)
if resource_factory is not None:
self.resource = resource_factory
def __call__(self, request):
resource = self.resource(self.__rsrc, request)
return resource
def _PageTemplate(self, path, name):
# PageTemplate doesn't take a name parameter,
# which makes it different from FileResource.
# This is probably an error.
template = PageTemplate(path)
template.__name__ = name
return template
class PageTemplateResourceFactory(ResourceFactory):
"""A factory for Page Template resources"""
factory = _PageTemplate
resource = PageTemplateResource
class FileResourceFactory(ResourceFactory):
"""A factory for File resources"""
factory = File
resource = FileResource
class ImageResourceFactory(ResourceFactory):
"""A factory for Image resources"""
factory = Image
resource = FileResource
# we only need this class a context for DirectoryResource
class Directory:
def __init__(self, path, name):
self.path = path
self.__name__ = name
class DirectoryResource(BrowserView, Resource, OFSTraversable):
#implements(IBrowserPublisher)
resource_factories = {
'gif': ImageResourceFactory,
'png': ImageResourceFactory,
'jpg': ImageResourceFactory,
'pt': PageTemplateResourceFactory,
'zpt': PageTemplateResourceFactory,
'html': PageTemplateResourceFactory,
}
default_factory = FileResourceFactory
def __init__(self, context, request):
BrowserView.__init__(self, context, request)
# OFSTraversable.absolute_url() assumes self.REQUEST being
# accessible:
self.REQUEST = request
def getId(self):
name = self.__name__
if not name.startswith('++resource++'):
name = '++resource++%s' % self.__name__
return name
def __browser_default__(self, request):
'''See interface IBrowserPublisher'''
return empty, ()
def __getitem__(self, name):
res = self.get(name, None)
if res is None:
raise KeyError, name
return res
def get(self, name, default=_marker):
path = self.context.path
filename = os.path.join(path, name)
if not os.path.isfile(filename):
if default is _marker:
raise KeyError(name)
return default
ext = name.split('.')[-1]
factory = self.resource_factories.get(ext, self.default_factory)
resource = factory(name, filename)(self.request)
resource.__name__ = name
resource.__parent__ = self
# XXX __of__ wrapping is usually done on traversal.
# However, we don't want to subclass Traversable (or do we?)
# The right thing should probably be a specific (and very simple)
# traverser that does __getitem__ and __of__.
return resource.__of__(self)
class DirectoryResourceFactory(ResourceFactory):
factory = Directory
resource = DirectoryResource
============
Adding tests
============
ObjectManagerNameChooser
------------------------
First we need to import and setup some prerequisites:
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> from Products.Five.browser.adding import ObjectManagerNameChooser
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> chooser = ObjectManagerNameChooser(self.folder)
Now we can start. ``INameChooser`` defines a ``checkName()`` method
that checks whether a given name is valid in the container or not.
Under the hood, ``ObjectManagerNameChooser`` calls ``_checkId()`` of
the object manager. Valid names/ids are those that aren't in use yet
and don't contain invalid characters.
>>> chooser.checkName('abc', object())
>>> chooser.checkName('testoid', object())
Traceback (most recent call last):
...
UserError: The id "testoid" is invalid - it is already in use.
>>> chooser.checkName('slash/slash', object())
Traceback (most recent call last):
...
UserError: The id "slash/slash" contains characters illegal in URLs.
``INameChooser`` also promises us a ``chooseName()`` method that
chooses a name for us in case we don't have one or that chooses a
different name in case the one we chose was invalid.
>>> chooser.chooseName('', self.folder.testoid)
'FiveTraversableFolder'
>>> chooser.chooseName('abc', self.folder.testoid)
'abc'
>>> chooser.chooseName('testoid', self.folder.testoid)
'testoid-1'
Of course, if we start out with something bad, it isn't going to
become good automagically:
>>> chooser.chooseName('slash/slash', object())
Traceback (most recent call last):
...
UserError: The id "slash/slash" contains characters illegal in URLs.
<html metal:define-macro="birdmacro"><head><title>bird macro</title></head><body>Color: <metal:block define-slot="color" /><metal:block define-slot="birdinfo" /></body></html>
<p>Have you ever seen a cockatiel?</p>
<p tal:content="string:maybe">dunno</p>
<p tal:content="context/mymethod">Alpha</p>
<p tal:content="view/eagle">Beta</p>
<div tal:replace="structure context/@@flamingo.html">Gamma</div>
\ No newline at end of file
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain -->
<p i18n:domain="fivetest" i18n:translate="">This is a message</p>
<p i18n:domain="default" i18n:translate="">Object actions</p>
</body>
</html>
##############################################################################
#
# 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 the Localizer language integration for CPS. This test
requires a full blown CPS installation to run. It is therefore
prefixed with ``cps_`` so it won't be picked up by the test runner.
$Id: cps_test_localizer.py 14400 2005-07-07 17:55:08Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
from Testing.ZopeTestCase import installProduct, FunctionalDocFileSuite
installProduct('Five')
installProduct('BTreeFolder2')
installProduct('CMFCalendar')
installProduct('CMFCore')
installProduct('CMFDefault')
installProduct('CMFTopic')
installProduct('DCWorkflow')
installProduct('Localizer')
installProduct('MailHost')
installProduct('CPSCore')
installProduct('CPSDefault')
installProduct('CPSDirectory')
installProduct('CPSUserFolder')
installProduct('TranslationService')
installProduct('SiteAccess')
# these products should (and used to be) be optional, but they
# aren't right now.
installProduct('CPSForum')
installProduct('CPSSubscriptions')
installProduct('CPSNewsLetters')
installProduct('CPSSchemas')
installProduct('CPSDocument')
installProduct('PortalTransforms')
installProduct('Epoz')
# optional products, but apparently still needed...
installProduct('CPSRSS')
installProduct('CPSChat')
installProduct('CPSCalendar')
installProduct('CPSCollector')
installProduct('CPSMailBoxer')
return FunctionalDocFileSuite('cps_test_localizer.txt',
package='Products.Five.browser.tests')
if __name__ == '__main__':
framework()
Localizer languages
===================
Before we start, we need to set up a manager user to be able to create
the portal:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
We need to 1) configure the Zope 3 i18n message catalogs, 2) make the
CPS portal traversable, 3) register the Localizer languagees adapter
and 4) register our test page:
>>> configure_zcml = """
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:browser="http://namespaces.zope.org/browser"
... xmlns:five="http://namespaces.zope.org/five"
... xmlns:i18n="http://namespaces.zope.org/i18n"
... >
... <configure package="Products.Five.tests">
... <i18n:registerTranslations directory="locales" />
... </configure>
...
... <five:traversable class="Products.CPSDefault.Portal.CPSDefaultSite" />
...
... <adapter
... for="zope.publisher.interfaces.http.IHTTPRequest"
... provides="zope.i18n.interfaces.IUserPreferredLanguages"
... factory="Products.Five.i18n.LocalizerLanguages"
... />
...
... <configure package="Products.Five.browser.tests">
... <browser:page
... for="*"
... template="cps_test_localizer.pt"
... name="cps_test_localizer.html"
... permission="zope2.View"
... />
... </configure>
... </configure>
... """
>>> from Products.Five import zcml
>>> zcml.load_string(configure_zcml)
Create a CPS portal. We print an additional line before creating it
because PortalTransforms might print stuff to stdout and doctest
doesn't allow us to ellide a first line.
>>> print "Ignore lines after me"; print http(r"""
... POST /test_folder_1_/manage_addProduct/CPSDefault/manage_addCPSDefaultSite HTTP/1.1
... Authorization: Basic manager:r00t
... Content-Length: 269
... Content-Type: application/x-www-form-urlencoded
...
... id=cps&title=CPS+Portal&description=&manager_id=manager&manager_sn=CPS+manager&manager_givenName=Manager&manager_email=root%40localhost&manager_password=root&manager_password_confirmation=root&langs_list%3Alist=en&langs_list%3Alist=fr&langs_list%3Alist=de&submit=Create""")
Ignore lines after me
...
HTTP/1.1 200 OK
...
Now for some actual testing... Our test page is a simple ZPT
translating two messages from different domains. The first domain is
a Zope 3 style one, the second one comes from Localizer.
Both systems should yield the same default language (English) when no
language is specified whatsoever:
>>> print http(r"""
... GET /test_folder_1_/cps/cps_test_localizer.html HTTP/1.1
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain -->
<p>This is a message</p>
<p>Object actions</p>
</body>
</html>
Both systems should honour the HTTP ``Accept-Language`` header in the
same way:
>>> print http(r"""
... GET /test_folder_1_/cps/cps_test_localizer.html HTTP/1.1
... Accept-Language: de
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain -->
<p>Dies ist eine Nachricht</p>
<p>Objekt Aktionen</p>
</body>
</html>
Both systems should also honour Localizer-specific ways of determining
the language, for example the ``LOCALIZER_LANGUAGE`` cookie:
>>> print http(r"""
... GET /test_folder_1_/cps/cps_test_localizer.html HTTP/1.1
... Accept-Language: de
... Cookie: LOCALIZER_LANGUAGE=en
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain -->
<p>This is a message</p>
<p>Object actions</p>
</body>
</html>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<five:defaultViewable
class="Products.Five.tests.testing.simplecontent.SimpleContent" />
<browser:defaultView
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="eagledefaultview.txt"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="eagledefaultview.txt"
class=".pages.SimpleView"
attribute="eagle"
permission="zope2.Public"
/>
<!-- this tests whether five:defaultViewable can be called on a class that
already provides __call__, such as our CallableSimpleContent -->
<five:defaultViewable
class="Products.Five.tests.testing.simplecontent.CallableSimpleContent" />
<!-- this tests whether five:defaultViewable can be called on a class that
already provides index_html, such as our IndexSimpleContent -->
<five:defaultViewable
class="Products.Five.tests.testing.simplecontent.IndexSimpleContent" />
<browser:defaultView
for="Products.Five.tests.testing.simplecontent.IIndexSimpleContent"
name="index_html"
/>
</configure>
<p>The falcon has taken flight</p>
\ No newline at end of file
<p tal:content="context/mymethod">Replaced</p>
<p tal:content="python:context.mymethod()">Replaced</p>
##############################################################################
#
# 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()
<html i18n:domain="fivetest">
<body>
<p i18n:translate="">This is a message</p>
<p i18n:translate="explicit-msg">This is an explicit message</p>
<p i18n:translate="">These are <span tal:replace="python:4" i18n:name="number" /> messages</p>
<p i18n:translate="explicit-msgs">These are <span tal:replace="python:4" i18n:name="number" /> explicit messages</p>
<table summary="This is an attribute" i18n:attributes="summary">
</table>
<table summary="Explicit summary" title="Explicit title" i18n:attributes="summary explicit-summary; title explicit-title" >
</table>
</body>
</html>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta"
xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="fivetest">
<!-- make the zope2.Public permission work -->
<meta:redefinePermission from="zope2.Public" to="zope.Public" />
<!-- browser menu support -->
<browser:menu
id="testmenu"
title="Test menu"
/>
<browser:menuItem
for="OFS.interfaces.IFolder"
menu="testmenu"
title="Test Menu Item"
action="seagull.html"
description="This is a test menu item"
permission="zope2.Public"
/>
<browser:menuItem
for="OFS.interfaces.IFolder"
menu="testmenu"
title="Protected Test Menu Item"
action="seagull.html"
description="This is a protected test menu item"
permission="zope2.ViewManagementScreens"
/>
<browser:menuItems
for="OFS.interfaces.IFolder"
menu="testmenu">
<menuItem
title="Test Menu Item 2"
action="parakeet.html"
description="This is a test menu item"
permission="zope2.Public"
/>
<menuItem
title="Test Menu Item 3"
action="falcon.html"
description="This is a test menu item"
permission="zope2.Public"
/>
<menuItem
title="Protected Test Menu Item 2"
action="falcon.html"
description="This is a protected test menu item"
permission="zope2.ViewManagementScreens"
/>
</browser:menuItems>
<!-- page in a menu -->
<browser:page
for="OFS.interfaces.IFolder"
template="cockatiel.pt"
name="cockatiel_menu_public.html"
permission="zope2.Public"
title="Page in a menu (public)"
menu="testmenu"
/>
<browser:page
for="OFS.interfaces.IFolder"
template="cockatiel.pt"
name="cockatiel_menu_protected.html"
permission="zope2.ViewManagementScreens"
title="Page in a menu (protected)"
menu="testmenu"
/>
</configure>
\ No newline at end of file
<ul>
<li tal:repeat="item python:['Alpha', 'Beta', 'Gamma']" tal:content="item"/>
</ul>
<ul>
<li tal:repeat="item python:['Alpha', 'Beta', 'Gamma']" tal:content="python:repeat['item'].index"/>
</ul>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<!-- mouse instead of eagle -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="mouse"
name="overridden_view"
permission="zope2.Public"
/>
</configure>
<p tal:content="python:1+1">Some content</p>
\ No newline at end of file
##############################################################################
#
# 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.
#
##############################################################################
"""Test browser pages
$Id: pages.py 12884 2005-05-30 13:10:41Z philikon $
"""
from Products.Five import BrowserView
class SimpleView(BrowserView):
"""More docstring. Please Zope"""
def eagle(self):
"""Docstring"""
return "The eagle has landed"
def mouse(self):
"""Docstring"""
return "The mouse has been eaten by the eagle"
class FancyView(BrowserView):
"""Fancy, fancy stuff"""
def view(self):
return "Fancy, fancy"
class CallView(BrowserView):
def __call__(self):
return "I was __call__()'ed"
class CallableNoDocstring:
def __call__(self):
return "No docstring"
def function_no_docstring(self):
return "No docstring"
class NoDocstringView(BrowserView):
def method(self):
return "No docstring"
function = function_no_docstring
object = CallableNoDocstring()
class NewStyleClass(object):
"""
This is a testclass to verify that new style classes are ignored
in browser:page
"""
def __init__(self, context, request):
"""Docstring"""
self.context = context
self.request = request
def method(self):
"""Docstring"""
return
Test browser pages
==================
Let's register a quite large amount of test pages:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests)
Let's add a test object that we view most of the pages off of:
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
We also need to create a stub user account and login; otherwise we
wouldn't have all the rights to do traversal etc.:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.login('manager')
Now for some actual testing...
Simple pages
------------
A browser page that is a view class's attribute (method):
>>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
>>> view is not None
True
>>> from Products.Five.browser.tests.pages import SimpleView
>>> isinstance(view, SimpleView)
True
>>> view()
'The eagle has landed'
A browser page that is a Page Template.
>>> view = self.folder.unrestrictedTraverse('testoid/owl.html')
>>> view()
'<p>2</p>\n'
A browser page that is a PageTemplate plus a view class:
>>> view = self.folder.unrestrictedTraverse('testoid/falcon.html')
>>> isinstance(view, SimpleView)
True
>>> view()
'<p>The falcon has taken flight</p>\n'
Test pages that have been registered through the cumulative
<browser:pages> directive:
>>> view = self.folder.unrestrictedTraverse('testoid/eagle-page.txt')
>>> isinstance(view, SimpleView)
True
>>> view()
'The eagle has landed'
>>> view = self.folder.unrestrictedTraverse('testoid/mouse-page.txt')
>>> isinstance(view, SimpleView)
True
>>> view()
'The mouse has been eaten by the eagle'
Zope 2 objects always need a docstring in order to be published. Five
adds a docstring automatically if a view method doesn't have it, but
it shouldn't modify existing ones:
>>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
>>> view.eagle.__doc__ == SimpleView.eagle.__doc__
True
Test whether new-style classes are ignored when registering browser
pages with view classes. When traversing for a non-existing view, we
should get an AttributeError:
>>> self.folder.unrestrictedTraverse('testoid/@@new_style_class')
Traceback (most recent call last):
...
AttributeError: @@new_style_class
ZPT-based browser pages
-----------------------
Test access to ``context`` from ZPTs:
>>> view = self.folder.unrestrictedTraverse('testoid/flamingo.html')
>>> print view()
<p>Hello world</p>
<p>Hello world</p>
Test macro access from ZPT pages:
>>> view = self.folder.unrestrictedTraverse('testoid/seagull.html')
>>> view()
'<html><head><title>bird macro</title></head><body>Color: gray</body></html>\n'
Test whether old-style direct traversal still works with a
five:traversable class:
>>> old_view = self.folder.unrestrictedTraverse('testoid/direct')
>>> old_view()
'Direct traversal worked'
test_zpt_things:
>>> view = self.folder.unrestrictedTraverse('testoid/condor.html')
>>> print view()
<p>Hello world</p>
<p>The eagle has landed</p>
<p>Hello world</p>
<p>Hello world</p>
Make sure that tal:repeat works in ZPT browser pages:
>>> view = self.folder.unrestrictedTraverse('testoid/ostrich.html')
>>> print view()
<ul>
<li>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
</ul>
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
Test TALES traversal in ZPT pages:
>>> view = self.folder.unrestrictedTraverse('testoid/tales_traversal.html')
>>> print view()
<p>testoid</p>
<p>test_folder_1_</p>
Make sure that global template variables in ZPT pages are correct:
>>> view = self.folder.unrestrictedTraverse('testoid/template_variables.html')
>>> print view()
View is a view: True
Context is testoid: True
Contaxt.aq_parent is test_folder_1_: True
Container is context: True
Here is context: True
Nothing is None: True
Default works: True
Root is the application: True
Template is a template: True
Traverse_subpath exists and is empty: True
Request is a request: True
User is manager: True
Options exist: True
Attrs exist: True
Repeat exists: True
Loop exists: True
Modules exists: True
Make sure that ZPT's aren't a security-less zone. Let's logout and
try to access some protected stuff. Let's not forgot to login again,
of course:
>>> from AccessControl import allow_module
>>> allow_module('smtpd')
>>> self.logout()
>>> view = self.folder.unrestrictedTraverse('testoid/security.html')
>>> print view()
<div>NoneType</div>
<div>smtpd</div>
>>> self.login('manager')
Test pages registered through the <five:pagesFromDirectory /> directive:
>>> view = self.folder.unrestrictedTraverse('testoid/dirpage1')
>>> print view()
<html>
<p>This is page 1</p>
</html>
>>> view = self.folder.unrestrictedTraverse('testoid/dirpage2')
>>> print view()
<html>
<p>This is page 2</p>
</html>
Low-level security
------------------
This tests security on a low level (functional pages test has
high-level security tests). Let's manually look up a protected view:
>>> from Products.Five.traversable import FakeRequest
>>> from zope.app import zapi
>>> from zope.app.publication.browser import setDefaultSkin
>>> request = FakeRequest()
>>> 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,
so we get ('',) instead of ('eagle',):
>>> getattr(view, '__ac_permissions__')
(('View management screens', ('',)),)
Wrap into an acquisition so that imPermissionRole objects can be
evaluated. __roles__ is a imPermissionRole object:
>>> view = view.__of__(self.folder.testoid)
>>> view_roles = getattr(view, '__roles__', None)
>>> view_roles
('Manager',)
Check to see if view's context properly acquires its true
parent
>>> from Acquisition import aq_parent, aq_base, aq_inner
>>> context = getattr(view, 'context')
Check the wrapper type
>>> from Acquisition import ImplicitAcquisitionWrapper
>>> type(context) == ImplicitAcquisitionWrapper
True
The acquired parent is the view. This isn't
usually what you want.
>>> aq_parent(context) == view
True
To get what you usually want, do this
>>> context.aq_inner.aq_parent
<Folder at /test_folder_1_>
C methods work the same
>>> aq_parent(aq_inner(context))
<Folder at /test_folder_1_>
High-level security
-------------------
>>> protected_view_names = [
... 'eagle.txt', 'falcon.html', 'owl.html', 'flamingo.html',
... 'condor.html', 'protectededitform.html']
>>>
>>> public_view_names = [
... 'public_attribute_page',
... 'public_template_page',
... 'public_template_class_page',
... 'nodoc-method', 'nodoc-function', 'nodoc-object',
... 'dirpage1', 'dirpage2']
>>> from Products.Five.tests.testing.restricted import checkRestricted
>>> from Products.Five.tests.testing.restricted import checkUnauthorized
As long as we're not authenticated, we should get Unauthorized for
protected views, but we should be able to view the public ones:
>>> self.logout()
>>> for view_name in protected_view_names:
... checkUnauthorized(
... self.folder,
... 'context.restrictedTraverse("testoid/%s")()' % view_name)
>>> for view_name in public_view_names:
... checkRestricted(
... self.folder,
... 'context.restrictedTraverse("testoid/%s")()' % view_name)
>>> self.login('manager')
Being logged in as a manager again, we find that the protected pages
are not accessible to us:
>>> for view_name in protected_view_names:
... checkRestricted(
... self.folder,
... 'context.restrictedTraverse("testoid/%s")()' % view_name)
>>> checkRestricted(
... self.folder,
... 'context.restrictedTraverse("testoid/eagle.method").eagle()')
Other
-----
Make sure that browser pages can be overridden:
>>> zcml.load_string('''
... <includeOverrides
... package="Products.Five.browser.tests"
... file="overrides.zcml" />
... ''')
>>> view = self.folder.unrestrictedTraverse('testoid/overridden_view')
>>> view()
'The mouse has been eaten by the eagle'
Test traversal to resources from within ZPT pages:
>>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests)
>>> view = self.folder.unrestrictedTraverse('testoid/parakeet.html')
>>> print view()
<html><body><img alt=""
src="http://nohost/test_folder_1_/testoid/++resource++pattern.png" /></body></html>
Clean up
--------
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<!-- make the zope2.Public permission work -->
<meta:redefinePermission from="zope2.Public" to="zope.Public" />
<!-- attribute page -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="eagle.txt"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
name="eagle.method"
permission="zope2.ViewManagementScreens"
allowed_attributes="eagle"
/>
<!-- attribute page -->
<browser:pages
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
permission="zope2.ViewManagementScreens"
>
<browser:page
name="eagle-page.txt"
attribute="eagle"
/>
<browser:page
name="mouse-page.txt"
attribute="mouse"
/>
</browser:pages>
<!-- template/class page -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="falcon.pt"
name="falcon.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template page (with simple python expression) -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="owl.pt"
name="owl.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template page which calls on context using python and path
expressions -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="flamingo.pt"
name="flamingo.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template/class page which calls on context, view, views -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="condor.pt"
name="condor.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template page that defines a macro page -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="birdmacro.pt"
name="bird.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template page that uses macro page -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="seagull.pt"
name="seagull.html"
permission="zope2.ViewManagementScreens"
/>
<!-- test TALES -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="ostrich.pt"
name="ostrich.html"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="tales_traversal.pt"
name="tales_traversal.html"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="template_variables.pt"
name="template_variables.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template security -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="security.pt"
name="security.html"
permission="zope2.View"
/>
<!-- a publicly accessible page, attribute, template, template/class -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="public_attribute_page"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="owl.pt"
name="public_template_page"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="falcon.pt"
name="public_template_class_page"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="parakeet.pt"
name="parakeet.html"
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 -->
<browser:pages
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class="Products.Five.browser.tests.pages.NoDocstringView"
permission="zope2.Public">
<browser:page
name="nodoc-method"
attribute="method"
/>
<browser:page
name="nodoc-function"
attribute="function"
/>
<browser:page
name="nodoc-object"
attribute="object"
/>
</browser:pages>
<!-- five:pagesFromDirectory loads all .pt files in a directory as pages.
This is mainly used to load Zope2 skin templates so they can be used
in five skins and layers. -->
<five:pagesFromDirectory
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
module="Products.Five.browser.tests"
directory="pages"
permission="zope2.Public"
/>
<!-- browser:page directives with new style classes are ignored -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.NewStyleClass"
name="new_style_class"
attribute="method"
permission="zope2.Public"
/>
<!-- Verify that browser:view works, especially when no specific
view attribute is specified -->
<browser:view
name=""
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
permission="zope2.Public"
/>
<!-- XXX this should really be in Five.form.tests -->
<!-- protected edit form for permission check -->
<browser:editform
schema="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="protectededitform.html"
permission="zope2.ViewManagementScreens"
/>
<!-- stuff that we'll override in overrides.zcml -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="overridden_view"
permission="zope2.Public"
/>
</configure>
Functional Browser Pages Test
=============================
This test tests publishing aspects of browser pages. Let's register
some:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests)
Let's also add one of our stub objects to play with:
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
Docstrings
----------
In Zope 2, objects normally have to have a docstring in order to be
published. This crazy requirement luckily isn't true for Zope 3, so
it should be possible to write docstring-less view classes that are
still published through ZPublisher.
We see that even though the callables have no docstring, they are
published nevertheless:
>>> print http(r"""
... GET /test_folder_1_/testoid/nodoc-function HTTP/1.1
... """)
HTTP/1.1 200 OK
...
No docstring
>>> print http(r"""
... GET /test_folder_1_/testoid/nodoc-method HTTP/1.1
... """)
HTTP/1.1 200 OK
...
No docstring
>>> print http(r"""
... GET /test_folder_1_/testoid/nodoc-object HTTP/1.1
... """)
HTTP/1.1 200 OK
...
No docstring
Security
--------
Browser pages need to be protected with a permission. Let's test
those; we start by adding two users:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('viewer', 'secret', [], [])
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> protected_view_names = [
... 'eagle.txt', 'falcon.html', 'owl.html', 'flamingo.html',
... 'condor.html', 'protectededitform.html']
>>>
>>> public_view_names = [
... 'public_attribute_page',
... 'public_template_page',
... 'public_template_class_page',
... 'nodoc-method', 'nodoc-function', 'nodoc-object',
... 'dirpage1', 'dirpage2']
>>>
>>> ViewManagementScreens = 'View management screens'
As a normal user we shouldn't get to see those pages protected with
the 'View management screens' permission. Thus we expect a 401
Unauthorized:
>>> for view_name in protected_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name,
... basic='viewer:secret')
... status = response.getStatus()
... self.failUnless(status == 401, (status, 401, view_name))
The same should apply for the user if he has all other permissions
except 'View management screens':
>>> permissions = self.folder.possible_permissions()
>>> permissions.remove(ViewManagementScreens)
>>> self.folder._addRole('Viewer')
>>> self.folder.manage_role('Viewer', permissions)
>>> self.folder.manage_addLocalRoles('viewer', ['Viewer'])
>>> for view_name in protected_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name,
... basic='viewer:secret')
... status = response.getStatus()
... self.failUnless(status == 401, (status, 401, view_name))
If we grant 'View management screens' now, the protected views should
become viewable:
>>> self.folder.manage_role('Viewer', [ViewManagementScreens])
>>> for view_name in protected_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name,
... basic='viewer:secret')
... status = response.getStatus()
... self.failUnless(status == 200, (status, 200, view_name))
Managers should always be able to view anything, including proctected
stuff:
>>> for view_name in protected_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name,
... basic='manager:r00t')
... self.assertEqual(response.getStatus(), 200)
All public views should always be accessible by anyone:
>>> for view_name in public_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name)
... status = response.getStatus()
... 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
--------
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
<html><body><img alt="" src="" tal:attributes="src context/++resource++pattern.png" /></body></html>
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain -->
<p i18n:domain="fivetest" i18n:translate="">This is a message</p>
<p i18n:domain="PlacelessTranslationService" i18n:translate="">Reload this catalog</p>
</body>
</html>
##############################################################################
#
# 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 the PTS language integration.
$Id: pts_test_languages.py 14400 2005-07-07 17:55:08Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
from Testing.ZopeTestCase import installProduct, FunctionalDocFileSuite
installProduct('Five')
installProduct('PlacelessTranslationService')
return FunctionalDocFileSuite('pts_test_languages.txt',
package='Products.Five.browser.tests')
if __name__ == '__main__':
framework()
PTS languages
=============
Before we start, we need to set up a manager user to be able to create
the portal:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
We need to 1) configure the Zope 3 i18n message catalogs, 3) register
the PTS languagees adapter and 3) register our test page:
>>> configure_zcml = """
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:browser="http://namespaces.zope.org/browser"
... xmlns:i18n="http://namespaces.zope.org/i18n"
... >
... <configure package="Products.Five.tests">
... <i18n:registerTranslations directory="locales" />
... </configure>
...
... <adapter
... for="zope.publisher.interfaces.http.IHTTPRequest"
... provides="zope.i18n.interfaces.IUserPreferredLanguages"
... factory="Products.Five.i18n.PTSLanguages"
... />
...
... <configure package="Products.Five.browser.tests">
... <browser:page
... for="Products.Five.interfaces.IFolder"
... template="pts_test_languages.pt"
... name="pts_test_languages.html"
... permission="zope2.View"
... />
... </configure>
... </configure>
... """
>>> from Products.Five import zcml
>>> zcml.load_string(configure_zcml)
Finally, we need a traversable folder so that the test page we
registered is found:
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'ftf')
Now for some actual testing... Our test page is a simple ZPT
translating two messages from different domains. The first domain is
a Zope 3 style one, the second one comes from PTS.
Both systems should yield the same default language (English) when no
language is specified whatsoever:
>>> print http(r"""
... GET /test_folder_1_/ftf/pts_test_languages.html HTTP/1.1
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain -->
<p>This is a message</p>
<p>Reload this catalog</p>
</body>
</html>
Both systems should honour the HTTP ``Accept-Language`` header in the
same way:
>>> print http(r"""
... GET /test_folder_1_/ftf/pts_test_languages.html HTTP/1.1
... Accept-Language: de
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain -->
<p>Dies ist eine Nachricht</p>
<p>Diesen Katalog neu einlesen</p>
</body>
</html>
Both systems should also honour Localizer-specific ways of determining
the language, for example the ``pts_language`` cookie...
>>> print http(r"""
... GET /test_folder_1_/ftf/pts_test_languages.html HTTP/1.1
... Accept-Language: de
... Cookie: pts_language=en
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain -->
<p>This is a message</p>
<p>Reload this catalog</p>
</body>
</html>
... and the ``language`` form field...
>>> print http(r"""
... GET /test_folder_1_/ftf/pts_test_languages.html?language=en HTTP/1.1
... Accept-Language: de
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain -->
<p>This is a message</p>
<p>Reload this catalog</p>
</body>
</html>
... and both the ``pts_language`` cookie and the ``language`` form field:
>>> print http(r"""
... GET /test_folder_1_/ftf/pts_test_languages.html?language=de HTTP/1.1
... Accept-Language: en
... Cookie: pts_language=fr
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain -->
<p>Dies ist eine Nachricht</p>
<p>Diesen Katalog neu einlesen</p>
</body>
</html>
Testing resources
=================
Set up the test fixtures:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> import os, glob
>>> _prefix = os.path.dirname(Products.Five.browser.tests.__file__)
>>> dir_resource_names = [os.path.basename(r) for r in (
... glob.glob('%s/*.png' % _prefix) +
... glob.glob('%s/*.pt' % _prefix) +
... glob.glob('%s/[a-z]*.py' % _prefix) +
... glob.glob('%s/*.css' % _prefix))]
Resource types
--------------
>>> from Products.Five.browser.resource import Resource, PageTemplateResource
Template resource
~~~~~~~~~~~~~~~~~
>>> resource = self.folder.unrestrictedTraverse('testoid/++resource++cockatiel.html')
>>> isinstance(resource, Resource)
True
>>> resource()
'http://nohost/test_folder_1_/testoid/++resource++cockatiel.html'
File resource
~~~~~~~~~~~~~
>>> resource = self.folder.unrestrictedTraverse('testoid/++resource++style.css')
>>> isinstance(resource, Resource)
True
>>> resource()
'http://nohost/test_folder_1_/testoid/++resource++style.css'
Image resource
~~~~~~~~~~~~~~
>>> resource = self.folder.unrestrictedTraverse('testoid/++resource++pattern.png')
>>> isinstance(resource, Resource)
True
>>> resource()
'http://nohost/test_folder_1_/testoid/++resource++pattern.png'
Resource directory
~~~~~~~~~~~~~~~~~~
>>> base = 'testoid/++resource++fivetest_resources/%s'
>>> base_url = 'http://nohost/test_folder_1_/' + base
>>> abs_url = self.folder.unrestrictedTraverse(base % '')()
>>> abs_url + '/' == base_url % ''
True
PageTemplateResource's __call__ renders the template
>>> for r in dir_resource_names:
... resource = self.folder.unrestrictedTraverse(base % r)
... self.assert_(isinstance(resource, Resource))
... if not isinstance(resource, PageTemplateResource):
... self.assertEquals(resource(), base_url % r)
Security
--------
>>> from Products.Five.tests.testing.restricted import checkRestricted
>>> from Products.Five.tests.testing.restricted import checkUnauthorized
>>> resource_names = ['cockatiel.html', 'style.css', 'pattern.png']
We should get Unauthorized as long as we're unauthenticated:
>>> for resource in resource_names:
... checkUnauthorized(
... self.folder,
... 'context.restrictedTraverse("testoid/++resource++%s")()' % resource)
>>> base = 'testoid/++resource++fivetest_resources/%s'
>>> for resource in dir_resource_names:
... path = base % resource
... checkUnauthorized(self.folder, 'context.restrictedTraverse("%s")' % path)
Now let's create a manager user account and log in:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.login('manager')
We can now view them all:
>>> for resource in resource_names:
... checkRestricted(
... self.folder,
... 'context.restrictedTraverse("testoid/++resource++%s")()' % resource)
>>> base = 'testoid/++resource++fivetest_resources/%s'
>>> for resource in dir_resource_names:
... path = base % resource
... checkRestricted(self.folder, 'context.restrictedTraverse("%s")' % path)
Clean up
--------
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<!-- a couple simple resources -->
<browser:resource
template="cockatiel.pt"
name="cockatiel.html"
permission="zope2.ViewManagementScreens"
/>
<browser:resource
file="style.css"
name="style.css"
permission="zope2.ViewManagementScreens"
/>
<browser:resource
image="pattern.png"
name="pattern.png"
permission="zope2.ViewManagementScreens"
/>
<browser:resourceDirectory
name="fivetest_resources"
directory="."
permission="zope2.ViewManagementScreens"
/>
</configure>
\ No newline at end of file
Functional Resource Test
========================
Set up the test fixtures:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> import os, glob
>>> _prefix = os.path.dirname(Products.Five.browser.tests.__file__)
>>> dir_resource_names = [os.path.basename(r) for r in (
... glob.glob('%s/*.png' % _prefix) +
... glob.glob('%s/*.pt' % _prefix) +
... glob.glob('%s/[a-z]*.py' % _prefix) +
... glob.glob('%s/*.css' % _prefix))]
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
Image resource
~~~~~~~~~~~~~~
>>> print http(r'''
... GET /test_folder_1_/testoid/++resource++pattern.png HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
HTTP/1.1 200 OK
...
File resource
~~~~~~~~~~~~~
>>> print http(r'''
... GET /test_folder_1_/testoid/++resource++style.css HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
HTTP/1.1 200 OK
...
Template resource
~~~~~~~~~~~~~~~~~
>>> print http(r'''
... GET /test_folder_1_/testoid/++resource++cockatiel.html HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
HTTP/1.1 200 OK
...
Resource directory
~~~~~~~~~~~~~~~~~~
Page templates aren't guaranteed to render, so exclude them from the test:
>>> base_url = '/test_folder_1_/testoid/++resource++fivetest_resources/%s'
>>> for r in dir_resource_names:
... if r.endswith('.pt'):
... continue
... response = self.publish(base_url % r, basic='manager:r00t')
... self.assertEquals(200, response.getStatus())
Clean up
--------
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
<html metal:use-macro="context/@@bird.html/birdmacro"><metal:block fill-slot="color">gray</metal:block></html>
<div tal:define="comment string:Testing unrestricted code"
tal:content="python:None.__class__.__name__" />
<div tal:define="comment string:Testing unrestricted modules access;
smtpd nocall:modules/smtpd"
tal:content="python:smtpd.__name__" />
Test layer and skin support
===========================
Let's register a test layer and test skin:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config("skin.zcml", package=Products.Five.browser.tests)
Let's add a test object that we'll access the test page from:
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
The view was registered on a different layer than 'default', that's
why we can't access it straight away:
>>> print http(r"""
... GET /test_folder_1_/testoid/eagle.html HTTP/1.1
... """)
HTTP/1.1 404 Not Found
...
It works when we explicitly use the skin that includes that layer:
>>> print http(r"""
... GET /test_folder_1_/testoid/++skin++TestSkin/eagle.html HTTP/1.1
... """)
HTTP/1.1 200 OK
...
The eagle has landed
Or when we make that skin the default skin:
>>> zcml.load_string('''
... <browser:defaultSkin
... xmlns:browser="http://namespaces.zope.org/browser"
... name="TestSkin" />
... ''')
>>> print http(r"""
... GET /test_folder_1_/testoid/eagle.html HTTP/1.1
... """)
HTTP/1.1 200 OK
...
The eagle has landed
Clean up
--------
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta"
xmlns:browser="http://namespaces.zope.org/browser">
<!-- make the zope2.Public permission work -->
<meta:redefinePermission from="zope2.Public" to="zope.Public" />
<browser:layer name="test" />
<browser:skin
name="TestSkin"
layers="test default"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="eagle.html"
permission="zope2.Public"
layer="test"
/>
</configure>
<p tal:content="context/non_existent_thingie/fubared|context/getId">dunno</p>
<p tal:content="context/test_folder_1_/test_folder_1_/getId">dunno</p>
View is a view: <tal:block
content="python:hasattr(view,'context') and hasattr(view, 'request')" />
Context is testoid: <tal:block content="python:context.id == 'testoid'" />
Contaxt.aq_parent is test_folder_1_: <tal:block
content="python:context.aq_parent.id =='test_folder_1_'" />
Container is context: <tal:block content="python:container is context" />
Here is context: <tal:block content="python:here is context"/>
Nothing is None: <tal:block content="python:nothing is None"/>
Default works: <tal:block replace="non_existent_var|default" />True
Root is the application: <tal:block
replace="python:repr(root).find('Application') != -1" />
Template is a template: <tal:block
replace="python:repr(template.aq_base).startswith('<ZopeTwoPageTemplateFile')" />
Traverse_subpath exists and is empty: <tal:block
replace="python:traverse_subpath == []" />
Request is a request: <tal:block
replace="python:getattr(request, 'RESPONSE', None) is not None" />
User is manager: <tal:block replace="python:str(user) == 'manager'" />
Options exist: <tal:block replace="python:options is not None" />
Attrs exist: <tal:block replace="python:attrs is not None" />
Repeat exists: <tal:block replace="python:repeat is not None" />
Loop exists: <tal:block replace="python:loop is not None" />
Modules exists: <tal:block replace="python:modules is not None" />
\ No newline at end of file
##############################################################################
#
# 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.
#
##############################################################################
"""Test AbsoluteURL
$Id: test_absoluteurl.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_absoluteurl():
"""This tests the absolute url view (IAbsoluteURL or @@absolute_url),
in particular the breadcrumb functionality.
First we make some preparations:
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
A simple traversal will yield us the @@absolute_url view:
>>> view = self.folder.unrestrictedTraverse('testoid/@@absolute_url')
>>> view()
'http://nohost/test_folder_1_/testoid'
IAbsoluteURL also defines a breadcrumbs() method that returns a
simple Python structure:
>>> for crumb in view.breadcrumbs():
... info = crumb.items()
... info.sort()
... info
[('name', ''), ('url', 'http://nohost')]
[('name', 'test_folder_1_'), ('url', 'http://nohost/test_folder_1_')]
[('name', 'testoid'), ('url', 'http://nohost/test_folder_1_/testoid')]
This test assures and demonstrates that the absolute url stops
traversing through an object's parents when it has reached the
root object. In Zope 3 this is marked with the IContainmentRoot
interface:
>>> from zope.interface import directlyProvides, providedBy
>>> from zope.app.traversing.interfaces import IContainmentRoot
>>> directlyProvides(self.folder, IContainmentRoot)
>>> for crumb in view.breadcrumbs():
... info = crumb.items()
... info.sort()
... info
[('name', 'test_folder_1_'), ('url', 'http://nohost/test_folder_1_')]
[('name', 'testoid'), ('url', 'http://nohost/test_folder_1_/testoid')]
>>> directlyProvides(self.folder,
... providedBy(self.folder) - IContainmentRoot)
The absolute url view is obviously not affected by virtual hosting:
>>> request = self.app.REQUEST
>>> request['PARENTS'] = [self.folder.test_folder_1_]
>>> url = request.setServerURL(
... protocol='http', hostname='foo.bar.com', port='80')
>>> request.setVirtualRoot('')
>>> for crumb in view.breadcrumbs():
... info = crumb.items()
... info.sort()
... info
[('name', 'test_folder_1_'), ('url', 'http://foo.bar.com')]
[('name', 'testoid'), ('url', 'http://foo.bar.com/testoid')]
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# 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.
#
##############################################################################
"""Test adding views
$Id: test_adding.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
from Testing.ZopeTestCase import ZopeDocFileSuite
return ZopeDocFileSuite('adding.txt',
package="Products.Five.browser.tests")
if __name__ == '__main__':
framework()
##############################################################################
#
# 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.
#
##############################################################################
"""Test Default View functionality
$Id: test_defaultview.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_default_view():
"""
Test default view functionality
Let's register a couple of default views and make our stub classes
default viewable:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('defaultview.zcml', Products.Five.browser.tests)
Now let's add a couple of stub objects:
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.tests.testing.simplecontent import manage_addCallableSimpleContent
>>> from Products.Five.tests.testing.simplecontent import manage_addIndexSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> manage_addCallableSimpleContent(self.folder, 'testcall', 'TestCall')
>>> manage_addIndexSimpleContent(self.folder, 'testindex', 'TestIndex')
As a last act of preparation, we create a manager login:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
Test a simple default view:
>>> print http(r'''
... GET /test_folder_1_/testoid HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
HTTP/1.1 200 OK
...
The eagle has landed
This tests whether an existing ``index_html`` method is still
supported and called:
>>> print http(r'''
... GET /test_folder_1_/testindex HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
Default index_html called
Disabled __call__ overriding for now. Causese more trouble than it
fixes. Thus, no test here:
#>>> print http(r'''
#... GET /test_folder_1_/testcall HTTP/1.1
#... ''')
#HTTP/1.1 200 OK
#...
#Default __call__ called
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import FunctionalDocTestSuite
return FunctionalDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# 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 i18n framework
$Id: test_i18n.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_zpt_i18n():
"""
Test i18n functionality in ZPTs
>>> configure_zcml = '''
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:browser="http://namespaces.zope.org/browser"
... xmlns:i18n="http://namespaces.zope.org/i18n">
... <configure package="Products.Five.tests">
... <i18n:registerTranslations directory="locales" />
... </configure>
... <configure package="Products.Five.browser.tests">
... <browser:page
... for="OFS.interfaces.IFolder"
... template="i18n.pt"
... name="i18n.html"
... permission="zope2.View"
... />
... </configure>
... </configure>'''
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_string(configure_zcml)
In order to be able to traverse to the PageTemplate view, we need
a traversable object:
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
We tell Zope to translate the messages by passing the
``Accept-Language`` header which is processed by the
``IUserPreferredLangauges`` adapter:
>>> print http(r'''
... GET /test_folder_1_/testoid/@@i18n.html HTTP/1.1
... Accept-Language: de
... ''')
HTTP/1.1 200 OK
...
<html>
<body>
<p>Dies ist eine Nachricht</p>
<p>Dies ist eine explizite Nachricht</p>
<p>Dies sind 4 Nachrichten</p>
<p>Dies sind 4 explizite Nachrichten</p>
<table summary="Dies ist ein Attribut">
</table>
<table summary="Explizite Zusammenfassung"
title="Expliziter Titel">
</table>
</body>
</html>
...
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import FunctionalDocTestSuite
from zope.testing.doctest import ELLIPSIS
return FunctionalDocTestSuite(optionflags=ELLIPSIS)
if __name__ == '__main__':
framework()
##############################################################################
#
# 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.
#
##############################################################################
"""Test browser menus
$Id: test_menu.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_menu():
"""
Test menus
Before we can start we need to set up a few things. For menu
configuration, we have to start a new interaction:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("meta.zcml", Products.Five)
>>> zcml.load_config("permissions.zcml", Products.Five)
>>> zcml.load_config('menu.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.security import newInteraction
>>> newInteraction()
Now for some actual testing... Let's look up the menu we registered:
>>> from Products.Five.traversable import FakeRequest
>>> from zope.app.publication.browser import setDefaultSkin
>>> from zope.app.publisher.browser.menu import getMenu
>>> request = FakeRequest()
>>> setDefaultSkin(request)
>>> menu = getMenu('testmenu', self.folder, request)
It should have
>>> len(menu)
4
Sort menu items by title so we get a stable testable result:
>>> menu.sort(lambda x, y: cmp(x['title'], y['title']))
>>> from pprint import pprint
>>> pprint(menu[0])
{'action': u'@@cockatiel_menu_public.html',
'description': u'',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Page in a menu (public)'}
>>> pprint(menu[1])
{'action': u'seagull.html',
'description': u'This is a test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item'}
>>> pprint(menu[2])
{'action': u'parakeet.html',
'description': u'This is a test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item 2'}
>>> pprint(menu[3])
{'action': u'falcon.html',
'description': u'This is a test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item 3'}
Let's create a manager user account and log in.
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.login('manager')
>>> newInteraction()
>>> menu = getMenu('testmenu', self.folder, request)
We should get the protected menu items now:
>>> len(menu)
7
>>> menu.sort(lambda x, y: cmp(x['title'], y['title']))
>>> pprint(menu[0])
{'action': u'@@cockatiel_menu_protected.html',
'description': u'',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Page in a menu (protected)'}
>>> pprint(menu[1])
{'action': u'@@cockatiel_menu_public.html',
'description': u'',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Page in a menu (public)'}
>>> pprint(menu[2])
{'action': u'seagull.html',
'description': u'This is a protected test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Protected Test Menu Item'}
>>> pprint(menu[3])
{'action': u'falcon.html',
'description': u'This is a protected test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Protected Test Menu Item 2'}
>>> pprint(menu[4])
{'action': u'seagull.html',
'description': u'This is a test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item'}
>>> pprint(menu[5])
{'action': u'parakeet.html',
'description': u'This is a test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item 2'}
>>> pprint(menu[6])
{'action': u'falcon.html',
'description': u'This is a test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item 3'}
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# 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.
#
##############################################################################
"""Test browser pages
$Id: test_pages.py 18840 2005-10-23 09:47:10Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_ViewAcquisitionWrapping():
"""
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.login('manager')
>>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
>>> view is not None
True
>>> from Products.Five.browser.tests.pages import SimpleView
>>> isinstance(view, SimpleView)
True
>>> view()
'The eagle has landed'
This sucks, but we know it
>>> from Acquisition import aq_parent, aq_base
>>> aq_parent(view.context) is view
True
This is the right way to get the context parent
>>> view.context.aq_inner.aq_parent is not view
True
>>> view.context.aq_inner.aq_parent is self.folder
True
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
import unittest
from Testing.ZopeTestCase import installProduct, ZopeDocTestSuite
from Testing.ZopeTestCase import ZopeDocFileSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
installProduct('PythonScripts') # for Five.tests.testing.restricted
return unittest.TestSuite((
ZopeDocTestSuite(),
ZopeDocFileSuite('pages.txt', package='Products.Five.browser.tests'),
FunctionalDocFileSuite('pages_ftest.txt',
package='Products.Five.browser.tests')
))
return suite
if __name__ == '__main__':
framework()
##############################################################################
#
# 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.
#
##############################################################################
"""Test default view recursion
$Id: test_recurse.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_recursion():
"""
Test recursion
This test makes sure that recursion is avoided for view lookup.
First, we need to set up a stub interface...
>>> from zope.interface import Interface, implements
>>> class IRecurse(Interface):
... pass
...
and a class that is callable and has a view method:
>>> from OFS.Traversable import Traversable
>>> class Recurse(Traversable):
... implements(IRecurse)
... def view(self):
... return self()
... def __call__(self):
... return 'foo'
...
Now we make the class default viewable and register a default view
name for it:
>>> from Products.Five.fiveconfigure import classDefaultViewable
>>> classDefaultViewable(Recurse)
>>> from zope.component import provideAdapter
>>> from zope.publisher.interfaces.browser import IBrowserRequest
>>> from zope.component.interfaces import IDefaultViewName
>>> provideAdapter(u'view', (IRecurse, IBrowserRequest), IDefaultViewName)
Here comes the actual test:
>>> ob = Recurse()
>>> ob.view()
'foo'
>>> ob()
'foo'
Clean up adapter registry and monkey patches to classes:
>>> from zope.testing.cleanup import cleanUp
>>> cleanUp()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# 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.
#
##############################################################################
"""Test browser resources
$Id: test_resource.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
import unittest
from Testing.ZopeTestCase import installProduct, ZopeDocFileSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
installProduct('PythonScripts') # for Five.tests.testing.restricted
return unittest.TestSuite((
ZopeDocFileSuite('resource.txt',
package='Products.Five.browser.tests'),
FunctionalDocFileSuite('resource_ftest.txt',
package='Products.Five.browser.tests'),
))
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 browser pages
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
from Testing.ZopeTestCase import FunctionalDocFileSuite
return FunctionalDocFileSuite('skin.txt',
package='Products.Five.browser.tests')
if __name__ == '__main__':
framework()
##############################################################################
#
# 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.
#
##############################################################################
"""Test Five-traversable classes
$Id: test_traversable.py 17580 2005-09-15 16:05:47Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
class SimpleClass(object):
"""Class with no __bobo_traverse__."""
def test_traversable():
"""
Test the behaviour of Five-traversable classes.
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
``SimpleContent`` is a traversable class by default. Its fallback
traverser should raise NotFound when traversal fails. (Note: If
we return None in __fallback_traverse__, this test passes but for
the wrong reason: None doesn't have a docstring so BaseRequest
raises NotFoundError.)
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> print http(r'''
... GET /test_folder_1_/testoid/doesntexist HTTP/1.1
... ''')
HTTP/1.1 404 Not Found
...
Now let's take class which already has a __bobo_traverse__ method.
Five should correctly use that as a fallback.
>>> configure_zcml = '''
... <configure xmlns="http://namespaces.zope.org/zope"
... xmlns:meta="http://namespaces.zope.org/meta"
... xmlns:browser="http://namespaces.zope.org/browser"
... xmlns:five="http://namespaces.zope.org/five">
...
... <!-- make the zope2.Public permission work -->
... <meta:redefinePermission from="zope2.Public" to="zope.Public" />
...
... <five:traversable
... class="Products.Five.tests.testing.fancycontent.FancyContent"
... />
... <five:traversable
... class="Products.Five.browser.tests.test_traversable.SimpleClass"
... />
...
... <browser:page
... for="Products.Five.tests.testing.fancycontent.IFancyContent"
... class="Products.Five.browser.tests.pages.FancyView"
... attribute="view"
... name="fancyview"
... permission="zope2.Public"
... />
...
... </configure>'''
>>> zcml.load_string(configure_zcml)
>>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent
>>> info = manage_addFancyContent(self.folder, 'fancy', '')
In the following test we let the original __bobo_traverse__ method
kick in:
>>> print http(r'''
... GET /test_folder_1_/fancy/something-else HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
something-else
Of course we also need to make sure that Zope 3 style view lookup
actually works:
>>> print http(r'''
... GET /test_folder_1_/fancy/fancyview HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
Fancy, fancy
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
Verify that after cleanup, there's no cruft left from five:traversable::
>>> from Products.Five.browser.tests.test_traversable import SimpleClass
>>> hasattr(SimpleClass, '__bobo_traverse__')
False
>>> hasattr(SimpleClass, '__fallback_traverse__')
False
>>> from Products.Five.tests.testing.fancycontent import FancyContent
>>> hasattr(FancyContent, '__bobo_traverse__')
True
>>> hasattr(FancyContent.__bobo_traverse__, '__five_method__')
False
>>> hasattr(FancyContent, '__fallback_traverse__')
False
"""
def test_suite():
from Testing.ZopeTestCase import FunctionalDocTestSuite
return FunctionalDocTestSuite()
if __name__ == '__main__':
framework()
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<include file="meta.zcml" />
<include file="permissions.zcml" />
<include file="i18n.zcml" />
<include file="event.zcml"/>
<include file="deprecated.zcml"/>
<include package=".site" />
<include package=".browser" />
<include package=".form" />
<include package=".skin" />
<include package=".utilities" />
<include package="zope.app.event" />
<include package="zope.app.traversing" />
<!-- do 'traditional' traversing by default; needed by ZPT -->
<adapter
for="*"
factory=".traversable.FiveTraversable"
provides="zope.app.traversing.interfaces.ITraversable"
/>
<adapter
for="*"
factory="zope.app.traversing.adapters.Traverser"
provides="zope.app.traversing.interfaces.ITraverser"
/>
<adapter
for="*"
factory=".viewable.BrowserDefault"
provides=".interfaces.IBrowserDefault"
/>
<!-- this is really lying, but it's to please checkContainer -->
<five:implements class="OFS.ObjectManager.ObjectManager"
interface="zope.app.container.interfaces.IContainer" />
<!-- make Zope 2's REQUEST implement the right thing -->
<five:implements class="ZPublisher.HTTPRequest.HTTPRequest"
interface="zope.publisher.interfaces.browser.IBrowserRequest"
/>
</configure>
<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>
Zope Public License (ZPL) Version 2.1
-------------------------------------
A copyright notice accompanies this license document that
identifies the copyright holders.
This license has been certified as open source. It has also
been designated as GPL compatible by the Free Software
Foundation (FSF).
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the
following conditions are met:
1. Redistributions in source code must retain the
accompanying copyright notice, this list of conditions,
and the following disclaimer.
2. Redistributions in binary form must reproduce the accompanying
copyright notice, this list of conditions, and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
3. Names of the copyright holders must not be used to
endorse or promote products derived from this software
without prior written permission from the copyright
holders.
4. The right to distribute this software or to use it for
any purpose does not give you the right to use
Servicemarks (sm) or Trademarks (tm) of the copyright
holders. Use of them is covered by separate agreement
with the copyright holders.
5. If any files are modified, you must cause the modified
files to carry prominent notices stating that you changed
the files and the date of any change.
Disclaimer
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS''
AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL THE COPYRIGHT HOLDERS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
=================================
ZCML Directives supported by Five
=================================
Five tries to use the Zope 3 ZCML directives where possible, though
does sometimes subset the possible attributes. It also introduces a
few directives of its own under the ``five`` namespace.
Directives are listed per namespace, in alphabetic order.
zope ``http://namespaces.zope.org/zope``
========================================
adapter
-------
Hook an adapter factory to an interface.
content
-------
Declare interface and permissions on content object. Declares Zope 2
permissions.
permission
----------
Way to make Zope 2 permissions available to Five, ``title`` is
permission name.
redefinePermission
------------------
Redefine a permission in included ZCML as another one.
skin
----
Declare a skin, consisting of layers.
utility
-------
Declare a global utility.
interface
---------
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``
==============================================
page
----
Declare a page view for an interface. Permission is a Zope 2
permission.
pages
-----
Declare multiple page views for an interface. Permissions are Zope 2
permissions.
defaultView
-----------
Declare the name of the view that should be used for the default when viewing
the object; i.e. when the object is traversed to without a view.
defaultSkin
-----------
Declare the default skin used.
editform
--------
Create an edit form based on a schema.
addform
--------
Create an add form based on a schema.
layer
-----
Declare a layer.
menu
----
Declare a menu
menuItem, menuItems
-------------------
Declare menuItems
five ``http://namespaces.zope.org/five``
========================================
implements
----------
Make a class declare it implements an interface.
loadProducts
------------
Loads ZCML in all Zope 2 products. First processes all ``meta.zcml``
files, then processes all ``configure.zcml`` files.
loadProductsOverrides
---------------------
Loads overriding ZCML in all products (``overrides.zcml``).
traversable
-----------
Make a Zope 2 content class traversable in the Zope 3 manner using
Five. This is used to attached views, resources and other things to
Zope 2 objects.
defaultViewable
---------------
Make a Zope 2 content class use a Zope 3 default view when looking at
it without any paths appended to it. This works then instead of
``index_html`` in Zope 2.
sizable
-------
Retrieve size information for a Zope 2 content class via a Zope 3
style ``ISized`` adapter.
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.
deprecatedManageAddDelete
-------------------------
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
------------------
Loads all files with .pt extension in a directory as pages.
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.
Events in Zope 2.9
==================
Zope 2.9 (and Zope 2.8 when using Five 1.2) introduces a big change:
Zope 3 style container events.
With container events, you finally have the ability to react to things
happening to objects without have to subclass ``manage_afterAdd``,
``manage_beforeDelete`` or ``manage_afterClone``. Instead, you just have
to register a subscriber for the appropriate event, for instance
IObjectAddedEvent, and make it do the work.
Indeed, the old methods like ``manage_afterAdd`` are now deprecated, you
shouldn't use them anymore.
Let's see how to migrate your products.
Old product
-----------
Suppose that in an old product you have code that needs to register
through a central tool whenever a document is created. Or it could be
indexing itself. Or it could initialize an attribute according to its
current path. Code like::
class CoolDocument(...):
...
def manage_afterAdd(self, item, container):
self.mangled_path = mangle('/'.join(self.getPhysicalPath()))
getToolByName(self, 'portal_cool').registerCool(self)
super(CoolDocument, self).manage_afterAdd(item, container)
def manage_afterClone(self, item):
self.mangled_path = mangle('/'.join(self.getPhysicalPath()))
getToolByName(self, 'portal_cool').registerCool(self)
super(CoolDocument, self).manage_afterClone(item)
def manage_beforeDelete(self, item, container):
super(CoolDocument, self).manage_beforeDelete(item, container)
getToolByName(self, 'portal_cool').unregisterCool(self)
This would be the best practice in Zope 2.8. Note the use of ``super()``
to call the base class, which is often omitted because people "know"
that SimpleItem for instance doesn't do anything in these methods.
If you run this code in Zope 2.9, you will get deprecation warnings,
telling you that::
Calling Products.CoolProduct.CoolDocument.CoolDocument.manage_afterAdd
is deprecated when using Five, instead use event subscribers or mark
the class with <five:deprecatedManageAddDelete/>
Using five:deprecatedManageAddDelete
------------------------------------
The simplest thing you can do to deal with the deprecation warnings, and
have correct behavior, is to add in your products a ``configure.zcml``
file containing::
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<five:deprecatedManageAddDelete
class="Products.CoolProduct.CoolDocument.CoolDocument"/>
</configure>
This tells Zope that you acknowledge that your class contains deprecated
methods, and ask it to still call them in the proper manner. So Zope
will be sending events when an object is added, for instance, and in
addition call your old ``manage_afterAdd`` method.
One subtlety here is that you may have to modify you methods to just do
their work, and not call their super class. This is necessary because
proper events are already dispatched to all relevant classes, and the
work of the super class will be done trough events, you must not redo it
by hand. If you call the super class, you will get a warning, saying for
instance::
CoolDocument.manage_afterAdd is deprecated and will be removed in
Zope 2.11, you should use an IObjectAddedEvent subscriber instead.
The fact that you must "just do your work" is especially important for
the rare cases where people subclass the ``manage_afterAdd`` of object
managers like folders, and decided to reimplement recursion into the
children themselves. If you do that, then there will be two recursions
going on in parallel, the one done by events, and the one done by your
code. This would be bad.
Using subscribers
-----------------
In the long run, and before Zope 2.11 where ``manage_afterAdd`` and
friends will be removed, you will want to use proper subscribers.
First, you'll have to write a subscriber that "does the work", for
instance::
def addedCoolDocument(ob, event):
"""A Cool Document was added to a container."""
self.mangled_path = mangle('/'.join(self.getPhysicalPath()))
Note that we're not calling the ``portal_cool`` tool anymore, because
presumably this tool will also be modified to do its work through
events, and will have a similar subscriber doing the necessary
``registerCool``. Note also that here we don't care about the event, but
in more complex cases we would.
Now we have to register our subscriber for our object. To do that, we
need to "mark" our object through an interface. We can define in our
product's ``interfaces.py``::
from zope.interface import Interface, Attribute
class ICoolDocument(Interface):
"""Cool Document."""
mangled_path = Attribute("Our mangled path.")
...
Then the class CoolDocument is marked with this interface::
from zope.interface import implements
from Products.CoolProduct.interfaces import ICoolDocument
class CoolDocument(...):
implements(ICoolDocument)
...
Finally we must link the event and the interface to the subscriber using
zcml, so in ``configure.zcml`` we'll add::
...
<subscriber
for="Products.CoolProduct.interfaces.ICoolDocument
zope.app.container.interfaces.IObjectAddedEvent"
handler="Products.CoolProduct.CoolDocument.addedCoolDocument"
/>
...
And that's it, everything is plugged. Note that IObjectAddedEvent takes
care of both ``manage_afterAdd`` and ``manage_afterClone``, as it's sent
whenever a new object is placed into a container. However this won't
take care of moves and renamings, we'll see below how to do that.
Event dispatching
-----------------
When an IObjectEvent (from which all the events we're talking here
derive) is initially sent, it concerns one object. For instance, a
specific object is removed. The ``event.object`` attribute is this
object.
To be able to know about removals, we could just subscribe to the
appropriate event using a standard event subscriber. In that case, we'd
have to filter "by hand" to check if the object removed is of the type
we're interested in, which would be a chore. In addition, any subobjects
of the removed object wouldn't know what happens to them, and for
instance they wouldn't have any way of doing some cleanup before they
disappear.
To solve these two problems, Zope 3 has an additional mechanism by which
any IObjectEvent is redispatched using multi-adapters of the form ``(ob,
event)``, so that a subscriber can be specific about the type of object
it's interested in. Furthermore, this is done recursively for all
sublocations ``ob`` of the initial object. The ``event`` won't change
though, and ``event.object`` will still be the original object for which
the event was initially sent (this corresponds to ``self`` and ``item``
in the ``manage_afterAdd`` method -- ``self`` is ``ob``, and ``item`` is
``event.object``).
Understanding the hierarchy of events is important to see how to
subscribe to them.
* IObjectEvent is the most general. Any event focused on an object
derives from this.
* IObjectMovedEvent is sent when an object changes location or is
renamed. It is quite general, as it also encompasses the case where
there's no old location (addition) or no new location (removal).
* IObjectAddedEvent and IObjectRemovedEvent both derive from
IObjectMovedEvent.
* IObjectCopiedEvent is sent just after an object copy is made, but
this doesn't mean the object has been put into its new container yet,
so it doesn't have a location.
There are only a few basic use cases about what one wants to do with
respect to events (but you might want to read the full story in
Five/tests/event.txt).
The first use case is the one where the object has to be aware of its
path, like in the CoolDocument example above. That's strictly a Zope 2
concern, as Zope 3 has others ways to deal with this.
In Zope 2 an object has a new path through creation, copy or move
(rename is a kind of move). The events sent during these three
operations are varied: creation sends IObjectAddedEvent, copy sends
IObjectCopiedEvent then IObjectAddedEvent, and move sends
IObjectMovedEvent.
So to react to new paths, we have to subscribe to IObjectMovedEvent, but
this will also get us any IObjectRemovedEvent, which we'll have to
filter out by hand (this is unfortunate, and due to the way the Zope 3
interface hierarchy is organized). So to fix the CoolDocument
configuration we have to add::
def movedCoolDocument(ob, event):
"""A Cool Document was moved."""
if not IObjectRemovedEvent.providedBy(event):
addedCoolDocument(ob, event)
And replace the subscriber with::
...
<subscriber
for="Products.CoolProduct.interfaces.ICoolDocument
zope.app.container.interfaces.IObjectMovedEvent"
handler="Products.CoolProduct.CoolDocument.movedCoolDocument"
/>
...
The second use case is when the object has to do some cleanup when it is
removed from its parent. This used to be in ``manage_beforeDelete``, now
we can do the work in a ``removedCoolDocument`` method and just
subscribe to IObjectRemovedEvent. But wait, this won't take into account
moves... So in the same vein as above, we would have to write::
def movedCoolDocument(ob, event):
"""A Cool Document was moved."""
if not IObjectRemovedEvent.providedBy(event):
addedCoolDocument(ob, event)
if not IObjectAddedEvent.providedBy(event):
removedCoolDocument(ob, event)
The third use case is when your object has to stay registered with some
tool, for instance indexed in a catalog, or as above registered with
``portal_cool``. Here we have to know the old object's path to
unregister it, so we have to be called *before* it is removed. We'll use
``IObjectWillBe...`` events, that are sent before the actual operations
take place::
from OFS.interfaces import IObjectWillBeAddedEvent
def beforeMoveCoolDocument(ob, event):
"""A Cool Document will be moved."""
if not IObjectWillBeAddedEvent.providedBy(event):
getToolByName(ob, 'portal_cool').unregisterCool(ob)
def movedCoolDocument(ob, event):
"""A Cool Document was moved."""
if not IObjectRemovedEvent.providedBy(event):
getToolByName(ob, 'portal_cool').registerCool(ob)
...
And use an additional subscriber::
...
<subscriber
for="Products.CoolProduct.interfaces.ICoolDocument
OFS.interfaces.IObjectWillBeMovedEvent"
handler="Products.CoolProduct.CoolDocument.beforeMoveCoolDocument"
/>
...
This has to be done if the tool cannot react by itself to objects being
added and removed, which obviously would be better as it's ultimately
the tool's responsibility and not the object's.
Note that if having tests like::
if not IObjectWillBeAddedEvent.providedBy(event):
if not IObjectRemovedEvent.providedBy(event):
seems cumbersome (and backwards), it is also possible to check what kind
of event you're dealing with using::
if event.oldParent is not None:
if event.newParent is not None:
(However be careful, the ``oldParent`` and ``newParent`` are the old and
new parents *of the original object* for which the event was sent, not
of the one to which the event was redispatched using the
multi-subscribers we have registered.)
The ``IObjectWillBe...`` events are specific to Zope 2 (and imported
from ``OFS.interfaces``). Zope 3 doesn't really need them, as object
identity is often enough.
=============
Five features
=============
Five features are mostly Zope 3 features, though Five has some extras,
and some limitations.
Zope 3 interfaces
=================
Everything from the ``zope.interface`` package works. Zope 3
interfaces are the foundation of the component architecture, and also
the foundation of schemas.
ZCML
====
ZCML is the Zope Configuration Markup Language, an XML application.
Zope 3 (and Five) code consists of a lot of components that can be
plugged together using ZCML.
If you put a ``site.zcml`` in the home directory of your Zope
instance, this is the root of the ZCML tree. An example of
``site.zcml`` is in ``site.zcml.in``. If you don't place a
``site.zcml``, Five falls back on ``fallback.zcml``.
ZCML in Five has special directive, ``five:loadProducts``, to load the
ZCML (``meta.zcml``, ``configure.zcml``) of all installed Zope 2
products, if available.
Another special directive, ``five:loadProductsOverrides`` is available
to load any overriding ZCML (``overrides.zcml``) in these products. In
the ``overrides.zcml`` you can override existing views or adapters, in
this or in other products.
Adapters
========
You can use adapters in Five, just like in Zope 3.
Zope 3 views
============
Zope 3 views work in Five, including layers and skins. To make them
work however, you need to make a Zope 2 class "traversable". This can
be done by using the ``five:traversable`` directive in ZCML.
Page templates
==============
Five before release 0.3 used to use Zope 3's page template engine, but
in the interests of increased compatibility with Zope 2, we've
switched to using Zope 2's. There should be no real difference to any
code, however. We may decide to switch back to Zope 3's engine again
eventually if we can resolve the compatibility issues.
One thing to be aware of is that the page template engine runs
completely in trusted mode, just like Python code. That is, as soon as
the page template engine is running, no Zope 2 or Zope 3 security
checks are made.
Edit and add forms
==================
Five supports edit and add forms. Typical Zope 3 examples of these
should work.
Security declarations
=====================
Five aims to eradicate ``declareProtected``, ``ClassSecurityInfo`` and
``initializeClass`` from your Zope 2 code.
In order to do this, Five provides the Zope 3 way of declaring
permissions from ZCML, but uses the Zope 2 mechanisms to actually set
them. To declare permissions for methods and templates on views you
use the ``permission`` attribute on the ``browser:page`` directive,
and specify a Zope 2 permission (given a Zope 3 name). You can find a
list of these permissions in ``permissions.zcml`` in Five. The
permission check takes place before the view is executed.
The ``content`` directive can also be used to declare permissions on
Zope 2 content classes. Note however that these permissions will be
ignored by views anyway, as they are trusted -- it only serves to
protect directly exposed methods on content classes (the python
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(...)
Internationalization
====================
Translation
-----------
Five registers its own translation service mockup with the Page
Templates machinery and prevents any other product from also doing so.
That means, Five always assumes control over ZPT i18n. When a certain
domain has not been registered the Zope 3 way, Five's translation
service will see that the utility lookup fails and use the next
available fallback translation service. In case of no other
translation service installed, that is just a dummy fallback. In case
you have Localizer and PTS installed, it falls back to that.
To register Zope 3 style translation domains, use the following ZCML
statement::
<i18n:registerTranslations directory="locales" />
where the 'i18n' prefix is bound to the
http://namespaces.zope.org/i18n namespace identifier. The directory
(in this case 'locales') should conform to the `standard gettext
locale directory layout`__.
.. __: http://www.gnu.org/software/gettext/manual/html_chapter/gettext_10.html#SEC148
Preferred languages and negotiation
-----------------------------------
Fallback translation services such as PTS and Localizer have their own
way of determining the user-preferred languages and negotiating that
with the available languages in the respective domain. Zope 3
translation domains typically adapt the request to
IUserPreferredLanguages to get a list of preferred languages; then
they use the INegotiator utility to negotiate between the preferred
and available languages.
The goal of the sprint was to allow both fallback translation services
(PTS, Localizer) and Zope 3 translation domains come to the same
conclusion regarding which language should be chosen. The use case is
that you have a site running Localizer or PTS and a bunch of "old"
products using either one of those for translation. Now you have an
additional, "new" Five-based product using Zope 3 translation domains.
Most of the time, a page contains user messages from more than one
domain, so you would all domains be translated to the same language.
Adjusting behaviour to your environment
---------------------------------------
The default behaviour for choosing languages in Five is the one of
Zope 3: analyze the Accept-Language HTTP header and nothing more. In
addition, Five providees ``IUserPreferredLanguages`` adapters for
Localizer and PTS that choose languages the exact same way Localizer
or PTS would. So, if you're using Five in a Localizer-environment,
you need this in your product's ``overrides.zcml``:
<adapter
for="zope.publisher.interfaces.http.IHTTPRequest"
provides="zope.i18n.interfaces.IUserPreferredLanguages"
factory="Products.Five.i18n.LocalizerLanguages"
/>
If you're using PTS:
<adapter
for="zope.publisher.interfaces.http.IHTTPRequest"
provides="zope.i18n.interfaces.IUserPreferredLanguages"
factory="Products.Five.i18n.PTSLanguages"
/>
That way Zope 3 translation domains will always come to the same
conclusion regarding the language as your original translation service
would.
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``.
Five, the Zope 3 in Zope 2 project
==================================
What is Five?
-------------
Five is a Zope 2 product that allows you to integrate Zope 3
technologies into Zope 2, today. Among others, it allows you to use
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.
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.
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
ships with Zope 3, Five 1.3 is included in Zope 2.9.
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
2 and Zope 3 developers to jump in. We're looking for cooperation
between different Zope 2 projects so that this can be a foundational
system for us all.
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
that will be included in Zope 2.8.1. Download it here:
http://codespeak.net/z3/five/release/Five-1.0.2.tgz
2005-05-31 -- We have released Five 1.0.1! This is also the version
that will be included in Zope 2.8.0. Download it here:
http://codespeak.net/z3/five/release/Five-1.0.1.tgz
2005-04-27 -- We have released Five 1.0! Download it here:
http://codespeak.net/z3/five/release/Five-1.0.tgz
And view changes here:
http://codespeak.net/z3/five/CHANGES.html
2005-03-11 -- We have released Five 0.3! Download it here:
http://codespeak.net/z3/five/release/Five-0.3.tgz
2004-09-24 -- Five 0.2b is released. Download it here:
http://codespeak.net/z3/five/release/Five-0.2b.tgz
2004-07-30 -- We have released Five 0.1! Download it here:
http://codespeak.net/z3/five/release/Five-0.1.tgz
Joining the project
-------------------
Five is kindly hosted on codespeak.net, and is part of the larger
*Zope 3 Base* project that offers an approachable area for
developers of Zope 3 related software.
Five has a mailing list:
http://codespeak.net/mailman/listinfo/z3-five
We're also active on IRC, at ``#z3-base`` on freenode.
Five is hosted in a subversion repository on codespeak.net. You can
browse this on the web here:
http://codespeak.net/svn/z3/Five/
You can check out Five using the following subversion command::
svn co http://codespeak.net/svn/z3/Five/trunk Five
There's also a checkins mailing list for the Z3 project, here:
http://codespeak.net/mailman/listinfo/z3-checkins
If you want checkin access, please join the z3-five mailing list or
the ``#z3-base`` IRC channel, and ask us there.
We hope to hear from you!
===========
Five Manual
===========
Introduction
------------
Five's goal is to let you, the Zope 2 developer, use Zope 3 code in
Zope 2. Our aim is to make as much of Zope 3 code work in Zope 2 as
possible, while integrating it with Zope 2.
Five can be used inside your current Zope 2 project. The benefits are:
* availability of Zope 3 technologies in Zope 2 like the component
architecture and declarative configuration.
* you can gradually evolve your Zope 2 project so it is better
positioned for the migration to Zope 3.
* you start learning about Zope 3 right now, preparing yourself better
for the future. Since Zope 3 is open to contributions, you could
even influence your future for the better.
Five can also be used to develop new Zope 2 products, though depending
on your deployment requirements it might in that case make more sense
to develop for Zope 3 directly.
Five is only useful on the Python (Product) level in Zope 2, not from
within the Zope Management Interface. Five makes no attempt to provide
a user interface, but is aimed squarely at the Python developer.
Zope 3 interfaces
-----------------
Interfaces?
===========
An interface is simply a description of what an object provides to the
world, i.e. its public attribute and methods. It looks very much like
a class, but contains no implementation::
from zope.interface import Interface
# by convention, all interfaces are prefixed with ``I``
class IElephant(Interface):
"""An elephant is a big object that barely fits in the cupboard.
"""
def getAngerLevel():
"""Anger level, maximum of 100.
The longer the elephant has been in the cupboard, the angrier.
"""
def isInCupboard():
"""Returns true if the elephant is indeed in cupboard.
"""
def trunkSmash(target):
"""Smash the target with trunk.
The anger level determines the force of the hit.
"""
def trample(target):
"""Trample the target.
The anger level determines the rate of flattening of the target.
"""
A concrete class somewhere can now claim that it implements the
interface (i.e. its instance will provide the interface)::
class PinkElephant:
# this says all instances of this class provide IElephant
implements(IElephant)
def getAngerLevel(self):
return 0 # this elephant is peaceful
def isInCupboard(self):
return False # it's never in a cupboard but can be found in bottles
def trunkSmash(self, target):
target.tickle()
def trample(self, target):
target.patOnHead()
Interfaces themselves are good for a number of reasons:
* They provide API documentation.
* They help you make explicit the design of your application,
hopefully improving it.
* If an object provides an interface, that object is considered to be
a *component*. This means you can use Zope 3's component
architecture with these objects.
In order to use Five, you'll have to make your objects provide
interfaces. Sometimes, you cannot change the code of class (as you are
not the maintainer), but you still want to make it implement an
interface. Five provides a ZCML directive to do this::
<five:implements class="tolkien.Oliphant"
implements="interfaces.IElephant" />
Interfaces in Zope 2 versus Zope 3
==================================
You may be familiar with Zope 2's way of declaring interfaces. Zope 2
has used the ``__implements__`` class attribute for interface
declarations. Zope 2 cannot detect Zope 3 interfaces and the Zope 3
machinery cannot detect Zope 2 interfaces. This is a good thing, as
Zope 2 has no way to deal with Zope 3 interfaces, and Zope 3 cannot
comprehend Zope 2 interfaces. This means you can safely make a class
declare both a Zope 2 and Zope 3 interface independently from each
other. It's a rare case where you need this though; you're usually
better off just switching to ``implements()`` for your application if
you are using Five.
Switching from Zope 2 interfaces to Zope 3 interfaces is easy -- just
make your interfaces inherit from ``zope.interface.Interface`` instead
of ``Interface.Interface`` (or ``Interface.Base``). Next, change all
``__implements__`` to ``implements()``.
This should get you going and your application may very well still
work. Later on, you will also have to change calls to
``isImplementedBy`` and such in your application to ``providedBy``, as
``isImplementedBy`` has been deprecated (you'll see the
DeprecationWarnings in your Zope log).
Adapters
--------
From a Python programmer's perspective, the immediate thing that Five
brings to do the table are adapters. This section goes through some
demo code to explain how everything is tied
together. ``demo/FiveDemo`` is a demo Product you can install and
examine that has all the presented here together.
Zope 3 adapters depend on Zope 3 interfaces. To create a Zope 3
interface you need to subclass it from
``zope.interface.Interface``. Here is an example::
from zope.interface import Interface
class IMyInterface(Interface):
"""This is a Zope 3 interface.
"""
def someMethod():
"""This method does amazing stuff.
"""
Now to make some class declare that it implements this interface, you
need to use the ``implements()`` function in the class::
from zope.interface import implements
from interfaces import IMyInterface
class MyClass:
implements(IMyInterface)
def someMethod(self):
return "I am alive! Alive!"
For an explanation of the relation of Zope 3 interfaces to Zope 2
interfaces, see below.
Now let's set up the interface that we are adapting to::
class INewInterface(Interface):
"""The interface we adapt to.
"""
def anotherMethod():
"""This method does more stuff.
"""
Next we'll work on the class that implements the adapter. The
requirement to make a class that is an adapter is very simple; you
only need to take a context object as the constructor. The context
object is the object being adapted. An example::
from zope.interface import implements
from interfaces import INewInterface
class MyAdapter:
implements(INewInterface)
def __init__(self, context):
self.context = context
def anotherMethod(self):
return "We have adapted: %s" % self.context.someMethod()
Next, we hook it all up using zcml. If the classes are in a module
called ``classes.py`` and the interfaces in a module called
``interfaces.py``, we can declare ``MyAdapter`` to be an adapter for
``IMyInterface`` to ``INewInterface`` like this (in a file called
``configure.zcml``)::
<configure xmlns="http://namespaces.zope.org/zope">
<adapter
for=".interfaces.IMyInterface"
provides=".interfaces.INewInterface"
factory=".classes.MyAdapter" />
</configure>
Five will automatically pickup ``configure.zcml`` when it's placed in
the product's directory. Any object that provides ``IMyInterface``
can now be adapted to ``INewInterface``, like this::
from classes import MyClass
from interfaces import INewInterface
object = MyClass()
adapted = INewInterface(object)
print adapted.anotherMethod()
Views in Five
-------------
This section will give a brief introduction on how to use the five
view system. ``demo/FiveViewsDemo`` is a demo Product you can install
and examine that has all the presented here tied together, please
consult it for more details. ``tests/products/FiveTest`` actually
contains a more detailed set of test views, trying a number of
features. Finally, read up on the way Zope 3 does it. While Five is a
subset of Zope 3 functionality and has been adapted to work with Zope
2, much of Zope 3's documentation still works.
Five enables you to create views for your own objects, or even built-in
Zope objects, as long as two things are the case:
* The object provides an Zope 3 interface, typically through its class.
* The object (typically its class) is made Zope 3 traversable. This
allows Zope 3 views, resources and other things to be attached to a
Zope 2 object.
Typically you give your classes an interface using the ``implements``
directive in the class body::
class MyClass:
implements(ISomeInterface)
For existing objects that you cannot modify this is not
possible. Instead, we provide a ZCML directive to accomplish this. As
an example, to make Zope's ``Folder`` (and all its subclasses)
implement ``IFolder`` (an interface you defined), you can do the
following in ZCML::
<five:implements class="OFS.Folder.Folder"
interface=".interfaces.IFolder" />
``five`` in this case refers to the XML namespace for Five,
``http://namespace.zope.org/five``.
We've provided another ZCML directive to make an object
traversable. To make your MyClass traversable, let's assume it is in
``mymodule``, in the same package as the zcml file we are editing::
<five:traversable class=".mymodule.MyClass" />
To continue our example, to make Zope's ``Folder`` traversable through
Five, you need to declare this in ZCML as well:
<five:traversable class="OFS.Folder.Folder"/>
This makes Folder traverse in the Zope 3 way first, looking up views
and other things, and then if they cannot be found, fall back on the
regular Zope 2 traversal. It does this by overriding the
``__bobo_traverse__`` hook. Old hooks that are already in place in an
object will be stored and become the secondary fallback. This allows
the ZMI to work still, but new views can be added on the fly.
Note that at the point of writing it is only possible to make an object
viewable through ZCML if this object does not already provide its own
``__bobo_traverse__`` method.
Views in Five are simple classes. The only requirements for a Five
view class are:
* They need an ``__init__()`` that take a context and a request
attribute. Typically this comes from a base class, such as
``BrowserView``.
* They need to be initialized with the Zope 2 security system, as
otherwise you cannot use the view.
* This also means they need to be part of the Zope 2 acquisition
system, as this is a requirement for Zope 2 security to
function. The ``BrowserView`` base class, available from
``Products.Five``, already inherits from ``Acquisition.Explicit`` to
make this be the case. Acquisition is explicit so no attributes can
be acquired by accident.
An example of a simple view::
from Products.Five import BrowserView
class SimpleFolderView(BrowserView):
security = ClassSecurityInfo()
security.declarePublic('eagle')
def eagle(self):
"""Test
"""
return "The eagle has landed: %s" % self.context.objectIds()
InitializeClass(SimpleFolderView)
Note that it is not a good idea to give a view class its own
``index_html``, as this confuses Five's view lookup machinery.
As you can see, the class is initialized with the Zope 2 security
system. This view uses methods in Python, but you can also use other
Zope 2 mechanisms such as ``PageTemplateFile``.
Finally, we need to hook up the pages through ZCML::
<browser:page
for=".interfaces.IFolder"
class=".browser.SimpleFolderView"
attribute="eagle"
name="eagle.txt"
permission="zope2.ViewManagementScreens"
/>
``browser`` in this refers to the XML namespace of Zope 3 for browser
related things; it's
``http://namespace.zope.org/browser``. ``permission`` declares the
Zope 2 permission needs in order to access this view. The file
``permissions.zcml`` in Five contains a mapping of Zope 2 permissions
to their Zope 3 names.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
%page
Motto
It was the dawn of the third age of Zope. The Five project was a dream given form. Its goal: to use Zope 3 technologies in Zope 2.7 by creating a Zope 2 product where Zope 3 and Zope 2 could work out their differences peacefully.
(Babylon 5 season 1 intro, creatively quoted)
%page
Motto 2
The Law of Fives states simply that: ALL THINGS HAPPEN IN FIVES, OR ARE DIVISIBLE BY OR ARE MULTIPLES OF FIVE, OR ARE SOMEHOW DIRECTLY OR INDIRECTLY RELATED TO FIVE.
THE LAW OF FIVES IS NEVER WRONG.
(Principia Discordia)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
The problem
We're using Zope 2 in production
Zope 2 is showing its age
Zope 3 has better ways to do things
But can't just switch, we have customers!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Benefits of using Zope 3 in Zope 2
Able to use Zope 3 technologies right away
Don't reinvent the wheel/APIs
Better prepared for Zope 3 transition
Evolution, not revolution
Convergence, not divergence
%page
What works now?
Interfaces (zope.interface)
Schema (zope.schema)
ZCML (zope.configuration)
Adapters (zope.component)
Views, including layers, skins (zope.component)
%page
Brief demo
Show ZCML, adapters and views in action
%page
Next?
Utilities (global ones should work)
Forms
Views (improve the current system)
Who knows?
%page
Plans
Relicense from BSD to generic ZPL 2.1
Move from CVS at Infrae into SVN at codespeak.net
Convergence; join us!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
Five future directions
What might happen
%page
Unique id service support
Foundation is there in form of events
Unfortunately implementation can not be the same
Objects are referenced differently in Zope 3
Local services/utilities are difficult in Zope 2
Could lead to Zope 3 catalog support
%page
Page template engine improvements
Right now we already use Zope 3 page templates
These are unicode-only (and work with plain ascii)
To support Zope 2 content, need classic non-ascii string support
Issue in Plone, not in Silva (heh heh)
%page
Zope 2.8 and ZODB 3.3
Should be able to work much better with new-style objects
Things like local services/utilities might be doable
%page
Integration with Plone, CMF, Silva, UnionCMS etc
Sharing a common base is good
Zope 3 is good
That base should be Zope 3
Five can help us start sharing today
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
Interfaces, adapters
What are interfaces?
What are adapters?
Why?
A very quick introduction
%page
Actually
This tutorial applies to Zope 3 as much as to Five
Indication Five reached its goal in this area
%page
Interface example
%size 4, fore "blue"
from zope.interface import Interface
class IElephant(Interface):
"""An elephant is a big grey animal.
"""
def getAngerLevel():
"Return anger level on scale 0 (placid) to 10 (raging)"
def trample(target):
"Trample the target."
def trumpet():
"Make loud noise with trunk."
%page
Interface example, continued
%size 4, fore "blue"
from zope.interface import implements
class AfricanElephant:
implements(IElephant)
def getAngerLevel(self):
return 5 # always pretty stroppy
def trample(self, target):
target.flatten()
def trumpet(self):
return "A terrible racket"
%page
Interfaces
Interfaces are about the what, not the how
Interfaces don't do anything, they just describe
Code can state what interfaces objects provide
Code can introspect whether objects provide interfaces
%page
Why interfaces?
They are documentation
Make multiple implementations of same interface easier
Allows you to program against published APIs
Allow glueing by interface
%page
Component architecture
zope.component part of Zope 3
allows glueing together of components in various ways
a component is an object which provides an interface
a Zope 2 object with a Zope 3 interface is a component
%page
Adapters, example
%size 4, fore "blue"
class INoiseMaker(Interface):
"""Something that makes noise.
"""
def makeNoise():
"Returns the noise that's made."
%page
Adapters, example continued
%size 4, fore "blue"
class ElephantNoiseMaker:
"""Adapts elephant to noise maker.
"""
implements(INoiseMaker)
def __init__(self, context):
self.context = context
def makeNoise(self):
return self.context.trumpet()
%page
Adapters, example continued 2
%size 4, fore "blue"
>>> elephant = AfricanElephant()
>>> noise_maker = ElephantNoiseMaker(elephant)
>>> print noise_maker.makeNoise()
'A terrible racket'
%page
Adapters
Add behavior to object without changing its class
More manageable than mixins
Define new behavior in terms of other behavior
%page
Adapters, continued
Less framework burden on adapted objects
They only need to be a component
Adapted doesn't know about the adapter
Adapter is a component itself
%page
Adapter lookup
We just manually glued the adapter to the adapted
What if we had INoiseMaker adapters for other objects?
We want a universal way to say: give me a INoiseMaker for this object
This allows use to write more generic code
%page
Adapter lookup, example
%size 4, fore "blue"
for animal in animal_farm:
noise_maker = INoiseMaker(animal)
print noise_maker.makeNoise()
%page
Adapter glueing
System need to be informed what can adapt what
Zope Configuration Markup Language (ZCML) is used for that
%page
ZCML example
%size 4, fore "blue"
<configure xmlns="http://namespaces.zope.org/zope">
<adapter
for=".module.IElephant"
provides=".module.INoiseMaker"
factory=".module.ElephantNoiseMaker" />
<adapter
for=".other.IChicken"
provides=".module.INoiseMaker"
factory=".other.ChickenNoiseMaker" />
</configure>
%page
ZCML, what we just said
The adapter ElephantNoiseMaker adapts any object that provides IElephant to a INoiseMaker
The adapter ChickenNoiseMaker adapts any object that provides IChicken to a INoiseMaker
%page
This works in Zope 2 with Five
This works in Zope 2 with Five
Your objects just need to be components (provide Zope 3 interfaces)
Your ZCML goes into configure.zcml in your product
That's it
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
An Introduction to Five
Why Five?
What is Five?
Where are we, where are we going?
%page
Motto
It was the dawn of the third age of Zope. The Five project was a dream given form. Its goal: to use Zope 3 technologies in Zope 2.7 by creating a Zope 2 product where Zope 3 and Zope 2 could work out their differences peacefully.
(Babylon 5 season 1 intro, creatively quoted)
%page
Motto 2
The Law of Fives states simply that: ALL THINGS HAPPEN IN FIVES, OR ARE DIVISIBLE BY OR ARE MULTIPLES OF FIVE, OR ARE SOMEHOW DIRECTLY OR INDIRECTLY RELATED TO FIVE.
THE LAW OF FIVES IS NEVER WRONG.
(Principia Discordia)
%page
The problem
We're using Zope 2 in production
Zope 2 is showing its age
Zope 3 has better ways to do things
But can't just switch, we have codebases, customers!
%page
Benefits of using Zope 3 in Zope 2
Able to use Zope 3 technologies right away
Don't reinvent the wheel/APIs
Better prepared for Zope 3 transition
Evolution, not revolution
Convergence, not divergence (this is important)
%page
Divergence
Infrae created Silva, Nuxeo CPS, etc
Everybody else started using Plone (why?!)
I want to use cool Plone technology
Silva is cool too, you may want to use it
I don't want to have to reinvent every wheel (just some)
%page
What's stopping us from sharing?
Zope 2 components are hard to share between apps
Even CMF components need work to share
Especially if you don't use CMF... (Silva)
Zope 2 framework burden is making it hard
Clean Python code is easier to share
%page
Convergence
Unify our diverse efforts
Zope 3 allows you to write Python, less framework sacrifices
Zope 3 allows the glueing of components
Zope 3 is the future
Five makes some of the future available today
%page
What works now? - an overview
Interfaces
Schema
ZCML
Adapters
Views, including layers, skins
%page
What works now, continued
Zope 3 page template engine
Traversal, resources
Zope 2 security from ZCML
Events
Beginnings of forms machinery
%page
Progress made since June
Initial announcement at Europython
As promised, moved to SVN at codespeak.net
Got website, mailing list
People joined the project
%page
Progress made since June, continued
Lots of excellent contributions!
Much better view infrastructure (traversal)
ZCML's interaction with Zope 2 products much improved
UnionCMS and other projects are starting to use it!
%page
The Zope 3 Base
Five is part of the Zope 3 Base
Zope 3 Base - All Your Bobobase Are Belong To Us
Possibly the cutest Zope 3 website anywhere
http://codespeak.net/z3
%page
Zope 3 Base
%center
%image "z3-banner.png"
%page
Zope 3 Base, continued
Second area of Zope 3 related development
Equivalent of Plone collective, for Zope 3
More freewheeling than dev.zope.org
Less freewheeling than Plone collective, however
Cuter than both
%page
Evolution: Five-ification
Five is not just for new Zope 2 projects
Five can interoperate with existing Zope 2 applications
Five in Plone - Flon
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
Five Misc Topics
A number of as-yet uncategorized Five-related topics
%page
Resources
Various kinds of resources available
File, image, page template resource
Accessible through ++resource++ namespace
%page
Resources, example
%size 4, fore "blue"
<browser:resource
image="z3base.png"
name="z3base.png"
permission="zope2.ViewManagementScreens"
/>
%page
Resources
Any Five traversable object now has can be used to get to resource
url: path/to/object/++resource++z3base.png
Jim says this is not exactly Zope 3 as it ruins caching
%page
ZCML
ZCML can optionally be put in etc/site.zcml
If not, Five will automatically use included zcml
This zcml is in skel
%page
ZCML continued
ZCML loads any configure.zcml in all products
This is driven by five:loadProducts in site.zcml
Overrides are possible in override.zcml
This is driven by five:loadProductsOverrides in site.zcml
%page
Bridging interfaces
"Bride of Frankenzope"
Utility functions in bridge.py
Can convert Zope 2 interface to Zope 3 interface
%page
Events
Can instruct Zope 2 object to send Zope 3 style events using five:sendEvents
These events sent upon copy/move/rename in Zope 2
IObjectMovedEvent, IObjectAddedEvent, IObjectCopiedEvent, IObjectRemovedEvent
Can set up functions to subscribe to these events
%page
Content directive and permissions
Use content directive to declare Zope 2 permissions Zope 3 style
Declare permissions from ZCML, no more declareProtected()
Your classes look cleaner as a result
%page
Macros
Zope 3 way to aggregate macros into single object
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
Views with Five
What are views?
Why?
How to make them work?
%page
Actually
This tutorial contains only a few Five specific bits
Otherwise it applies to Zope 3 as much as to Five
The Five specific bits are mainly some extra ZCML directives
These are in their own ZCML namespace
%page
Page example: overview.pt
%size 4, fore "blue"
<html>
<body>
<p tal:content="context/objectIds"></p>
</body>
</html>
%page
Page example: configure.zcml
%size 4, fore "blue"
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<five:traversable
class="OFS.Folder.Folder"
/>
<browser:page
for="OFS.interfaces.IFolder"
name="overview.html"
template="overview.pt"
permission="zope2.ViewManagementScreens"
/>
</configure>
%page
What works now
some/folder/overview.html
%page
Hooking up the page, explanation
Much like hooking up an adapter
Adapter provides new interface (API) for developer
View provides new interface (UI) for user
Only five-specific thing is making Folder Zope-3 traversable
Well, and the Zope 2 permission.
%page
Hooking up a page, with class
We need some helper methods
Very similar to the way you'd use Python scripts in Zope 2
%page
View class example: overview2.pt
%size 4, fore "blue"
<html>
<body>
<p tal:content="view/reversedIds"></p>
</body>
</html>
%page
View class example: browser.py
%size 4, fore "blue"
from Products.Five import BrowserView
class Overview(BrowserView):
def reversedIds(self):
result = []
for id in self.context.objectIds():
l = list(id)
l.reverse()
reversed_id = ''.join(l)
result.append(reversed_id)
return result
%page
Example: configure.zcml
%size 4, fore "blue"
<browser:page
for="OFS.interfaces.IFolder"
name="overview2.html"
template="overview2.pt"
permission="zope2.ViewManagementScreens"
class=".browser.Overview"
/>
%page
A note on security
There is none: both python code and ZPT are trusted
Only checks are happening on the outside
Performance benefit
Advantage of simplicity
%page
Publishing an attribute
Expose python method on view directly to the web
%page
Attribute example: browser.py
%size 4, fore "blue"
def directlyPublished(self):
return "This is directly published"
%page
Attribute example: configure zcml
%size 4, fore "blue"
<browser:page
for="OFS.interfaces.IFolder"
name="test.html"
class=".browser.Overview"
attribute="directlyPublished"
permission="zope2.ViewManagementScreens"
/>
%page
Publishing multiple pages
Convenience directive: browser:pages
%page
Multiple pages example
%size 4, fore "blue"
<browser:pages
for="OFS.interfaces.IFolder"
class=".browser.NewExample"
permission="zope2.ViewManagementScreens"
>
<browser:page
name="one.html"
template="one.pt"
/>
<browser:page
name="two.html"
attribute="two"
/>
</browser:pages>
%page
Default view for object
We can now set views that are named
What if we traverse to the object itself?
Use five:defaultViewable and browser:defaultView
%page
Uh oh
This doesn't seem to work yet with Zope folders. Sidnei, help!
So we'll try it with a custom SimpleItem-based object
%page
DefaultView example
%size 4, fore "blue"
<five:defaultViewable class=".democontent.DemoContent" />
<browser:defaultView
for=".democontent.IDemoContent"
name="someview.html" />
%page
Conclusions
This works much the same way as Zope 3 does too
Can supplement existing view systems in Zope 2
Five specific code is mostly isolated in five:traversable and five:defaultViewable
\ No newline at end of file
##############################################################################
#
# 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.
#
##############################################################################
# this is a package.
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five"
>
<five:traversable class="OFS.Folder.Folder" />
<browser:resource
image="z3base.png"
name="z3base.png"
permission="zope2.ViewManagementScreens"
/>
</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.
#
##############################################################################
import module, other
def initialize(context):
print "*" * 70
module.demo_manual_adaptation()
other.demo_animal_farm()
print "*" * 70
<configure xmlns="http://namespaces.zope.org/zope">
<adapter
for=".module.IElephant"
provides=".module.INoiseMaker"
factory=".module.ElephantNoiseMaker" />
<adapter
for=".other.IChicken"
provides=".module.INoiseMaker"
factory=".other.ChickenNoiseMaker" />
</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
class IElephant(Interface):
"""An elephant is a big grey animal.
"""
def getAngerLevel():
"Return anger level on scale 0 (placid) to 10 (raging)"
def trample(target):
"Trample the target."
def trumpet():
"Make loud noise with trunk."
def terribleRacket():
return "A terrible racket"
class AfricanElephant:
implements(IElephant)
def getAngerLevel(self):
return 5 # always pretty stroppy
def trample(self, target):
target.flatten()
def trumpet(self):
return "A terrible racket"
class INoiseMaker(Interface):
"""Something that makes noise.
"""
def makeNoise():
"Returns the noise that's made."
class ElephantNoiseMaker:
"""Adapts elephant to noise maker.
"""
implements(INoiseMaker)
def __init__(self, context):
self.context = context
def makeNoise(self):
return self.context.trumpet()
def demo_manual_adaptation():
elephant = AfricanElephant()
noise_maker = ElephantNoiseMaker(elephant)
print noise_maker.makeNoise()
##############################################################################
#
# 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 module import INoiseMaker, IElephant, AfricanElephant
class IChicken(Interface):
def getConfusionLevel():
"Get the confusion level of this chicken, 0 asleep, 10 frantic"
def cluck():
"""Return clucking sound of the chicken.
"""
class Chicken:
implements(IChicken)
def __init__(self, confusion_level):
self._confusion_level = confusion_level
def getConfusionLevel(self):
return self._confusion_level
def cluck(self):
return ' '.join(["cluck"] * self.getConfusionLevel())
class IndianElephant:
implements(IElephant)
def __init__(self, anger_level):
self._anger_level = anger_level
def hit(self):
"""Hit the indian elephant with a stick.
"""
if self._anger_level <= 10:
self._anger_level += 1
def getAngerLevel(self):
return self._anger_level
def trumpet(self):
return "t" + ("o" * self._anger_level) + "t"
class ChickenNoiseMaker:
implements(INoiseMaker)
def __init__(self, context):
self.context = context
def makeNoise(self):
return self.context.cluck()
def demo_animal_farm():
animal_farm = [Chicken(5), AfricanElephant(),
Chicken(3), IndianElephant(3)]
for animal in animal_farm:
noise_maker = INoiseMaker(animal)
print noise_maker.makeNoise()
This directory contains Five tutorial products.
<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>
import random
from democontent import DemoContent
class Overview:
"""View for overview.
"""
def reversedIds(self):
result = []
for id in self.context.objectIds():
l = list(id)
l.reverse()
reversed_id = ''.join(l)
result.append(reversed_id)
return result
def directlyPublished(self):
return "This is directly published"
class NewExample:
"""View for new example.
"""
def helpsWithOne(self):
return random.randrange(10)
def two(self):
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()
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<five:traversable class="OFS.Application.Application"/>
<!-- OFS.Folder.Folder views -->
<five:traversable class="OFS.Folder.Folder"/>
<browser:page
for="OFS.interfaces.IFolder"
name="overview.html"
template="overview.pt"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="OFS.interfaces.IFolder"
name="overview2.html"
template="overview2.pt"
class=".browser.Overview"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="OFS.interfaces.IFolder"
name="test.html"
class=".browser.Overview"
attribute="directlyPublished"
permission="zope2.ViewManagementScreens"
/>
<browser:pages
for="OFS.interfaces.IFolder"
class=".browser.NewExample"
permission="zope2.ViewManagementScreens"
>
<browser:page
name="one.html"
template="one.pt"
/>
<browser:page
name="two.html"
attribute="two"
/>
</browser:pages>
<!-- .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
for=".democontent.IDemoContent"
name="someview.html"
template="someview.pt"
permission="zope2.ViewManagementScreens"
/>
<five:defaultViewable class=".democontent.DemoContent"/>
<browser:defaultView
for=".democontent.IDemoContent"
name="someview.html"
/>
</configure>
from zope.interface import Interface, implements
from OFS.SimpleItem import SimpleItem
class IDemoContent(Interface):
def mymethod():
"""Return some text.
"""
class DemoContent(SimpleItem):
implements(IDemoContent)
def __init__(self, id, title):
self.id = id
self.title = title
def mymethod(self):
return "Hello world"
<html>
<body>
<p>The random number is <span tal:replace="view/helpsWithOne">0</span></p>
</body>
</html>
<html>
<body>
<p tal:content="context/objectIds"></p>
</body>
</html>
<html>
<body>
<p tal:content="view/reversedIds"></p>
</body>
</html>
<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) 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.
#
##############################################################################
"""
Use 'structured monkey patching' to enable zope.app.container event sending for
Zope 2 objects.
$Id: eventconfigure.py 19413 2005-11-02 14:37:52Z efge $
"""
import warnings
from OFS.subscribers import deprecatedManageAddDeleteClasses
def setContainerEvents():
warnings.warn("Using <five:containerEvents/> is deprecated (it is now "
"the default), it will be removed in Zope 2.11",
DeprecationWarning)
def setDeprecatedManageAddDelete(class_):
"""Instances of the class will still see their old methods called."""
deprecatedManageAddDeleteClasses.append(class_)
def cleanUp():
deprecatedManageAddDeleteClasses[:] = []
def containerEvents(_context):
_context.action(
discriminator=None,
callable=setContainerEvents,
args=(),
)
def deprecatedManageAddDelete(_context, class_):
_context.action(
discriminator=('five:deprecatedManageAddDelete', class_),
callable=setDeprecatedManageAddDelete,
args=(class_,),
)
from zope.testing.cleanup import addCleanUp
addCleanUp(cleanUp)
del addCleanUp
##############################################################################
#
# 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 17361 2005-09-08 12:13:30Z regebro $
"""
import os
import sys
import glob
import warnings
from logging import getLogger
import App.config
import Products
from zope.interface import classImplements, classImplementsOnly, implementedBy
from zope.interface.interface import InterfaceClass
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.metaconfigure import adapter
from zope.app.security.interfaces import IPermission
from viewable import Viewable
from traversable import Traversable
from bridge import fromZ2Interface
from browser.metaconfigure import page
debug_mode = App.config.getConfiguration().debug_mode
LOG = getLogger('Five')
def findProducts():
import Products
from types import ModuleType
products = []
for name in dir(Products):
product = getattr(Products, name)
if isinstance(product, ModuleType) and hasattr(product, '__file__'):
products.append(product)
return products
def handleBrokenProduct(product):
if debug_mode:
# Just reraise the error and let Zope handle it.
raise
# Not debug mode. Zope should continue to load. Print a log message:
# XXX It would be really cool if we could make this product appear broken
# in the control panel. However, all attempts to do so has failed from my
# side. //regebro
exc = sys.exc_info()
LOG.error('Could not import Product %s' % product.__name__, exc_info=exc)
def loadProducts(_context):
products = findProducts()
# first load meta.zcml files
for product in products:
zcml = os.path.join(os.path.dirname(product.__file__), 'meta.zcml')
if os.path.isfile(zcml):
try:
xmlconfig.include(_context, zcml, package=product)
except: # Yes, really, *any* kind of error.
handleBrokenProduct(product)
# now load their configure.zcml
for product in products:
zcml = os.path.join(os.path.dirname(product.__file__),
'configure.zcml')
if os.path.isfile(zcml):
try:
xmlconfig.include(_context, zcml, package=product)
except: # Yes, really, *any* kind of error.
handleBrokenProduct(product)
def loadProductsOverrides(_context):
for product in findProducts():
zcml = os.path.join(os.path.dirname(product.__file__),
'overrides.zcml')
if os.path.isfile(zcml):
try:
xmlconfig.includeOverrides(_context, zcml, package=product)
except: # Yes, really, *any* kind of error.
handleBrokenProduct(product)
def implements(_context, class_, interface):
for interface in interface:
_context.action(
discriminator = None,
callable = classImplements,
args = (class_, interface)
)
_context.action(
discriminator = None,
callable = provideInterface,
args = (interface.__module__ + '.' + interface.getName(),
interface)
)
def isFiveMethod(m):
return hasattr(m, '__five_method__')
_traversable_monkies = []
def classTraversable(class_):
# If a class already has this attribute, it means it is either a
# subclass of Traversable or was already processed with this
# directive; in either case, do nothing... except in the case were
# the class overrides __bobo_traverse__ instead of getting it from
# a base class. In this case, we suppose that the class probably
# didn't bother with the base classes __bobo_traverse__ anyway and
# we step __fallback_traverse__.
if hasattr(class_, '__five_traversable__'):
if (hasattr(class_, '__bobo_traverse__') and
isFiveMethod(class_.__bobo_traverse__)):
return
if hasattr(class_, '__bobo_traverse__'):
if not isFiveMethod(class_.__bobo_traverse__):
# if there's an existing bobo_traverse hook already, use that
# as the traversal fallback method
setattr(class_, '__fallback_traverse__', class_.__bobo_traverse__)
if not hasattr(class_, '__fallback_traverse__'):
setattr(class_, '__fallback_traverse__',
Traversable.__fallback_traverse__.im_func)
setattr(class_, '__bobo_traverse__',
Traversable.__bobo_traverse__.im_func)
setattr(class_, '__five_traversable__', True)
# remember class for clean up
_traversable_monkies.append(class_)
def traversable(_context, class_):
_context.action(
discriminator = None,
callable = classTraversable,
args = (class_,)
)
_defaultviewable_monkies = []
def classDefaultViewable(class_):
# If a class already has this attribute, it means it is either a
# subclass of DefaultViewable or was already processed with this
# directive; in either case, do nothing... except in the case were
# the class overrides the attribute instead of getting it from
# a base class. In this case, we suppose that the class probably
# didn't bother with the base classes attribute anyway.
if hasattr(class_, '__five_viewable__'):
if (hasattr(class_, '__browser_default__') and
isFiveMethod(class_.__browser_default__)):
return
if hasattr(class_, '__browser_default__'):
# if there's an existing __browser_default__ hook already, use that
# as the fallback
if not isFiveMethod(class_.__browser_default__):
setattr(class_, '__fallback_default__', class_.__browser_default__)
if not hasattr(class_, '__fallback_default__'):
setattr(class_, '__fallback_default__',
Viewable.__fallback_default__.im_func)
setattr(class_, '__browser_default__',
Viewable.__browser_default__.im_func)
setattr(class_, '__five_viewable__', True)
# remember class for clean up
_defaultviewable_monkies.append(class_)
def defaultViewable(_context, class_):
_context.action(
discriminator = None,
callable = classDefaultViewable,
args = (class_,)
)
def createZope2Bridge(zope2, package, name):
# Map a Zope 2 interface into a Zope3 interface, seated within 'package'
# as 'name'.
z3i = fromZ2Interface(zope2)
if name is not None:
z3i.__dict__['__name__'] = name
z3i.__dict__['__module__'] = package.__name__
setattr(package, z3i.getName(), z3i)
def bridge(_context, zope2, package, name=None):
# Directive handler for <five:bridge> directive.
# N.B.: We have to do the work early, or else we won't be able
# to use the synthesized interface in other ZCML directives.
createZope2Bridge(zope2, package, name)
# Faux action, only for conflict resolution.
_context.action(
discriminator = (zope2,),
)
def pagesFromDirectory(_context, directory, module, for_=None,
layer=IDefaultBrowserLayer, permission='zope.Public'):
if isinstance(module, basestring):
module = _context.resolve(module)
_prefix = os.path.dirname(module.__file__)
directory = os.path.join(_prefix, directory)
if not os.path.isdir(directory):
raise ConfigurationError(
"Directory %s does not exist" % directory
)
for fname in glob.glob(os.path.join(directory, '*.pt')):
name = os.path.splitext(os.path.basename(fname))[0]
page(_context, name=name, permission=permission,
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
def killMonkey(class_, name, fallback, attr=None):
"""Die monkey, die!"""
method = getattr(class_, name, None)
if isFiveMethod(method):
original = getattr(class_, fallback, None)
if original is not None:
delattr(class_, fallback)
if original is None or isFiveMethod(original):
try:
delattr(class_, name)
except AttributeError:
pass
else:
setattr(class_, name, original)
if attr is not None:
try:
delattr(class_, attr)
except (AttributeError, KeyError):
pass
def untraversable(class_):
"""Restore class's initial state with respect to traversability"""
killMonkey(class_, '__bobo_traverse__', '__fallback_traverse__',
'__five_traversable__')
def undefaultViewable(class_):
"""Restore class's initial state with respect to being default
viewable."""
killMonkey(class_, '__browser_default__', '__fallback_default__',
'__five_viewable__')
def unregisterClass(class_):
delattr(class_, 'meta_type')
try:
delattr(class_, 'icon')
except AttributeError:
pass
def cleanUp():
global _traversable_monkies
for class_ in _traversable_monkies:
untraversable(class_)
_traversable_monkies = []
global _defaultviewable_monkies
for class_ in _defaultviewable_monkies:
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
addCleanUp(cleanUp)
del addCleanUp
##############################################################################
#
# 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 ZCML directive schemas
$Id: fivedirectives.py 12884 2005-05-30 13:10:41Z philikon $
"""
from zope.interface import Interface
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 Bool
from zope.schema import ASCII
from zope.schema import TextLine
class IImplementsDirective(Interface):
"""State that a class implements something.
"""
class_ = GlobalObject(
title=u"Class",
required=True
)
interface = Tokens(
title=u"One or more interfaces",
required=True,
value_type=GlobalObject()
)
class ITraversableDirective(Interface):
"""Make instances of class traversable publically.
This can be used to browse to pages, resources, etc.
Traversal can be controlled by registering an ITraverser adapter.
"""
class_ = GlobalObject(
title=u"Class",
required=True
)
class IDefaultViewableDirective(Interface):
"""Make instances of class viewable publically.
The default view is looked up using a IBrowserDefault adapter.
"""
class_ = GlobalObject(
title=u"Class",
required=True
)
class ISizableDirective(Interface):
"""Make instances of class send events.
"""
class_ = GlobalObject(
title=u"Class",
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):
"""Bridge from a Zope 2 interface to an equivalent Zope3 interface.
"""
zope2 = GlobalObject(
title=u"Zope2",
required=True
)
package = GlobalObject(
title=u"Target package",
required=True
)
name = PythonIdentifier(
title=u"Zope3 Interface name",
description=u"If not supplied, the new interface will have the same "
u"name as the source interface.",
required=False
)
class IPagesFromDirectoryDirective(IBasicResourceInformation):
"""Register each file in a skin directory as a page resource
"""
for_ = GlobalObject(
title=u"The interface this view is for.",
required=False
)
module = GlobalObject(
title=u"Module",
required=True
)
directory = TextLine(
title=u"Directory",
description=u"The directory containing the resource data.",
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
)
##############################################################################
#
# 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.
#
##############################################################################
"""Add and edit views
$Id: __init__.py 15514 2005-08-02 16:37:34Z yuppie $
"""
import sys
from datetime import datetime
import Acquisition
import transaction
from zope.event import notify
from zope.schema.interfaces import ValidationError
from zope.publisher.browser import isCGI_NAME
from zope.i18n.interfaces import IUserPreferredCharsets
from zope.app.location.interfaces import ILocation
from zope.app.location import LocationProxy
from zope.app.form.utility import setUpEditWidgets, applyWidgetsChanges
from zope.app.form.browser.submit import Update
from zope.app.form.interfaces import WidgetsError, MissingInputError
from zope.app.form.utility import setUpWidgets, getWidgetsData
from zope.app.form.interfaces import IInputWidget, WidgetsError
from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.app.i18n import ZopeMessageFactory as _
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
class EditView(BrowserView):
"""Simple edit-view base class
Subclasses should provide a schema attribute defining the schema
to be edited.
"""
errors = ()
update_status = None
label = ''
charsets = None
# Fall-back field names computes from schema
fieldNames = property(lambda self: getFieldNamesInOrder(self.schema))
# Fall-back template
generated_form = ZopeTwoPageTemplateFile('edit.pt')
def __init__(self, context, request):
BrowserView.__init__(self, context, request)
self._processInputs()
self._setPageEncoding()
self._setUpWidgets()
def _setUpWidgets(self):
adapted = self.schema(self.context)
if adapted is not self.context:
if not ILocation.providedBy(adapted):
adapted = LocationProxy(adapted)
adapted.__parent__ = self.context
self.adapted = adapted
setUpEditWidgets(self, self.schema, source=self.adapted,
names=self.fieldNames)
# taken from zope.publisher.browser.BrowserRequest
def _decode(self, text):
"""Try to decode the text using one of the available charsets."""
if self.charsets is None:
envadapter = IUserPreferredCharsets(self.request)
self.charsets = envadapter.getPreferredCharsets() or ['utf-8']
for charset in self.charsets:
try:
text = unicode(text, charset)
break
except UnicodeError:
pass
return text
def _processInputs(self):
request = self.request
for name, value in request.form.items():
if (not (isCGI_NAME(name) or name.startswith('HTTP_'))
and isinstance(value, str)):
request.form[name] = self._decode(value)
def _setPageEncoding(self):
"""Set the encoding of the form page via the Content-Type header.
ZPublisher uses the value of this header to determine how to
encode unicode data for the browser."""
envadapter = IUserPreferredCharsets(self.request)
charsets = envadapter.getPreferredCharsets() or ['utf-8']
self.request.RESPONSE.setHeader(
'Content-Type', 'text/html; charset=%s' % charsets[0])
def setPrefix(self, prefix):
for widget in self.widgets():
widget.setPrefix(prefix)
def widgets(self):
return [getattr(self, name+'_widget')
for name in self.fieldNames]
def changed(self):
# This method is overridden to execute logic *after* changes
# have been made.
pass
def update(self):
if self.update_status is not None:
# We've been called before. Just return the status we previously
# computed.
return self.update_status
status = ''
content = self.adapted
if Update in self.request.form.keys():
changed = False
try:
changed = applyWidgetsChanges(self, self.schema,
target=content, names=self.fieldNames)
# We should not generate events when an adapter is used.
# That's the adapter's job.
if changed and self.context is self.adapted:
notify(ObjectModifiedEvent(content))
except WidgetsError, errors:
self.errors = errors
status = _("An error occurred.")
transaction.abort()
else:
setUpEditWidgets(self, self.schema, source=self.adapted,
ignoreStickyValues=True,
names=self.fieldNames)
if changed:
self.changed()
# XXX: Needs locale support:
#formatter = self.request.locale.dates.getFormatter(
# 'dateTime', 'medium')
#status = _("Updated on ${date_time}",
# mapping={'date_time':
# formatter.format(datetime.utcnow())})
status = _("Updated on ${date_time}",
mapping={'date_time': str(datetime.utcnow())})
self.update_status = status
return status
class AddView(EditView):
"""Simple edit-view base class.
Subclasses should provide a schema attribute defining the schema
to be edited.
"""
def _setUpWidgets(self):
setUpWidgets(self, self.schema, IInputWidget, names=self.fieldNames)
def update(self):
if self.update_status is not None:
# We've been called before. Just return the previous result.
return self.update_status
if self.request.form.has_key(Update):
self.update_status = ''
try:
data = getWidgetsData(self, self.schema, names=self.fieldNames)
self.createAndAdd(data)
except WidgetsError, errors:
self.errors = errors
self.update_status = _("An error occurred.")
return self.update_status
self.request.response.redirect(self.nextURL())
return self.update_status
def create(self, *args, **kw):
"""Do the actual instantiation."""
# hack to please typical Zope 2 factories, which expect id and title
args = ('tmp_id', 'Temporary title') + args
return self._factory(*args, **kw)
def createAndAdd(self, data):
"""Add the desired object using the data in the data argument.
The data argument is a dictionary with the data entered in the form.
"""
args = []
if self._arguments:
for name in self._arguments:
args.append(data[name])
kw = {}
if self._keyword_arguments:
for name in self._keyword_arguments:
if name in data:
kw[str(name)] = data[name]
content = self.create(*args, **kw)
adapted = self.schema(content)
errors = []
if self._set_before_add:
for name in self._set_before_add:
if name in data:
field = self.schema[name]
try:
field.set(adapted, data[name])
except ValidationError:
errors.append(sys.exc_info()[1])
if errors:
raise WidgetsError(*errors)
notify(ObjectCreatedEvent(content))
content = self.add(content)
adapted = self.schema(content)
if self._set_after_add:
for name in self._set_after_add:
if name in data:
field = self.schema[name]
try:
field.set(adapted, data[name])
except ValidationError:
errors.append(sys.exc_info()[1])
if errors:
raise WidgetsError(*errors)
return content
def add(self, content):
return self.context.add(content)
def nextURL(self):
return self.context.nextURL()
<html metal:use-macro="context/@@standard_macros/page"
i18n:domain="zope">
<body>
<div metal:fill-slot="body">
<div metal:define-macro="addform">
<form action="." tal:attributes="action request/URL" method="post"
enctype="multipart/form-data">
<div metal:define-macro="formbody">
<h3 tal:condition="view/label"
tal:content="view/label"
metal:define-slot="heading"
>Add something</h3>
<p tal:define="status view/update"
tal:condition="status"
tal:content="status" />
<p tal:condition="view/errors" i18n:translate="">
There are <strong tal:content="python:len(view.errors)"
i18n:name="num_errors">6</strong> input errors.
</p>
<div metal:define-slot="extra_info" tal:replace="nothing">
</div>
<div class="row" metal:define-slot="extra_top" tal:replace="nothing">
<div class="label">Extra top</div>
<div class="label"><input type="text" style="width:100%" /></div>
</div>
<div metal:use-macro="context/@@form_macros/widget_rows" />
<div class="separator"></div>
<div class="row"
metal:define-slot="extra_bottom" tal:replace="nothing">
<div class="label">Extra bottom</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div class="separator"></div>
</div>
<br/><br/>
<div class="row">
<div class="controls"><hr />
<input type='submit' value='Refresh'
i18n:attributes='value refresh-button' />
<input type='submit' value='Add' name='UPDATE_SUBMIT'
i18n:attributes='value add-button' />
<span tal:condition="context/nameAllowed|nothing" tal:omit-tag="">
&nbsp;&nbsp;<b i18n:translate="">Object Name</b>&nbsp;&nbsp;
<input type='text' name='add_input_name'
tal:attributes="value context/contentName" />
</span>
</div>
</div>
<div class="row" metal:define-slot="extra_buttons" tal:replace="nothing">
</div>
<div class="separator"></div>
</form>
</div>
</div>
</body>
</html>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<include package="zope.app.form.browser" />
<browser:page
for="*"
name="form_macros"
permission="zope2.View"
class=".macros.FormMacros"
allowed_interface="zope.interface.common.mapping.IItemMapping"
/>
</configure>
\ No newline at end of file
<tal:tag condition="view/update"/>
<html metal:use-macro="context/@@standard_macros/view"
i18n:domain="zope">
<body>
<div metal:fill-slot="body">
<div metal:define-macro="body">
<form action="." tal:attributes="action request/URL" method="POST"
enctype="multipart/form-data">
<div metal:define-macro="formbody">
<h3 tal:condition="view/label"
tal:content="view/label"
metal:define-slot="heading"
>Edit something</h3>
<p tal:define="status view/update"
tal:condition="status"
tal:content="status" />
<p tal:condition="view/errors" i18n:translate="">
There are <strong tal:content="python:len(view.errors)"
i18n:name="num_errors">6</strong> input errors.
</p>
<div metal:define-slot="extra_info" tal:replace="nothing">
</div>
<div class="row"
metal:define-slot="extra_top" tal:replace="nothing">
<div class="label">Extra top</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div metal:use-macro="context/@@form_macros/widget_rows" />
<div class="separator"></div>
<div class="row"
metal:define-slot="extra_bottom" tal:replace="nothing">
<div class="label">Extra bottom</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div class="separator"></div>
</div>
<div class="row">
<div class="controls">
<input type="submit" value="Refresh"
i18n:attributes="value refresh-button" />
<input type="submit" name="UPDATE_SUBMIT" value="Change"
i18n:attributes="value submit-button"/>
</div>
</div>
<div class="row" metal:define-slot="extra_buttons" tal:replace="nothing">
</div>
<div class="separator"></div>
</form>
</div>
</div>
</body>
</html>
##############################################################################
#
# 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.
#
##############################################################################
"""Form macros
$Id: macros.py 12884 2005-05-30 13:10:41Z philikon $
"""
from Products.Five.skin.standardmacros import StandardMacros
# copy of zope.app.form.browser.macros.FormMacros
class FormMacros(StandardMacros):
macro_pages = ('widget_macros', 'addform_macros')
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta">
<meta:directives namespace="http://namespaces.zope.org/browser">
<meta:complexDirective
name="editform"
schema="zope.app.form.browser.metadirectives.IEditFormDirective"
handler=".metaconfigure.EditFormDirective"
>
<meta:subdirective
name="widget"
schema="zope.app.form.browser.metadirectives.IWidgetSubdirective"
/>
</meta:complexDirective>
<meta:complexDirective
name="addform"
schema="zope.app.form.browser.metadirectives.IAddFormDirective"
handler=".metaconfigure.AddFormDirective"
>
<meta:subdirective
name="widget"
schema="zope.app.form.browser.metadirectives.IWidgetSubdirective"
/>
</meta:complexDirective>
</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.
#
##############################################################################
"""Edit form directives
$Id: metaconfigure.py 19283 2005-10-31 17:43:51Z philikon $
"""
import ExtensionClass
from Globals import InitializeClass as initializeClass
from zope.interface import Interface
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app import zapi
from zope.app.publisher.browser.menumeta import menuItemDirective
from zope.app.form.browser.metaconfigure import BaseFormDirective
from zope.app.container.interfaces import IAdding
from zope.app.i18n import ZopeMessageFactory as _
from Products.Five.form import EditView, AddView
from Products.Five.metaclass import makeClass
from Products.Five.security import protectClass
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Products.Five.browser.metaconfigure import makeClassForTemplate
def EditViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_, fields,
fulledit_path=None, fulledit_label=None, menu=u''):
class_ = makeClassForTemplate(template, globals(), used_for=schema,
bases=bases)
class_.schema = schema
class_.label = label
class_.fieldNames = fields
class_.fulledit_path = fulledit_path
if fulledit_path and (fulledit_label is None):
fulledit_label = "Full edit"
class_.fulledit_label = fulledit_label
class_.generated_form = ZopeTwoPageTemplateFile(default_template)
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)
initializeClass(class_)
class FiveFormDirective(BaseFormDirective):
def _processWidgets(self):
if self._widgets:
customWidgetsObject = makeClass(
'CustomWidgetsMixin', (ExtensionClass.Base,), self._widgets)
self.bases = self.bases + (customWidgetsObject,)
class EditFormDirective(FiveFormDirective):
view = EditView
default_template = 'edit.pt'
title = _('Edit')
def _handle_menu(self):
if self.menu:
menuItemDirective(
self._context, self.menu, self.for_ or self.schema,
'@@' + self.name, self.title, permission=self.permission,
layer=self.layer)
def __call__(self):
self._processWidgets()
self._handle_menu()
self._context.action(
discriminator=self._discriminator(),
callable=EditViewFactory,
args=self._args(),
kw={'menu': self.menu},
)
def AddViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_,
fields, content_factory, arguments,
keyword_arguments, set_before_add, set_after_add,
menu=u''):
class_ = makeClassForTemplate(template, globals(), used_for=schema,
bases=bases)
class_.schema = schema
class_.label = label
class_.fieldNames = fields
class_._factory = content_factory
class_._arguments = arguments
class_._keyword_arguments = keyword_arguments
class_._set_before_add = set_before_add
class_._set_after_add = set_after_add
class_.generated_form = ZopeTwoPageTemplateFile(default_template)
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)
initializeClass(class_)
class AddFormDirective(FiveFormDirective):
view = AddView
default_template = 'add.pt'
for_ = IAdding
# default add form information
description = None
content_factory = None
arguments = None
keyword_arguments = None
set_before_add = None
set_after_add = None
def _handle_menu(self):
if self.menu or self.title:
if (not self.menu) or (not self.title):
raise ValueError("If either menu or title are specified, "
"they must both be specified")
# Add forms are really for IAdding components, so do not use
# for=self.schema.
menuItemDirective(
self._context, self.menu, self.for_, '@@' + self.name,
self.title, permission=self.permission, layer=self.layer,
description=self.description)
def _handle_arguments(self, leftover=None):
schema = self.schema
fields = self.fields
arguments = self.arguments
keyword_arguments = self.keyword_arguments
set_before_add = self.set_before_add
set_after_add = self.set_after_add
if leftover is None:
leftover = fields
if arguments:
missing = [n for n in arguments if n not in fields]
if missing:
raise ValueError("Some arguments are not included in the form",
missing)
optional = [n for n in arguments if not schema[n].required]
if optional:
raise ValueError("Some arguments are optional, use"
" keyword_arguments for them",
optional)
leftover = [n for n in leftover if n not in arguments]
if keyword_arguments:
missing = [n for n in keyword_arguments if n not in fields]
if missing:
raise ValueError(
"Some keyword_arguments are not included in the form",
missing)
leftover = [n for n in leftover if n not in keyword_arguments]
if set_before_add:
missing = [n for n in set_before_add if n not in fields]
if missing:
raise ValueError(
"Some set_before_add are not included in the form",
missing)
leftover = [n for n in leftover if n not in set_before_add]
if set_after_add:
missing = [n for n in set_after_add if n not in fields]
if missing:
raise ValueError(
"Some set_after_add are not included in the form",
missing)
leftover = [n for n in leftover if n not in set_after_add]
self.set_after_add += leftover
else:
self.set_after_add = leftover
def __call__(self):
self._processWidgets()
self._handle_menu()
self._handle_arguments()
self._context.action(
discriminator=self._discriminator(),
callable=AddViewFactory,
args=self._args()+(self.content_factory, self.arguments,
self.keyword_arguments,
self.set_before_add, self.set_after_add),
kw={'menu': self.menu},
)
<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)
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five"
xmlns:i18n="http://namespaces.zope.org/i18n"
i18n_domain="formtest">
<!-- make the zope2.Public permission work -->
<meta:redefinePermission from="zope2.Public" to="zope.Public" />
<!-- browser forms -->
<five:traversable class=".schemacontent.FieldContent" />
<browser:editform
schema=".schemacontent.IFieldContent"
for=".schemacontent.IFieldContent"
name="edit.html"
label="Edit Field Content"
permission="zope2.Public"
/>
<five:traversable class=".schemacontent.ComplexSchemaContent" />
<browser:editform
schema=".schemacontent.IComplexSchemaContent"
for=".schemacontent.IComplexSchemaContent"
name="edit.html"
permission="zope2.Public"
class=".schemacontent.ComplexSchemaView"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IObject"
provides="zope.app.form.interfaces.IInputWidget"
factory="Products.Five.form.objectwidget.ObjectWidget"
permission="zope.Public"
/>
<!-- With a widget override -->
<browser:editform
schema=".schemacontent.IFieldContent"
for=".schemacontent.IFieldContent"
name="widgetoverride.html"
permission="zope2.Public"
>
<widget
field="description"
class="zope.app.form.browser.TextAreaWidget"
/>
</browser:editform>
<browser:addform
schema=".schemacontent.IFieldContent"
content_factory=".schemacontent.FieldContent"
name="addfieldcontent.html"
label="Add Field Content"
permission="zope2.Public"
/>
<browser:addform
schema=".schemacontent.IFieldContent"
content_factory=".schemacontent.FieldContent"
name="addwidgetoverride.html"
permission="zope2.Public">
<widget
field="description"
class="zope.app.form.browser.TextAreaWidget"
/>
</browser:addform>
<browser:addform
schema=".schemacontent.IFieldContent"
content_factory=".schemacontent.FieldContent"
name="protectedaddform.html"
permission="zope2.ViewManagementScreens"
/>
<i18n:registerTranslations directory="locales"/>
</configure>
Testing forms
=============
Before we can begin, we need to set up a few things. We need a
manager account:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
We need to configure all of Five for the functional test:
>>> import Products.Five.form.tests
>>> from Products.Five import zcml
>>> zcml.load_config('configure.zcml', package=Products.Five)
>>> zcml.load_config('configure.zcml', package=Products.Five.form.tests)
Finally, we need to setup a traversable folder. Otherwise, Five won't
get to to do its view lookup:
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'ftf')
Add forms
---------
We can add objects to containers (object managers) through add forms.
An unprotected form can be accessed with anonymously:
>>> print http(r"""
... GET /test_folder_1_/ftf/+/addfieldcontent.html HTTP/1.1
... """, handle_errors=False)
HTTP/1.1 200 OK
...
For a protected one we need a manager account:
>>> print http(r"""
... GET /test_folder_1_/ftf/+/protectedaddform.html HTTP/1.1
... Authorization: Basic manager:r00t
... """, handle_errors=False)
HTTP/1.1 200 OK
...
otherwise we will fail to access it (N.B: this test will fail on Zope 2.8.1,
which incorrectly ignored its 'handle_errors' argument):
>>> print http(r"""
... GET /test_folder_1_/ftf/+/protectedaddform.html HTTP/1.1
... """, handle_errors=False)
Traceback (most recent call last):
...
Unauthorized: ...
Now let's add a piece of our sample content object to test more things
on it:
>>> print http(r"""
... POST /test_folder_1_/ftf/+/addfieldcontent.html HTTP/1.1
... Authorization: Basic manager:r00t
... Content-Type: multipart/form-data; boundary=---------------------------968064918930967154199105236
... Content-Length: 527
...
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.title"
...
... title
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.description"
...
...
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Add
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="add_input_name"
...
... edittest
... -----------------------------968064918930967154199105236--
... """, handle_errors=False)
HTTP/1.1 302 Moved Temporarily
...
Location: http://localhost/test_folder_1_/ftf/manage_main
...
Having added this piece of content, we can access it under its URL:
>>> print http(r"""
... GET /test_folder_1_/ftf/edittest HTTP/1.1
... """, handle_errors=False)
HTTP/1.1 200 OK
...
We can also verify that the title was set correctly, and the not
specified attribute is the default value:
>>> edittest = self.folder.ftf.edittest
>>> edittest.title
u'title'
>>> edittest.description #XXX shouldn't we get a u'' here???
Edit forms
----------
First, it's important to note that forms validate user input.
Therefore, if we specify invalid data, our object won't change:
>>> print http(r"""
... POST /test_folder_1_/ftf/edittest/@@edit.html HTTP/1.1
... Authorization: Basic manager:r00t
... Content-Type: multipart/form-data; boundary=---------------------------968064918930967154199105236
... Content-Length: 418
...
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.title"
...
...
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.description"
...
... BarDescription
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Change
... -----------------------------968064918930967154199105236--
... """, handle_errors=False)
HTTP/1.1 200 OK
...
There are <strong>1</strong> input errors.
...
We will see that nothing has changed:
>>> edittest.title
u'title'
>>> edittest.description #XXX shouldn't we get a u'' here???
However, when we specify the correct fields:
>>> print http(r"""
... POST /test_folder_1_/ftf/edittest/@@edit.html HTTP/1.1
... Authorization: Basic manager:r00t
... Content-Type: multipart/form-data; boundary=---------------------------968064918930967154199105236
... Content-Length: 426
...
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.title"
...
... FooTitle
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.description"
...
... FooDescription
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Change
... -----------------------------968064918930967154199105236--
... """, handle_errors=False)
HTTP/1.1 200 OK
...
We will see that nothing has changed:
>>> edittest.title
u'FooTitle'
>>> edittest.description
u'FooDescription'
Unicode-safety of forms
-----------------------
Even though ZPublisher does not support unicode, automatically
generated forms do. In the following we will enter the following two
chinese sequences (How do you do? and I'm doing good) in forms
(they're encoded in UTF-8 here):
>>> ni_hao = '\xe4\xbd\xa0\xe5\xa5\xbd'
>>> wo_hen_hao = '\346\210\221\345\276\210\345\245\275'
First, it's imaginable that we make a mistake and enter one of the
phrases in the integer field:
>>> print http(r"""
... POST /test_folder_1_/ftf/+/addfieldcontent.html HTTP/1.1
... Authorization: Basic manager:r00t
... Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
... Content-Type: multipart/form-data; boundary=---------------------------968064918930967154199105236
... Content-Length: 418
...
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.title"
...
... ChineseTitle
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.description"
...
... ChineseDescription
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somenumber"
...
... %s
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Add
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="add_input_name"
...
... unicodetest
... -----------------------------968064918930967154199105236--
... """ % ni_hao, handle_errors=False)
HTTP/1.1 200 OK
...
There are <strong>1</strong> input errors.
...
When we enter the unicode data in the right fields (the text fields),
the form will submit correctly and create the object:
>>> print http(r"""
... POST /test_folder_1_/ftf/+/addfieldcontent.html HTTP/1.1
... Authorization: Basic manager:r00t
... Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
... Content-Type: multipart/form-data; boundary=---------------------------968064918930967154199105236
... Content-Length: 418
...
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.title"
...
... %s
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.description"
...
... %s
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somenumber"
...
... 0
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Add
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="add_input_name"
...
... unicodetest
... -----------------------------968064918930967154199105236--
... """ % (ni_hao, wo_hen_hao), handle_errors=False)
HTTP/1.1 302 Moved Temporarily
...
Location: http://localhost/test_folder_1_/ftf/manage_main
...
We can test the object has the correct values, as unicode strings, of
course:
>>> unicodetest = self.folder.ftf.unicodetest
>>> unicodetest.title == ni_hao.decode('utf-8')
True
>>> unicodetest.description == wo_hen_hao.decode('utf-8')
True
>>> unicodetest.somenumber
0
Of course, the same should apply to edit forms. First, we happen to
again make the mistake of entering unicode data in the integer field:
>>> print http(r"""
... POST /test_folder_1_/ftf/unicodetest/@@edit.html HTTP/1.1
... Authorization: Basic manager:r00t
... Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
... Content-Type: multipart/form-data; boundary=---------------------------968064918930967154199105236
... Content-Length: 418
...
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.title"
...
... ChineseTitle
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.description"
...
... ChineseDescription
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somenumber"
...
... %s
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Change
... -----------------------------968064918930967154199105236--
... """ % ni_hao, handle_errors=False)
HTTP/1.1 200 OK
...
There are <strong>1</strong> input errors.
...
We see that the object hasn't changed:
>>> unicodetest.title == ni_hao.decode('utf-8')
True
>>> unicodetest.description == wo_hen_hao.decode('utf-8')
True
>>> unicodetest.somenumber
0
Now we provide some valid form data:
>>> print http(r"""
... POST /test_folder_1_/ftf/unicodetest/@@edit.html HTTP/1.1
... Authorization: Basic manager:r00t
... Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
... Content-Type: multipart/form-data; boundary=---------------------------968064918930967154199105236
... Content-Length: 418
...
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.title"
...
... %s
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.description"
...
... %s
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somenumber"
...
... 1
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Change
... -----------------------------968064918930967154199105236--
... """ % (wo_hen_hao, ni_hao), handle_errors=False)
HTTP/1.1 200 OK
...
We see that the object's data has changed:
>>> unicodetest.title == wo_hen_hao.decode('utf-8')
True
>>> unicodetest.description == ni_hao.decode('utf-8')
True
>>> unicodetest.somenumber
1
Let's also not forget about List widgets. Let's see if we can add an
element to the list:
>>> print http(r"""
... POST /test_folder_1_/ftf/unicodetest/@@edit.html HTTP/1.1
... Authorization: Basic manager:r00t
... Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
... Content-Type: multipart/form-data; boundary=---------------------------968064918930967154199105236
... Content-Length: 418
...
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.title"
...
... %s
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.description"
...
... %s
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somenumber"
...
... 1
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somelist.add"
...
... Add Some item
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somelist.count"
...
... 0
... -----------------------------968064918930967154199105236--
... """ % (wo_hen_hao, ni_hao), handle_errors=False)
HTTP/1.1 200 OK
...
...<input class="textType" id="field.somelist.0." name="field.somelist.0." size="20" type="text" value="" />...
...
Now, let's enter some more Chinese:
>>> de_guo = '\345\276\267\345\233\275'
>>> print http(r"""
... POST /test_folder_1_/ftf/unicodetest/@@edit.html HTTP/1.1
... Authorization: Basic manager:r00t
... Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
... Content-Type: multipart/form-data; boundary=---------------------------968064918930967154199105236
... Content-Length: 418
...
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.title"
...
... %s
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.description"
...
... %s
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somenumber"
...
... 1
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somelist.0."
...
... %s
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somelist.count"
...
... 1
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Change
... -----------------------------968064918930967154199105236--
... """ % (wo_hen_hao, ni_hao, de_guo), handle_errors=False)
HTTP/1.1 200 OK
...
The object's data will have changed accordingly:
>>> unicodetest.somelist == [de_guo.decode('utf-8')]
True
Object widget:
--------------
A little more complex is the ``ObjectWidget``. Here we simply test
that the edit form works:
>>> from Products.Five.form.tests.schemacontent import \
... manage_addComplexSchemaContent
>>> n = manage_addComplexSchemaContent(self.folder.ftf, 'objecttest')
>>> print http(r"""
... GET /test_folder_1_/ftf/objecttest/@@edit.html HTTP/1.1
... """, handle_errors=False)
HTTP/1.1 200 OK
...
i18n:
-----
And now the add form in German:
>>> print http(r"""
... GET /test_folder_1_/ftf/+/addfieldcontent.html HTTP/1.1
... Accept-Language: de
... Authorization: Basic manager:r00t
... """, handle_errors=False)
HTTP/1.1 200 OK
...Felderinhalt hinzuf...
...Eine kurz...Titel...
...Eine ausf...Beschreibung...
...Irgendeine Zahl...
...Irgendeine Liste...
...hinzuf...
...Auffrischen...
...Hinzuf...
...Objektname...
The same with an input error:
>>> print http(r"""
... POST /test_folder_1_/ftf/+/addfieldcontent.html HTTP/1.1
... Accept-Language: de
... Authorization: Basic manager:r00t
... Content-Length: 670
... Content-Type: multipart/form-data; boundary=---------------------------19588947601368617292863650127
...
... -----------------------------19588947601368617292863650127
... Content-Disposition: form-data; name="field.title"
...
...
... -----------------------------19588947601368617292863650127
... Content-Disposition: form-data; name="field.description"
...
...
... -----------------------------19588947601368617292863650127
... Content-Disposition: form-data; name="field.somenumber"
...
... 0
... -----------------------------19588947601368617292863650127
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Hinzufxgen
... -----------------------------19588947601368617292863650127
... Content-Disposition: form-data; name="add_input_name"
...
...
... -----------------------------19588947601368617292863650127--
... """, handle_errors=False)
HTTP/1.1 200 OK
...Felderinhalt hinzuf...
...Ein Fehler ist aufgetreten...
...Es gab <strong>1</strong> Eingabefehler...
...Eine kurz...Titel...
...Erforderliche Eingabe fehlt...
...Eine ausf...Beschreibung...
...Irgendeine Zahl...
...Irgendeine Liste...
...hinzuf...
...Auffrischen...
...Hinzuf...
...Objektname...
And now the translated edit form:
>>> from Products.Five.form.tests.schemacontent import \
... manage_addFieldContent
>>> dummy = manage_addFieldContent(self.folder.ftf, 'i18ntest', 'titel')
>>> print http(r"""
... GET /test_folder_1_/ftf/i18ntest/edit.html HTTP/1.1
... Accept-Language: de
... Authorization: Basic manager:r00t
... """, handle_errors=False)
HTTP/1.1 200 OK
...Felderinhalt bearbeiten...
...Eine kurz...Titel...
...Eine ausf...Beschreibung...
...Irgendeine Zahl...
...Irgendeine Liste...
...hinzuf...
...Auffrischen...
...Abschicken...
Again with an input error:
>>> print http(r"""
... POST /test_folder_1_/ftf/i18ntest/edit.html HTTP/1.1
... Accept-Language: de
... Authorization: Basic manager:r00t
... Content-Length: 550
... Content-Type: multipart/form-data; boundary=---------------------------13070562555632576681565633754
...
... -----------------------------13070562555632576681565633754
... Content-Disposition: form-data; name="field.title"
...
...
... -----------------------------13070562555632576681565633754
... Content-Disposition: form-data; name="field.description"
...
...
... -----------------------------13070562555632576681565633754
... Content-Disposition: form-data; name="field.somenumber"
...
... 0
... -----------------------------13070562555632576681565633754
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Abschicken
... -----------------------------13070562555632576681565633754--
... """, handle_errors=False)
HTTP/1.1 200 OK
...Felderinhalt bearbeiten...
...Ein Fehler ist aufgetreten...
...Es gab <strong>1</strong> Eingabefehler...
...Eine kurz...Titel...
...Erforderliche Eingabe fehlt...
...Eine ausf...Beschreibung...
...Irgendeine Zahl...
...Irgendeine Liste...
...hinzuf...
...Auffrischen...
...Abschicken...
Clean up
--------
Finally, we need to clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
##############################################################################
#
# 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) 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.
#
##############################################################################
msgid ""
msgstr ""
"Project-Id-Version: Five form tests\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2005-07-29 11:38+0100\n"
"Last-Translator: \n"
"Language-Team: Five Developers <z3-five@zope.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "Title"
msgstr "Titel"
msgid "A short description of the event."
msgstr "Eine kurze Beschreibung des Ereignisses."
msgid "Description"
msgstr "Beschreibung"
msgid "A long description of the event."
msgstr "Eine ausführliche Beschreibung des Ereignisses."
msgid "Some number"
msgstr "Irgendeine Zahl"
msgid "Some List"
msgstr "Irgendeine Liste"
msgid "Some item"
msgstr "Irgendeine Element"
msgid "Edit Field Content"
msgstr "Felderinhalt bearbeiten"
msgid "Add Field Content"
msgstr "Felderinhalt hinzufügen"
##############################################################################
#
# 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.
#
##############################################################################
msgid ""
msgstr ""
"Project-Id-Version: Five form tests\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: Five Developers <z3-five@zope.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "Title"
msgstr ""
msgid "A short description of the event."
msgstr ""
msgid "Description"
msgstr ""
msgid "A long description of the event."
msgstr ""
msgid "Some number"
msgstr ""
msgid "Some List"
msgstr ""
msgid "Some item"
msgstr ""
msgid "Edit Field Content"
msgstr ""
msgid "Add Field Content"
msgstr ""
##############################################################################
#
# 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.
#
##############################################################################
"""Demo schema content
$Id: schemacontent.py 15514 2005-08-02 16:37:34Z yuppie $
"""
from OFS.SimpleItem import SimpleItem
from Globals import InitializeClass
from zope.i18nmessageid import MessageFactory
from zope.interface import implements, Interface
from zope.schema import TextLine, Text, Object, Int, List
from zope.app.form import CustomWidgetFactory
from Products.Five.form.objectwidget import ObjectWidget
_ = MessageFactory('formtest')
class IFieldContent(Interface):
title = TextLine(
title=_(u"Title"),
description=_(u"A short description of the event."),
default=u"",
required=True
)
description = Text(
title=_(u"Description"),
description=_(u"A long description of the event."),
default=u"",
required=False
)
somenumber = Int(
title=_(u"Some number"),
default=0,
required=False
)
somelist = List(
title=_(u"Some List"),
value_type=TextLine(title=_(u"Some item")),
default=[],
required=False
)
class FieldContent(SimpleItem):
"""A Viewable piece of content with fields"""
implements(IFieldContent)
meta_type = 'Five FieldContent'
def __init__(self, id, title):
self.id = id
self.title = title
InitializeClass(FieldContent)
def manage_addFieldContent(self, id, title, REQUEST=None):
"""Add the field content"""
id = self._setObject(id, FieldContent(id, title))
return ''
class IComplexSchemaContent(Interface):
fishtype = TextLine(
title=u"Fish type",
description=u"The type of fish",
default=u"It was a lovely little fish. And it went wherever I did go.",
required=False)
fish = Object(
title=u"Fish",
schema=IFieldContent,
description=u"The fishy object",
required=True)
class ComplexSchemaContent(SimpleItem):
implements(IComplexSchemaContent)
meta_type ="Five ComplexSchemaContent"
def __init__(self, id):
self.id = id
self.fish = FieldContent('fish', 'title')
self.fish.description = ""
self.fishtype = 'Lost fishy'
class ComplexSchemaView:
"""Needs a docstring"""
fish_widget = CustomWidgetFactory(ObjectWidget, FieldContent)
InitializeClass(ComplexSchemaContent)
def manage_addComplexSchemaContent(self, id, REQUEST=None):
"""Add the complex schema content"""
id = self._setObject(id, ComplexSchemaContent(id))
return ''
##############################################################################
#
# 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.
#
##############################################################################
"""Test forms
$Id: test_forms.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_get_widgets_for_schema_fields():
"""
Test widget lookup for schema fields
First, load the configuration files:
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config('configure.zcml', Products.Five)
Now for some actual testing...
>>> from zope.schema import Choice, TextLine
>>> salutation = Choice(title=u'Salutation',
... values=("Mr.", "Mrs.", "Captain", "Don"))
>>> contactname = TextLine(title=u'Name')
>>> from Products.Five.traversable import FakeRequest
>>> request = FakeRequest()
>>> salutation = salutation.bind(request)
>>> contactname = contactname.bind(request)
>>> from zope.app import zapi
>>> from zope.app.form.interfaces import IInputWidget
>>> from zope.app.form.browser.textwidgets import TextWidget
>>> from zope.app.form.browser.itemswidgets import DropdownWidget
>>> view1 = zapi.getMultiAdapter((contactname, request), IInputWidget)
>>> view1.__class__ == TextWidget
True
>>> view2 = zapi.getMultiAdapter((salutation, request), IInputWidget)
>>> view2.__class__ == DropdownWidget
True
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
import unittest
from zope.testing.doctest import DocTestSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
return unittest.TestSuite((
DocTestSuite(),
FunctionalDocFileSuite('forms.txt',
package="Products.Five.form.tests",),
))
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.
#
##############################################################################
"""Mimick Zope3 i18n machinery for Zope 2
$Id: i18n.py 19435 2005-11-02 16:34:58Z philikon $
"""
from Acquisition import aq_acquire
from zope.interface import implements
from zope.i18n import interpolate
from zope.i18n.interfaces import ITranslationDomain, IUserPreferredLanguages
from zope.app import zapi
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:
"""Translation service that delegates to ``zope.i18n`` machinery.
"""
# this is mostly a copy of zope.i18n.translate, with modifications
# regarding fallback and Zope 2 compatability
def translate(self, domain, msgid, mapping=None,
context=None, target_language=None, default=None):
if isinstance(msgid, (Message, MessageID)):
domain = msgid.domain
default = msgid.default
mapping = msgid.mapping
util = zapi.queryUtility(ITranslationDomain, domain)
if util is None:
# fallback to translation service that was registered,
# DummyTranslationService the worst
ts = _fallback_translation_service
return ts.translate(domain, msgid, mapping=mapping, context=context,
target_language=target_language, default=default)
# in Zope3, context is adapted to IUserPreferredLanguages,
# which means context should be the request in this case.
if context is not None:
context = aq_acquire(context, 'REQUEST', None)
return util.translate(msgid, mapping=mapping, context=context,
target_language=target_language, default=default)
class LocalizerLanguages(object):
"""Languages adapter that chooses languages according to Localizer
settings."""
implements(IUserPreferredLanguages)
def __init__(self, context):
self.context = context
def getPreferredLanguages(self):
accept_language = self.context.AcceptLanguage
langs = []
for lang, node in accept_language.children.items():
langs.append((node.get_quality(), lang))
langs.extend([(n.get_quality(), l) for l, n
in node.children.items()])
langs.sort()
langs.reverse()
langs = [l for q, l in langs]
if '' in langs:
langs.remove('')
return langs
class PTSLanguages(object):
"""Languages adapter that chooses languages like
PlacelessTranslationService."""
implements(IUserPreferredLanguages)
def __init__(self, context):
self.context = context
def getPreferredLanguages(self):
from Products.PlacelessTranslationService.Negotiator import getLangPrefs
return getLangPrefs(self.context)
# Hook that will be used by Products.PageTemplates.GlobalTranslationService
_fallback_translation_service = None
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:i18n="http://namespaces.zope.org/i18n"
>
<utility
provides="zope.i18n.interfaces.INegotiator"
component="zope.i18n.negotiator.negotiator"
/>
<adapter
for="zope.publisher.interfaces.http.IHTTPRequest"
provides="zope.i18n.interfaces.IUserPreferredLanguages"
factory="zope.publisher.browser.BrowserLanguages"
/>
<adapter
for="zope.publisher.interfaces.http.IHTTPRequest"
provides="zope.i18n.interfaces.IUserPreferredCharsets"
factory="zope.publisher.http.HTTPCharsets"
/>
<configure package="zope.app">
<i18n:registerTranslations directory="locales"/>
</configure>
</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 19283 2005-10-31 17:43:51Z philikon $
"""
from zope.interface import Interface
from zope.interface.interfaces import IInterface
class IBrowserDefault(Interface):
"""Provide a hook for deciding about the default view for an object"""
def defaultView(self, request):
"""Return the object to be published
(usually self) and a sequence of names to traverse to
find the method to be published.
"""
class IMenuItemType(IInterface):
"""Menu item type
Menu item types are interfaces that define classes of
menu items.
"""
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta">
<include package=".site" file="meta.zcml" />
<include package=".browser" file="meta.zcml" />
<include package=".form" file="meta.zcml" />
<meta:directives namespace="http://namespaces.zope.org/zope">
<meta:directive
name="permission"
schema="zope.app.security.metadirectives.IDefinePermissionDirective"
handler="zope.app.security.metaconfigure.definePermission"
/>
<meta:directive
name="interface"
schema="zope.app.component.metadirectives.IInterfaceDirective"
handler="zope.app.component.metaconfigure.interface"
/>
<meta:directive
name="view"
schema="zope.app.component.metadirectives.IViewDirective"
handler="zope.app.component.metaconfigure.view"
/>
<meta:directive
name="adapter"
schema="zope.app.component.metadirectives.IAdapterDirective"
handler="zope.app.component.metaconfigure.adapter"
/>
<meta:directive
name="subscriber"
schema="zope.app.component.metadirectives.ISubscriberDirective"
handler="zope.app.component.metaconfigure.subscriber"
/>
<meta:directive
name="utility"
schema="zope.app.component.metadirectives.IUtilityDirective"
handler="zope.app.component.metaconfigure.utility"
/>
<meta:directive
name="factory"
schema="zope.app.component.metadirectives.IFactoryDirective"
handler="zope.app.component.metaconfigure.factory"
/>
<meta:complexDirective
name="content"
schema="zope.app.component.metadirectives.IClassDirective"
handler=".metaconfigure.ContentDirective"
>
<meta:subdirective
name="implements"
schema="zope.app.component.metadirectives.IImplementsSubdirective"
/>
<meta:subdirective
name="require"
schema="zope.app.component.metadirectives.IRequireSubdirective"
/>
<meta:subdirective
name="allow"
schema="zope.app.component.metadirectives.IAllowSubdirective"
/>
</meta:complexDirective>
<meta:directive
name="vocabulary"
schema="zope.app.schema.metadirectives.IVocabularyDirective"
handler="zope.app.schema.metaconfigure.vocabulary"
/>
<meta:directive
name="defaultLayer"
schema="zope.app.component.metadirectives.IDefaultLayerDirective"
handler="zope.app.component.metaconfigure.defaultLayer"
/>
</meta:directives>
<meta:directives namespace="http://namespaces.zope.org/five">
<!-- specific to Five -->
<meta:directive
name="loadProducts"
schema="zope.interface.Interface"
handler=".fiveconfigure.loadProducts"
/>
<meta:directive
name="loadProductsOverrides"
schema="zope.interface.Interface"
handler=".fiveconfigure.loadProductsOverrides"
/>
<meta:directive
name="implements"
schema=".fivedirectives.IImplementsDirective"
handler=".fiveconfigure.implements"
/>
<meta:directive
name="defaultViewable"
schema=".fivedirectives.IDefaultViewableDirective"
handler=".fiveconfigure.defaultViewable"
/>
<meta:directive
name="traversable"
schema=".fivedirectives.ITraversableDirective"
handler=".fiveconfigure.traversable"
/>
<meta:directive
name="containerEvents"
schema=".fivedirectives.IContainerEventsDirective"
handler=".eventconfigure.containerEvents"
/>
<meta:directive
name="deprecatedManageAddDelete"
schema=".fivedirectives.IDeprecatedManageAddDeleteDirective"
handler=".eventconfigure.deprecatedManageAddDelete"
/>
<meta:directive
name="sizable"
schema=".fivedirectives.ISizableDirective"
handler=".sizeconfigure.sizable"
/>
<meta:directive
name="pagesFromDirectory"
schema=".fivedirectives.IPagesFromDirectoryDirective"
handler=".fiveconfigure.pagesFromDirectory"
/>
<meta:directive
name="bridge"
schema=".fivedirectives.IBridgeDirective"
handler=".fiveconfigure.bridge"
/>
<meta:directive
name="registerClass"
schema=".fivedirectives.IRegisterClassDirective"
handler=".fiveconfigure.registerClass"
/>
</meta:directives>
<meta:directive
name="redefinePermission"
namespace="http://namespaces.zope.org/meta"
schema="zope.app.security.metadirectives.IRedefinePermission"
handler="zope.app.security.metaconfigure.redefinePermission"
/>
<!-- load the zope:modulealias directive -->
<include package="zope.modulealias" file="meta.zcml" />
<!-- load the i18n:registerTranslations directive -->
<include package="zope.app.i18n" file="meta.zcml" />
</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.
#
##############################################################################
# metaclass taken from PEAK.
# Martijn doesn't pretend to understand this.
from weakref import WeakValueDictionary
from types import ClassType
def makeClass(name,bases,dict):
"""makeClass(name, bases, dict) - enhanced class creation
"""
# Create either a "classic" Python class, an ExtensionClass, or a new-style
# class with autogenerated metaclass, based on the nature of the base
# classes involved
name = str(name) # De-unicode
metaclasses = [getattr(b,'__class__',type(b)) for b in bases]
if dict.has_key('__metaclass__'):
metaclasses.insert(0,dict['__metaclass__'])
if dict.has_key('__metaclasses__'):
metaclasses[0:0] = list(dict['__metaclasses__'])
metaclasses = normalizeBases(metaclasses)
if metaclasses:
# If we have metaclasses, it's not a classic class, so derive a
# single metaclass, and ask it to create the class.
if len(metaclasses)==1:
metaclass = metaclasses[0]
else:
metaclass = derivedMeta(metaclasses)
return metaclass(name,bases,dict)
# No metaclasses, it's a classic class, so use 'new.classobj'
from new import classobj; return classobj(name,bases,dict)
def normalizeBases(allBases):
return minimalBases([b for b in allBases if b is not makeClass])
def minimalBases(classes):
"""Reduce a list of base classes to its ordered minimum equivalent"""
classes = [c for c in classes if c is not ClassType]
candidates = []
for m in classes:
for n in classes:
if issubclass(n,m) and m is not n:
break
else:
# m has no subclasses in 'classes'
if m in candidates:
candidates.remove(m) # ensure that we're later in the list
candidates.append(m)
return candidates
metaReg = WeakValueDictionary()
def derivedMeta(metaclasses):
metaclasses = tuple(metaclasses)
derived = metaReg.get(metaclasses)
if derived is None:
normalized = tuple(normalizeBases(metaclasses))
derived = metaReg.get(normalized)
if derived is None:
if len(normalized)==1:
derived = normalized[0]
else:
derived = metaFromBases(normalized)(
'_'.join([n.__name__ for n in normalized]),
metaclasses, {}
)
try: metaReg[normalized] = derived
except TypeError: pass # Some metatypes can't be weakref'd
try: metaReg[metaclasses] = derived
except TypeError: pass
return derived
def metaFromBases(bases):
meta = tuple([getattr(b,'__class__',type(b)) for b in bases])
if meta==bases: raise TypeError("Incompatible root metatypes",bases)
return derivedMeta(meta)
##############################################################################
#
# 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.
#
##############################################################################
"""Generic Components ZCML Handlers
$Id: metaconfigure.py 19283 2005-10-31 17:43:51Z philikon $
"""
from Products.Five.security import CheckerPublic, protectName
from Globals import InitializeClass as initializeClass
from zope.app.component.contentdirective import ContentDirective as \
zope_app_ContentDirective
class ContentDirective(zope_app_ContentDirective):
def __protectName(self, name, permission_id):
self.__context.action(
discriminator = ('five:protectName', self.__class, name),
callable = protectName,
args = (self.__class, name, permission_id)
)
def __call__(self):
"""Handle empty/simple declaration."""
return self.__context.action(
discriminator = ('five:initialize:class', self.__class),
callable = initializeClass,
args = (self.__class,)
)
<configure xmlns="http://namespaces.zope.org/zope"
i18n_domain="Five">
<permission
id="five.ManageSite"
title="Manage Five local sites"
/>
<!-- Give common Zope2 and CMF permissions a permission ID
The title of the permission is what Zope 2 knows it under -->
<permission
id="zope2.Public"
title="Public, everyone can access"
/>
<permission
id="zope2.Private"
title="Private, only accessible from trusted code"
/>
<permission
id="zope2.AccessContentsInformation"
title="Access contents information"
/>
<permission
id="zope2.ChangeImagesFiles"
title="Change Images and Files"
/>
<permission
id="zope2.ChangeConfig"
title="Change configuration"
/>
<permission
id="zope2.ChangePermissions"
title="Change permissions"
/>
<permission
id="zope2.CopyOrMove"
title="Copy or Move"
/>
<permission
id="zope2.DefinePermissions"
title="Define permissions"
/>
<permission
id="zope2.DeleteObjects"
title="Delete objects"
/>
<permission
id="zope2.FTPAccess"
title="FTP access"
/>
<permission
id="zope2.ImportExport"
title="Import/Export objects"
/>
<permission
id="zope2.ManageProperties"
title="Manage properties"
/>
<permission
id="zope2.ManageUsers"
title="Manage users"
/>
<permission
id="zope2.Undo"
title="Undo changes"
/>
<permission
id="zope2.View"
title="View"
/>
<permission
id="zope2.ViewHistory"
title="View History"
/>
<permission
id="zope2.ViewManagementScreens"
title="View management screens"
/>
<permission
id="zope2.WebDAVLock"
title="WebDAV Lock items"
/>
<permission
id="zope2.WebDAVUnlock"
title="WebDAV Unlock items"
/>
<permission
id="zope2.WebDAVAccess"
title="WebDAV access"
/>
<!-- CMF Core Permissions -->
<permission
id="cmf.ListFolderContents"
title="List folder contents"
/>
<permission
id="cmf.ListUndoableChanges"
title="List undoable changes"
/>
<permission
id="cmf.AccessInactivePortalContent"
title="Access inactive portal content"
/>
<permission
id="cmf.ManagePortal"
title="Manage portal"
/>
<permission
id="cmf.ModifyPortalContent"
title="Modify portal content"
/>
<permission
id="cmf.ManageProperties"
title="Manage properties"
/>
<permission
id="cmf.ListPortalMembers"
title="List portal members"
/>
<permission
id="cmf.AddPortalFolders"
title="Add portal folders"
/>
<permission
id="cmf.AddPortalContent"
title="Add portal content"
/>
<permission
id="cmf.AddPortalMember"
title="Add portal member"
/>
<permission
id="cmf.SetOwnPassword"
title="Set own password"
/>
<permission
id="cmf.SetOwnProperties"
title="Set own properties"
/>
<permission
id="cmf.MailForgottonPassword"
title="Mail forgotten password"
/>
<permission
id="cmf.RequestReview"
title="Request review"
/>
<permission
id="cmf.ReviewPortalContent"
title="Review portal content"
/>
<permission
id="cmf.AccessFuturePortalContent"
title="Access future portal content"
/>
</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 security handling
$Id: security.py 14595 2005-07-12 21:26:12Z philikon $
"""
from zope.interface import implements, classProvides
from zope.component import queryUtility, getUtility
from zope.security.management import thread_local
from zope.security.interfaces import IInteraction, ISecurityPolicy
from zope.security.simplepolicies import ParanoidSecurityPolicy
from zope.app.security.interfaces import IPermission
from zope.app import zapi
from AccessControl import ClassSecurityInfo, getSecurityManager
from Globals import InitializeClass as initializeClass
from types import StringTypes
CheckerPublicId = 'zope.Public'
CheckerPrivateId = 'zope2.Private'
from zope.security.checker import CheckerPublic
def getSecurityInfo(klass):
sec = {}
info = vars(klass)
if info.has_key('__ac_permissions__'):
sec['__ac_permissions__'] = info['__ac_permissions__']
for k, v in info.items():
if k.endswith('__roles__'):
sec[k] = v
return sec
def clearSecurityInfo(klass):
sec = {}
info = vars(klass)
if info.has_key('__ac_permissions__'):
delattr(klass, '__ac_permissions__')
for k, v in info.items():
if k.endswith('__roles__'):
delattr(klass, k)
def checkPermission(permission, object, interaction=None):
"""Return whether security policy allows permission on object.
Arguments:
permission -- A permission name
object -- The object being accessed according to the permission
interaction -- This Zope 3 concept has no equivalent in Zope 2,
and is ignored.
checkPermission is guaranteed to return True if permission is
CheckerPublic or None.
"""
if (permission in ('zope.Public', 'zope2.Public') or
permission is None or permission is CheckerPublic):
return True
if isinstance(permission, StringTypes):
permission = zapi.queryUtility(IPermission, unicode(permission))
if permission is None:
return False
if getSecurityManager().checkPermission(permission.title, object):
return True
return False
class FiveSecurityPolicy(ParanoidSecurityPolicy):
"""Security policy that bridges between Zope 3 security mechanisms and
Zope 2's security policy.
Don't let the name of the base class fool you... This really just
delegates to Zope 2's security manager."""
classProvides(ISecurityPolicy)
implements(IInteraction)
def checkPermission(self, permission, object):
return checkPermission(permission, object)
def newInteraction():
"""Con Zope 3 to use Zope 2's checkPermission.
Zope 3 when it does a checkPermission will turn around and
ask the thread local interaction for the checkPermission method.
By making the interaction *be* Zope 2's security manager, we can
con Zope 3 into using Zope 2's checker...
"""
if getattr(thread_local, 'interaction', None) is None:
thread_local.interaction = FiveSecurityPolicy()
def _getSecurity(klass):
# a Zope 2 class can contain some attribute that is an instance
# of ClassSecurityInfo. Zope 2 scans through things looking for
# an attribute that has the name __security_info__ first
info = vars(klass)
for k, v in info.items():
if hasattr(v, '__security_info__'):
return v
# we stuff the name ourselves as __security__, not security, as this
# could theoretically lead to name clashes, and doesn't matter for
# zope 2 anyway.
security = ClassSecurityInfo()
setattr(klass, '__security__', security)
return security
def protectName(klass, name, permission_id):
"""Protect the attribute 'name' on 'klass' using the given
permission"""
security = _getSecurity(klass)
# Zope 2 uses string, not unicode yet
name = str(name)
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)
elif permission_id == CheckerPrivateId:
security.declarePrivate(name)
else:
permission = getUtility(IPermission, name=permission_id)
# Zope 2 uses string, not unicode yet
perm = str(permission.title)
security.declareProtected(perm, name)
def protectClass(klass, permission_id):
"""Protect the whole class with the given permission"""
security = _getSecurity(klass)
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()
elif permission_id == CheckerPrivateId:
security.declareObjectPrivate()
else:
permission = getUtility(IPermission, name=permission_id)
# Zope 2 uses string, not unicode yet
perm = str(permission.title)
security.declareObjectProtected(perm)
##############################################################################
#
# 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):
next = self.next
if next is None:
next = getGlobalSiteManager()
return next.adapters
@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):
next = self.next
if next is None:
next = getGlobalSiteManager()
return next.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
_localsite_monkies = []
def classSiteHook(class_, site_class):
setattr(class_, 'getSiteManager',
site_class.getSiteManager.im_func)
setattr(class_, 'setSiteManager',
site_class.setSiteManager.im_func)
_localsite_monkies.append(class_)
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)
)
# 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
##############################################################################
#
# 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.
#
##############################################################################
"""Site support ZCML directive schemas
$Id: fivedirectives.py 18581 2005-10-14 16:54:25Z regebro $
"""
from zope.interface import Interface
from zope.configuration.fields import GlobalObject
class ILocalSiteDirective(Interface):
"""Make instances of class hookable for Site.
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
)
site_class = GlobalObject(
title=u"Site Class",
required=False
)
##############################################################################
#
# 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
===============================
This tests how local site managers are found during traversal and how
that affects component lookup (depending on whether a site is
traversed or not, local components might or might not be found).
First, we set up all of Five:
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
Then we hook up the 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.
>>> from zope.app.component.hooks import setHooks
>>> setHooks()
Next 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
...
zapi.getSiteManager() is zapi.getGlobalSiteManager(): True
IFiveUtilityRegistry.providedBy(utility_service): False
isinstance(zapi.getSiteManager(), FiveSiteManager): False
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
...
zapi.getSiteManager() is zapi.getGlobalSiteManager(): False
IFiveUtilityRegistry.providedBy(utility_service): True
isinstance(zapi.getSiteManager(), FiveSiteManager): True
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
...
zapi.getSiteManager() is zapi.getGlobalSiteManager(): True
IFiveUtilityRegistry.providedBy(utility_service): False
isinstance(zapi.getSiteManager(), FiveSiteManager): False
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.
We test some adapter look-up here. It is also indirectly covered in
the functional test; view look up, for example, is adapter look up.
First we provide an adapter:
>>> from Products.Five.tests.adapters import Adaptable, Adapter, IAdapted
>>> import zope.component
>>> zope.component.provideAdapter(Adapter)
Now let's check for a simple adaption:
>>> adaptable = Adaptable()
>>> IAdapted(adaptable) #doctest: +ELLIPSIS
<Products.Five.tests.adapters.Adapter instance at ...>
Let's get all the adapters for ``adaptable``:
>>> zapi.getAdapters((adaptable,), IAdapted) #doctest: +ELLIPSIS
[(u'', <Products.Five.tests.adapters.Adapter instance at ...>)]
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
The adapters is registry is passed through to the global one:
>>> subsite.getSiteManager().adapters is zapi.getGlobalSiteManager().adapters
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(): %s\n'
'IFiveUtilityRegistry.providedBy(utility_service): %s\n'
'isinstance(zapi.getSiteManager(), FiveSiteManager): %s'
% (sm is zapi.getGlobalSiteManager(),
IFiveUtilityRegistry.providedBy(sm.utilities),
isinstance(sm, FiveSiteManager)))
return 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
return FunctionalDocFileSuite('functional.txt',
package='Products.Five.site.tests')
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) 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 Five site manager
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
from Testing.ZopeTestCase import ZopeDocFileSuite
return ZopeDocFileSuite('sitemanager.txt', package="Products.Five.site.tests")
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."
)
##############################################################################
#
# 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.
#
##############################################################################
"""Use structured monkey-patching to enable ``ISized`` adapters for
Zope 2 objects.
$Id: sizeconfigure.py 14449 2005-07-09 21:12:22Z philikon $
"""
from zope.app.size.interfaces import ISized
from Products.Five.fiveconfigure import isFiveMethod
# holds classes that were monkeyed with; for clean up
_monkied = []
def get_size(self):
size = ISized(self, None)
if size is not None:
unit, amount = size.sizeForSorting()
if unit == 'byte':
return amount
method = getattr(self, '__five_original_get_size', None)
if method is not None:
return self.__five_original_get_size()
get_size.__five_method__ = True
def classSizable(class_):
"""Monkey the class to be sizable through Five"""
# tuck away the original method if necessary
if hasattr(class_, "get_size") and not isFiveMethod(class_.get_size):
class_.__five_original_get_size = class_.get_size
class_.get_size = get_size
# remember class for clean up
_monkied.append(class_)
def sizable(_context, class_):
_context.action(
discriminator = ('five:sizable', class_),
callable = classSizable,
args=(class_,)
)
# clean up code
from Products.Five.fiveconfigure import killMonkey
from zope.testing.cleanup import addCleanUp
def unsizable(class_):
"""Restore class's initial state with respect to being sizable"""
killMonkey(class_, 'get_size', '__five_original_get_size')
def cleanUp():
for class_ in _monkied:
unsizable(class_)
addCleanUp(cleanUp)
del addCleanUp
This directory contains Zope3-style instance configuration files:
* ``site.zcml`` is the root node of the instance's ZCML
configuration tree.
* ``package-includes`` may contain Zope3-style ZCML slugs to enable
3rd party packages that are not dropped into ``Products``.
Copy these files to your ``$INSTANCE_HOME/etc`` directory for
customization. If Five cannot find them there, it falls back to these
skeleton files.
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta"
xmlns:five="http://namespaces.zope.org/five">
<!-- Copy this file to your ``INSTANCE_HOME/etc`` directory -->
<include package="Products.Five" />
<meta:redefinePermission from="zope2.Public" to="zope.Public" />
<include files="package-includes/*-meta.zcml" />
<include files="package-includes/*-configure.zcml" />
<five:loadProducts />
<five:loadProductsOverrides />
</configure>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<browser:page
for="*"
name="standard_macros"
permission="zope2.View"
class=".standardmacros.StandardMacros"
allowed_interface="zope.interface.common.mapping.IItemMapping"
/>
<browser:page
for="*"
template="five_template.pt"
name="five_template"
permission="zope.Public"
/>
</configure>
<html metal:define-macro="page">
<head>
<metal:block define-slot="style_slot">
</metal:block>
</head>
<body>
<metal:block define-slot="body">
</metal:block>
</body>
</html>
##############################################################################
#
# 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.
#
##############################################################################
"""Mimick the Zope 3 skinning system in Five.
$Id: standardmacros.py 19283 2005-10-31 17:43:51Z philikon $
"""
import zope.interface
from zope.app import zapi
from Products.Five.browser import BrowserView
# this is a verbatim copy of zope.app.basicskin except that it doesn't
# derive from ``object``
class Macros:
zope.interface.implements(zope.interface.common.mapping.IItemMapping)
macro_pages = ()
aliases = {
'view': 'page',
'dialog': 'page',
'addingdialog': 'page'
}
def __getitem__(self, key):
key = self.aliases.get(key, key)
context = self.context
request = self.request
for name in self.macro_pages:
page = zapi.getMultiAdapter((context, request), name=name)
try:
v = page[key]
except KeyError:
pass
else:
return v
raise KeyError, key
class StandardMacros(BrowserView, Macros):
macro_pages = ('five_template',
'widget_macros',
'form_macros',)
<html metal:define-macro="birdmacro"><head><title>bird macro</title></head><body>Color: <metal:block define-slot="color" /><metal:block define-slot="birdinfo" /></body></html>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<!-- macro aggregation page -->
<browser:page
for="*"
name="fivetest_macros"
permission="zope2.View"
class=".demomacros.StandardMacros"
allowed_interface="zope.interface.common.mapping.IItemMapping"
/>
<!-- macro pages -->
<browser:page
for="OFS.interfaces.IFolder"
template="bird.pt"
name="bird.pt"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="OFS.interfaces.IFolder"
template="bird.pt"
name="bird_macros"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="OFS.interfaces.IFolder"
template="dog.pt"
name="dog_macros"
permission="zope2.ViewManagementScreens"
/>
</configure>
\ No newline at end of file
##############################################################################
#
# 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.
#
##############################################################################
"""Demo StandardMacros
$Id: demomacros.py 12884 2005-05-30 13:10:41Z philikon $
"""
from Products.Five import StandardMacros as BaseMacros
class StandardMacros(BaseMacros):
macro_pages = ('bird_macros', 'dog_macros')
aliases = {'flying':'birdmacro',
'walking':'dogmacro'}
<html metal:define-macro="dogmacro"><head><title>dog macro</title></head><body>Breed: <metal:block define-slot="breed" /></body></html>
##############################################################################
#
# 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.
#
##############################################################################
"""Test standard macros
$Id: test_standardmacros.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_standard_macros():
"""Test standard macros
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.login('manager')
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> import Products.Five.skin.tests
>>> from Products.Five import zcml
>>> zcml.load_config('configure.zcml', package=Products.Five)
>>> zcml.load_config('configure.zcml', package=Products.Five.skin.tests)
Test macro access through our flavour of StandardMacros. First,
when looking up a non-existing macro, we get a KeyError:
>>> view = self.folder.unrestrictedTraverse('testoid/@@fivetest_macros')
>>> view['non-existing-macro']
Traceback (most recent call last):
...
KeyError: 'non-existing-macro'
Existing macros are accessible through index notation:
>>> for macroname in ('birdmacro', 'dogmacro', 'flying', 'walking'):
... view[macroname] is not None
True
True
True
True
Aliases are resolve correctly:
>>> view['flying'] is view['birdmacro']
True
>>> view['walking'] is view['dogmacro']
True
One can also access the macros through regular traversal:
>>> base = 'testoid/@@fivetest_macros/%s'
>>> for macro in ('birdmacro', 'dogmacro', 'flying', 'walking'):
... view = self.folder.unrestrictedTraverse(base % macro)
... view is not None
True
True
True
True
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
Five tests
==========
All you have to do is type::
$ bin/zopectl test -s Products.Five
to run the Five tests.
##############################################################################
#
# 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.
#
##############################################################################
"""Adapter test fixtures
$Id: adapters.py 12884 2005-05-30 13:10:41Z philikon $
"""
from zope.interface import implements, Interface
from zope.component import adapts
class IAdaptable(Interface):
"""This is a Zope 3 interface.
"""
def method():
"""This method will be adapted
"""
class IAdapted(Interface):
"""The interface we adapt to.
"""
def adaptedMethod():
"""A method to adapt.
"""
class IOrigin(Interface):
"""Something we'll adapt"""
class IDestination(Interface):
"""The result of an adaption"""
def method():
"""Do something"""
class Adaptable:
implements(IAdaptable)
def method(self):
return "The method"
class Adapter:
implements(IAdapted)
adapts(IAdaptable)
def __init__(self, context):
self.context = context
def adaptedMethod(self):
return "Adapted: %s" % self.context.method()
class Origin:
implements(IOrigin)
class OriginalAdapter:
implements(IDestination)
def __init__(self, context):
self.context = context
def method(self):
return "Original"
class OverrideAdapter:
implements(IDestination)
def __init__(self, context):
self.context = context
def method(self):
return "Overridden"
##############################################################################
#
# 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.
#
##############################################################################
"""Boiler plate test module
$Id: boilerplate.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_boilerplate():
"""
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
>>> import Products.Five.tests
>>> from Products.Five import zcml
>>> zcml.load_config('boilerplate.zcml', Products.Five.tests)
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
======
Bridge
======
The ``Five.bridge`` module provides functionality to convert a Zope 2
interface into a Zope 3 one. First we'll import all we know about
interfaces from the two generations:
>>> from Interface import Interface as Z2_Interface
>>> from Interface import Attribute as Z2_Attribute
>>> from zope.interface import Interface as Z3_Interface
>>> from zope.interface import Attribute as Z3_Attribute
>>> from zope.interface.interface import Method as Z3_Method
An empty interface
------------------
>>> class IEmpty(Z2_Interface):
... pass
>>> from Products.Five.bridge import fromZ2Interface
>>> IEmptyConverted = fromZ2Interface(IEmpty)
>>> Z3_Interface.isEqualOrExtendedBy(IEmptyConverted)
True
>>> len(IEmptyConverted.names())
0
Bases
-----
>>> class IBase(Z2_Interface):
... pass
>>> class IDerived(IBase):
... pass
>>> IBase.getBases() == (Z2_Interface,)
True
>>> IDerived.getBases() == (IBase,)
True
>>> IDerived.extends(IBase)
1
>>> IDerived.extends(IEmpty)
0
>>> IBaseConverted = fromZ2Interface(IBase)
>>> IDerivedConverted = fromZ2Interface(IDerived)
>>> IBaseConverted.getBases() == (Z3_Interface,)
True
>>> IDerivedConverted.getBases() == (IBaseConverted,)
True
>>> IDerivedConverted.extends(IBaseConverted)
True
>>> IDerivedConverted.extends(IEmptyConverted)
False
Attributes
----------
>>> class IAttributes(Z2_Interface):
... one = Z2_Attribute('one', 'One attribute')
... another = Z2_Attribute('another', 'Another attribute')
>>> converted = fromZ2Interface(IAttributes)
>>> Z3_Interface.isEqualOrExtendedBy(converted)
True
>>> len(converted.names())
2
>>> 'one' in converted.names()
True
>>> 'another' in converted.names()
True
>>> one = converted.getDescriptionFor('one')
>>> isinstance(one, Z3_Attribute)
True
>>> one.getName()
'one'
>>> one.getDoc()
'One attribute'
>>> another = converted.getDescriptionFor('another')
>>> isinstance(another, Z3_Attribute)
True
>>> another.getName()
'another'
>>> another.getDoc()
'Another attribute'
Methods
-------
>>> class IMethods(Z2_Interface):
... def one():
... """One method."""
... def another(arg1, arg2):
... """Another method, taking arguments."""
>>> converted = fromZ2Interface(IMethods)
>>> Z3_Interface.isEqualOrExtendedBy(converted)
True
>>> len(converted.names())
2
>>> 'one' in converted.names()
True
>>> 'another' in converted.names()
True
>>> one = converted.getDescriptionFor('one')
>>> isinstance(one, Z3_Method)
True
>>> one.getName()
'one'
>>> one.getDoc()
'One method.'
>>> one.getSignatureString()
'()'
>>> another = converted.getDescriptionFor('another')
>>> isinstance(another, Z3_Method)
True
>>> another.getName()
'another'
>>> another.getDoc()
'Another method, taking arguments.'
>>> another.getSignatureString()
'(arg1, arg2)'
Invalid parameters
------------------
>>> fromZ2Interface(None)
Traceback (most recent call last):
...
ValueError: Not a Zope 2 interface!
>>> fromZ2Interface(object())
Traceback (most recent call last):
...
ValueError: Not a Zope 2 interface!
>>> class IZ3_NotAllowed(Z3_Interface):
... pass
>>> fromZ2Interface(IZ3_NotAllowed)
Traceback (most recent call last):
...
ValueError: Not a Zope 2 interface!
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<adapter
for=".adapters.IAdaptable"
provides=".adapters.IAdapted"
factory=".adapters.Adapter"
/>
<!-- this is a test whether five:traversable can be called more than
once on a class; SimpleContent inherits from Traversable, so
one directive suffices here -->
<five:traversable class="Products.Five.tests.testing.simplecontent.SimpleContent" />
<!-- this is a test whether the *directive* can be called more than
once without raising a conflicting configuration exception -->
<five:traversable class="Products.Five.tests.testing.simplecontent.SimpleContent" />
<!-- this tests whether five:traversable can be called on a class that
already provides __bobo_traverse__, such as our FancyContent -->
<five:traversable class="Products.Five.tests.testing.fancycontent.FancyContent" />
<!-- Testing the vocabulary directive -->
<vocabulary
name="aVocabulary"
factory="zope.schema.tests.test_vocabulary.SampleVocabulary"
/>
<!-- testing that products meta.zcml statements are picked up. -->
<include file="meta.zcml" />
<five:parrot
class=".metaconfigure.NorwegianBlue"
name="Polly"
/>
<!-- stuff that we'll override in overrides.zcml -->
<adapter
for=".adapters.IOrigin"
provides=".adapters.IDestination"
factory=".adapters.OriginalAdapter"
/>
</configure>
================
Container events
================
Zope 3 container events are used to inform subscribers that an object is
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.
These events replace the old Zope 2 manage_afterAdd, manage_beforeDelete
and manage_afterClone methods.
All standard Zope containers will only call manage_afterAdd & co on
classes specified with the directive::
<five:deprecatedManageAddDelete class="some.content.class"/>
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).
Test setup
==========
A bit of setup for the tests. Because we'll test copy/paste, we need to
work inside a database::
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
>>> import ZODB.tests.util
>>> 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)
We need at least one fake deprecated method to tell the compatibility
framework that component architecture is initialized::
>>> from Products.Five.eventconfigure import setDeprecatedManageAddDelete
>>> class C(object): pass
>>> setDeprecatedManageAddDelete(C)
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 with a deprecation warning::
>>> sub = MyFolder('sub')
>>> folder._setObject('sub', sub)
ObjectWillBeAddedEvent sub
ObjectAddedEvent sub
old manage_afterAdd sub sub folder
ContainerModifiedEvent folder
'sub'
>>> sub = folder.sub
>>> ob = MyContent('dog')
>>> sub._setObject('dog', ob)
ObjectWillBeAddedEvent dog
ObjectAddedEvent dog
old manage_afterAdd dog dog sub
ContainerModifiedEvent sub
'dog'
And when we rename the subfolder, manage_beforeDelete is also called
bottom-up and events are sent::
>>> folder.manage_renameObject('sub', 'marine')
ObjectWillBeMovedEvent sub
ObjectWillBeMovedEvent dog
old manage_beforeDelete dog sub folder
old manage_beforeDelete sub sub folder
ObjectMovedEvent marine
old manage_afterAdd marine marine folder
ObjectMovedEvent dog
old manage_afterAdd dog marine folder
ContainerModifiedEvent folder
Same thing for clone::
>>> res = folder.manage_clone(folder.marine, 'tank')
ObjectCopiedEvent tank
ObjectWillBeAddedEvent tank
ObjectWillBeAddedEvent dog
ObjectAddedEvent tank
old manage_afterAdd tank tank folder
ObjectAddedEvent dog
old manage_afterAdd dog tank folder
ContainerModifiedEvent folder
ObjectClonedEvent tank
old manage_afterClone tank tank
ObjectClonedEvent dog
old manage_afterClone dog tank
>>> res.getId()
'tank'
Old class with deprecatedManageAddDelete
========================================
We specifiy that our class is deprecated (using zcml in real life)::
>>> 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
ContainerModifiedEvent 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
ContainerModifiedEvent folder
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
ContainerModifiedEvent 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
ContainerModifiedEvent 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
ContainerModifiedEvent 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
ContainerModifiedEvent 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
ContainerModifiedEvent 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
ContainerModifiedEvent btfolder
'luckyluke'
>>> btfolder.manage_delObjects('luckyluke')
ObjectWillBeRemovedEvent luckyluke
old manage_beforeDelete luckyluke luckyluke btfolder
ObjectRemovedEvent luckyluke
ContainerModifiedEvent btfolder
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
ContainerModifiedEvent folder
'subfolder'
>>> subfolder = folder.subfolder
>>> ob = MyContent('donald')
>>> subfolder._setObject('donald', ob)
ObjectWillBeAddedEvent donald
ObjectAddedEvent donald
old manage_afterAdd donald donald subfolder
ContainerModifiedEvent 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
ContainerModifiedEvent 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
ContainerModifiedEvent 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) # doctest: +NORMALIZE_WHITESPACE
ObjectWillBeAddedEvent folder
ObjectAddedEvent folder
ContainerModifiedEvent
'folder'
>>> folder = app.folder
>>> ob = MyNewContent('dogbert')
>>> folder._setObject('dogbert', ob)
ObjectWillBeAddedEvent dogbert
ObjectAddedEvent dogbert
ContainerModifiedEvent folder
'dogbert'
>>> folder.manage_delObjects('dogbert')
ObjectWillBeRemovedEvent dogbert
ObjectRemovedEvent dogbert
ContainerModifiedEvent folder
Now move::
>>> ob = MyNewContent('dilbert')
>>> folder._setObject('dilbert', ob)
ObjectWillBeAddedEvent dilbert
ObjectAddedEvent dilbert
ContainerModifiedEvent folder
'dilbert'
>>> cp = folder.manage_cutObjects('dilbert')
>>> folder.manage_pasteObjects(cp)
ObjectWillBeMovedEvent dilbert
ObjectMovedEvent dilbert
ContainerModifiedEvent folder
[{'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
ContainerModifiedEvent folder
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
ContainerModifiedEvent folder
Or copy using manage_clone::
>>> res = folder.manage_clone(folder.dilbert, 'phb')
ObjectCopiedEvent phb
ObjectWillBeAddedEvent phb
ObjectAddedEvent phb
ContainerModifiedEvent folder
ObjectClonedEvent phb
>>> res.getId()
'phb'
Also on a BTreeFolder::
>>> ob = MyNewContent('alice')
>>> btfolder._setObject('alice', ob)
ObjectWillBeAddedEvent alice
ObjectAddedEvent alice
ContainerModifiedEvent btfolder
'alice'
>>> btfolder.manage_renameObject('alice', 'rabbit')
ObjectWillBeMovedEvent alice
ObjectMovedEvent rabbit
ContainerModifiedEvent btfolder
>>> btfolder.manage_delObjects('rabbit')
ObjectWillBeRemovedEvent rabbit
ObjectRemovedEvent rabbit
ContainerModifiedEvent btfolder
Now for a tree of objects. Let's create a simple one::
>>> subfolder = MyNewFolder('subfolder')
>>> folder._setObject('subfolder', subfolder)
ObjectWillBeAddedEvent subfolder
ObjectAddedEvent subfolder
ContainerModifiedEvent folder
'subfolder'
>>> subfolder = folder.subfolder
>>> ob = MyNewContent('mel')
>>> subfolder._setObject('mel', ob)
ObjectWillBeAddedEvent mel
ObjectAddedEvent mel
ContainerModifiedEvent subfolder
'mel'
Renaming a tree of objects::
>>> folder.manage_renameObject('subfolder', 'firefly')
ObjectWillBeMovedEvent subfolder
ObjectWillBeMovedEvent mel
ObjectMovedEvent firefly
ObjectMovedEvent mel
ContainerModifiedEvent folder
Cloning a tree of objects::
>>> res = folder.manage_clone(folder.firefly, 'serenity')
ObjectCopiedEvent serenity
ObjectWillBeAddedEvent serenity
ObjectWillBeAddedEvent mel
ObjectAddedEvent serenity
ObjectAddedEvent mel
ContainerModifiedEvent folder
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
ContainerModifiedEvent
'ofolder'
>>> ob1 = MyNewContent('ob1')
>>> ofolder._setObject('ob1', ob1)
ObjectWillBeAddedEvent ob1
ObjectAddedEvent ob1
ContainerModifiedEvent ofolder
'ob1'
>>> ob2 = MyNewContent('ob2')
>>> ofolder._setObject('ob2', ob2)
ObjectWillBeAddedEvent ob2
ObjectAddedEvent ob2
ContainerModifiedEvent ofolder
'ob2'
>>> ofolder.manage_renameObject('ob1', 'ob4')
ObjectWillBeMovedEvent ob1
ObjectMovedEvent ob4
ContainerModifiedEvent ofolder
>>> ofolder.objectIds()
['ob4', 'ob2']
When subobjects are reordered, an event about the container is sent::
>>> ofolder.moveObjectsUp('ob2')
ContainerModifiedEvent ofolder
1
>>> ofolder.objectIds()
['ob2', 'ob4']
Now cleanup::
>>> import transaction
>>> transaction.abort()
>>> tearDown()
##############################################################################
#
# 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.
#
##############################################################################
msgid ""
msgstr ""
"Project-Id-Version: Five 1.1\n"
"POT-Creation-Date: Sun Jun 12 17:22:49 2005\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Philipp von Weitershausen <philikon@philikon.de>\n"
"Language-Team: Five Developers <z3-five@zope.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "This is a message"
msgstr "Dies ist eine Nachricht"
# Default: "This is an explicit message"
msgid "explicit-msg"
msgstr "Dies ist eine explizite Nachricht"
msgid "These are ${number} messages"
msgstr "Dies sind ${number} Nachrichten"
# Default: "These are ${number} explicit messages"
msgid "explicit-msgs"
msgstr "Dies sind ${number} explizite Nachrichten"
msgid "This is an attribute"
msgstr "Dies ist ein Attribut"
# Default: "Explicit title"
msgid "explicit-title"
msgstr "Expliziter Titel"
# Default: "Explicit summary"
msgid "explicit-summary"
msgstr "Explizite Zusammenfassung"
##############################################################################
#
# 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.
#
##############################################################################
# This file contains no message ids because Zope's default language
# is English
msgid ""
msgstr ""
"Project-Id-Version: Five 1.1\n"
"POT-Creation-Date: Sun Jun 12 17:22:49 2005\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Philipp von Weitershausen <philikon@philikon.de>\n"
"Language-Team: Five Developers <z3-five@zope.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
##############################################################################
#
# 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.
#
##############################################################################
msgid ""
msgstr ""
"Project-Id-Version: Five 1.1\n"
"POT-Creation-Date: Sun Jun 12 17:22:49 2005\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Philipp von Weitershausen <philikon@philikon.de>\n"
"Language-Team: Five Developers <z3-five@zope.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "This is a message"
msgstr ""
# Default: "This is an explicit message"
msgid "explicit-msg"
msgstr ""
msgid "These are ${number} messages"
msgstr ""
# Default: "These are ${number} explicit messages"
msgid "explicit-msgs"
msgstr ""
msgid "This is an attribute"
msgstr ""
# Default: "Explicit title"
msgid "explicit-title"
msgstr ""
# Default: "Explicit summary"
msgid "explicit-summary"
msgstr ""
<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="parrot"
schema=".metaconfigure.IParrotDirective"
handler=".metaconfigure.parrot"
/>
</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.
#
##############################################################################
"""Parrot directive and support classes
$Id: metaconfigure.py 12884 2005-05-30 13:10:41Z philikon $
"""
from zope.interface import Interface
from zope.configuration.fields import GlobalObject
from zope.schema import TextLine
class IParrotDirective(Interface):
"""State that a class implements something.
"""
class_ = GlobalObject(
title=u"Class",
required=True
)
name = TextLine(
title=u"Name",
description=u"The parrots name.",
required=True
)
def parrot(_context, class_, name):
parrot = class_()
parrot.pineForFjords()
class NorwegianBlue(object):
def pineForFjords(self):
return "This parrot is no more!"
<configure xmlns="http://namespaces.zope.org/zope">
<!-- OverrideAdapter instead of OriginalAdapter -->
<adapter
for=".adapters.IOrigin"
provides=".adapters.IDestination"
factory=".adapters.OverrideAdapter"
/>
</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.
#
##############################################################################
""" Unit tests for Z2 -> Z3 bridge utilities.
$Id: test_bridge.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
from Testing.ZopeTestCase import ZopeDocFileSuite
return ZopeDocFileSuite('bridge.txt', package="Products.Five.tests")
if __name__ == '__main__':
framework()
##############################################################################
#
# 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.
#
##############################################################################
"""Test the basic ZCML directives
$Id: test_directives.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_directives():
"""
Test ZCML directives
There isn't much to test here since the actual directive handlers
are either tested in other, more specific tests, or they're
already tested in Zope 3. We'll just do a symbolic test of
adapters and overrides of adapters here as well as registering
meta directives.
But first, we load the configuration file:
>>> import Products.Five.tests
>>> from Products.Five import zcml
>>> zcml.load_config('meta.zcml', Products.Five)
>>> zcml.load_config('directives.zcml', Products.Five.tests)
Now for some testing. Here we check whether the registered
adapter works:
>>> from Products.Five.tests.adapters import IAdapted, IDestination
>>> from Products.Five.tests.adapters import Adaptable, Origin
>>> obj = Adaptable()
>>> adapted = IAdapted(obj)
>>> adapted.adaptedMethod()
'Adapted: The method'
Now let's load some overriding ZCML statements:
>>> zcml.load_string(
... '''<includeOverrides
... package="Products.Five.tests"
... file="overrides.zcml" />''')
>>> origin = Origin()
>>> dest = IDestination(origin)
>>> dest.method()
'Overridden'
Clean up adapter registry and others:
>>> from zope.testing.cleanup import cleanUp
>>> cleanUp()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# 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.
#
##############################################################################
"""Test events triggered by Five
$Id: test_event.py 19706 2005-11-10 14:05:16Z efge $
"""
import os, sys
if __name__ == '__main__':
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())
super(NotifyBase, self).manage_afterAdd(item, container)
manage_afterAdd.__five_method__ = True # Shut up deprecation warnings
def manage_beforeDelete(self, item, container):
super(NotifyBase, self).manage_beforeDelete(item, container)
print 'old manage_beforeDelete %s %s %s' % (self.getId(), item.getId(),
container.getId())
manage_beforeDelete.__five_method__ = True # Shut up deprecation warnings
def manage_afterClone(self, item):
print 'old manage_afterClone %s %s' % (self.getId(), item.getId())
super(NotifyBase, self).manage_afterClone(item)
manage_afterClone.__five_method__ = True # Shut up deprecation warnings
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():
from zope.testing.doctest import DocFileSuite
return DocFileSuite('event.txt', package="Products.Five.tests")
if __name__ == '__main__':
framework()
##############################################################################
#
# 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 i18n framework
$Id: test_i18n.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_directive():
"""
Test the i18n directive
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
First, we need to register the ZCML directive:
>>> import zope.app.i18n
>>> from Products.Five import zcml
>>> zcml.load_config('meta.zcml', zope.app.i18n)
Let's register the gettext locales using the ZCML directive:
>>> configure_zcml = '''
... <configure xmlns="http://namespaces.zope.org/zope"
... xmlns:i18n="http://namespaces.zope.org/i18n"
... package="Products.Five.tests">
... <i18n:registerTranslations directory="locales" />
... </configure>'''
>>> zcml.load_string(configure_zcml)
Now, take an arbitrary message id from that domain:
>>> from zope.i18nmessageid import MessageFactory
>>> from zope.i18n import translate
>>> _ = MessageFactory('fivetest')
>>> msg = _(u'explicit-msg', u'This is an explicit message')
As you can see, both the default functionality and translation to
German work:
>>> translate(msg)
u'This is an explicit message'
>>> translate(msg, target_language='de')
u'Dies ist eine explizite Nachricht'
Clean up:
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# 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()
##############################################################################
#
# 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.
#
##############################################################################
"""Test security induced by ZCML
$Id: test_security.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
from zope.interface import Interface, implements
from AccessControl import ClassSecurityInfo
class IDummy(Interface):
"""Just a marker interface"""
class Dummy1:
implements(IDummy)
def foo(self): pass
def bar(self): pass
def baz(self): pass
def keg(self): pass
def wot(self): pass
class Dummy2(Dummy1):
security = ClassSecurityInfo()
security.declarePublic('foo')
security.declareProtected('View management screens', 'bar')
security.declarePrivate('baz')
security.declareProtected('View management screens', 'keg')
def test_security_equivalence():
"""This test demonstrates that the traditional declarative security of
Zope 2 can be replaced by ZCML statements without any loss of
information.
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
We start out with two classes, ``Dummy1`` and ``Dummy2``. They
are identical in every way, except that ``Dummy2`` has security
declarations and ``Dummy1`` does not. Before we do anything, none
of them have security access controls:
>>> from Products.Five.tests.test_security import Dummy1, Dummy2
>>> hasattr(Dummy1, '__ac_permissions__')
False
>>> hasattr(Dummy2, '__ac_permissions__')
False
Before we can make security declarations through ZCML, we need to
register the directive and the permission:
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config('meta.zcml', Products.Five)
>>> zcml.load_config('permissions.zcml', Products.Five)
Now we initialize the security for ``Dummy2`` and provide some
ZCML declarations for ``Dummy1``:
>>> configure_zcml = '''
... <configure xmlns="http://namespaces.zope.org/zope">
... <content class="Products.Five.tests.test_security.Dummy1">
... <allow attributes="foo" />
... <!--deny attributes="baz" /--> <!-- XXX not yet supported -->
... <require attributes="bar keg"
... permission="zope2.ViewManagementScreens"
... />
... </content>
... </configure>
... '''
>>> zcml.load_string(configure_zcml)
>>> from Globals import InitializeClass
>>> InitializeClass(Dummy2)
Now we compare their access controls:
>>> ac1 = getattr(Dummy1, '__ac_permissions__')
>>> ac2 = getattr(Dummy2, '__ac_permissions__')
>>> ac1 == ac2
True
Now we look at the individual permissions:
>>> from AccessControl.ZopeSecurityPolicy import getRoles
>>> from AccessControl import ACCESS_PUBLIC
>>> from AccessControl import ACCESS_PRIVATE
>>> dummy1 = Dummy1()
>>> getRoles(dummy1, 'bar', dummy1.bar, ('Def',))
('Manager',)
>>> getRoles(dummy1, 'keg', dummy1.keg, ('Def',))
('Manager',)
>>> getRoles(dummy1, 'foo', dummy1.foo, ('Def',)) is ACCESS_PUBLIC
True
#>>> getRoles(dummy1, 'baz', dummy1.baz, ('Def',)) is ACCESS_PRIVATE
#True XXX Not yet supported.
>>> dummy2 = Dummy2()
>>> getRoles(dummy2, 'bar', dummy2.bar, ('Def',))
('Manager',)
>>> getRoles(dummy2, 'keg', dummy2.keg, ('Def',))
('Manager',)
>>> getRoles(dummy2, 'foo', dummy2.foo, ('Def',)) is ACCESS_PUBLIC
True
>>> getRoles(dummy2, 'baz', dummy2.baz, ('Def',)) is ACCESS_PRIVATE
True
Before we end we should clean up after ourselves:
>>> from Products.Five.security import clearSecurityInfo
>>> clearSecurityInfo(Dummy1)
>>> clearSecurityInfo(Dummy2)
>>> tearDown()
"""
def test_checkPermission():
"""
Test checkPermission
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
Zope 3 has a function zope.security.checkPermission which provides
an easy way of checking whether the currently authenticated user
has the permission to access an object. The function delegates to
the security policy's checkPermission() method.
Five has the same function, Five.security.checkPermission, but in
a Zope2-compatible implementation. It too uses the currently
active security policy of Zope 2 for the actual permission
checking.
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config('meta.zcml', Products.Five)
>>> zcml.load_config('permissions.zcml', Products.Five)
In the following we want to test Five's checkPermission function.
We do that by taking the test's folder and asserting several
standard permissions. What we want to assure is that
checkPermission translates the Zope 2 permissions correctly,
especially the edge cases:
a) zope2.Public (which should always be available to everyone)
>>> from Products.Five.security import checkPermission
>>> checkPermission('zope2.Public', self.folder)
True
b) zope2.Private (which should never available to anyone)
>>> checkPermission('zope.Private', self.folder)
False
>>> checkPermission('zope2.Private', self.folder)
False
Any other standard Zope 2 permission will also resolve correctly:
>>> checkPermission('zope2.AccessContentsInformation', self.folder)
True
Invalid permissions will obviously result in a negative response:
>>> checkPermission('notapermission', self.folder)
False
In addition to using Five's ``checkPermission`` function directly,
we also expect the same behaviour when we use Zope 3's
zope.security.checkPermission function. Code from within Zope 3
will use that and therefore it should work transparently. For
that to work, a new Five "interaction" needs to be started (the
old one from placelesssetup needs to be ended first):
>>> from zope.security.management import endInteraction
>>> endInteraction()
>>> from Products.Five.security import newInteraction
>>> newInteraction()
a) zope2.Public (which should always be available to everyone)
>>> from zope.security import checkPermission
>>> checkPermission('zope2.Public', self.folder)
True
b) zope2.Private (which should never available to anyone)
>>> checkPermission('zope.Private', self.folder)
False
>>> checkPermission('zope2.Private', self.folder)
False
Any other standard Zope 2 permission will also resolve correctly:
>>> checkPermission('zope2.AccessContentsInformation', self.folder)
True
Invalid permissions will obviously result in a negative response:
>>> checkPermission('notapermission', self.folder)
False
Clean up:
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# 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.
#
##############################################################################
"""Size adapters for testing
$Id: test_size.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
from zope.interface import implements
from zope.app.size.interfaces import ISized
class SimpleContentSize(object):
"""Size for ``SimpleContent`` objects."""
implements(ISized)
def __init__(self, context):
self.context = context
def sizeForSorting(self):
return ('byte', 42)
def sizeForDisplay(self):
return "What is the meaning of life?"
class FancyContentSize(object):
"""Size for ``SimpleContent`` objects."""
implements(ISized)
def __init__(self, context):
self.context = context
def sizeForSorting(self):
return ('line', 143)
def sizeForDisplay(self):
return "That's not the meaning of life!"
def test_size():
"""
Test size adapters
Set up:
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
>>> configure_zcml = '''
... <configure xmlns="http://namespaces.zope.org/zope"
... xmlns:five="http://namespaces.zope.org/five">
... <five:sizable class="Products.Five.tests.testing.simplecontent.SimpleContent" />
... <five:sizable class="Products.Five.tests.testing.fancycontent.FancyContent" />
... <adapter
... for="Products.Five.tests.testing.simplecontent.ISimpleContent"
... provides="zope.app.size.interfaces.ISized"
... factory="Products.Five.tests.test_size.SimpleContentSize"
... />
... <adapter
... for="Products.Five.tests.testing.fancycontent.IFancyContent"
... provides="zope.app.size.interfaces.ISized"
... factory="Products.Five.tests.test_size.FancyContentSize"
... />
... </configure>'''
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config('meta.zcml', Products.Five)
>>> zcml.load_string(configure_zcml)
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent
We have registered an ``ISized`` adapter for SimpleContent:
>>> n = manage_addSimpleContent(self.folder, 'simple', 'Simple')
>>> self.folder.simple.get_size()
42
Fancy content already has a ``get_size`` method
>>> n = manage_addFancyContent(self.folder, 'fancy', 'Fancy')
>>> self.folder.fancy.get_size()
43
Clean up:
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# 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 viewable module.
$Id: test_viewable.py 19646 2005-11-08 15:46:36Z yuppie $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_defaultView():
"""
Testing default view functionality
Take a class Foo and an interface IFoo:
>>> class Foo:
... pass
>>> from zope.interface import Interface
>>> class IFoo(Interface):
... pass
Set up a default view for IFoo:
>>> from zope.component import provideAdapter
>>> from zope.component.interfaces import IDefaultViewName
>>> from zope.publisher.interfaces.browser import IBrowserRequest
and default view names for everything and IFoo objects in particular:
>>> provideAdapter(u'index.html', (None, IBrowserRequest), IDefaultViewName)
>>> provideAdapter(u'foo.html', (IFoo, IBrowserRequest), IDefaultViewName)
Now take a BrowserDefault for an instance of Foo::
>>> foo = Foo()
>>> from Products.Five.viewable import BrowserDefault
>>> bd = BrowserDefault(foo)
For now the default view name is index.html, like we set above:
>>> from Products.Five.traversable import FakeRequest
>>> request = FakeRequest()
>>> obj, path = bd.defaultView(request)
>>> obj is foo
True
>>> path
[u'index.html']
until we mark the object with IFoo:
>>> from zope.interface import directlyProvides
>>> directlyProvides(foo, IFoo)
>>> obj, path = bd.defaultView(request)
>>> obj is foo
True
>>> path
[u'foo.html']
Clean up adapter registry:
>>> from zope.testing.cleanup import cleanUp
>>> cleanUp()
"""
def test_suite():
import unittest
from zope.testing.doctest import DocTestSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
return unittest.TestSuite((
DocTestSuite(),
FunctionalDocFileSuite('viewable.txt',
package="Products.Five.tests",),
))
if __name__ == '__main__':
framework()
##############################################################################
#
# 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.
#
##############################################################################
"""Test helpers
$Id$
"""
from Products.Five.tests.testing.restricted import RestrictedPythonTestCase
from Products.Five.tests.testing.folder import FiveTraversableFolder
from Products.Five.tests.testing.folder import manage_addFiveTraversableFolder
from Products.Five.tests.testing.folder import NoVerifyPasteFolder
from Products.Five.tests.testing.folder import manage_addNoVerifyPasteFolder
##############################################################################
#
# 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.
#
##############################################################################
"""Test content objects.
$Id$
"""
import Acquisition
from AccessControl import ClassSecurityInfo
from OFS.SimpleItem import SimpleItem
from Globals import InitializeClass
from zope.interface import Interface, implements
class IFancyContent(Interface):
pass
class FancyAttribute(Acquisition.Explicit):
"""Doc test fanatics"""
def __init__(self, name):
self.name = name
security = ClassSecurityInfo()
security.declarePublic('index_html')
def index_html(self, REQUEST):
"""Doc test fanatics"""
return self.name
InitializeClass(FancyAttribute)
class FancyContent(SimpleItem):
"""A class that already comes with its own __bobo_traverse__ handler.
Quite fancy indeed.
It also comes with its own get_size method.
"""
implements(IFancyContent)
meta_type = "Fancy Content"
security = ClassSecurityInfo()
def __bobo_traverse__(self, REQUEST, name):
return FancyAttribute(name).__of__(self)
def get_size(self):
return 43
InitializeClass(FancyContent)
def manage_addFancyContent(self, id, REQUEST=None):
"""Add the fancy fancy content."""
id = self._setObject(id, FancyContent(id))
return ''
##############################################################################
#
# 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.
#
##############################################################################
"""Test folders
$Id$
"""
from OFS.Folder import Folder
from OFS.interfaces import IFolder
from zope.interface import implements
from Products.Five.traversable import Traversable
class NoVerifyPasteFolder(Folder):
"""Folder that does not perform paste verification.
Used by test_events
"""
def _verifyObjectPaste(self, object, validate_src=1):
pass
def manage_addNoVerifyPasteFolder(container, id, title=''):
container._setObject(id, NoVerifyPasteFolder())
folder = container[id]
folder.id = id
folder.title = title
class FiveTraversableFolder(Traversable, Folder):
"""Folder that is five-traversable
"""
implements(IFolder)
def manage_addFiveTraversableFolder(container, id, title=''):
container._setObject(id, FiveTraversableFolder())
folder = container[id]
folder.id = id
folder.title = title
##############################################################################
#
# 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.
#
##############################################################################
"""Restricted python test helpers
Based on Plone's RestrictedPythonTestCase, with kind permission by the
Plone developers.
$Id$
"""
from AccessControl import Unauthorized
from Testing.ZopeTestCase import ZopeTestCase
def addPythonScript(folder, id, params='', body=''):
"""Add a PythonScript to folder."""
# clean up any 'ps' that's already here..
try:
folder._getOb(id)
folder.manage_delObjects([id])
except AttributeError:
pass # it's okay, no 'ps' exists yet
factory = folder.manage_addProduct['PythonScripts']
factory.manage_addPythonScript(id)
folder[id].ZPythonScript_edit(params, body)
def checkRestricted(folder, psbody):
"""Perform a check by running restricted Python code."""
addPythonScript(folder, 'ps', body=psbody)
try:
folder.ps()
except Unauthorized, e:
raise AssertionError, e
def checkUnauthorized(folder, psbody):
"""Perform a check by running restricted Python code. Expect to
encounter an Unauthorized exception."""
addPythonScript(folder, 'ps', body=psbody)
try:
folder.ps()
except Unauthorized:
pass
else:
raise AssertionError, "Authorized but shouldn't be"
class RestrictedPythonTestCase(ZopeTestCase):
"""Javiotic test case for restricted code."""
def addPS(self, id, params='', body=''):
addPythonScript(self.folder, id, params, body)
def check(self, psbody):
checkRestricted(self.folder, psbody)
def checkUnauthorized(self, psbody):
checkUnauthorized(self.folder, psbody)
##############################################################################
#
# 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.
#
##############################################################################
"""Simple content class(es) for browser tests
$Id$
"""
from OFS.SimpleItem import SimpleItem
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from zope.interface import Interface, implements
from Products.Five.traversable import Traversable
class ISimpleContent(Interface):
pass
class ICallableSimpleContent(ISimpleContent):
pass
class IIndexSimpleContent(ISimpleContent):
pass
class SimpleContent(Traversable, SimpleItem):
implements(ISimpleContent)
meta_type = 'Five SimpleContent'
security = ClassSecurityInfo()
def __init__(self, id, title):
self.id = id
self.title = title
security.declarePublic('mymethod')
def mymethod(self):
return "Hello world"
security.declarePublic('direct')
def direct(self):
"""Should be able to traverse directly to this as there is no view.
"""
return "Direct traversal worked"
InitializeClass(SimpleContent)
class CallableSimpleContent(SimpleItem):
"""A Viewable piece of content"""
implements(ICallableSimpleContent)
meta_type = "Five CallableSimpleContent"
def __call__(self, *args, **kw):
""" """
return "Default __call__ called"
InitializeClass(CallableSimpleContent)
class IndexSimpleContent(SimpleItem):
"""A Viewable piece of content"""
implements(IIndexSimpleContent)
meta_type = 'Five IndexSimpleContent'
def index_html(self, *args, **kw):
""" """
return "Default index_html called"
InitializeClass(IndexSimpleContent)
def manage_addSimpleContent(self, id, title, REQUEST=None):
"""Add the simple content."""
self._setObject(id, SimpleContent(id, title))
def manage_addCallableSimpleContent(self, id, title, REQUEST=None):
"""Add the viewable simple content."""
self._setObject(id, CallableSimpleContent(id, title))
def manage_addIndexSimpleContent(self, id, title, REQUEST=None):
"""Add the viewable simple content."""
self._setObject(id, IndexSimpleContent(id, title))
Testing defaultViewable
=======================
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config('configure.zcml', package=Products.Five)
PROPFIND without defaultViewable
--------------------------------
>>> print http(r"""
... PROPFIND /test_folder_1_ HTTP/1.1
... Authorization: Basic test_user_1_:secret
... Content-Length: 250
... Content-Type: application/xml
... Depth: 1
...
... <?xml version="1.0" encoding="utf-8"?>
... <propfind xmlns="DAV:"><prop>
... <getlastmodified xmlns="DAV:"/>
... <creationdate xmlns="DAV:"/>
... <resourcetype xmlns="DAV:"/>
... <getcontenttype xmlns="DAV:"/>
... <getcontentlength xmlns="DAV:"/>
... </prop></propfind>
... """, handle_errors=False)
HTTP/1.1 207 Multi-Status
Connection: close
Content-Length: ...
Content-Location: http://localhost/test_folder_1_/
Content-Type: text/xml; charset="utf-8"
Date: ...
<BLANKLINE>
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/test_folder_1_/</d:href>
<d:propstat>
<d:prop>
<n:getlastmodified xmlns:n="DAV:">...
<n:creationdate xmlns:n="DAV:">...
<n:resourcetype xmlns:n="DAV:"><n:collection/></n:resourcetype>
<n:getcontenttype xmlns:n="DAV:"></n:getcontenttype>
<n:getcontentlength xmlns:n="DAV:"></n:getcontentlength>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<d:response>
<d:href>/test_folder_1_/acl_users</d:href>
<d:propstat>
<d:prop>
<n:getlastmodified xmlns:n="DAV:">...
<n:creationdate xmlns:n="DAV:">...
<n:resourcetype xmlns:n="DAV:"></n:resourcetype>
<n:getcontenttype xmlns:n="DAV:"></n:getcontenttype>
<n:getcontentlength xmlns:n="DAV:"></n:getcontentlength>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
PROPFIND with defaultViewable
-----------------------------
Now make the class default viewable:
>>> from Products.Five.fiveconfigure import classDefaultViewable
>>> from OFS.Folder import Folder
>>> classDefaultViewable(Folder)
And try it again:
>>> print http(r"""
... PROPFIND /test_folder_1_ HTTP/1.1
... Authorization: Basic test_user_1_:secret
... Content-Length: 250
... Content-Type: application/xml
... Depth: 1
...
... <?xml version="1.0" encoding="utf-8"?>
... <propfind xmlns="DAV:"><prop>
... <getlastmodified xmlns="DAV:"/>
... <creationdate xmlns="DAV:"/>
... <resourcetype xmlns="DAV:"/>
... <getcontenttype xmlns="DAV:"/>
... <getcontentlength xmlns="DAV:"/>
... </prop></propfind>
... """, handle_errors=False)
HTTP/1.1 207 Multi-Status
Connection: close
Content-Length: ...
Content-Location: http://localhost/test_folder_1_/
Content-Type: text/xml; charset="utf-8"
Date: ...
<BLANKLINE>
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/test_folder_1_/</d:href>
<d:propstat>
<d:prop>
<n:getlastmodified xmlns:n="DAV:">...
<n:creationdate xmlns:n="DAV:">...
<n:resourcetype xmlns:n="DAV:"><n:collection/></n:resourcetype>
<n:getcontenttype xmlns:n="DAV:"></n:getcontenttype>
<n:getcontentlength xmlns:n="DAV:"></n:getcontentlength>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<d:response>
<d:href>/test_folder_1_/acl_users</d:href>
<d:propstat>
<d:prop>
<n:getlastmodified xmlns:n="DAV:">...
<n:creationdate xmlns:n="DAV:">...
<n:resourcetype xmlns:n="DAV:"></n:resourcetype>
<n:getcontenttype xmlns:n="DAV:"></n:getcontenttype>
<n:getcontentlength xmlns:n="DAV:"></n:getcontentlength>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
Clean up
--------
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
##############################################################################
#
# 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.
#
##############################################################################
"""Machinery for making things traversable through adaptation
$Id: traversable.py 19283 2005-10-31 17:43:51Z philikon $
"""
from zExceptions import NotFound
from zope.component import getMultiAdapter, ComponentLookupError
from zope.interface import implements, Interface
from zope.publisher.interfaces import ILayer
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.traversing.interfaces import ITraverser, ITraversable
from zope.app.traversing.adapters import DefaultTraversable
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 Products.Five.security import newInteraction
_marker = object
class FakeRequest(dict):
implements(IBrowserRequest)
def has_key(self, key):
return False
def getURL(self):
return "http://codespeak.net/z3/five"
class Traversable:
"""A mixin to make an object traversable using an ITraverser adapter.
"""
__five_traversable__ = True
def __fallback_traverse__(self, REQUEST, name):
"""Method hook for fallback traversal
This method is called by __bobo_traverse___ when Zope3-style
ITraverser traversal fails.
Just raise a AttributeError to indicate traversal has failed
and let Zope do it's job.
"""
raise AttributeError, name
__fallback_traverse__.__five_method__ = True
def __bobo_traverse__(self, REQUEST, name):
"""Hook for Zope 2 traversal
This method is called by Zope 2's ZPublisher upon traversal.
It allows us to trick it into faking the Zope 3 traversal system
by using an ITraverser adapter.
"""
if not IBrowserRequest.providedBy(REQUEST):
# Try to get the REQUEST by acquisition
REQUEST = getattr(self, 'REQUEST', None)
if not IBrowserRequest.providedBy(REQUEST):
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
newInteraction()
try:
return ITraverser(self).traverse(
path=[name], request=REQUEST).__of__(self)
except (ComponentLookupError, LookupError,
AttributeError, KeyError, NotFound):
pass
try:
return getattr(self, name)
except AttributeError:
pass
try:
return self[name]
except (AttributeError, KeyError):
pass
return self.__fallback_traverse__(REQUEST, name)
__bobo_traverse__.__five_method__ = True
class FiveTraversable(DefaultTraversable):
def traverse(self, name, furtherPath):
context = self._subject
__traceback_info__ = (context, name, furtherPath)
# Find the REQUEST
REQUEST = getattr(context, 'REQUEST', None)
if not IBrowserRequest.providedBy(REQUEST):
REQUEST = FakeRequest()
setDefaultSkin(REQUEST)
# Try to lookup a view
try:
return getMultiAdapter((context, REQUEST), Interface, name)
except ComponentLookupError:
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)
##############################################################################
#
# 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.
#
##############################################################################
"""Machinery for making things viewable
$Id: viewable.py 19646 2005-11-08 15:46:36Z yuppie $
"""
import inspect
from zExceptions import NotFound
from zope.component import ComponentLookupError
from zope.interface import implements
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.zapi import getDefaultViewName
from Products.Five.traversable import FakeRequest
from Products.Five.interfaces import IBrowserDefault
_marker = object
class Viewable:
"""A mixin to make an object viewable.
"""
__five_viewable__ = True
def __fallback_default__(self, request):
"""Try to dispatch to existing index_html or __call__"""
if getattr(self, 'index_html', None):
return self, ('index_html',)
if getattr(self, 'fallback_call__', None):
return self, ('fallback_call__',)
# XXX Should never get this far. But if it does?
# def fallback_call__(self, *args, **kw):
# """By default, return self"""
# return self
# we have a default view, tell zpublisher to go there
def __browser_default__(self, request):
obj = self
path = None
if request['REQUEST_METHOD'] not in ('GET', 'POST'):
return obj, [request['REQUEST_METHOD']]
try:
obj, path = IBrowserDefault(self).defaultView(request)
except ComponentLookupError:
pass
if path:
if len(path) == 1 and path[0] == '__call__':
return obj, ('fallback_call__',)
return obj, path
return self.__fallback_default__(request)
__browser_default__.__five_method__ = True
# this is technically not needed because ZPublisher finds our
# attribute through __browser_default__; but we also want to be
# able to call pages from python modules, PythonScripts or ZPT
# def __call__(self, *args, **kw):
# """ """
# request = kw.get('REQUEST')
# if not IBrowserRequest.providedBy(request):
# request = getattr(self, 'REQUEST', None)
# if not IBrowserRequest.providedBy(request):
# request = FakeRequest()
# obj, path = self.__browser_default__(request)
# if path and not simpleRecursion():
# meth = obj.unrestrictedTraverse(path)
# if meth is not None:
# return meth(*args, **kw)
# return self.fallback_call__(*args, **kw)
# __call__.__five_method__ = True
# def simpleRecursion():
# # This tests for simple recursion, which can easily happen
# # in CMF, like the following:
# # - Object has a method named 'view'
# # - 'view' method calls '__call__'
# # - five:viewable overrides call to use '__browser_default__'
# # to find a default view and call it
# # - defaultView is set to 'view'
# # Bang. Infinite recursion.
# stack = inspect.stack()
# try:
# if len(stack) < 4:
# return False
# if stack[2][1:4] == stack[4][1:4]:
# return True
# finally:
# del stack
# return False
class BrowserDefault(object):
implements(IBrowserDefault)
def __init__(self, context):
self.context = context
def defaultView(self, request):
context = self.context
try:
name = getDefaultViewName(context, request)
return context, [name,]
except ComponentLookupError:
return context, None
##############################################################################
#
# 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.
#
##############################################################################
"""ZCML machinery
$Id: zcml.py 14452 2005-07-09 21:15:33Z philikon $
"""
import os
from zope.configuration import xmlconfig
_initialized = False
_context = None
def load_site():
"""Load a Five/Zope site by finding and loading the appropriate site
configuration file."""
global _initialized
if _initialized:
return
_initialized = True
# load instance site configuration file
site_zcml = os.path.join(INSTANCE_HOME, "etc", "site.zcml")
if os.path.exists(site_zcml):
file = site_zcml
else:
file = os.path.join(os.path.dirname(__file__), "skel", "site.zcml")
global _context
_context = xmlconfig.file(file)
def load_config(file, package=None, execute=True):
"""Load an additional ZCML file into the context.
Use with extreme care.
"""
global _context
_context = xmlconfig.file(file, package, _context, execute=execute)
def load_string(s):
"""Load a snipped of ZCML into the context.
Use with extreme care.
"""
global _context
_context = xmlconfig.string(s, _context)
# clean up code
def cleanUp():
global _context
_context = None
from zope.testing.cleanup import addCleanUp
addCleanUp(cleanUp)
del addCleanUp
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