Commit cf1e9069 authored by Philipp von Weitershausen's avatar Philipp von Weitershausen

Merge philikon-zope32-integration branch. Basically, this branch entails:

* Updated svn externals to include Zope 3.2 (currently the Zope3 trunk);
  that includes two new top-level packages, pytz and zodbcode, as well as
  the following new zope.* packages:
  - zope.deprecation
  - zope.dottedname
  - zope.formlib
  - zope.index
  - zope.testbrowser

* Moved to a zpkgutils-based build system, as the Zope 3.2 extension modules
  require to be built with it. If everything goes ahead as planned, the release
  tarball will also be built with zpkgutils (some work has also been done in
  that direction).

* Upgraded Five to the 1.3b release (released today) which is a Zope 3.2-compatible
  version of Five 1.2b (also released today). Biggest implication of this on the
  Zope 2 trunk is that the event work by Florent Guillaume has been folded back
  into the Zope 2 core, i.e. the OFS package.

* A few fixes to the Zope 2 PageTemplate/TAL/TALES implementation to work with the
  new immutable i18n Messages available since Zope 3.1+ (and standard in Zope 3.2).
parents b78ac8e9 528f46df
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.
# This allows the ZConfig checked out with Zope to be used by the zpkg
# code invoked by setup.py.
#
# If zpkg ever requires a different version of ZConfig than Zope
# provides as part of it's checkout, this file should be replaced by
# an svn:external to the required version.
#
../lib/python
...@@ -8,11 +8,11 @@ ...@@ -8,11 +8,11 @@
##################################################################### #####################################################################
# Place the Zope major version number below. # Place the Zope major version number below.
ZOPE_VERS=2.8 ZOPE_VERS=2.9
# Place the optimal target version number for Zope (as returned by sys.version) # Place the optimal target version number for Zope (as returned by sys.version)
# below # below
TARGET="2.3.5" TARGET="2.4.1"
# Order a list of "acceptable" python version numbers (as returned by # Order a list of "acceptable" python version numbers (as returned by
# sys.version) below in "best" to "worst" order, not including the # sys.version) below in "best" to "worst" order, not including the
...@@ -22,7 +22,7 @@ ACCEPTABLE="" ...@@ -22,7 +22,7 @@ ACCEPTABLE=""
# provide the executable names for all the acceptable versions # provide the executable names for all the acceptable versions
# (and the target version) below # (and the target version) below
EXENAMES="python python2 python2.3" EXENAMES="python python2 python2.4"
##################################################################### #####################################################################
# END EDITABLE PARAMETERS # # END EDITABLE PARAMETERS #
...@@ -38,8 +38,7 @@ usage() ...@@ -38,8 +38,7 @@ usage()
{ {
echo echo
echo "configure [--help] [--quiet] [--with-python=path] [--prefix=path] " echo "configure [--help] [--quiet] [--with-python=path] [--prefix=path] "
echo " [--build-base=path] [--ignore-largefile] [--ignore-zlib]" echo " [--ignore-largefile] [--ignore-zlib] [--optimize]"
echo " [--optimize]"
echo echo
echo " Creates a Makefile suitable for building and installing Zope" echo " Creates a Makefile suitable for building and installing Zope"
echo echo
...@@ -48,7 +47,6 @@ usage() ...@@ -48,7 +47,6 @@ usage()
echo " --quiet suppress nonessential output" echo " --quiet suppress nonessential output"
echo " --with-python specify a path to a Python interpreter to use" echo " --with-python specify a path to a Python interpreter to use"
echo " --prefix specify an installation path for binary data" echo " --prefix specify an installation path for binary data"
echo " --build-base specify a temporary path for build files"
echo " --ignore-largefile ignore large file support warnings" echo " --ignore-largefile ignore large file support warnings"
echo " --ignore-expat ignore warnings about expat/pyexpat" echo " --ignore-expat ignore warnings about expat/pyexpat"
echo " --ignore-zlib ignore warnings about zlib" echo " --ignore-zlib ignore warnings about zlib"
......
...@@ -26,6 +26,11 @@ Zope Changes ...@@ -26,6 +26,11 @@ Zope Changes
Features added Features added
- Zope now sends Zope 3 events when objects are added or removed
from standard containers. manage_afterAdd, manage_beforeDelete
and manage_afterClone are now deprecated. See
lib/python/Products/Five/tests/event.txt for details.
- Zope now utilizes ZODB 3.6. It had previously used - Zope now utilizes ZODB 3.6. It had previously used
ZODB 3.4. As a result, the DBTab package was removed, as ZODB 3.4. As a result, the DBTab package was removed, as
ZODB 3.6 has multidatabase support that makes DBTab ZODB 3.6 has multidatabase support that makes DBTab
......
...@@ -10,17 +10,14 @@ RELEASE_TAG=<<VERSION_RELEASE_TAG>> ...@@ -10,17 +10,14 @@ RELEASE_TAG=<<VERSION_RELEASE_TAG>>
PACKAGE_NAME=${NAME}-${MAJOR_VERSION}.${MINOR_VERSION}-${RELEASE_TAG} PACKAGE_NAME=${NAME}-${MAJOR_VERSION}.${MINOR_VERSION}-${RELEASE_TAG}
PYTHON="<<PYTHON>>" PYTHON="<<PYTHON>>"
ZPKG=zpkg
TMPDIR=/tmp TMPDIR=/tmp
PREFIX=<<PREFIX>> PREFIX=<<PREFIX>>
BASE_DIR=<<BASE_DIR>> BASE_DIR=<<BASE_DIR>>
BUILD_BASE=<<BUILD_BASE>>
DISTUTILS_OPTS=<<DISTUTILS_OPTS>> DISTUTILS_OPTS=<<DISTUTILS_OPTS>>
INSTALL_FLAGS=<<INSTALL_FLAGS>> INSTALL_FLAGS=<<INSTALL_FLAGS>>
TESTOPTS=-v TESTOPTS=-v
BUILD_FLAGS=--build-base="${BUILD_BASE}" \ BUILD_FLAGS=-i
--build-lib="${BUILD_BASE}/build-lib" \
--build-scripts="${BUILD_BASE}/build-scripts"\
--build-temp="${BUILD_BASE}/build-temp"
RM=rm -f RM=rm -f
RMRF=rm -rf RMRF=rm -rf
...@@ -32,7 +29,7 @@ CP=cp ...@@ -32,7 +29,7 @@ CP=cp
TAR=tar TAR=tar
MKDIR=mkdir -p MKDIR=mkdir -p
.PHONY : clean install instance untestinst testinst build unbuild .PHONY : clean install instance untestinst testinst build
.PHONY : default .PHONY : default
# default: The default step (invoked when make is called without a target) # default: The default step (invoked when make is called without a target)
...@@ -42,36 +39,22 @@ default: build ...@@ -42,36 +39,22 @@ default: build
@echo to run a Zope instance directly from the build directory\). @echo to run a Zope instance directly from the build directory\).
@echo @echo
# build: Do whatever 'setup.py build' implies # build:
build: build:
${PYTHON} "${BASE_DIR}/setup.py" \ ${PYTHON} "${BASE_DIR}/setup.py" \
${DISTUTILS_OPTS} build ${BUILD_FLAGS} ${DISTUTILS_OPTS} build_ext ${BUILD_FLAGS}
# unbuild: Remove the build directory (undo the make build step)
unbuild:
${RMRF} ${BUILD_BASE}
# install: Install a software home. # install: Install a software home.
install: build version_txt install: version_txt
${PYTHON} "${BASE_DIR}/setup.py" ${DISTUTILS_OPTS} install \ ${PYTHON} "${BASE_DIR}/setup.py" ${DISTUTILS_OPTS} \
--home="${PREFIX}" ${BUILD_FLAGS} ${INSTALL_FLAGS} build_ext ${BUILD_FLAGS} \
install --skip-build --home="${PREFIX}" ${INSTALL_FLAGS}
[ -f ${PREFIX}/bin/python ] || ${LN} ${PYTHON} ${PREFIX}/bin/python [ -f ${PREFIX}/bin/python ] || ${LN} ${PYTHON} ${PREFIX}/bin/python
@echo @echo
@echo Zope binaries installed successfully. @echo Zope binaries installed successfully.
@echo Now run \'${PREFIX}/bin/mkzopeinstance.py\' @echo Now run \'${PREFIX}/bin/mkzopeinstance.py\'
# inplace: Install a software home into to the source directory. # inplace: Install a software home into to the source directory.
#
# Note: We used to run 'build_ext -i' for 'inplace', but that was
# suboptimal because it had a tendency to try to rebuild all of the
# (possibly already-built) extensions that might be built during a
# previous 'make' step. built_ext doesn't understand '--build-base'
# and friends so we can't stop it from doing this easily. So instead,
# we rely on the stock install step and name the prefix as the current
# directory. This is a little less efficient than just building the
# extensions because it also compiles bytecode, but it's more intuitive and
# less expensive in the common case than letting distutils
# potentially rebuild the binaries when we've done that already.
inplace: PREFIX=${BASE_DIR} inplace: PREFIX=${BASE_DIR}
inplace: install inplace: install
...@@ -101,7 +84,7 @@ test: inplace ...@@ -101,7 +84,7 @@ test: inplace
# clean: Delete the build files and any binaries/bytecode files in # clean: Delete the build files and any binaries/bytecode files in
# the source directory for good measure. # the source directory for good measure.
clean: unbuild clean:
${FIND} "${BASE_DIR}" \ ${FIND} "${BASE_DIR}" \
-name '*.py[co]' -o -name '*.so' -o -name '*.o' | ${XARGS} ${RM} -name '*.py[co]' -o -name '*.so' -o -name '*.o' | ${XARGS} ${RM}
...@@ -110,23 +93,9 @@ version_txt: ...@@ -110,23 +93,9 @@ version_txt:
printf "Zope ${MAJOR_VERSION}.${MINOR_VERSION}-${RELEASE_TAG}" >\ printf "Zope ${MAJOR_VERSION}.${MINOR_VERSION}-${RELEASE_TAG}" >\
"${BASE_DIR}/lib/python/version.txt" "${BASE_DIR}/lib/python/version.txt"
# sdist: Create a source distribution file (implies clobber). # Building a source distribution requires that zpkg be available:
# sdist:
sdist: clobber sdist_tgz ${ZPKG} -C ${BASE_DIR}/releases/Zope2.cfg
# sdist_tgz: Create a tgz archive file as a source distribution.
#
sdist_tgz: version_txt
${MKDIR} ${TMPDIR}
${CD} ${TMPDIR} && ${LN} ${BASE_DIR} ${PACKAGE_NAME} && \
${TAR} czfh ${BASE_DIR}/${PACKAGE_NAME}.tgz \
--exclude=${PACKAGE_NAME}.tgz\
--exclude=.svn\
--exclude=makefile \
--exclude=build-base \
--exclude=*~ \
--exclude=.#* ${PACKAGE_NAME}
${RMRF} ${TMPDIR}/${PACKAGE_NAME}
# clobber: Make the source tree 'pristine' again. # clobber: Make the source tree 'pristine' again.
clobber: clean uninstance clobber: clean uninstance
......
...@@ -33,7 +33,6 @@ def main(): ...@@ -33,7 +33,6 @@ def main():
# below assumes this script is in the BASE_DIR/inst directory # below assumes this script is in the BASE_DIR/inst directory
global PREFIX global PREFIX
BASE_DIR=os.path.abspath(os.path.dirname(os.path.dirname(sys.argv[0]))) BASE_DIR=os.path.abspath(os.path.dirname(os.path.dirname(sys.argv[0])))
BUILD_BASE=os.path.join(os.getcwd(), 'build-base', 'python-%s.%s' % sys.version_info[:2])
PYTHON=sys.executable PYTHON=sys.executable
MAKEFILE=open(os.path.join(BASE_DIR, 'inst', IN_MAKEFILE)).read() MAKEFILE=open(os.path.join(BASE_DIR, 'inst', IN_MAKEFILE)).read()
REQUIRE_LF_ENABLED = 1 REQUIRE_LF_ENABLED = 1
...@@ -66,8 +65,6 @@ def main(): ...@@ -66,8 +65,6 @@ def main():
INSTALL_FLAGS = '--optimize=1 --no-compile' INSTALL_FLAGS = '--optimize=1 --no-compile'
if o == '--no-compile': if o == '--no-compile':
INSTALL_FLAGS = '--no-compile' INSTALL_FLAGS = '--no-compile'
if o == '--build-base':
BUILD_BASE = a
if o == '--quiet': if o == '--quiet':
DISTUTILS_OPTS = '-q' DISTUTILS_OPTS = '-q'
global QUIET global QUIET
...@@ -85,7 +82,6 @@ def main(): ...@@ -85,7 +82,6 @@ def main():
'<<PYTHON>>':PYTHON, '<<PYTHON>>':PYTHON,
'<<PREFIX>>':PREFIX, '<<PREFIX>>':PREFIX,
'<<BASE_DIR>>':BASE_DIR, '<<BASE_DIR>>':BASE_DIR,
'<<BUILD_BASE>>':BUILD_BASE,
'<<INSTALL_FLAGS>>':INSTALL_FLAGS, '<<INSTALL_FLAGS>>':INSTALL_FLAGS,
'<<ZOPE_MAJOR_VERSION>>':versions.ZOPE_MAJOR_VERSION, '<<ZOPE_MAJOR_VERSION>>':versions.ZOPE_MAJOR_VERSION,
'<<ZOPE_MINOR_VERSION>>':versions.ZOPE_MINOR_VERSION, '<<ZOPE_MINOR_VERSION>>':versions.ZOPE_MINOR_VERSION,
......
...@@ -48,8 +48,8 @@ ...@@ -48,8 +48,8 @@
*/ */
#include "ExtensionClass.h" #include "ExtensionClass/ExtensionClass.h"
#include "Acquisition.h" #include "Acquisition/Acquisition.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
......
...@@ -12,10 +12,10 @@ ...@@ -12,10 +12,10 @@
****************************************************************************/ ****************************************************************************/
#include "ExtensionClass.h" #include "ExtensionClass/ExtensionClass.h"
#define _IN_ACQUISITION_C #define _IN_ACQUISITION_C
#include "Acquisition.h" #include "Acquisition/Acquisition.h"
static ACQUISITIONCAPI AcquisitionCAPI; static ACQUISITIONCAPI AcquisitionCAPI;
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
FOR A PARTICULAR PURPOSE FOR A PARTICULAR PURPOSE
****************************************************************************/ ****************************************************************************/
#include "ExtensionClass.h" #include "ExtensionClass/ExtensionClass.h"
#define UNLESS(E) if(!(E)) #define UNLESS(E) if(!(E))
#define OBJECT(O) ((PyObject*)(O)) #define OBJECT(O) ((PyObject*)(O))
......
...@@ -15,7 +15,7 @@ static char cDocumentTemplate_module_documentation[] = ...@@ -15,7 +15,7 @@ static char cDocumentTemplate_module_documentation[] =
"\n$Id$" "\n$Id$"
; ;
#include "ExtensionClass.h" #include "ExtensionClass/ExtensionClass.h"
static PyObject *py_isDocTemp=0, *py_blocks=0, *py_=0, *join=0, *py_acquire; static PyObject *py_isDocTemp=0, *py_blocks=0, *py_=0, *join=0, *py_acquire;
static PyObject *py___call__, *py___roles__, *py_AUTHENTICATED_USER; static PyObject *py___call__, *py___roles__, *py_AUTHENTICATED_USER;
......
...@@ -17,7 +17,7 @@ static char _extensionclass_module_documentation[] = ...@@ -17,7 +17,7 @@ static char _extensionclass_module_documentation[] =
"$Id$\n" "$Id$\n"
; ;
#include "ExtensionClass.h" #include "ExtensionClass/ExtensionClass.h"
#define EC PyTypeObject #define EC PyTypeObject
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
****************************************************************************/ ****************************************************************************/
#include "ExtensionClass.h" #include "ExtensionClass/ExtensionClass.h"
static PyObject * static PyObject *
of(PyObject *self, PyObject *args) of(PyObject *self, PyObject *args)
......
...@@ -17,7 +17,7 @@ static char Missing_module_documentation[] = ...@@ -17,7 +17,7 @@ static char Missing_module_documentation[] =
"\n$Id$" "\n$Id$"
; ;
#include "ExtensionClass.h" #include "ExtensionClass/ExtensionClass.h"
/* Declarations for objects of type Missing */ /* Declarations for objects of type Missing */
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
****************************************************************************/ ****************************************************************************/
#include "ExtensionClass.h" #include "ExtensionClass/ExtensionClass.h"
#define UNLESS(E) if(!(E)) #define UNLESS(E) if(!(E))
......
...@@ -459,6 +459,10 @@ class AppInitializer: ...@@ -459,6 +459,10 @@ class AppInitializer:
from Products.Sessions.BrowserIdManager import BrowserIdManager from Products.Sessions.BrowserIdManager import BrowserIdManager
bid = BrowserIdManager('browser_id_manager', 'Browser Id Manager') bid = BrowserIdManager('browser_id_manager', 'Browser Id Manager')
app._setObject('browser_id_manager', bid) app._setObject('browser_id_manager', bid)
# FIXME explicitely call manage_afterAdd, as sometimes
# events are initialized too late
browser_id_manager = app.browser_id_manager
browser_id_manager.manage_afterAdd(browser_id_manager, app)
app._setInitializerFlag('browser_id_manager') app._setInitializerFlag('browser_id_manager')
self.commit('Added browser_id_manager') self.commit('Added browser_id_manager')
...@@ -475,6 +479,10 @@ class AppInitializer: ...@@ -475,6 +479,10 @@ class AppInitializer:
path='/temp_folder/session_data', path='/temp_folder/session_data',
requestName='SESSION') requestName='SESSION')
app._setObject('session_data_manager', sdm) app._setObject('session_data_manager', sdm)
# FIXME explicitely call manage_afterAdd, as sometimes
# events are initialized too late
session_data_manager = app.session_data_manager
session_data_manager.manage_afterAdd(session_data_manager, app)
app._setInitializerFlag('session_data_manager') app._setInitializerFlag('session_data_manager')
self.commit('Added session_data_manager') self.commit('Added session_data_manager')
...@@ -523,6 +531,10 @@ class AppInitializer: ...@@ -523,6 +531,10 @@ class AppInitializer:
from Products.SiteErrorLog.SiteErrorLog import SiteErrorLog from Products.SiteErrorLog.SiteErrorLog import SiteErrorLog
error_log = SiteErrorLog() error_log = SiteErrorLog()
app._setObject('error_log', error_log) app._setObject('error_log', error_log)
# FIXME explicitely call manage_afterAdd, as sometimes
# events are initialized too late
error_log = app.error_log
error_log.manage_afterAdd(error_log, app)
app._setInitializerFlag('error_log') app._setInitializerFlag('error_log')
self.commit('Added site error_log at /error_log') self.commit('Added site error_log at /error_log')
......
This diff is collapsed.
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
$Id$ $Id$
""" """
import warnings
import marshal import marshal
import sys, fnmatch, copy, os, re import sys, fnmatch, copy, os, re
from cgi import escape from cgi import escape
...@@ -42,6 +43,12 @@ from zope.interface import implements ...@@ -42,6 +43,12 @@ from zope.interface import implements
import CopySupport import CopySupport
from interfaces import IObjectManager from interfaces import IObjectManager
from Traversable import Traversable from Traversable import Traversable
from zope.event import notify
from zope.app.container.contained import ObjectAddedEvent
from zope.app.container.contained import ObjectRemovedEvent
from OFS.event import ObjectWillBeAddedEvent
from OFS.event import ObjectWillBeRemovedEvent
import OFS.subscribers
# the name BadRequestException is relied upon by 3rd-party code # the name BadRequestException is relied upon by 3rd-party code
...@@ -266,11 +273,17 @@ class ObjectManager( ...@@ -266,11 +273,17 @@ class ObjectManager(
raise AttributeError, id raise AttributeError, id
return default return default
def _setObject(self, id, object, roles=None, user=None, set_owner=1): def _setObject(self, id, object, roles=None, user=None, set_owner=1,
v=self._checkId(id) suppress_events=False):
if v is not None: id=v """Set an object into this container.
try: t=object.meta_type
except: t=None Also sends IObjectWillBeAddedEvent and IObjectAddedEvent.
"""
ob = object # better name, keep original function signature
v = self._checkId(id)
if v is not None:
id = v
t = getattr(ob, 'meta_type', None)
# If an object by the given id already exists, remove it. # If an object by the given id already exists, remove it.
for object_info in self._objects: for object_info in self._objects:
...@@ -278,78 +291,75 @@ class ObjectManager( ...@@ -278,78 +291,75 @@ class ObjectManager(
self._delObject(id) self._delObject(id)
break break
self._objects=self._objects+({'id':id,'meta_type':t},) if not suppress_events:
self._setOb(id,object) notify(ObjectWillBeAddedEvent(ob, self, id))
object=self._getOb(id)
self._objects = self._objects + ({'id': id, 'meta_type': t},)
self._setOb(id, ob)
ob = self._getOb(id)
if set_owner: if set_owner:
object.manage_fixupOwnershipAfterAdd() # TODO: eventify manage_fixupOwnershipAfterAdd
# This will be called for a copy/clone, or a normal _setObject.
ob.manage_fixupOwnershipAfterAdd()
# Try to give user the local role "Owner", but only if # Try to give user the local role "Owner", but only if
# no local roles have been set on the object yet. # no local roles have been set on the object yet.
if hasattr(object, '__ac_local_roles__'): if getattr(ob, '__ac_local_roles__', _marker) is None:
if object.__ac_local_roles__ is None: user = getSecurityManager().getUser()
user=getSecurityManager().getUser() if user is not None:
if user is not None: userid = user.getId()
userid=user.getId() if userid is not None:
if userid is not None: ob.manage_setLocalRoles(userid, ['Owner'])
object.manage_setLocalRoles(userid, ['Owner'])
if not suppress_events:
object.manage_afterAdd(object, self) notify(ObjectAddedEvent(ob, self, id))
OFS.subscribers.maybeCallDeprecated('manage_afterAdd', ob, self)
return id return id
def manage_afterAdd(self, item, container): def manage_afterAdd(self, item, container):
for object in self.objectValues(): # Don't do recursion anymore, a subscriber does that.
try: s=object._p_changed warnings.warn(
except: s=0 "%s.manage_afterAdd is deprecated and will be removed in "
if hasattr(aq_base(object), 'manage_afterAdd'): "Zope 2.11, you should use an IObjectAddedEvent "
object.manage_afterAdd(item, container) "subscriber instead." % self.__class__.__name__,
if s is None: object._p_deactivate() DeprecationWarning, stacklevel=2)
manage_afterAdd.__five_method__ = True
def manage_afterClone(self, item): def manage_afterClone(self, item):
for object in self.objectValues(): # Don't do recursion anymore, a subscriber does that.
try: s=object._p_changed warnings.warn(
except: s=0 "%s.manage_afterClone is deprecated and will be removed in "
if hasattr(aq_base(object), 'manage_afterClone'): "Zope 2.11, you should use an IObjectClonedEvent "
object.manage_afterClone(item) "subscriber instead." % self.__class__.__name__,
if s is None: object._p_deactivate() DeprecationWarning, stacklevel=2)
manage_afterClone.__five_method__ = True
def manage_beforeDelete(self, item, container): def manage_beforeDelete(self, item, container):
for object in self.objectValues(): # Don't do recursion anymore, a subscriber does that.
try: s=object._p_changed warnings.warn(
except: s=0 "%s.manage_beforeDelete is deprecated and will be removed in "
try: "Zope 2.11, you should use an IObjectWillBeRemovedEvent "
if hasattr(aq_base(object), 'manage_beforeDelete'): "subscriber instead." % self.__class__.__name__,
object.manage_beforeDelete(item, container) DeprecationWarning, stacklevel=2)
except BeforeDeleteException, ob: manage_beforeDelete.__five_method__ = True
raise
except ConflictError: def _delObject(self, id, dp=1, suppress_events=False):
raise """Delete an object from this container.
except:
LOG('Zope',ERROR,'manage_beforeDelete() threw', Also sends IObjectWillBeRemovedEvent and IObjectRemovedEvent.
error=sys.exc_info()) """
# In debug mode when non-Manager, let exceptions propagate. ob = self._getOb(id)
if getConfiguration().debug_mode:
if not getSecurityManager().getUser().has_role('Manager'): OFS.subscribers.maybeCallDeprecated('manage_beforeDelete', ob, self)
raise
if s is None: object._p_deactivate() if not suppress_events:
notify(ObjectWillBeRemovedEvent(ob, self, id))
def _delObject(self, id, dp=1):
object=self._getOb(id) self._objects = tuple([i for i in self._objects
try: if i['id'] != id])
object.manage_beforeDelete(object, self)
except BeforeDeleteException, ob:
raise
except ConflictError:
raise
except:
LOG('Zope', ERROR, '_delObject() threw',
error=sys.exc_info())
# In debug mode when non-Manager, let exceptions propagate.
if getConfiguration().debug_mode:
if not getSecurityManager().getUser().has_role('Manager'):
raise
self._objects=tuple(filter(lambda i,n=id: i['id']!=n, self._objects))
self._delOb(id) self._delOb(id)
# Indicate to the object that it has been deleted. This is # Indicate to the object that it has been deleted. This is
...@@ -357,8 +367,13 @@ class ObjectManager( ...@@ -357,8 +367,13 @@ class ObjectManager(
# tolerate failure here because the object being deleted could # tolerate failure here because the object being deleted could
# be a Broken object, and it is not possible to set attributes # be a Broken object, and it is not possible to set attributes
# on Broken objects. # on Broken objects.
try: object._v__object_deleted__ = 1 try:
except: pass ob._v__object_deleted__ = 1
except:
pass
if not suppress_events:
notify(ObjectRemovedEvent(ob, self, id))
def objectIds(self, spec=None): def objectIds(self, spec=None):
# Returns a list of subobject ids of the current object. # Returns a list of subobject ids of the current object.
......
...@@ -30,7 +30,7 @@ from IOrderSupport import IOrderedContainer as z2IOrderedContainer ...@@ -30,7 +30,7 @@ from IOrderSupport import IOrderedContainer as z2IOrderedContainer
from ObjectManager import ObjectManager from ObjectManager import ObjectManager
class OrderSupport: class OrderSupport(object):
""" Ordered container mixin class. """ Ordered container mixin class.
...@@ -251,13 +251,12 @@ class OrderSupport: ...@@ -251,13 +251,12 @@ class OrderSupport:
# Override Inherited Method of ObjectManager Subclass # Override Inherited Method of ObjectManager Subclass
# #
_old_manage_renameObject = ObjectManager.inheritedAttribute(
'manage_renameObject')
def manage_renameObject(self, id, new_id, REQUEST=None): def manage_renameObject(self, id, new_id, REQUEST=None):
""" Rename a particular sub-object without changing its position. """ Rename a particular sub-object without changing its position.
""" """
old_position = self.getObjectPosition(id) old_position = self.getObjectPosition(id)
result = self._old_manage_renameObject(id, new_id, REQUEST) result = super(OrderSupport, self).manage_renameObject(id, new_id,
REQUEST)
self.moveObjectToPosition(new_id, old_position) self.moveObjectToPosition(new_id, old_position)
return result return result
......
...@@ -20,6 +20,7 @@ item types. ...@@ -20,6 +20,7 @@ item types.
$Id$ $Id$
""" """
import warnings
import marshal, re, sys, time import marshal, re, sys, time
import AccessControl.Role, AccessControl.Owned, App.Common import AccessControl.Role, AccessControl.Owned, App.Common
...@@ -60,13 +61,28 @@ class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable, ...@@ -60,13 +61,28 @@ class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable,
isTopLevelPrincipiaApplicationObject=0 isTopLevelPrincipiaApplicationObject=0
def manage_afterAdd(self, item, container): def manage_afterAdd(self, item, container):
pass warnings.warn(
"%s.manage_afterAdd is deprecated and will be removed in "
"Zope 2.11, you should use an IObjectAddedEvent "
"subscriber instead." % self.__class__.__name__,
DeprecationWarning, stacklevel=2)
manage_afterAdd.__five_method__ = True
def manage_beforeDelete(self, item, container): def manage_beforeDelete(self, item, container):
pass warnings.warn(
"%s.manage_beforeDelete is deprecated and will be removed in "
"Zope 2.11, you should use an IObjectWillBeRemovedEvent "
"subscriber instead." % self.__class__.__name__,
DeprecationWarning, stacklevel=2)
manage_beforeDelete.__five_method__ = True
def manage_afterClone(self, item): def manage_afterClone(self, item):
pass warnings.warn(
"%s.manage_afterClone is deprecated and will be removed in "
"Zope 2.11, you should use an IObjectClonedEvent "
"subscriber instead." % self.__class__.__name__,
DeprecationWarning, stacklevel=2)
manage_afterClone.__five_method__ = True
# Direct use of the 'id' attribute is deprecated - use getId() # Direct use of the 'id' attribute is deprecated - use getId()
id='' id=''
......
##############################################################################
#
# 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.
#
##############################################################################
"""
OFS event definitions.
$Id$
"""
from zope.interface import implements
from zope.app.event.objectevent import ObjectEvent
import OFS.interfaces
class ObjectWillBeMovedEvent(ObjectEvent):
"""An object will be moved."""
implements(OFS.interfaces.IObjectWillBeMovedEvent)
def __init__(self, object, oldParent, oldName, newParent, newName):
ObjectEvent.__init__(self, object)
self.oldParent = oldParent
self.oldName = oldName
self.newParent = newParent
self.newName = newName
class ObjectWillBeAddedEvent(ObjectWillBeMovedEvent):
"""An object will be added to a container."""
implements(OFS.interfaces.IObjectWillBeAddedEvent)
def __init__(self, object, newParent=None, newName=None):
#if newParent is None:
# newParent = object.__parent__
#if newName is None:
# newName = object.__name__
ObjectWillBeMovedEvent.__init__(self, object, None, None,
newParent, newName)
class ObjectWillBeRemovedEvent(ObjectWillBeMovedEvent):
"""An object will be removed from a container."""
implements(OFS.interfaces.IObjectWillBeRemovedEvent)
def __init__(self, object, oldParent=None, oldName=None):
#if oldParent is None:
# oldParent = object.__parent__
#if oldName is None:
# oldName = object.__name__
ObjectWillBeMovedEvent.__init__(self, object, oldParent, oldName,
None, None)
class ObjectClonedEvent(ObjectEvent):
"""An object has been cloned into a container."""
implements(OFS.interfaces.IObjectClonedEvent)
...@@ -894,3 +894,33 @@ class IApplication(IFolder, IContainmentRoot): ...@@ -894,3 +894,33 @@ class IApplication(IFolder, IContainmentRoot):
"""Check the global (zclass) registry for problems, which can """Check the global (zclass) registry for problems, which can
be caused by things like disk-based products being deleted. be caused by things like disk-based products being deleted.
Return true if a problem is found""" Return true if a problem is found"""
##################################################
# Event interfaces
from zope.app.event.interfaces import IObjectEvent
class IObjectWillBeMovedEvent(IObjectEvent):
"""An object will be moved."""
oldParent = Attribute("The old location parent for the object.")
oldName = Attribute("The old location name for the object.")
newParent = Attribute("The new location parent for the object.")
newName = Attribute("The new location name for the object.")
class IObjectWillBeAddedEvent(IObjectWillBeMovedEvent):
"""An object will be added to a container."""
class IObjectWillBeRemovedEvent(IObjectWillBeMovedEvent):
"""An object will be removed from a container"""
class IObjectClonedEvent(IObjectEvent):
"""An object has been cloned (a la Zope 2).
This is for Zope 2 compatibility, subscribers should really use
IObjectCopiedEvent or IObjectAddedEvent, depending on their use
cases.
event.object is the copied object, already added to its container.
Note that this event is dispatched to all sublocations.
"""
##############################################################################
#
# 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 subscriber definitions.
$Id$
"""
import warnings
import sys
from zLOG import LOG, ERROR
from App.config import getConfiguration
from AccessControl import getSecurityManager
from ZODB.POSException import ConflictError
import OFS.interfaces
from zope.interface import implements
from zope.component import adapts
from zope.app.container.contained import dispatchToSublocations
from zope.app.location.interfaces import ISublocations
deprecatedManageAddDeleteClasses = []
def hasDeprecatedMethods(ob):
"""Do we need to call the deprecated methods?
"""
for class_ in deprecatedManageAddDeleteClasses:
if isinstance(ob, class_):
return True
return False
def maybeCallDeprecated(method_name, ob, *args):
"""Call a deprecated method, if the framework doesn't call it already.
"""
if hasDeprecatedMethods(ob):
# Already deprecated through zcml
return
method = getattr(ob, method_name)
if getattr(method, '__five_method__', False):
# Method knows it's deprecated
return
if deprecatedManageAddDeleteClasses:
# Not deprecated through zcml and directives fully loaded
class_ = ob.__class__
warnings.warn(
"Calling %s.%s.%s is deprecated when using Five, "
"instead use event subscribers or "
"mark the class with <five:deprecatedManageAddDelete/>"
% (class_.__module__, class_.__name__, method_name),
DeprecationWarning)
# Note that calling the method can lead to incorrect behavior
# but in the most common case that's better than not calling it.
method(ob, *args)
##################################################
class ObjectManagerSublocations(object):
"""Get the sublocations for an ObjectManager.
"""
adapts(OFS.interfaces.IObjectManager)
implements(ISublocations)
def __init__(self, container):
self.container = container
def sublocations(self):
for ob in self.container.objectValues():
yield ob
# The following subscribers should really be defined in ZCML
# but we don't have enough control over subscriber ordering for
# that to work exactly right.
# (Sometimes IItem comes before IObjectManager, sometimes after,
# depending on some of Zope's classes.)
# This code can be simplified when Zope is completely rid of
# manage_afterAdd & co, then IItem wouldn't be relevant anymore and we
# could have a simple subscriber for IObjectManager that directly calls
# dispatchToSublocations.
def dispatchObjectWillBeMovedEvent(ob, event):
"""Multi-subscriber for IItem + IObjectWillBeMovedEvent.
"""
# First, dispatch to sublocations
if OFS.interfaces.IObjectManager.providedBy(ob):
dispatchToSublocations(ob, event)
# Next, do the manage_beforeDelete dance
#import pdb; pdb.set_trace()
if hasDeprecatedMethods(ob):
callManageBeforeDelete(ob, event)
def dispatchObjectMovedEvent(ob, event):
"""Multi-subscriber for IItem + IObjectMovedEvent.
"""
# First, do the manage_afterAdd dance
if hasDeprecatedMethods(ob):
callManageAfterAdd(ob, event)
# Next, dispatch to sublocations
if OFS.interfaces.IObjectManager.providedBy(ob):
dispatchToSublocations(ob, event)
def dispatchObjectClonedEvent(ob, event):
"""Multi-subscriber for IItem + IObjectClonedEvent.
"""
# First, do the manage_afterClone dance
if hasDeprecatedMethods(ob):
callManageAfterClone(ob, event)
# Next, dispatch to sublocations
if OFS.interfaces.IObjectManager.providedBy(ob):
dispatchToSublocations(ob, event)
def callManageAfterAdd(ob, event):
"""Compatibility subscriber for manage_afterAdd.
"""
container = event.newParent
if container is None:
# this is a remove
return
ob.manage_afterAdd(event.object, container)
def callManageBeforeDelete(ob, event):
"""Compatibility subscriber for manage_beforeDelete.
"""
import OFS.ObjectManager # avoid circular imports
container = event.oldParent
if container is None:
# this is an add
return
try:
ob.manage_beforeDelete(event.object, container)
except OFS.ObjectManager.BeforeDeleteException:
raise
except ConflictError:
raise
except:
LOG('Zope', ERROR, '_delObject() threw', error=sys.exc_info())
# In debug mode when non-Manager, let exceptions propagate.
if getConfiguration().debug_mode:
if not getSecurityManager().getUser().has_role('Manager'):
raise
def callManageAfterClone(ob, event):
"""Compatibility subscriber for manage_afterClone.
"""
ob.manage_afterClone(event.object)
...@@ -3,12 +3,16 @@ import unittest ...@@ -3,12 +3,16 @@ import unittest
from AccessControl.Owned import EmergencyUserCannotOwn from AccessControl.Owned import EmergencyUserCannotOwn
from AccessControl.SecurityManagement import newSecurityManager from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManagement import noSecurityManager from AccessControl.SecurityManagement import noSecurityManager
from AccessControl.User import User # before SpecialUsers
from AccessControl.SpecialUsers import emergency_user, nobody, system from AccessControl.SpecialUsers import emergency_user, nobody, system
from AccessControl.User import User
from Acquisition import Implicit from Acquisition import Implicit
from App.config import getConfiguration from App.config import getConfiguration
from OFS.ObjectManager import ObjectManager from OFS.ObjectManager import ObjectManager
from OFS.SimpleItem import SimpleItem from OFS.SimpleItem import SimpleItem
from zope.app.testing.placelesssetup import PlacelessSetup
import Products.Five
from Products.Five import zcml
from Products.Five.eventconfigure import setDeprecatedManageAddDelete
class FauxRoot( Implicit ): class FauxRoot( Implicit ):
...@@ -46,26 +50,39 @@ class ItemForDeletion(SimpleItem): ...@@ -46,26 +50,39 @@ class ItemForDeletion(SimpleItem):
self.before_delete_called = True self.before_delete_called = True
if self.fail_on_delete: if self.fail_on_delete:
raise DeleteFailed raise DeleteFailed
return SimpleItem.manage_beforeDelete(self, item, container)
def manage_afterAdd(self, item, container):
pass
class ObjectManagerTests( unittest.TestCase ): def manage_afterClone(self, item):
pass
from zope.interface import implements
from OFS.interfaces import IItem
class ObjectManagerWithIItem(ObjectManager):
"""The event subscribers work on IItem."""
implements(IItem)
class ObjectManagerTests(PlacelessSetup, unittest.TestCase):
def setUp(self): def setUp(self):
super(ObjectManagerTests, self).setUp()
self.saved_cfg_debug_mode = getConfiguration().debug_mode self.saved_cfg_debug_mode = getConfiguration().debug_mode
zcml.load_config('meta.zcml', Products.Five)
zcml.load_config('event.zcml', Products.Five)
zcml.load_config('deprecated.zcml', Products.Five)
setDeprecatedManageAddDelete(ItemForDeletion)
def tearDown( self ): def tearDown( self ):
noSecurityManager() noSecurityManager()
getConfiguration().debug_mode = self.saved_cfg_debug_mode getConfiguration().debug_mode = self.saved_cfg_debug_mode
super(ObjectManagerTests, self).tearDown()
def setDebugMode(self, mode): def setDebugMode(self, mode):
getConfiguration().debug_mode = mode getConfiguration().debug_mode = mode
def _getTargetClass( self ): def _getTargetClass( self ):
return ObjectManagerWithIItem
from OFS.ObjectManager import ObjectManager
return ObjectManager
def _makeOne( self, *args, **kw ): def _makeOne( self, *args, **kw ):
......
...@@ -14,6 +14,8 @@ class DummyObject(CopySource): ...@@ -14,6 +14,8 @@ class DummyObject(CopySource):
return return
def manage_beforeDelete(self, item, container): def manage_beforeDelete(self, item, container):
return return
manage_afterAdd.__five_method__ = True
manage_beforeDelete.__five_method__ = True
def wl_isLocked(self): def wl_isLocked(self):
return 0 return 0
......
...@@ -17,8 +17,8 @@ static char _Persistence_module_documentation[] = ...@@ -17,8 +17,8 @@ static char _Persistence_module_documentation[] =
"$Id$\n" "$Id$\n"
; ;
#include "ExtensionClass.h" #include "ExtensionClass/ExtensionClass.h"
#include "cPersistence.h" #include "persistent/cPersistence.h"
/* convert_name() returns a new reference to a string name /* convert_name() returns a new reference to a string name
......
...@@ -37,6 +37,12 @@ from AccessControl.Permissions import access_contents_information, \ ...@@ -37,6 +37,12 @@ from AccessControl.Permissions import access_contents_information, \
view_management_screens view_management_screens
from zLOG import LOG, INFO, ERROR, WARNING from zLOG import LOG, INFO, ERROR, WARNING
from Products.ZCatalog.Lazy import LazyMap from Products.ZCatalog.Lazy import LazyMap
from zope.event import notify
from zope.app.container.contained import ObjectAddedEvent
from zope.app.container.contained import ObjectRemovedEvent
from OFS.event import ObjectWillBeAddedEvent
from OFS.event import ObjectWillBeRemovedEvent
import OFS.subscribers
manage_addBTreeFolderForm = DTMLFile('folderAdd', globals()) manage_addBTreeFolderForm = DTMLFile('folderAdd', globals())
...@@ -404,47 +410,58 @@ class BTreeFolder2Base (Persistent): ...@@ -404,47 +410,58 @@ class BTreeFolder2Base (Persistent):
'it is already in use.' % id) 'it is already in use.' % id)
def _setObject(self, id, object, roles=None, user=None, set_owner=1): def _setObject(self, id, object, roles=None, user=None, set_owner=1,
v=self._checkId(id) suppress_events=False):
if v is not None: id=v ob = object # better name, keep original function signature
v = self._checkId(id)
if v is not None:
id = v
# If an object by the given id already exists, remove it. # If an object by the given id already exists, remove it.
if self.has_key(id): if self.has_key(id):
self._delObject(id) self._delObject(id)
self._setOb(id, object) if not suppress_events:
object = self._getOb(id) notify(ObjectWillBeAddedEvent(ob, self, id))
self._setOb(id, ob)
ob = self._getOb(id)
if set_owner: if set_owner:
object.manage_fixupOwnershipAfterAdd() # TODO: eventify manage_fixupOwnershipAfterAdd
# This will be called for a copy/clone, or a normal _setObject.
ob.manage_fixupOwnershipAfterAdd()
# Try to give user the local role "Owner", but only if # Try to give user the local role "Owner", but only if
# no local roles have been set on the object yet. # no local roles have been set on the object yet.
if hasattr(object, '__ac_local_roles__'): if getattr(ob, '__ac_local_roles__', _marker) is None:
if object.__ac_local_roles__ is None: user = getSecurityManager().getUser()
user=getSecurityManager().getUser() if user is not None:
if user is not None: userid = user.getId()
userid=user.getId() if userid is not None:
if userid is not None: ob.manage_setLocalRoles(userid, ['Owner'])
object.manage_setLocalRoles(userid, ['Owner'])
if not suppress_events:
object.manage_afterAdd(object, self) notify(ObjectAddedEvent(ob, self, id))
OFS.subscribers.maybeCallDeprecated('manage_afterAdd', ob, self)
return id return id
def _delObject(self, id, dp=1): def _delObject(self, id, dp=1, suppress_events=False):
object = self._getOb(id) ob = self._getOb(id)
try:
object.manage_beforeDelete(object, self) OFS.subscribers.maybeCallDeprecated('manage_beforeDelete', ob, self)
except BeforeDeleteException, ob:
raise if not suppress_events:
except ConflictError: notify(ObjectWillBeRemovedEvent(ob, self, id))
raise
except:
LOG('Zope', ERROR, 'manage_beforeDelete() threw',
error=sys.exc_info())
self._delOb(id) self._delOb(id)
if not suppress_events:
notify(ObjectRemovedEvent(ob, self, id))
# Aliases for mapping-like access. # Aliases for mapping-like access.
__len__ = objectCount __len__ = objectCount
......
...@@ -2,6 +2,95 @@ ...@@ -2,6 +2,95 @@
Five Changes Five Changes
============ ============
Five 1.3b (2005-11-02)
======================
This version is also included in Zope 2.9b1.
Restructuring
-------------
* Support for Zope 3.2 was added. Five now requires Zope 2.9 (which
ships with Zope 3.2).
* As scheduled, the temporary fork of the new test runner
(``zope.testing``) at ``Five.testing`` was removed. So was the
``runtests.py`` script. Use the regular Zope test runner
(``test.py`` or ``bin/zopectl test``) to run tests.
* To reflect the Component Architecture simplification in Zope 3 since
the X3 3.0 release, ``IFiveUtilityService`` was renamed to
``IFiveUtilityRegistry`` and ``SimpleLocalUtilityService`` was
renamed to ``SimpleLocalUtilityRegistry``. The old names are still
available for a short period of time.
* Event support: ``<five:containerEvents/>`` is the default.
* Due to an incompatability with Zope 3.2's ObjectWidget and Zope 2's
Page Templates, Five now ships with its own ObjectWidget
implementation (which is just a thin wrapper around Zope's one to
make it work in Zope 2). If you use the ObjectWidget, please change
your imports to ``Products.Five.form.objectwidget.ObjectWidget``.
* Backwards compatability for Zope 3-style interfaces of Zope 2
components has been removed as that functionality is now in the Zope
2 core as of Zope 2.9.
Five 1.2b (2005-11-02)
======================
Features
--------
* Added IMarkerInterfaces adapter: This adapter provides methods for
inspecting and assigning marker interfaces. 'edit-markers.html' (or
'manage_interfaces' in the ZMI) allows to change the behavior of specific
objects by adding or removing marker interfaces TTW.
* Added the five:registerClass directive: This does the necessary Zope 2
registration for Five-based content. It is no longer necessary to add an
``initialize()`` function to the product's __init__ in order to register
a meta type to be addable through the ZMI. See doc/products/ViewsTutorial
for an example how to use the directive.
* Local site support: Five has now support for creating local sites
and thereby local utilities. This is mostly needed for allowing CMF
to convert it's portal tools into local utilities. See
doc/localsite.txt for more information
* Event support: When ``<five:containerEvents/>`` is specified, Five
makes the standard Zope 2 containers send events instead of using
manage_afterAdd, manage_beforeDelete and manage_afterClone. These
methods are still called for a class declared
``<five:deprecatedManageAddDelete class=.../>``, and are called in
compatibility mode with a deprecation warning for classes that don't
use this directive.
Restructuring
-------------
* Removed backwards compatibility for Five 1.0 Zope core interfaces.
* Removed backwards compatibility for Zope 2.7 and 2.8.0.
* Added a (temporarily) forked copy of the "new-and-improved" test
runner and supporting 'zope.testing' package, lifted from
http://svn.zope.org/zope.testing. This code should be removed for
Five 1.3, which will use the updated version of 'zope.testing' in
the Zope 2.9 / Zope 3.2 tree.
There is a test runner invoking script in the ``Five`` package. For
example, to run the Five tests with the new test runner, simply
execute the following command line from your instance home::
$ bin/zopectl run Products/Five/runtests.py -v -s Products.Five
* Moved the 'Five.testing' package down to 'Five.tests.testing', in
order to make room for the 'zope.testing' code.
* Removed backwards compatibility for some moved classes (AddForm,
EditForm, ContentAdding)
Five 1.1 (2005-10-04) Five 1.1 (2005-10-04)
===================== =====================
...@@ -100,7 +189,7 @@ Restructuring ...@@ -100,7 +189,7 @@ Restructuring
* The former test product, ``FiveTest``, was converted into separate * The former test product, ``FiveTest``, was converted into separate
modules that provide the mock objects for the corresponding tests modules that provide the mock objects for the corresponding tests
and are located right next to them. Common test helpers have been and are located right next to them. Common test helpers have been
moved to the Five.testing package. Overall, the testing framework moved to the Five.tests.testing package. Overall, the testing framework
was much simplified and the individual tests clean up after was much simplified and the individual tests clean up after
themselves much like they do in Zope 3. themselves much like they do in Zope 3.
......
...@@ -9,7 +9,7 @@ Five contributors ...@@ -9,7 +9,7 @@ Five contributors
- Lennart Regebro (regebro@nuxeo.com) - Lennart Regebro (regebro@nuxeo.com)
- Tres Seaver (tres@zope.com) - Tres Seaver (tseaver@palladion.com)
- Jan-Wijbrand Kolman (jw@infrae.com) - Jan-Wijbrand Kolman (jw@infrae.com)
...@@ -33,6 +33,9 @@ Five contributors ...@@ -33,6 +33,9 @@ Five contributors
- Tarek Ziad (tziade@nuxeo.com) - Tarek Ziad (tziade@nuxeo.com)
- Whit Morriss (whit@longnow.org)
Thank you Thank you
--------- ---------
......
...@@ -70,7 +70,7 @@ def trustedTraverse(ob, path, ignored,): ...@@ -70,7 +70,7 @@ def trustedTraverse(ob, path, ignored,):
o = get(object, name, M) o = get(object, name, M)
if o is M: if o is M:
try: o = object[name] try: o = object[name]
except AttributeError: # better exception except (AttributeError, TypeError): # better exception
raise AttributeError(name) raise AttributeError(name)
object = o object = o
......
...@@ -13,20 +13,13 @@ ...@@ -13,20 +13,13 @@
############################################################################## ##############################################################################
"""Provide basic browser functionality """Provide basic browser functionality
$Id: __init__.py 18841 2005-10-23 09:57:38Z philikon $ $Id: __init__.py 19283 2005-10-31 17:43:51Z philikon $
""" """
import Acquisition import Acquisition
from AccessControl import ClassSecurityInfo import zope.app.publisher.browser
from Globals import InitializeClass
class BrowserView(Acquisition.Explicit): class BrowserView(Acquisition.Explicit, zope.app.publisher.browser.BrowserView):
security = ClassSecurityInfo() """Five browser view
def __init__(self, context, request): Mixes in explicit acquisition so that security can be acquired for
self.context = context views"""
self.request = request
# XXX do not create any methods on the subclass called index_html,
# as this makes Zope 2 traverse into that first!
InitializeClass(BrowserView)
...@@ -52,7 +52,7 @@ class AbsoluteURL(BrowserView): ...@@ -52,7 +52,7 @@ class AbsoluteURL(BrowserView):
return ( return (
{'name': name, 'url': context.absolute_url()},) {'name': name, 'url': context.absolute_url()},)
view = zapi.getViewProviding(container, IAbsoluteURL, request) view = zapi.getMultiAdapter((container, request), IAbsoluteURL)
base = tuple(view.breadcrumbs()) base = tuple(view.breadcrumbs())
base += ( base += (
{'name': name, 'url': ("%s/%s" % (base[-1]['url'], name))},) {'name': name, 'url': ("%s/%s" % (base[-1]['url'], name))},)
......
...@@ -28,6 +28,8 @@ from zope.app.container.interfaces import IAdding, INameChooser ...@@ -28,6 +28,8 @@ from zope.app.container.interfaces import IAdding, INameChooser
from zope.app.container.interfaces import IContainerNamesContainer from zope.app.container.interfaces import IContainerNamesContainer
from zope.app.container.constraints import checkFactory, checkObject from zope.app.container.constraints import checkFactory, checkObject
from zope.app.publisher.browser.menu import getMenu
from zope.app import zapi from zope.app import zapi
from zope.app.event.objectevent import ObjectCreatedEvent from zope.app.event.objectevent import ObjectCreatedEvent
from zope.event import notify from zope.event import notify
...@@ -84,8 +86,8 @@ class BasicAdding(Implicit, BrowserView): ...@@ -84,8 +86,8 @@ class BasicAdding(Implicit, BrowserView):
# XXX this is definitely not right for all or even most uses # XXX this is definitely not right for all or even most uses
# of Five, but can be overridden by an AddView subclass, using # of Five, but can be overridden by an AddView subclass, using
# the class attribute of a zcml:addform directive # the class attribute of a zcml:addform directive
return (str(zapi.getView(self.context, "absolute_url", self.request)) return str(zapi.getMultiAdapter((self.context, self.request),
+ '/manage_main') name=u"absolute_url")) + '/manage_main'
# set in BrowserView.__init__ # set in BrowserView.__init__
request = None request = None
...@@ -104,7 +106,7 @@ class BasicAdding(Implicit, BrowserView): ...@@ -104,7 +106,7 @@ class BasicAdding(Implicit, BrowserView):
if view_name.startswith('@@'): if view_name.startswith('@@'):
view_name = view_name[2:] view_name = view_name[2:]
return zapi.getView(self, view_name, request) return zapi.getMultiAdapter((self, request), name=view_name)
if name.startswith('@@'): if name.startswith('@@'):
view_name = name[2:] view_name = name[2:]
...@@ -135,7 +137,7 @@ class BasicAdding(Implicit, BrowserView): ...@@ -135,7 +137,7 @@ class BasicAdding(Implicit, BrowserView):
if zapi.queryView(self, view_name, self.request) is not None: if zapi.queryView(self, view_name, self.request) is not None:
url = "%s/%s=%s" % ( url = "%s/%s=%s" % (
zapi.getView(self, "absolute_url", self.request), zapi.getMultiAdapter((self, self.request), name=u"absolute_url"),
type_name, id) type_name, id)
self.request.response.redirect(url) self.request.response.redirect(url)
return return
...@@ -169,12 +171,11 @@ class Adding(BasicAdding): ...@@ -169,12 +171,11 @@ class Adding(BasicAdding):
This is sorted by title. This is sorted by title.
""" """
container = self.context container = self.context
menu_service = zapi.getService("BrowserMenu")
result = [] result = []
for menu_id in (self.menu_id, 'zope.app.container.add'): for menu_id in (self.menu_id, 'zope.app.container.add'):
if not menu_id: if not menu_id:
continue continue
for item in menu_service.getMenu(menu_id, self, self.request): for item in getMenu(menu_id, self, self.request):
extra = item.get('extra') extra = item.get('extra')
if extra: if extra:
factory = extra.get('factory') factory = extra.get('factory')
......
<configure xmlns="http://namespaces.zope.org/zope" <configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"> xmlns:browser="http://namespaces.zope.org/browser">
<serviceType
id="BrowserMenu"
interface="zope.app.publisher.interfaces.browser.IBrowserMenuService"
/>
<service
serviceType="BrowserMenu"
permission="zope.Public"
component="zope.app.publisher.browser.globalbrowsermenuservice.globalBrowserMenuService"
/>
<browser:defaultView name="index.html" /> <browser:defaultView name="index.html" />
<browser:page <browser:page
......
...@@ -13,17 +13,15 @@ ...@@ -13,17 +13,15 @@
############################################################################## ##############################################################################
"""Some menu code """Some menu code
$Id: menu.py 14512 2005-07-11 18:40:51Z philikon $ $Id: menu.py 19283 2005-10-31 17:43:51Z philikon $
""" """
from zope.interface import implements from zope.interface import implements
from zope.app import zapi
from zope.app.publisher.interfaces.browser import IMenuAccessView from zope.app.publisher.interfaces.browser import IMenuAccessView
from zope.app.servicenames import BrowserMenu from zope.app.publisher.browser.menu import getMenu
from Products.Five import BrowserView from Products.Five import BrowserView
class MenuAccessView(BrowserView): class MenuAccessView(BrowserView):
implements(IMenuAccessView) implements(IMenuAccessView)
def __getitem__(self, menu_id): def __getitem__(self, menu_id):
browser_menu_service = zapi.getService(BrowserMenu) return getMenu(menu_id, self.context, self.request)
return browser_menu_service.getMenu(menu_id, self.context, self.request)
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<meta:directive <meta:directive
name="defaultView" name="defaultView"
schema="zope.app.publisher.browser.metadirectives.IDefaultViewDirective" schema="zope.app.publisher.browser.metadirectives.IDefaultViewDirective"
handler=".metaconfigure.defaultView" handler="zope.app.publisher.browser.metaconfigure.defaultView"
/> />
<meta:directive <meta:directive
...@@ -62,19 +62,19 @@ ...@@ -62,19 +62,19 @@
<meta:directive <meta:directive
name="menu" name="menu"
schema="zope.app.publisher.browser.metadirectives.IMenuDirective" schema="zope.app.publisher.browser.metadirectives.IMenuDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuDirective" handler="zope.app.publisher.browser.menumeta.menuDirective"
/> />
<meta:directive <meta:directive
name="menuItem" name="menuItem"
schema="zope.app.publisher.browser.metadirectives.IMenuItemDirective" schema="zope.app.publisher.browser.metadirectives.IMenuItemDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuItemDirective" handler="zope.app.publisher.browser.menumeta.menuItemDirective"
/> />
<meta:complexDirective <meta:complexDirective
name="menuItems" name="menuItems"
schema="zope.app.publisher.browser.metadirectives.IMenuItemsDirective" schema="zope.app.publisher.browser.metadirectives.IMenuItemsDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuItemsDirective" handler="zope.app.publisher.browser.menumeta.menuItemsDirective"
> >
<meta:subdirective <meta:subdirective
......
...@@ -21,18 +21,16 @@ $Id: metaconfigure.py 13257 2005-06-09 21:56:39Z philikon $ ...@@ -21,18 +21,16 @@ $Id: metaconfigure.py 13257 2005-06-09 21:56:39Z philikon $
import os import os
from zope.interface import Interface from zope.interface import Interface
from zope.component import getGlobalService, ComponentLookupError
from zope.configuration.exceptions import ConfigurationError from zope.configuration.exceptions import ConfigurationError
from zope.component.servicenames import Presentation from zope.publisher.interfaces.browser import IBrowserRequest, \
from zope.publisher.interfaces.browser import IBrowserRequest IDefaultBrowserLayer
from zope.app.publisher.browser.viewmeta import pages as zope_app_pages from zope.app.publisher.browser.viewmeta import pages as zope_app_pages
from zope.app.publisher.browser.viewmeta import view as zope_app_view from zope.app.publisher.browser.viewmeta import view as zope_app_view
from zope.app.publisher.browser.viewmeta import providesCallable from zope.app.publisher.browser.viewmeta import providesCallable, \
from zope.app.publisher.browser.globalbrowsermenuservice import\ _handle_menu, _handle_for
menuItemDirective
from zope.app.component.metaconfigure import handler from zope.app.component.metaconfigure import handler
from zope.app.component.interface import provideInterface from zope.app.component.interface import provideInterface
from zope.app.container.interfaces import IAdding
from Products.Five.browser import BrowserView from Products.Five.browser import BrowserView
from Products.Five.browser.resource import FileResourceFactory, ImageResourceFactory from Products.Five.browser.resource import FileResourceFactory, ImageResourceFactory
...@@ -40,22 +38,16 @@ from Products.Five.browser.resource import PageTemplateResourceFactory ...@@ -40,22 +38,16 @@ from Products.Five.browser.resource import PageTemplateResourceFactory
from Products.Five.browser.resource import DirectoryResourceFactory from Products.Five.browser.resource import DirectoryResourceFactory
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Products.Five.metaclass import makeClass from Products.Five.metaclass import makeClass
from Products.Five.security import getSecurityInfo, protectClass, \ from Products.Five.security import getSecurityInfo, protectClass, protectName
protectName, initializeClass
import ExtensionClass from Globals import InitializeClass as initializeClass
def page(_context, name, permission, for_, def page(_context, name, permission, for_,
layer='default', template=None, class_=None, layer=IDefaultBrowserLayer, template=None, class_=None,
allowed_interface=None, allowed_attributes=None, allowed_interface=None, allowed_attributes=None,
attribute='__call__', menu=None, title=None, attribute='__call__', menu=None, title=None,
): ):
try:
s = getGlobalService(Presentation)
except ComponentLookupError, err:
pass
_handle_menu(_context, menu, title, [for_], name, permission) _handle_menu(_context, menu, title, [for_], name, permission)
if not (class_ or template): if not (class_ or template):
...@@ -64,8 +56,7 @@ def page(_context, name, permission, for_, ...@@ -64,8 +56,7 @@ def page(_context, name, permission, for_,
allowed_attributes = [] allowed_attributes = []
if allowed_interface is not None: if allowed_interface is not None:
for interface in allowed_interface: for interface in allowed_interface:
attrs = [n for n, d in interface.namesAndDescriptions(1)] allowed_attributes.extend(interface.names())
allowed_attributes.extend(attrs)
if attribute != '__call__': if attribute != '__call__':
if template: if template:
...@@ -92,9 +83,10 @@ def page(_context, name, permission, for_, ...@@ -92,9 +83,10 @@ def page(_context, name, permission, for_,
"The provided class doesn't have the specified attribute " "The provided class doesn't have the specified attribute "
) )
cdict = getSecurityInfo(class_) cdict = getSecurityInfo(class_)
cdict['__name__'] = name
if template: if template:
new_class = makeClassForTemplate(template, bases=(class_, ), new_class = makeClassForTemplate(template, bases=(class_, ),
cdict=cdict) cdict=cdict, name=name)
elif attribute != "__call__": elif attribute != "__call__":
# we're supposed to make a page for an attribute (read: # we're supposed to make a page for an attribute (read:
# method) and it's not __call__. We thus need to create a # method) and it's not __call__. We thus need to create a
...@@ -125,16 +117,15 @@ def page(_context, name, permission, for_, ...@@ -125,16 +117,15 @@ def page(_context, name, permission, for_,
else: else:
# template # template
new_class = makeClassForTemplate(template) new_class = makeClassForTemplate(template, name=name)
_handle_for(_context, for_) _handle_for(_context, for_)
_context.action( _context.action(
discriminator = ('view', for_, name, IBrowserRequest, layer), discriminator = ('view', for_, name, IBrowserRequest, layer),
callable = handler, callable = handler,
args = (Presentation, 'provideAdapter', args = ('provideAdapter',
IBrowserRequest, new_class, name, [for_], Interface, layer, (for_, layer), Interface, name, new_class, _context.info),
_context.info),
) )
_context.action( _context.action(
discriminator = ('five:protectClass', new_class), discriminator = ('five:protectClass', new_class),
...@@ -165,19 +156,6 @@ class pages(zope_app_pages): ...@@ -165,19 +156,6 @@ class pages(zope_app_pages):
menu=menu, title=title, menu=menu, title=title,
**(self.opts)) **(self.opts))
def defaultView(_context, name, for_=None):
type = IBrowserRequest
_context.action(
discriminator = ('defaultViewName', for_, type, name),
callable = handler,
args = (Presentation,
'setDefaultViewName', for_, type, name),
)
_handle_for(_context, for_)
# view (named view with pages) # view (named view with pages)
class view(zope_app_view): class view(zope_app_view):
...@@ -192,11 +170,6 @@ class view(zope_app_view): ...@@ -192,11 +170,6 @@ class view(zope_app_view):
pages = {} pages = {}
for pname, attribute, template in self.pages: for pname, attribute, template in self.pages:
try:
s = getGlobalService(Presentation)
except ComponentLookupError, err:
pass
if template: if template:
cdict[pname] = ZopeTwoPageTemplateFile(template) cdict[pname] = ZopeTwoPageTemplateFile(template)
if attribute and attribute != name: if attribute and attribute != name:
...@@ -255,7 +228,7 @@ class view(zope_app_view): ...@@ -255,7 +228,7 @@ class view(zope_app_view):
if class_ is not None: if class_ is not None:
bases = (class_, ViewMixinForTemplates) bases = (class_, ViewMixinForTemplates)
else: else:
bases = (ViewMixinForTemplates) bases = (ViewMixinForTemplates,)
try: try:
cname = str(name) cname = str(name)
...@@ -277,37 +250,11 @@ class view(zope_app_view): ...@@ -277,37 +250,11 @@ class view(zope_app_view):
discriminator = ('view', for_, name, IBrowserRequest, layer, discriminator = ('view', for_, name, IBrowserRequest, layer,
self.provides), self.provides),
callable = handler, callable = handler,
args = (Presentation, 'provideAdapter', args = ('provideAdapter',
IBrowserRequest, newclass, name, [for_], self.provides, (for_, layer), self.provides, name, newclass,
layer, _context.info), _context.info),
) )
def _handle_for(_context, for_):
if for_ is not None:
_context.action(
discriminator = None,
callable = provideInterface,
args = ('', for_)
)
def _handle_menu(_context, menu, title, for_, name, permission):
if menu or title:
if not (menu and title):
raise ConfigurationError(
"If either menu or title are specified, they must "
"both be specified.")
if len(for_) != 1:
raise ConfigurationError(
"Menus can be specified only for single-view, not for "
"multi-views.")
return menuItemDirective(
_context, menu, for_[0], '@@' + str(name), title,
permission=permission)
return []
_factory_map = {'image':{'prefix':'ImageResource', _factory_map = {'image':{'prefix':'ImageResource',
'count':0, 'count':0,
'factory':ImageResourceFactory}, 'factory':ImageResourceFactory},
...@@ -319,7 +266,7 @@ _factory_map = {'image':{'prefix':'ImageResource', ...@@ -319,7 +266,7 @@ _factory_map = {'image':{'prefix':'ImageResource',
'factory':PageTemplateResourceFactory} 'factory':PageTemplateResourceFactory}
} }
def resource(_context, name, layer='default', permission='zope.Public', def resource(_context, name, layer=IDefaultBrowserLayer, permission='zope.Public',
file=None, image=None, template=None): file=None, image=None, template=None):
if ((file and image) or (file and template) or if ((file and image) or (file and template) or
...@@ -343,8 +290,8 @@ def resource(_context, name, layer='default', permission='zope.Public', ...@@ -343,8 +290,8 @@ def resource(_context, name, layer='default', permission='zope.Public',
_context.action( _context.action(
discriminator = ('resource', name, IBrowserRequest, layer), discriminator = ('resource', name, IBrowserRequest, layer),
callable = handler, callable = handler,
args = (Presentation, 'provideResource', args = ('provideAdapter',
name, IBrowserRequest, factory, layer), (layer,), Interface, name, factory, _context.info),
) )
_context.action( _context.action(
discriminator = ('five:protectClass', new_class), discriminator = ('five:protectClass', new_class),
...@@ -367,7 +314,7 @@ _rd_map = {ImageResourceFactory:{'prefix':'DirContainedImageResource', ...@@ -367,7 +314,7 @@ _rd_map = {ImageResourceFactory:{'prefix':'DirContainedImageResource',
'count':0} 'count':0}
} }
def resourceDirectory(_context, name, directory, layer='default', def resourceDirectory(_context, name, directory, layer=IDefaultBrowserLayer,
permission='zope.Public'): permission='zope.Public'):
if not os.path.isdir(directory): if not os.path.isdir(directory):
...@@ -410,8 +357,8 @@ def resourceDirectory(_context, name, directory, layer='default', ...@@ -410,8 +357,8 @@ def resourceDirectory(_context, name, directory, layer='default',
_context.action( _context.action(
discriminator = ('resource', name, IBrowserRequest, layer), discriminator = ('resource', name, IBrowserRequest, layer),
callable = handler, callable = handler,
args = (Presentation, 'provideResource', args = ('provideAdapter',
name, IBrowserRequest, factory, layer), (layer,), Interface, name, factory, _context.info),
) )
for new_class in new_classes: for new_class in new_classes:
_context.action( _context.action(
...@@ -456,11 +403,12 @@ class ViewMixinForTemplates(BrowserView): ...@@ -456,11 +403,12 @@ class ViewMixinForTemplates(BrowserView):
return self.index(self, *args, **kw) return self.index(self, *args, **kw)
def makeClassForTemplate(filename, globals=None, used_for=None, def makeClassForTemplate(filename, globals=None, used_for=None,
bases=(), cdict=None): bases=(), cdict=None, name=u''):
# XXX needs to deal with security from the bases? # XXX needs to deal with security from the bases?
if cdict is None: if cdict is None:
cdict = {} cdict = {}
cdict.update({'index': ZopeTwoPageTemplateFile(filename, globals)}) cdict.update({'index': ZopeTwoPageTemplateFile(filename, globals),
'__name__': name})
bases += (ViewMixinForTemplates,) bases += (ViewMixinForTemplates,)
class_ = makeClass("SimpleViewClass from %s" % filename, bases, cdict) class_ = makeClass("SimpleViewClass from %s" % filename, bases, cdict)
......
...@@ -18,15 +18,15 @@ $Id: resource.py 13268 2005-06-10 14:18:23Z philikon $ ...@@ -18,15 +18,15 @@ $Id: resource.py 13268 2005-06-10 14:18:23Z philikon $
import os import os
import urllib import urllib
from Acquisition import Explicit import Acquisition
from ComputedAttribute import ComputedAttribute from ComputedAttribute import ComputedAttribute
from OFS.Traversable import Traversable as OFSTraversable from OFS.Traversable import Traversable as OFSTraversable
from zope.exceptions import NotFoundError
from zope.interface import implements from zope.interface import implements
from zope.component.interfaces import IResource from zope.component.interfaces import IResource
from zope.component import getViewProviding
from zope.publisher.interfaces.browser import IBrowserPublisher from zope.publisher.interfaces.browser import IBrowserPublisher
from zope.app import zapi
from zope.app.traversing.browser.interfaces import IAbsoluteURL from zope.app.traversing.browser.interfaces import IAbsoluteURL
from zope.app.datetimeutils import time as timeFromDateTimeString from zope.app.datetimeutils import time as timeFromDateTimeString
from zope.app.publisher.fileresource import File, Image from zope.app.publisher.fileresource import File, Image
...@@ -37,7 +37,7 @@ from Products.Five.browser import BrowserView ...@@ -37,7 +37,7 @@ from Products.Five.browser import BrowserView
_marker = [] _marker = []
class Resource(Explicit): class Resource(Acquisition.Explicit):
"""A publishable resource """A publishable resource
""" """
implements(IResource) implements(IResource)
...@@ -49,7 +49,9 @@ class Resource(Explicit): ...@@ -49,7 +49,9 @@ class Resource(Explicit):
name = self.__name__ name = self.__name__
container = self.__parent__ container = self.__parent__
url = str(getViewProviding(container, IAbsoluteURL, self.request)) # TODO Zope 3 uses site = getSite() instead of container here
# and the @@ resource access view
url = str(zapi.getMultiAdapter((container, self.request), IAbsoluteURL))
url = urllib.unquote(url) url = urllib.unquote(url)
if not isinstance(container, DirectoryResource): if not isinstance(container, DirectoryResource):
name = '++resource++%s' % name name = '++resource++%s' % name
...@@ -213,7 +215,7 @@ class DirectoryResource(BrowserView, Resource, OFSTraversable): ...@@ -213,7 +215,7 @@ class DirectoryResource(BrowserView, Resource, OFSTraversable):
filename = os.path.join(path, name) filename = os.path.join(path, name)
if not os.path.isfile(filename): if not os.path.isfile(filename):
if default is _marker: if default is _marker:
raise NotFoundError(name) raise KeyError(name)
return default return default
ext = name.split('.')[-1] ext = name.split('.')[-1]
factory = self.resource_factories.get(ext, self.default_factory) factory = self.resource_factories.get(ext, self.default_factory)
......
...@@ -7,7 +7,7 @@ ObjectManagerNameChooser ...@@ -7,7 +7,7 @@ ObjectManagerNameChooser
First we need to import and setup some prerequisites: First we need to import and setup some prerequisites:
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> from Products.Five.browser.adding import ObjectManagerNameChooser >>> from Products.Five.browser.adding import ObjectManagerNameChooser
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
......
...@@ -3,15 +3,15 @@ ...@@ -3,15 +3,15 @@
xmlns:five="http://namespaces.zope.org/five"> xmlns:five="http://namespaces.zope.org/five">
<five:defaultViewable <five:defaultViewable
class="Products.Five.testing.simplecontent.SimpleContent" /> class="Products.Five.tests.testing.simplecontent.SimpleContent" />
<browser:defaultView <browser:defaultView
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="eagledefaultview.txt" name="eagledefaultview.txt"
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="eagledefaultview.txt" name="eagledefaultview.txt"
class=".pages.SimpleView" class=".pages.SimpleView"
attribute="eagle" attribute="eagle"
...@@ -22,16 +22,16 @@ ...@@ -22,16 +22,16 @@
already provides __call__, such as our CallableSimpleContent --> already provides __call__, such as our CallableSimpleContent -->
<five:defaultViewable <five:defaultViewable
class="Products.Five.testing.simplecontent.CallableSimpleContent" /> class="Products.Five.tests.testing.simplecontent.CallableSimpleContent" />
<!-- this tests whether five:defaultViewable can be called on a class that <!-- this tests whether five:defaultViewable can be called on a class that
already provides index_html, such as our IndexSimpleContent --> already provides index_html, such as our IndexSimpleContent -->
<five:defaultViewable <five:defaultViewable
class="Products.Five.testing.simplecontent.IndexSimpleContent" /> class="Products.Five.tests.testing.simplecontent.IndexSimpleContent" />
<browser:defaultView <browser:defaultView
for="Products.Five.testing.simplecontent.IIndexSimpleContent" for="Products.Five.tests.testing.simplecontent.IIndexSimpleContent"
name="index_html" name="index_html"
/> />
......
...@@ -3,11 +3,11 @@ ...@@ -3,11 +3,11 @@
<!-- mouse instead of eagle --> <!-- mouse instead of eagle -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
attribute="mouse" attribute="mouse"
name="overridden_view" name="overridden_view"
permission="zope2.Public" permission="zope2.Public"
/> />
</configure> </configure>
\ No newline at end of file
...@@ -34,6 +34,11 @@ class FancyView(BrowserView): ...@@ -34,6 +34,11 @@ class FancyView(BrowserView):
def view(self): def view(self):
return "Fancy, fancy" return "Fancy, fancy"
class CallView(BrowserView):
def __call__(self):
return "I was __call__()'ed"
class CallableNoDocstring: class CallableNoDocstring:
def __call__(self): def __call__(self):
......
...@@ -10,7 +10,7 @@ Let's register a quite large amount of test pages: ...@@ -10,7 +10,7 @@ Let's register a quite large amount of test pages:
Let's add a test object that we view most of the pages off of: Let's add a test object that we view most of the pages off of:
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
We also need to create a stub user account and login; otherwise we We also need to create a stub user account and login; otherwise we
...@@ -29,7 +29,7 @@ Simple pages ...@@ -29,7 +29,7 @@ Simple pages
A browser page that is a view class's attribute (method): A browser page that is a view class's attribute (method):
>>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt') >>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
>>> view != None >>> view is not None
True True
>>> from Products.Five.browser.tests.pages import SimpleView >>> from Products.Five.browser.tests.pages import SimpleView
>>> isinstance(view, SimpleView) >>> isinstance(view, SimpleView)
...@@ -196,8 +196,10 @@ high-level security tests). Let's manually look up a protected view: ...@@ -196,8 +196,10 @@ high-level security tests). Let's manually look up a protected view:
>>> from Products.Five.traversable import FakeRequest >>> from Products.Five.traversable import FakeRequest
>>> from zope.app import zapi >>> from zope.app import zapi
>>> from zope.app.publication.browser import setDefaultSkin
>>> request = FakeRequest() >>> request = FakeRequest()
>>> view = zapi.getView(self.folder.testoid, 'eagle.txt', request) >>> setDefaultSkin(request)
>>> view = zapi.getMultiAdapter((self.folder.testoid, request), name=u'eagle.txt')
It's protecting the object with the permission, and not the attribute, It's protecting the object with the permission, and not the attribute,
so we get ('',) instead of ('eagle',): so we get ('',) instead of ('eagle',):
...@@ -213,7 +215,7 @@ evaluated. __roles__ is a imPermissionRole object: ...@@ -213,7 +215,7 @@ evaluated. __roles__ is a imPermissionRole object:
>>> view_roles >>> view_roles
('Manager',) ('Manager',)
Check to see if view's context properly acquires it's true Check to see if view's context properly acquires its true
parent parent
>>> from Acquisition import aq_parent, aq_base, aq_inner >>> from Acquisition import aq_parent, aq_base, aq_inner
...@@ -255,8 +257,8 @@ High-level security ...@@ -255,8 +257,8 @@ High-level security
... 'nodoc-method', 'nodoc-function', 'nodoc-object', ... 'nodoc-method', 'nodoc-function', 'nodoc-object',
... 'dirpage1', 'dirpage2'] ... 'dirpage1', 'dirpage2']
>>> from Products.Five.testing.restricted import checkRestricted >>> from Products.Five.tests.testing.restricted import checkRestricted
>>> from Products.Five.testing.restricted import checkUnauthorized >>> from Products.Five.tests.testing.restricted import checkUnauthorized
As long as we're not authenticated, we should get Unauthorized for As long as we're not authenticated, we should get Unauthorized for
protected views, but we should be able to view the public ones: protected views, but we should be able to view the public ones:
...@@ -312,5 +314,5 @@ Test traversal to resources from within ZPT pages: ...@@ -312,5 +314,5 @@ Test traversal to resources from within ZPT pages:
Clean up Clean up
-------- --------
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<!-- attribute page --> <!-- attribute page -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
attribute="eagle" attribute="eagle"
name="eagle.txt" name="eagle.txt"
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
name="eagle.method" name="eagle.method"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<!-- attribute page --> <!-- attribute page -->
<browser:pages <browser:pages
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
> >
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
<!-- template/class page --> <!-- template/class page -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
template="falcon.pt" template="falcon.pt"
name="falcon.html" name="falcon.html"
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
<!-- template page (with simple python expression) --> <!-- template page (with simple python expression) -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="owl.pt" template="owl.pt"
name="owl.html" name="owl.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
<!-- template page which calls on context using python and path <!-- template page which calls on context using python and path
expressions --> expressions -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="flamingo.pt" template="flamingo.pt"
name="flamingo.html" name="flamingo.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
<!-- template/class page which calls on context, view, views --> <!-- template/class page which calls on context, view, views -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
template="condor.pt" template="condor.pt"
name="condor.html" name="condor.html"
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
<!-- template page that defines a macro page --> <!-- template page that defines a macro page -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="birdmacro.pt" template="birdmacro.pt"
name="bird.html" name="bird.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
<!-- template page that uses macro page --> <!-- template page that uses macro page -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="seagull.pt" template="seagull.pt"
name="seagull.html" name="seagull.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
...@@ -92,21 +92,21 @@ ...@@ -92,21 +92,21 @@
<!-- test TALES --> <!-- test TALES -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="ostrich.pt" template="ostrich.pt"
name="ostrich.html" name="ostrich.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="tales_traversal.pt" template="tales_traversal.pt"
name="tales_traversal.html" name="tales_traversal.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="template_variables.pt" template="template_variables.pt"
name="template_variables.html" name="template_variables.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
...@@ -115,7 +115,7 @@ ...@@ -115,7 +115,7 @@
<!-- template security --> <!-- template security -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="security.pt" template="security.pt"
name="security.html" name="security.html"
permission="zope2.View" permission="zope2.View"
...@@ -124,7 +124,7 @@ ...@@ -124,7 +124,7 @@
<!-- a publicly accessible page, attribute, template, template/class --> <!-- a publicly accessible page, attribute, template, template/class -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
attribute="eagle" attribute="eagle"
name="public_attribute_page" name="public_attribute_page"
...@@ -132,14 +132,14 @@ ...@@ -132,14 +132,14 @@
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="owl.pt" template="owl.pt"
name="public_template_page" name="public_template_page"
permission="zope2.Public" permission="zope2.Public"
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
template="falcon.pt" template="falcon.pt"
name="public_template_class_page" name="public_template_class_page"
...@@ -147,16 +147,23 @@ ...@@ -147,16 +147,23 @@
/> />
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
template="parakeet.pt" template="parakeet.pt"
name="parakeet.html" name="parakeet.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
/> />
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.CallView"
name="callview.html"
permission="zope2.Public"
/>
<!-- pages from methods/functions/callables that don't have docstrings --> <!-- pages from methods/functions/callables that don't have docstrings -->
<browser:pages <browser:pages
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class="Products.Five.browser.tests.pages.NoDocstringView" class="Products.Five.browser.tests.pages.NoDocstringView"
permission="zope2.Public"> permission="zope2.Public">
<browser:page <browser:page
...@@ -177,7 +184,7 @@ ...@@ -177,7 +184,7 @@
This is mainly used to load Zope2 skin templates so they can be used This is mainly used to load Zope2 skin templates so they can be used
in five skins and layers. --> in five skins and layers. -->
<five:pagesFromDirectory <five:pagesFromDirectory
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
module="Products.Five.browser.tests" module="Products.Five.browser.tests"
directory="pages" directory="pages"
permission="zope2.Public" permission="zope2.Public"
...@@ -186,7 +193,7 @@ ...@@ -186,7 +193,7 @@
<!-- browser:page directives with new style classes are ignored --> <!-- browser:page directives with new style classes are ignored -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.NewStyleClass" class=".pages.NewStyleClass"
name="new_style_class" name="new_style_class"
attribute="method" attribute="method"
...@@ -198,7 +205,7 @@ ...@@ -198,7 +205,7 @@
<browser:view <browser:view
name="" name=""
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
permission="zope2.Public" permission="zope2.Public"
/> />
...@@ -207,18 +214,18 @@ ...@@ -207,18 +214,18 @@
<!-- protected edit form for permission check --> <!-- protected edit form for permission check -->
<browser:editform <browser:editform
schema="Products.Five.testing.simplecontent.ISimpleContent" schema="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="protectededitform.html" name="protectededitform.html"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
/> />
<!-- stuff that we'll override in overrides.zcml --> <!-- stuff that we'll override in overrides.zcml -->
<browser:page <browser:page
for="Products.Five.testing.simplecontent.ISimpleContent" for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView" class=".pages.SimpleView"
attribute="eagle" attribute="eagle"
name="overridden_view" name="overridden_view"
permission="zope2.Public" permission="zope2.Public"
/> />
</configure> </configure>
\ No newline at end of file
...@@ -11,7 +11,7 @@ some: ...@@ -11,7 +11,7 @@ some:
Let's also add one of our stub objects to play with: Let's also add one of our stub objects to play with:
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
...@@ -122,8 +122,23 @@ All public views should always be accessible by anyone: ...@@ -122,8 +122,23 @@ All public views should always be accessible by anyone:
... self.failUnless(status == 200, (status, 200, view_name)) ... self.failUnless(status == 200, (status, 200, view_name))
Miscellaneous
-------------
Zope 2 always wants objects in the traversal graph to have a __name__.
That is also true for views, e.g. a view constructed from a simple
class bearing only a __call__ method:
>>> print http(r'''
... GET /test_folder_1_/testoid/callview.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
I was __call__()'ed
Clean up Clean up
-------- --------
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
...@@ -42,7 +42,7 @@ the PTS languagees adapter and 3) register our test page: ...@@ -42,7 +42,7 @@ the PTS languagees adapter and 3) register our test page:
Finally, we need a traversable folder so that the test page we Finally, we need a traversable folder so that the test page we
registered is found: registered is found:
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'ftf') >>> manage_addFiveTraversableFolder(self.folder, 'ftf')
Now for some actual testing... Our test page is a simple ZPT Now for some actual testing... Our test page is a simple ZPT
......
...@@ -8,7 +8,7 @@ Set up the test fixtures: ...@@ -8,7 +8,7 @@ Set up the test fixtures:
>>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests) >>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> import os, glob >>> import os, glob
...@@ -73,8 +73,8 @@ PageTemplateResource's __call__ renders the template ...@@ -73,8 +73,8 @@ PageTemplateResource's __call__ renders the template
Security Security
-------- --------
>>> from Products.Five.testing.restricted import checkRestricted >>> from Products.Five.tests.testing.restricted import checkRestricted
>>> from Products.Five.testing.restricted import checkUnauthorized >>> from Products.Five.tests.testing.restricted import checkUnauthorized
>>> resource_names = ['cockatiel.html', 'style.css', 'pattern.png'] >>> resource_names = ['cockatiel.html', 'style.css', 'pattern.png']
...@@ -112,5 +112,5 @@ We can now view them all: ...@@ -112,5 +112,5 @@ We can now view them all:
Clean up Clean up
-------- --------
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
...@@ -8,7 +8,7 @@ Set up the test fixtures: ...@@ -8,7 +8,7 @@ Set up the test fixtures:
>>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests) >>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> import os, glob >>> import os, glob
...@@ -69,5 +69,5 @@ Page templates aren't guaranteed to render, so exclude them from the test: ...@@ -69,5 +69,5 @@ Page templates aren't guaranteed to render, so exclude them from the test:
Clean up Clean up
-------- --------
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
...@@ -29,7 +29,7 @@ def test_absoluteurl(): ...@@ -29,7 +29,7 @@ def test_absoluteurl():
>>> from Products.Five import zcml >>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config("configure.zcml", Products.Five)
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
A simple traversal will yield us the @@absolute_url view: A simple traversal will yield us the @@absolute_url view:
...@@ -86,7 +86,7 @@ def test_absoluteurl(): ...@@ -86,7 +86,7 @@ def test_absoluteurl():
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
......
...@@ -33,9 +33,9 @@ def test_default_view(): ...@@ -33,9 +33,9 @@ def test_default_view():
Now let's add a couple of stub objects: Now let's add a couple of stub objects:
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.testing.simplecontent import manage_addCallableSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addCallableSimpleContent
>>> from Products.Five.testing.simplecontent import manage_addIndexSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addIndexSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> manage_addCallableSimpleContent(self.folder, 'testcall', 'TestCall') >>> manage_addCallableSimpleContent(self.folder, 'testcall', 'TestCall')
...@@ -79,7 +79,7 @@ def test_default_view(): ...@@ -79,7 +79,7 @@ def test_default_view():
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
......
...@@ -33,7 +33,7 @@ def test_zpt_i18n(): ...@@ -33,7 +33,7 @@ def test_zpt_i18n():
... </configure> ... </configure>
... <configure package="Products.Five.browser.tests"> ... <configure package="Products.Five.browser.tests">
... <browser:page ... <browser:page
... for="Products.Five.interfaces.IFolder" ... for="OFS.interfaces.IFolder"
... template="i18n.pt" ... template="i18n.pt"
... name="i18n.html" ... name="i18n.html"
... permission="zope2.View" ... permission="zope2.View"
...@@ -49,7 +49,7 @@ def test_zpt_i18n(): ...@@ -49,7 +49,7 @@ def test_zpt_i18n():
In order to be able to traverse to the PageTemplate view, we need In order to be able to traverse to the PageTemplate view, we need
a traversable object: a traversable object:
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
We tell Zope to translate the messages by passing the We tell Zope to translate the messages by passing the
...@@ -80,7 +80,7 @@ def test_zpt_i18n(): ...@@ -80,7 +80,7 @@ def test_zpt_i18n():
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
......
...@@ -28,7 +28,8 @@ def test_menu(): ...@@ -28,7 +28,8 @@ def test_menu():
>>> import Products.Five.browser.tests >>> import Products.Five.browser.tests
>>> from Products.Five import zcml >>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config("meta.zcml", Products.Five)
>>> zcml.load_config("permissions.zcml", Products.Five)
>>> zcml.load_config('menu.zcml', package=Products.Five.browser.tests) >>> zcml.load_config('menu.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.security import newInteraction >>> from Products.Five.security import newInteraction
...@@ -37,12 +38,12 @@ def test_menu(): ...@@ -37,12 +38,12 @@ def test_menu():
Now for some actual testing... Let's look up the menu we registered: Now for some actual testing... Let's look up the menu we registered:
>>> from Products.Five.traversable import FakeRequest >>> from Products.Five.traversable import FakeRequest
>>> from zope.app.publisher.browser.globalbrowsermenuservice import \\ >>> from zope.app.publication.browser import setDefaultSkin
... globalBrowserMenuService >>> from zope.app.publisher.browser.menu import getMenu
>>> request = FakeRequest() >>> request = FakeRequest()
>>> menu = globalBrowserMenuService.getMenu( >>> setDefaultSkin(request)
... 'testmenu', self.folder, request) >>> menu = getMenu('testmenu', self.folder, request)
It should have It should have
...@@ -53,27 +54,41 @@ def test_menu(): ...@@ -53,27 +54,41 @@ def test_menu():
>>> menu.sort(lambda x, y: cmp(x['title'], y['title'])) >>> menu.sort(lambda x, y: cmp(x['title'], y['title']))
>>> from pprint import pprint >>> from pprint import pprint
>>> pprint(menu) >>> pprint(menu[0])
[{'action': '@@cockatiel_menu_public.html', {'action': u'@@cockatiel_menu_public.html',
'description': '', 'description': u'',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Page in a menu (public)'}, 'selected': u'',
{'action': u'seagull.html', 'submenu': None,
'description': u'This is a test menu item', 'title': u'Page in a menu (public)'}
'extra': None,
'selected': '', >>> pprint(menu[1])
'title': u'Test Menu Item'}, {'action': u'seagull.html',
{'action': u'parakeet.html', 'description': u'This is a test menu item',
'description': u'This is a test menu item', 'extra': None,
'extra': None, 'icon': None,
'selected': '', 'selected': u'',
'title': u'Test Menu Item 2'}, 'submenu': None,
{'action': u'falcon.html', 'title': u'Test Menu Item'}
'description': u'This is a test menu item',
'extra': None, >>> pprint(menu[2])
'selected': '', {'action': u'parakeet.html',
'title': u'Test Menu Item 3'}] '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. Let's create a manager user account and log in.
...@@ -82,8 +97,7 @@ def test_menu(): ...@@ -82,8 +97,7 @@ def test_menu():
>>> self.login('manager') >>> self.login('manager')
>>> newInteraction() >>> newInteraction()
>>> menu = globalBrowserMenuService.getMenu( >>> menu = getMenu('testmenu', self.folder, request)
... 'testmenu', self.folder, request)
We should get the protected menu items now: We should get the protected menu items now:
...@@ -91,47 +105,73 @@ def test_menu(): ...@@ -91,47 +105,73 @@ def test_menu():
7 7
>>> menu.sort(lambda x, y: cmp(x['title'], y['title'])) >>> menu.sort(lambda x, y: cmp(x['title'], y['title']))
>>> pprint(menu) >>> pprint(menu[0])
[{'action': '@@cockatiel_menu_protected.html', {'action': u'@@cockatiel_menu_protected.html',
'description': '', 'description': u'',
'extra': None,
'selected': '',
'title': u'Page in a menu (protected)'},
{'action': '@@cockatiel_menu_public.html',
'description': '',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Page in a menu (public)'}, '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', {'action': u'seagull.html',
'description': u'This is a protected test menu item', 'description': u'This is a protected test menu item',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Protected Test Menu Item'}, 'selected': u'',
'submenu': None,
'title': u'Protected Test Menu Item'}
>>> pprint(menu[3])
{'action': u'falcon.html', {'action': u'falcon.html',
'description': u'This is a protected test menu item', 'description': u'This is a protected test menu item',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Protected Test Menu Item 2'}, 'selected': u'',
'submenu': None,
'title': u'Protected Test Menu Item 2'}
>>> pprint(menu[4])
{'action': u'seagull.html', {'action': u'seagull.html',
'description': u'This is a test menu item', 'description': u'This is a test menu item',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Test Menu Item'}, 'selected': u'',
'submenu': None,
'title': u'Test Menu Item'}
>>> pprint(menu[5])
{'action': u'parakeet.html', {'action': u'parakeet.html',
'description': u'This is a test menu item', 'description': u'This is a test menu item',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Test Menu Item 2'}, 'selected': u'',
'submenu': None,
'title': u'Test Menu Item 2'}
>>> pprint(menu[6])
{'action': u'falcon.html', {'action': u'falcon.html',
'description': u'This is a test menu item', 'description': u'This is a test menu item',
'extra': None, 'extra': None,
'selected': '', 'icon': None,
'title': u'Test Menu Item 3'}] 'selected': u'',
'submenu': None,
'title': u'Test Menu Item 3'}
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
......
...@@ -26,7 +26,7 @@ def test_ViewAcquisitionWrapping(): ...@@ -26,7 +26,7 @@ def test_ViewAcquisitionWrapping():
>>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests) >>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> uf = self.folder.acl_users >>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], []) >>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
...@@ -56,7 +56,7 @@ def test_ViewAcquisitionWrapping(): ...@@ -56,7 +56,7 @@ def test_ViewAcquisitionWrapping():
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
...@@ -65,11 +65,10 @@ def test_suite(): ...@@ -65,11 +65,10 @@ def test_suite():
from Testing.ZopeTestCase import installProduct, ZopeDocTestSuite from Testing.ZopeTestCase import installProduct, ZopeDocTestSuite
from Testing.ZopeTestCase import ZopeDocFileSuite from Testing.ZopeTestCase import ZopeDocFileSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite from Testing.ZopeTestCase import FunctionalDocFileSuite
installProduct('PythonScripts') # for Five.testing.restricted installProduct('PythonScripts') # for Five.tests.testing.restricted
return unittest.TestSuite(( return unittest.TestSuite((
ZopeDocTestSuite(), ZopeDocTestSuite(),
ZopeDocFileSuite('pages.txt', ZopeDocFileSuite('pages.txt', package='Products.Five.browser.tests'),
package='Products.Five.browser.tests'),
FunctionalDocFileSuite('pages_ftest.txt', FunctionalDocFileSuite('pages_ftest.txt',
package='Products.Five.browser.tests') package='Products.Five.browser.tests')
)) ))
......
...@@ -23,9 +23,6 @@ def test_recursion(): ...@@ -23,9 +23,6 @@ def test_recursion():
""" """
Test recursion Test recursion
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> setUp()
This test makes sure that recursion is avoided for view lookup. This test makes sure that recursion is avoided for view lookup.
First, we need to set up a stub interface... First, we need to set up a stub interface...
...@@ -51,10 +48,10 @@ def test_recursion(): ...@@ -51,10 +48,10 @@ def test_recursion():
>>> from Products.Five.fiveconfigure import classDefaultViewable >>> from Products.Five.fiveconfigure import classDefaultViewable
>>> classDefaultViewable(Recurse) >>> classDefaultViewable(Recurse)
>>> from zope.app import zapi >>> from zope.component import provideAdapter
>>> from zope.publisher.interfaces.browser import IBrowserRequest >>> from zope.publisher.interfaces.browser import IBrowserRequest
>>> pres = zapi.getGlobalService('Presentation') >>> from zope.component.interfaces import IDefaultViewName
>>> pres.setDefaultViewName(IRecurse, IBrowserRequest, 'view') >>> provideAdapter(u'view', (IRecurse, IBrowserRequest), IDefaultViewName)
Here comes the actual test: Here comes the actual test:
...@@ -65,9 +62,10 @@ def test_recursion(): ...@@ -65,9 +62,10 @@ def test_recursion():
'foo' 'foo'
Clean up: Clean up adapter registry and monkey patches to classes:
>>> tearDown() >>> from zope.testing.cleanup import cleanUp
>>> cleanUp()
""" """
def test_suite(): def test_suite():
......
...@@ -23,7 +23,7 @@ def test_suite(): ...@@ -23,7 +23,7 @@ def test_suite():
import unittest import unittest
from Testing.ZopeTestCase import installProduct, ZopeDocFileSuite from Testing.ZopeTestCase import installProduct, ZopeDocFileSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite from Testing.ZopeTestCase import FunctionalDocFileSuite
installProduct('PythonScripts') # for Five.testing.restricted installProduct('PythonScripts') # for Five.tests.testing.restricted
return unittest.TestSuite(( return unittest.TestSuite((
ZopeDocFileSuite('resource.txt', ZopeDocFileSuite('resource.txt',
package='Products.Five.browser.tests'), package='Products.Five.browser.tests'),
......
...@@ -33,7 +33,7 @@ def test_traversable(): ...@@ -33,7 +33,7 @@ def test_traversable():
the wrong reason: None doesn't have a docstring so BaseRequest the wrong reason: None doesn't have a docstring so BaseRequest
raises NotFoundError.) raises NotFoundError.)
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> print http(r''' >>> print http(r'''
... GET /test_folder_1_/testoid/doesntexist HTTP/1.1 ... GET /test_folder_1_/testoid/doesntexist HTTP/1.1
...@@ -54,21 +54,21 @@ def test_traversable(): ...@@ -54,21 +54,21 @@ def test_traversable():
... <meta:redefinePermission from="zope2.Public" to="zope.Public" /> ... <meta:redefinePermission from="zope2.Public" to="zope.Public" />
... ...
... <five:traversable ... <five:traversable
... class="Products.Five.testing.fancycontent.FancyContent" ... class="Products.Five.tests.testing.fancycontent.FancyContent"
... /> ... />
... ...
... <browser:page ... <browser:page
... for="Products.Five.testing.fancycontent.IFancyContent" ... for="Products.Five.tests.testing.fancycontent.IFancyContent"
... class="Products.Five.browser.tests.pages.FancyView" ... class="Products.Five.browser.tests.pages.FancyView"
... attribute="view" ... attribute="view"
... name="fancy" ... name="fancyview"
... permission="zope2.Public" ... permission="zope2.Public"
... /> ... />
... ...
... </configure>''' ... </configure>'''
>>> zcml.load_string(configure_zcml) >>> zcml.load_string(configure_zcml)
>>> from Products.Five.testing.fancycontent import manage_addFancyContent >>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent
>>> info = manage_addFancyContent(self.folder, 'fancy', '') >>> info = manage_addFancyContent(self.folder, 'fancy', '')
In the following test we let the original __bobo_traverse__ method In the following test we let the original __bobo_traverse__ method
...@@ -85,7 +85,7 @@ def test_traversable(): ...@@ -85,7 +85,7 @@ def test_traversable():
actually works: actually works:
>>> print http(r''' >>> print http(r'''
... GET /test_folder_1_/fancy/fancy HTTP/1.1 ... GET /test_folder_1_/fancy/fancyview HTTP/1.1
... ''') ... ''')
HTTP/1.1 200 OK HTTP/1.1 200 OK
... ...
...@@ -94,7 +94,7 @@ def test_traversable(): ...@@ -94,7 +94,7 @@ def test_traversable():
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
......
...@@ -2,13 +2,15 @@ ...@@ -2,13 +2,15 @@
xmlns:five="http://namespaces.zope.org/five"> xmlns:five="http://namespaces.zope.org/five">
<include file="meta.zcml" /> <include file="meta.zcml" />
<include file="services.zcml" />
<include file="interfaces.zcml" />
<include file="permissions.zcml" /> <include file="permissions.zcml" />
<include file="i18n.zcml" /> <include file="i18n.zcml" />
<include file="event.zcml"/>
<include file="deprecated.zcml"/>
<include package=".site" />
<include package=".browser" /> <include package=".browser" />
<include package=".form" /> <include package=".form" />
<include package=".skin" /> <include package=".skin" />
<include package=".utilities" />
<include package="zope.app.event" /> <include package="zope.app.event" />
<include package="zope.app.traversing" /> <include package="zope.app.traversing" />
......
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<!-- deprecated in core Zope, should be fixed there in Zope 2.9 -->
<five:deprecatedManageAddDelete
class="AccessControl.User.BasicUserFolder"/>
<five:deprecatedManageAddDelete
class="App.Factory.Factory"/>
<five:deprecatedManageAddDelete
class="App.Permission.Permission"/>
<five:deprecatedManageAddDelete
class="HelpSys.HelpTopic.HelpTopicBase"/>
<five:deprecatedManageAddDelete
class="OFS.Cache.CacheManager"/>
<five:deprecatedManageAddDelete
class="Products.OFSP.Draft.Draft"/>
<five:deprecatedManageAddDelete
class="Products.OFSP.Version.Version"/>
<five:deprecatedManageAddDelete
class="Products.PythonScripts.PythonScript.PythonScript"/>
<five:deprecatedManageAddDelete
class="Products.Sessions.BrowserIdManager.BrowserIdManager"/>
<five:deprecatedManageAddDelete
class="Products.Sessions.SessionDataManager.SessionDataManager"/>
<five:deprecatedManageAddDelete
class="Products.SiteAccess.VirtualHostMonster.VirtualHostMonster"/>
<five:deprecatedManageAddDelete
class="Products.SiteAccess.SiteRoot.Traverser"/>
<five:deprecatedManageAddDelete
class="Products.SiteErrorLog.SiteErrorLog.SiteErrorLog"/>
<five:deprecatedManageAddDelete
class="Products.ZCatalog.CatalogAwareness.CatalogAware"/>
<five:deprecatedManageAddDelete
class="Products.ZCatalog.CatalogPathAwareness.CatalogAware"/>
<five:deprecatedManageAddDelete
class="ZClasses.Property.ZCommonSheet"/>
<five:deprecatedManageAddDelete
class="ZClasses.ZClass.ZClass"/>
</configure>
...@@ -33,16 +33,6 @@ redefinePermission ...@@ -33,16 +33,6 @@ redefinePermission
Redefine a permission in included ZCML as another one. Redefine a permission in included ZCML as another one.
service
-------
Declare a global service
serviceType
-----------
Declare a type of service.
skin skin
---- ----
...@@ -55,8 +45,24 @@ Declare a global utility. ...@@ -55,8 +45,24 @@ Declare a global utility.
interface interface
--------- ---------
Register an interface in ZCML. Register an interface in ZCML.
factory
-------
Register an object factory.
modulealias
-----------
Provide a module under an alias name, e.g. for persistent backward
compatability.
hook
----
Install a hook on a hookable object.
browser ``http://namespaces.zope.org/browser`` browser ``http://namespaces.zope.org/browser``
============================================== ==============================================
...@@ -148,16 +154,34 @@ sizable ...@@ -148,16 +154,34 @@ sizable
Retrieve size information for a Zope 2 content class via a Zope 3 Retrieve size information for a Zope 2 content class via a Zope 3
style ``ISized`` adapter. style ``ISized`` adapter.
sendEvents containerEvents
---------- ---------------
Make events be sent for Zope 2 container objects, instead of calling old
methods like ``manage_afterAdd``. These old methods will still be called
for classes specified in a ``deprecatedManageAddDelete`` directive.
Lets a Zope 2 content class send out Zope 3 object events that deprecatedManageAddDelete
correspond to the Zope 2 methods ``manage_afterAdd`` and -------------------------
``manage_beforeDelete``.
Specify a class that needs its old deprecated methods like
``manage_afterAdd``, ``manage_beforeDelete`` and ``manage_afterClone``
to be called. Modern classes should use event subscribers instead.
pagesFromDirectory pagesFromDirectory
------------------ ------------------
Load all *.pt files in a directory as pages. Useful when you want to Loads all files with .pt extension in a directory as pages.
share templates between Five and CMF, so you can declare pages like
this is a similar way to setting up skin folders in portal_skins. registerClass
-------------
Registers Five content with Zope 2.
localsite
---------
Turns a class into an implementation of ``IPossibleSite`` so that its
instances can be serve as local sites. Unless otherwise specified, a
default implementation's methods will be used to make the class comply
with the ``IPossibleSite`` interface.
...@@ -8,7 +8,7 @@ and some limitations. ...@@ -8,7 +8,7 @@ and some limitations.
Zope 3 interfaces Zope 3 interfaces
================= =================
Everything in the ``zope.interface`` package should work. Zope 3 Everything from the ``zope.interface`` package works. Zope 3
interfaces are the foundation of the component architecture, and also interfaces are the foundation of the component architecture, and also
the foundation of schemas. the foundation of schemas.
...@@ -84,3 +84,18 @@ Zope 2 content classes. Note however that these permissions will be ...@@ -84,3 +84,18 @@ Zope 2 content classes. Note however that these permissions will be
ignored by views anyway, as they are trusted -- it only serves to ignored by views anyway, as they are trusted -- it only serves to
protect directly exposed methods on content classes (the python protect directly exposed methods on content classes (the python
scripts and the ZPublisher). scripts and the ZPublisher).
Local Sites
===========
Five supports the concept of a local sites and local site managers.
See localsite.txt_ for more information.
.. _localsite.txt: localsite.html
Object events
=============
Five supports sending Zope 3 object events when objects are added,
moved, renamed, copied and deleted. The use of ``manage_afterAdd`` & co
methods is deprecated.
===============================
Porting Five to Zope 3.1+ notes
===============================
Introduction
------------
Five needs to work in Zope 2.9. Zope 2.9 will ship with Zope 3.2. This
means Five will need to work with Zope 3.2. Since Zope 3.2 doesn't
truly exist yet we'll target Zope 3.1 for now.
A Five Roadmap
--------------
Here is a tentative Five roadmap:
Five 1.1 is to be released shortly, and its main feature is a
refactored directory structure and Zope 3 i18n for Zope 2. It's still
targeting the Zope X3.0 that's in Zope 2.8.
Five 1.2 is still targetting Zope 2.8, and its main expected feature
is support for local utilities.
Five 1.3 is targetting Zope 2.9 and thus Zope 3.2. We're talking about
this release of Five in this document.
Main problem
------------
Zope 3.1 has internal changes that Five needs to support. Five works
by reimplementing ZCML statements it supplies in the context of Zope
2. This reimplementation is hard to maintain, as for each Zope 3
upgrade we need to review all these ZCML statements and port them into
Five again.
The straightforward way to start supporting Zope 3.1+ with Five would
be to review all the ZCML statements in Five and update them to work
with Zope 3.1+.
A more ambitious but nicer solution would be if we could reuse the
Zope 3 ZCML statements directly. If we could accomplish this,
maintainability of Five would be improved by a lot. Far less review of
Five would be necessary for each Zope 3 upgrade. In the rest of this
document we'll be discussing this scenario.
Reasons for Five's modified ZCML statements
-------------------------------------------
Five ships with modified implementations of Zope 3 ZCML statements for
a number of reasons:
* could not use new-style classes that are in Zope 3 due to
ExtensionClass.
* Five views need to work with the Zope 2 publisher, and this expects
different things than the Zope 3 publisher.
* cannot use the Zope 3 security system, while the Zope 3 ZCML calls
into this to configure it.
* Five views need to work with the Zope 2 security system. This means
Five needs to issue Zope 2 style security declarations for views.
We'll go into more detail about each of these points below.
New-style ExtensionClass
========================
Five needed to be compatible with Zope 2.7, which uses old-style
ExtensionClass. This made life difficult for Five, as Zope 3 uses
new-style Python classes in many places. It's not easy to mix the two.
Zope 2.8 changed to allow new-style ExtensionClasses, which are
compatible with new-style Python classes. This means Five can
hopefully be simplified as we can forget about old-style
ExtensionClasses.
Five views need to work with the Zope 2 publisher
=================================================
The Zope 2 publisher expects something quite different than the Zope 3
publisher.
* does what is returned to the publisher need to inherit from
Acquisition.Explicit? (security reasons?)
* we may need something that calls the right methods on the Zope 3
view (such as browserDefault, __call__ and publishTraverse)
Cannot use the Zope 3 security system
=====================================
Do the Zope 3 security calls get in the way? Five currently removes
these calls, but perhaps doing the calls does not harm.
If they do interface, we could perhaps still trick things into
working harmlessly.
Five must issue Zope 2 security declarations for views
======================================================
This cannot be done by the ZCML implementation of Zope 3. We could
hopefully do this by following the following pattern::
def our_directive_implementation(...):
original_directive_implementation(...)
do_the_zope2_work(...)
Local sites in Five
===================
Intro
-----
Zope 3 has a concept of local sites and site managers. They allow one
to locally override component registrations and have components and
their configuration be persisted in the ZODB as well as managed
through the web interface.
By default, Zope 3 has a global site which is configured through ZCML.
It provides the fallback for all component look-up. Local sites are
typically set during traversal, when the traverser encounters an
``ISite`` object. The last encountered ``ISite`` wins. Component
look-up will cascade through all the sites in the hierarchy and fall
back to the global site where it can finally fail.
Five also supports local sites, however by default only local
utilities. Local adapters, such as ZODB-based views, could be
supported with a custom implementation of the local site manager and
local adapter registry. This is not the focus of local sites in Five,
though.
Turning possible sites into sites
---------------------------------
Five uses the same technique as Zope 3 for determining local sites:
sites are found during URL traversal. However, since the Zope 2
ZPublisher doesn't emit the necessary events by default, Five needs to
set a ``BeforeTraverse`` hook on site objects.
Setting this hook needs to be done an object-per-object basis and can
be performed through the ``manage_site.html`` browser page. This view
operates on ``IPossibleSite`` objects (in other words, objects that
*can* be sites but aren't yet). It sets the traversal hook on the
object and then marks it with the ``ISite`` interface to indicate that
it is a real site now, not just a possible site.
Note that unlike the Zope 3 equivalent of this view, it does not set
the site manager to site; it is assumed that the site already knows
how to get its site manager.
Also note that in order for the view to work, the object's class needs
to be Five-traversable, e.g. with the following ZCML statement:
<five:traversable class=".module.MyClass" />
Custom site implementations
---------------------------
Anything can be a site, there are no restrictions (sites don't have to
be folders, for examples). Sites can also be nested. For all the
Component Architecture cares, every object in your URL graph could be
a site.
The only requirement are two interfaces:
``IPossibleSite``
Objects that can potentially be turned into a site need to provide
this interface. That requires them to have a ``setSiteManager()``
and ``getSiteManager()`` method for setting and getting the local
site manager of that site. The site manager is the registry that
takes care of local component look-up.
``IFiveSiteManager``
This interface is a slight extension of the ``IServiceService`` or
``ISiteManager`` interface, respectively (the former in Zope X3
3.0, the latter in later versions). It defines the API of a local
site manager that is to be used in a Five environment. The site's
``getSiteManager()`` method should return an object providing this
interface.
Five's default site manager
----------------------------
If you want to instantly make your custom class an ``IPossibleSite``
implementation, you can use a default mix-in class from Five, e.g.::
class MySite(OFS.Folder, Products.Five.site.localsite.FiveSite):
pass
This default implementation of ``IPossibleSite`` features a site
manager implementation that knows how to register and look-up local
utilities. It does so by adapting the site to
``IFiveUtilityRegistry``.
The default adapter for this local utility registry simply stores the
utilities in a standard OFS Folder on called ``utilities`` on the site
object. You probably want to exchange that simple behaviour with
something that works better in your application. You can do so by
plugging in your own utility registry adapter, e.g.::
<adapter for=".interfaces.IMySite"
provides="Products.Five.site.interfaces.IFiveUtilityRegistry"
fatory=".module.MyUtilityRegistry" />
All this implementation needs to do is comply with the
``IFiveUtilityRegistry`` interface, which essentially means the
standard utility look-up methods like ``queryUtility()``,
``getUtilitiesFor()``, etc.
Turning existing classes into possible sites
--------------------------------------------
If you cannot or do not want to modify existing classes to mix in the
``FiveSite`` class, you can also use a structured monkey patch via
ZCML::
<five:localsite class=".module.MyClass" />
This makes ``MyClass`` an ``IPossibleSite`` and sticks ``FiveSite``'s
``getSiteManager()`` and ``setSiteManager()`` methods on the class as
well. You can also tell it to use a different site implementation's
methods for the monkey patch::
<five:localsite class=".module.MyClass"
site_class=".module.MySiteImpl" />
Just make sure that this class implements ``IPossibleSite``.
...@@ -5,33 +5,42 @@ What is Five? ...@@ -5,33 +5,42 @@ What is Five?
------------- -------------
Five is a Zope 2 product that allows you to integrate Zope 3 Five is a Zope 2 product that allows you to integrate Zope 3
technologies into Zope 2, today. Five right now allows you to use the technologies into Zope 2, today. Among others, it allows you to use
following Zope 3 technologies in Zope 2: Zope 3 interfaces, ZCML-based configuration, adapters, browser pages
(including skins, layers, and resources), automated add and edit forms
based on schemas, object events, as well as Zope 3-style i18n message
catalogs.
* Zope 3 interfaces We've tried to keep the Five experience as close to Zope 3 as
possible, so this means that what you learn while using Five should
also be applicable to Zope 3, and viceversa.
* adapters Five 1.0 and 1.1 work on a straight Zope 2.7 installation, as long as
Zope 3 has been installed. Five 1.2 requires Zope 2.8 which already
* pages (views), including skins and layers, and edit and add forms ships with Zope 3, Five 1.3 is included in Zope 2.9.
* ZCML
It is possible to add Zope 3 style views to your own Zope 2 objects,
or to existing ones, even normal Folders!
Five works with a straight Zope 2.7 installation, as long as Zope 3
has been installed. See Five's INSTALL.txt for more information on how
to set it up.
We're in the process of evaluating lots more Zope 3 technologies for We're in the process of evaluating lots more Zope 3 technologies for
integration into Zope 2. This is the right moment for interested Zope integration into Zope 2. This is the right moment for interested Zope
2 and Zope 3 developers to jump in. We're looking for cooperation 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 between different Zope 2 projects so that this can be a foundational
system for us all. system for us all.
Download Download
-------- --------
2005-11-02 -- We have released Five 1.2b and 1.3b! Download Five 1.2b
here:
http://codespeak.net/z3/five/release/Five-1.2b.tgz
2005-10-04 -- We have released Five 1.1! Download it here:
http://codespeak.net/z3/five/release/Five-1.1.tgz
2005-07-13 -- We have released Five 1.1b! Download it here:
http://codespeak.net/z3/five/release/Five-1.1b.tgz
2005-07-12 -- We have released Five 1.0.2! This is also the version 2005-07-12 -- We have released Five 1.0.2! This is also the version
that will be included in Zope 2.8.1. Download it here: that will be included in Zope 2.8.1. Download it here:
......
############################################################################## # make this directory a package
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
import democontent
def initialize(context):
context.registerClass(
democontent.DemoContent,
constructors = (democontent.manage_addDemoContentForm,
democontent.manage_addDemoContent),
)
<h1 tal:replace="structure context/manage_page_header">Header</h1>
<h2 tal:define="form_title string:Add Demo Content"
tal:replace="structure context/manage_form_title">Form Title</h2>
<p class="form-help">
Add Demo Content
</p>
<form action="." method="post"
tal:attributes="action request/ACTUAL_URL">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
</td>
<td align="left" valign="top">
<input type="text" name="add_input_name" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Title
</div>
</td>
<td align="left" valign="top">
<input type="text" name="title" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit_add"
value=" Add " />
</div>
</td>
</tr>
</table>
</form>
<h1 tal:replace="structure context/manage_page_footer">Footer</h1>
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
from Products.Five import BrowserView
import random import random
from democontent import DemoContent
class Overview:
"""View for overview.
"""
class Overview(BrowserView):
def reversedIds(self): def reversedIds(self):
result = [] result = []
for id in self.context.objectIds(): for id in self.context.objectIds():
...@@ -27,9 +19,28 @@ class Overview(BrowserView): ...@@ -27,9 +19,28 @@ class Overview(BrowserView):
def directlyPublished(self): def directlyPublished(self):
return "This is directly published" return "This is directly published"
class NewExample(BrowserView):
class NewExample:
"""View for new example.
"""
def helpsWithOne(self): def helpsWithOne(self):
return random.randrange(10) return random.randrange(10)
def two(self): def two(self):
return "Two got called" return "Two got called"
class DemoContentAddView:
"""Add view for demo content.
"""
def __call__(self, add_input_name='', title='', submit_add=''):
if submit_add:
obj = DemoContent(add_input_name, title)
self.context.add(obj)
self.request.response.redirect(self.context.nextURL())
return ''
return self.index()
<configure <configure
xmlns="http://namespaces.zope.org/zope" xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser" xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five"> xmlns:five="http://namespaces.zope.org/five">
<five:traversable <five:traversable class="OFS.Application.Application"/>
class="OFS.Folder.Folder"
/> <!-- OFS.Folder.Folder views -->
<five:traversable class="OFS.Folder.Folder"/>
<browser:page <browser:page
for="Products.Five.interfaces.IFolder" for="OFS.interfaces.IFolder"
name="overview.html" name="overview.html"
template="overview.pt" template="overview.pt"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
/> />
<browser:page <browser:page
for="Products.Five.interfaces.IFolder" for="OFS.interfaces.IFolder"
name="overview2.html" name="overview2.html"
template="overview2.pt" template="overview2.pt"
permission="zope2.ViewManagementScreens" class=".browser.Overview"
class=".browser.Overview" permission="zope2.ViewManagementScreens"
/> />
<browser:page <browser:page
for="Products.Five.interfaces.IFolder" for="OFS.interfaces.IFolder"
name="test.html" name="test.html"
class=".browser.Overview" class=".browser.Overview"
attribute="directlyPublished" attribute="directlyPublished"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
/> />
<browser:pages <browser:pages
for="Products.Five.interfaces.IFolder" for="OFS.interfaces.IFolder"
class=".browser.NewExample" class=".browser.NewExample"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
> >
...@@ -45,19 +47,43 @@ ...@@ -45,19 +47,43 @@
/> />
</browser:pages> </browser:pages>
<five:traversable class=".democontent.DemoContent" /> <!-- .democontent.IDemoContent views -->
<five:traversable class=".democontent.DemoContent"/>
<browser:page
for="zope.app.container.interfaces.IAdding"
name="addDemoContent.html"
template="addDemoContent.pt"
class=".browser.DemoContentAddView"
permission="zope2.ViewManagementScreens"
/>
<browser:resource
name="green5.png"
image="green5.png"
/>
<five:registerClass
class=".democontent.DemoContent"
meta_type="Five Demo Content"
addview="addDemoContent.html"
icon="green5.png"
permission="zope2.ViewManagementScreens"
/>
<browser:page <browser:page
for=".democontent.IDemoContent" for=".democontent.IDemoContent"
name="someview.html" name="someview.html"
template="someview.pt" template="someview.pt"
permission="zope2.ViewManagementScreens" permission="zope2.ViewManagementScreens"
/> />
<five:defaultViewable class=".democontent.DemoContent" /> <five:defaultViewable class=".democontent.DemoContent"/>
<browser:defaultView <browser:defaultView
for=".democontent.IDemoContent" for=".democontent.IDemoContent"
name="someview.html" /> name="someview.html"
/>
</configure> </configure>
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
from zope.interface import Interface, implements from zope.interface import Interface, implements
from OFS.SimpleItem import SimpleItem from OFS.SimpleItem import SimpleItem
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
class IDemoContent(Interface): class IDemoContent(Interface):
def mymethod(): def mymethod():
"Return some text" """Return some text.
"""
class DemoContent(SimpleItem): class DemoContent(SimpleItem):
implements(IDemoContent)
meta_type = 'Five Demo Content' implements(IDemoContent)
def __init__(self, id, title): def __init__(self, id, title):
self.id = id self.id = id
...@@ -30,15 +19,3 @@ class DemoContent(SimpleItem): ...@@ -30,15 +19,3 @@ class DemoContent(SimpleItem):
def mymethod(self): def mymethod(self):
return "Hello world" return "Hello world"
manage_addDemoContentForm = PageTemplateFile(
"www/demoContentAdd", globals(),
__name__ = 'manage_addDemoContentForm')
def manage_addDemoContent(self, id, title, REQUEST=None):
"""Add the demo content."""
id = self._setObject(id, DemoContent(id, title))
if REQUEST is None:
return
REQUEST.RESPONSE.redirect(REQUEST['URL1'] + '/manage_main')
<configure xmlns="http://namespaces.zope.org/zope">
<!-- Adapter giving sublocations for ObjectManagers, used
by dispatchToSublocations -->
<adapter
for="OFS.interfaces.IObjectManager"
provides="zope.app.location.interfaces.ISublocations"
factory="OFS.subscribers.ObjectManagerSublocations"
/>
<!-- dispatch IObjectWillBeMovedEvent with "bottom-up" semantics -->
<subscriber
for="OFS.interfaces.IItem
OFS.interfaces.IObjectWillBeMovedEvent"
handler="OFS.subscribers.dispatchObjectWillBeMovedEvent"
/>
<!-- dispatch IObjectMovedEvent with "top-down" semantics -->
<subscriber
for="OFS.interfaces.IItem
zope.app.container.interfaces.IObjectMovedEvent"
handler="OFS.subscribers.dispatchObjectMovedEvent"
/>
<!-- dispatch IObjectClonedEvent with "top-down" semantics -->
<subscriber
for="OFS.interfaces.IItem
OFS.interfaces.IObjectClonedEvent"
handler="OFS.subscribers.dispatchObjectClonedEvent"
/>
</configure>
############################################################################## ##############################################################################
# #
# Copyright (c) 2004, 2005 Zope Corporation and Contributors. # Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved. # All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
...@@ -15,109 +15,38 @@ ...@@ -15,109 +15,38 @@
Use 'structured monkey patching' to enable zope.app.container event sending for Use 'structured monkey patching' to enable zope.app.container event sending for
Zope 2 objects. Zope 2 objects.
$Id: eventconfigure.py 17810 2005-09-24 09:12:59Z efge $ $Id: eventconfigure.py 19413 2005-11-02 14:37:52Z efge $
""" """
from Products.Five.fiveconfigure import isFiveMethod
from zope.event import notify
from zope.interface import implements
from zope.app.container.interfaces import IObjectAddedEvent,\
IObjectRemovedEvent
from zope.app.container.contained import ObjectMovedEvent
from zope.app.event.objectevent import ObjectCopiedEvent
# holds classes that were monkeyed with; for clean up import warnings
_monkied = [] from OFS.subscribers import deprecatedManageAddDeleteClasses
# ObjectAddedEvent and ObjectRemovedEvent are different in Zope 2 def setContainerEvents():
class ObjectAddedEvent(ObjectMovedEvent): warnings.warn("Using <five:containerEvents/> is deprecated (it is now "
implements(IObjectAddedEvent) "the default), it will be removed in Zope 2.11",
DeprecationWarning)
def __init__(self, object, newParent=None, newName=None): def setDeprecatedManageAddDelete(class_):
if newParent is None: """Instances of the class will still see their old methods called."""
newParent = object.aq_inner.aq_parent deprecatedManageAddDeleteClasses.append(class_)
if newName is None:
newName = object.id
ObjectMovedEvent.__init__(self, object, None, None, newParent, newName)
class ObjectRemovedEvent(ObjectMovedEvent):
implements(IObjectRemovedEvent)
def __init__(self, object, oldParent=None, oldName=None): def cleanUp():
if oldParent is None: deprecatedManageAddDeleteClasses[:] = []
oldParent = object.aq_inner.aq_parent
if oldName is None:
oldName = object.id
ObjectMovedEvent.__init__(self, object, oldParent, oldName, None, None)
def manage_afterAdd(self, item, container):
original_location_path = getattr(self, '__five_location_path__', None)
self.__five_location_path__ = self.getPhysicalPath()
# if there still is an object in the original location, we're copied
# we cannot rely on manage_afterClone, as this gets triggered only
# *after* a manage_afterAdd. This logic might fail in the case where
# something *is* somehow left in the original location that can
# be traversed to.
is_copied = original_location_path and (self.unrestrictedTraverse(
original_location_path, None) is not None)
if is_copied:
notify(ObjectCopiedEvent(self))
if original_location_path is None or is_copied:
notify(ObjectAddedEvent(self))
else:
original_location = self.unrestrictedTraverse(
original_location_path[:-1])
notify(ObjectMovedEvent(self,
original_location, original_location_path[-1],
container, self.id))
# call original
method = getattr(self, '__five_original_manage_afterAdd', None)
if method is not None:
self.__five_original_manage_afterAdd(item, container)
manage_afterAdd.__five_method__ = True
def manage_beforeDelete(self, item, container):
notify(ObjectRemovedEvent(self))
# call original
method = getattr(self, '__five_original_manage_beforeDelete', None)
if method is not None:
self.__five_original_manage_beforeDelete(item, container)
manage_beforeDelete.__five_method__ = True
def classSendEvents(class_): def containerEvents(_context):
"""Make instances of the class send Object*Event.""" _context.action(
# tuck away original methods if necessary discriminator=None,
for name in ['manage_afterAdd', 'manage_beforeDelete']: callable=setContainerEvents,
method = getattr(class_, name, None) args=(),
if not isFiveMethod(method): )
# if we haven't alread overridden this, tuck away originals
setattr(class_, '__five_original_' + name, method)
class_.manage_afterAdd = manage_afterAdd def deprecatedManageAddDelete(_context, class_):
class_.manage_beforeDelete = manage_beforeDelete
# remember class for clean up
_monkied.append(class_)
def sendEvents(_context, class_):
_context.action( _context.action(
discriminator = ('five:sendEvents', class_), discriminator=('five:deprecatedManageAddDelete', class_),
callable = classSendEvents, callable=setDeprecatedManageAddDelete,
args=(class_,) args=(class_,),
) )
# clean up code
from Products.Five.fiveconfigure import killMonkey
from zope.testing.cleanup import addCleanUp from zope.testing.cleanup import addCleanUp
def unsendEvents(class_):
"""Restore class's initial state with respect to sending events"""
for name in ['manage_afterAdd', 'manage_beforeDelete']:
killMonkey(class_, name, '__five_original_'+name)
def cleanUp():
for class_ in _monkied:
unsendEvents(class_)
addCleanUp(cleanUp) addCleanUp(cleanUp)
del addCleanUp del addCleanUp
...@@ -22,12 +22,20 @@ import sys ...@@ -22,12 +22,20 @@ import sys
import glob import glob
import warnings import warnings
import App import App.config
import Products
from zLOG import LOG, ERROR from zLOG import LOG, ERROR
from zope.interface import classImplements from zope.interface import classImplements, classImplementsOnly, implementedBy
from zope.interface.interface import InterfaceClass
from zope.configuration import xmlconfig from zope.configuration import xmlconfig
from zope.configuration.exceptions import ConfigurationError
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
from zope.app import zapi
from zope.app.component.interface import provideInterface from zope.app.component.interface import provideInterface
from zope.app.component.metaconfigure import adapter
from zope.app.security.interfaces import IPermission
from viewable import Viewable from viewable import Viewable
from traversable import Traversable from traversable import Traversable
...@@ -55,7 +63,7 @@ def handleBrokenProduct(product): ...@@ -55,7 +63,7 @@ def handleBrokenProduct(product):
# in the control panel. However, all attempts to do so has failed from my # in the control panel. However, all attempts to do so has failed from my
# side. //regebro # side. //regebro
exc = sys.exc_info() exc = sys.exc_info()
LOG('Five', ERROR, 'Could not import Product %s' % name, error=exc) LOG('Five', ERROR, 'Could not import Product %s' % product.__name__, error=exc)
def loadProducts(_context): def loadProducts(_context):
products = findProducts() products = findProducts()
...@@ -177,20 +185,6 @@ def defaultViewable(_context, class_): ...@@ -177,20 +185,6 @@ def defaultViewable(_context, class_):
args = (class_,) args = (class_,)
) )
def viewable(_context, class_):
# XXX do not need to mark where this is used, as simple search
# should find all instances easily
warnings.warn(
'The five:viewable directive has been deprecated. '
'Please use the five:traversable directive instead.',
DeprecationWarning)
_context.action(
discriminator = None,
callable = classTraversable,
args=(class_,)
)
def createZope2Bridge(zope2, package, name): def createZope2Bridge(zope2, package, name):
# Map a Zope 2 interface into a Zope3 interface, seated within 'package' # Map a Zope 2 interface into a Zope3 interface, seated within 'package'
# as 'name'. # as 'name'.
...@@ -215,7 +209,7 @@ def bridge(_context, zope2, package, name=None): ...@@ -215,7 +209,7 @@ def bridge(_context, zope2, package, name=None):
) )
def pagesFromDirectory(_context, directory, module, for_=None, def pagesFromDirectory(_context, directory, module, for_=None,
layer='default', permission='zope.Public'): layer=IDefaultBrowserLayer, permission='zope.Public'):
if isinstance(module, basestring): if isinstance(module, basestring):
module = _context.resolve(module) module = _context.resolve(module)
...@@ -233,6 +227,41 @@ def pagesFromDirectory(_context, directory, module, for_=None, ...@@ -233,6 +227,41 @@ def pagesFromDirectory(_context, directory, module, for_=None,
page(_context, name=name, permission=permission, page(_context, name=name, permission=permission,
layer=layer, for_=for_, template=fname) layer=layer, for_=for_, template=fname)
_register_monkies = []
_meta_type_regs = []
def _registerClass(class_, meta_type, permission, addview, icon, global_):
setattr(class_, 'meta_type', meta_type)
permission_obj = zapi.getUtility(IPermission, permission)
if icon:
setattr(class_, 'icon', '++resource++%s' % icon)
interfaces = tuple(implementedBy(class_))
info = {'name': meta_type,
'action': addview and ('+/%s' % addview) or '',
'product': 'Five',
'permission': str(permission_obj.title),
'visibility': global_ and 'Global' or None,
'interfaces': interfaces,
'instance': class_,
'container_filter': None}
Products.meta_types += (info,)
_register_monkies.append(class_)
_meta_type_regs.append(meta_type)
def registerClass(_context, class_, meta_type, permission, addview=None,
icon=None, global_=True):
_context.action(
discriminator = ('registerClass', meta_type),
callable = _registerClass,
args = (class_, meta_type, permission, addview, icon, global_)
)
# clean up code # clean up code
def killMonkey(class_, name, fallback, attr=None): def killMonkey(class_, name, fallback, attr=None):
...@@ -265,11 +294,33 @@ def undefaultViewable(class_): ...@@ -265,11 +294,33 @@ def undefaultViewable(class_):
killMonkey(class_, '__browser_default__', '__fallback_default__', killMonkey(class_, '__browser_default__', '__fallback_default__',
'__five_viewable__') '__five_viewable__')
def unregisterClass(class_):
delattr(class_, 'meta_type')
try:
delattr(class_, 'icon')
except AttributeError:
pass
def cleanUp(): def cleanUp():
global _traversable_monkies
for class_ in _traversable_monkies: for class_ in _traversable_monkies:
untraversable(class_) untraversable(class_)
_traversable_monkies = []
global _defaultviewable_monkies
for class_ in _defaultviewable_monkies: for class_ in _defaultviewable_monkies:
undefaultViewable(class_) undefaultViewable(class_)
_defaultviewable_monkies = []
global _register_monkies
for class_ in _register_monkies:
unregisterClass(class_)
_register_monkies = []
global _meta_type_regs
Products.meta_types = tuple([ info for info in Products.meta_types
if info['name'] not in _meta_type_regs ])
_meta_type_regs = []
from zope.testing.cleanup import addCleanUp from zope.testing.cleanup import addCleanUp
addCleanUp(cleanUp) addCleanUp(cleanUp)
......
...@@ -17,7 +17,10 @@ $Id: fivedirectives.py 12884 2005-05-30 13:10:41Z philikon $ ...@@ -17,7 +17,10 @@ $Id: fivedirectives.py 12884 2005-05-30 13:10:41Z philikon $
""" """
from zope.interface import Interface from zope.interface import Interface
from zope.app.publisher.browser.metadirectives import IBasicResourceInformation from zope.app.publisher.browser.metadirectives import IBasicResourceInformation
from zope.app.security.fields import Permission
from zope.configuration.fields import GlobalObject, Tokens, PythonIdentifier from zope.configuration.fields import GlobalObject, Tokens, PythonIdentifier
from zope.configuration.fields import Bool
from zope.schema import ASCII
from zope.schema import TextLine from zope.schema import TextLine
class IImplementsDirective(Interface): class IImplementsDirective(Interface):
...@@ -56,7 +59,7 @@ class IDefaultViewableDirective(Interface): ...@@ -56,7 +59,7 @@ class IDefaultViewableDirective(Interface):
required=True required=True
) )
class ISendEventsDirective(Interface): class ISizableDirective(Interface):
"""Make instances of class send events. """Make instances of class send events.
""" """
...@@ -65,6 +68,19 @@ class ISendEventsDirective(Interface): ...@@ -65,6 +68,19 @@ class ISendEventsDirective(Interface):
required=True required=True
) )
class IContainerEventsDirective(Interface):
"""Global switch to enable container events
"""
class IDeprecatedManageAddDeleteDirective(Interface):
"""Call manage_afterAdd & co for these contained content classes.
"""
class_ = GlobalObject(
title=u"Class",
required=True,
)
class IBridgeDirective(Interface): class IBridgeDirective(Interface):
"""Bridge from a Zope 2 interface to an equivalent Zope3 interface. """Bridge from a Zope 2 interface to an equivalent Zope3 interface.
""" """
...@@ -104,3 +120,51 @@ class IPagesFromDirectoryDirective(IBasicResourceInformation): ...@@ -104,3 +120,51 @@ class IPagesFromDirectoryDirective(IBasicResourceInformation):
description=u"The directory containing the resource data.", description=u"The directory containing the resource data.",
required=True required=True
) )
class IRegisterClassDirective(Interface):
"""registerClass directive schema.
Register Five content with Zope 2.
"""
class_ = GlobalObject(
title=u'Instance Class',
description=u'Dotted name of the class that is registered.',
required=True
)
meta_type = ASCII(
title=u'Meta Type',
description=u'A human readable unique identifier for the class.',
required=True
)
permission = Permission(
title=u'Add Permission',
description=u'The permission for adding objects of this class.',
required=True
)
addview = ASCII(
title=u'Add View ID',
description=u'The ID of the add view used in the ZMI. Consider this '
u'required unless you know exactly what you do.',
default=None,
required=False
)
icon = ASCII(
title=u'Icon ID',
description=u'The ID of the icon used in the ZMI.',
default=None,
required=False
)
global_ = Bool(
title=u'Global scope?',
description=u'If "global" is False the class is only available in '
u'containers that explicitly allow one of its interfaces.',
default=True,
required=False
)
...@@ -33,7 +33,7 @@ from zope.app.form.interfaces import WidgetsError, MissingInputError ...@@ -33,7 +33,7 @@ from zope.app.form.interfaces import WidgetsError, MissingInputError
from zope.app.form.utility import setUpWidgets, getWidgetsData from zope.app.form.utility import setUpWidgets, getWidgetsData
from zope.app.form.interfaces import IInputWidget, WidgetsError from zope.app.form.interfaces import IInputWidget, WidgetsError
from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.app.i18n import ZopeMessageIDFactory as _ from zope.app.i18n import ZopeMessageFactory as _
from Products.Five.browser import BrowserView from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
...@@ -144,12 +144,13 @@ class EditView(BrowserView): ...@@ -144,12 +144,13 @@ class EditView(BrowserView):
if changed: if changed:
self.changed() self.changed()
# XXX: Needs locale support: # XXX: Needs locale support:
# formatter = self.request.locale.dates.getFormatter( #formatter = self.request.locale.dates.getFormatter(
# 'dateTime', 'medium') # 'dateTime', 'medium')
status = _("Updated on ${date_time}") #status = _("Updated on ${date_time}",
# status.mapping = {'date_time': formatter.format( # mapping={'date_time':
# datetime.utcnow())} # formatter.format(datetime.utcnow())})
status.mapping = {'date_time': str(datetime.utcnow())} status = _("Updated on ${date_time}",
mapping={'date_time': str(datetime.utcnow())})
self.update_status = status self.update_status = status
return status return status
...@@ -249,9 +250,3 @@ class AddView(EditView): ...@@ -249,9 +250,3 @@ class AddView(EditView):
def nextURL(self): def nextURL(self):
return self.context.nextURL() return self.context.nextURL()
# BBB: Will be removed in future versions
from Products.Five import browser
browser.AddView = AddView
browser.EditView = EditView
...@@ -13,28 +13,29 @@ ...@@ -13,28 +13,29 @@
############################################################################## ##############################################################################
"""Edit form directives """Edit form directives
$Id: metaconfigure.py 12884 2005-05-30 13:10:41Z philikon $ $Id: metaconfigure.py 19283 2005-10-31 17:43:51Z philikon $
""" """
import ExtensionClass import ExtensionClass
from Globals import InitializeClass as initializeClass
from zope.component import getGlobalService from zope.interface import Interface
from zope.component.servicenames import Presentation
from zope.publisher.interfaces.browser import IBrowserRequest from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.publisher.browser.globalbrowsermenuservice import \
menuItemDirective from zope.app import zapi
from zope.app.publisher.browser.menumeta import menuItemDirective
from zope.app.form.browser.metaconfigure import BaseFormDirective from zope.app.form.browser.metaconfigure import BaseFormDirective
from zope.app.container.interfaces import IAdding from zope.app.container.interfaces import IAdding
from zope.app.i18n import ZopeMessageFactory as _
from Products.Five.form import EditView, AddView from Products.Five.form import EditView, AddView
from Products.Five.metaclass import makeClass from Products.Five.metaclass import makeClass
from Products.Five.security import protectClass, initializeClass from Products.Five.security import protectClass
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Products.Five.browser.metaconfigure import makeClassForTemplate from Products.Five.browser.metaconfigure import makeClassForTemplate
def EditViewFactory(name, schema, label, permission, layer, def EditViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_, fields, template, default_template, bases, for_, fields,
fulledit_path=None, fulledit_label=None, menu=u''): fulledit_path=None, fulledit_label=None, menu=u''):
s = getGlobalService(Presentation)
class_ = makeClassForTemplate(template, globals(), used_for=schema, class_ = makeClassForTemplate(template, globals(), used_for=schema,
bases=bases) bases=bases)
class_.schema = schema class_.schema = schema
...@@ -49,8 +50,15 @@ def EditViewFactory(name, schema, label, permission, layer, ...@@ -49,8 +50,15 @@ def EditViewFactory(name, schema, label, permission, layer,
class_.generated_form = ZopeTwoPageTemplateFile(default_template) class_.generated_form = ZopeTwoPageTemplateFile(default_template)
if layer is None:
layer = IDefaultBrowserLayer
s = zapi.getGlobalSiteManager()
s.provideAdapter((for_, layer), Interface, name, class_)
s.provideView(for_, name, IBrowserRequest, class_, layer) # Reminder: the permission we got has already been processed by
# BaseFormDirective, that means that zope.Public has been
# translated to the CheckerPublic object
protectClass(class_, permission) protectClass(class_, permission)
initializeClass(class_) initializeClass(class_)
...@@ -58,20 +66,22 @@ class FiveFormDirective(BaseFormDirective): ...@@ -58,20 +66,22 @@ class FiveFormDirective(BaseFormDirective):
def _processWidgets(self): def _processWidgets(self):
if self._widgets: if self._widgets:
customWidgetsObject = makeClass('CustomWidgetsMixin', (ExtensionClass.Base,), self._widgets) customWidgetsObject = makeClass(
'CustomWidgetsMixin', (ExtensionClass.Base,), self._widgets)
self.bases = self.bases + (customWidgetsObject,) self.bases = self.bases + (customWidgetsObject,)
class EditFormDirective(FiveFormDirective): class EditFormDirective(FiveFormDirective):
view = EditView view = EditView
default_template = 'edit.pt' default_template = 'edit.pt'
title = 'Edit' title = _('Edit')
def _handle_menu(self): def _handle_menu(self):
if self.menu: if self.menu:
menuItemDirective( menuItemDirective(
self._context, self.menu, self.for_ or self.schema, self._context, self.menu, self.for_ or self.schema,
'@@' + self.name, self.title, permission=self.permission) '@@' + self.name, self.title, permission=self.permission,
layer=self.layer)
def __call__(self): def __call__(self):
self._processWidgets() self._processWidgets()
...@@ -89,8 +99,6 @@ def AddViewFactory(name, schema, label, permission, layer, ...@@ -89,8 +99,6 @@ def AddViewFactory(name, schema, label, permission, layer,
fields, content_factory, arguments, fields, content_factory, arguments,
keyword_arguments, set_before_add, set_after_add, keyword_arguments, set_before_add, set_after_add,
menu=u''): menu=u''):
s = getGlobalService(Presentation)
class_ = makeClassForTemplate(template, globals(), used_for=schema, class_ = makeClassForTemplate(template, globals(), used_for=schema,
bases=bases) bases=bases)
...@@ -105,7 +113,15 @@ def AddViewFactory(name, schema, label, permission, layer, ...@@ -105,7 +113,15 @@ def AddViewFactory(name, schema, label, permission, layer,
class_.generated_form = ZopeTwoPageTemplateFile(default_template) class_.generated_form = ZopeTwoPageTemplateFile(default_template)
s.provideView(for_, name, IBrowserRequest, class_, layer) if layer is None:
layer = IDefaultBrowserLayer
s = zapi.getGlobalSiteManager()
s.provideAdapter((for_, layer), Interface, name, class_)
# Reminder: the permission we got has already been processed by
# BaseFormDirective, that means that zope.Public has been
# translated to the CheckerPublic object
protectClass(class_, permission) protectClass(class_, permission)
initializeClass(class_) initializeClass(class_)
...@@ -132,7 +148,7 @@ class AddFormDirective(FiveFormDirective): ...@@ -132,7 +148,7 @@ class AddFormDirective(FiveFormDirective):
# for=self.schema. # for=self.schema.
menuItemDirective( menuItemDirective(
self._context, self.menu, self.for_, '@@' + self.name, self._context, self.menu, self.for_, '@@' + self.name,
self.title, permission=self.permission, self.title, permission=self.permission, layer=self.layer,
description=self.description) description=self.description)
def _handle_arguments(self, leftover=None): def _handle_arguments(self, leftover=None):
......
<fieldset>
<legend tal:content="context/legendTitle"
i18n:translate="">The Legend</legend>
<div class="row" tal:repeat="widget context/subwidgets">
<tal:comment condition="nothing">
This is why we have to duplicate this template: we want to look
up the @@form_macros browser page from something that's
definitely five:traversable (it doesn't really matter where we
look it up, just *that* we look it up); we know the object we're
editing is five:traversable, so we just use that. Yes, three
times context. Weird, eh?
</tal:comment>
<metal:block use-macro="context/context/context/@@form_macros/widget_row" />
</div>
</fieldset>
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Five-compatible version of ObjectWidget
This is needed because ObjectWidget uses ViewPageTemplateFile whose
macro definition is unfortunately incompatible with
ZopeTwoPageTemplateFile. So this subclass uses
ZopeTwoPageTemplateFile for the template that renders the widget's
sub-editform. Acquisition has to be mixed in to provide the
ZopeTwoPageTemplateFile with the proper acquisition context.
$Id$
"""
import os.path
import Acquisition
import zope.app.form.browser.objectwidget
from AccessControl import ClassSecurityInfo
from Globals import InitializeClass as initializeClass
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
class ObjectWidgetView(Acquisition.Explicit,
zope.app.form.browser.objectwidget.ObjectWidgetView):
security = ClassSecurityInfo()
security.declareObjectPublic()
template = ZopeTwoPageTemplateFile('objectwidget.pt')
initializeClass(ObjectWidgetView)
class ObjectWidgetClass(Acquisition.Explicit,
zope.app.form.browser.objectwidget.ObjectWidget):
def __init__(self, context, request, factory, **kw):
super(ObjectWidgetClass, self).__init__(context, request, factory, **kw)
self.view = ObjectWidgetView(self, request)
def setRenderedValue(self, value):
"""Slightly more robust re-implementation this method."""
# re-call setupwidgets with the content
self._setUpEditWidgets()
for name in self.names:
val = getattr(value, name, None)
if val is None:
# this is where we are more robust than Zope 3.2's
# object widget: we supply subwidgets with the default
# from the schema, not None (Zope 3.2's list widget
# breaks when the rendered value is None)
val = self.context.schema[name].default
self.getSubWidget(name).setRenderedValue(val)
def ObjectWidget(context, request, factory, **kw):
"""Return an ObjectWidget suitable in the Five environment, with
right acquisition context"""
return ObjectWidgetClass(context, request, factory, **kw
).__of__(context.context)
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
type="zope.publisher.interfaces.browser.IBrowserRequest" type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IObject" for="zope.schema.interfaces.IObject"
provides="zope.app.form.interfaces.IInputWidget" provides="zope.app.form.interfaces.IInputWidget"
factory="zope.app.form.browser.objectwidget.ObjectWidget" factory="Products.Five.form.objectwidget.ObjectWidget"
permission="zope.Public" permission="zope.Public"
/> />
......
...@@ -17,7 +17,7 @@ We need to configure all of Five for the functional test: ...@@ -17,7 +17,7 @@ We need to configure all of Five for the functional test:
Finally, we need to setup a traversable folder. Otherwise, Five won't Finally, we need to setup a traversable folder. Otherwise, Five won't
get to to do its view lookup: get to to do its view lookup:
>>> from Products.Five.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'ftf') >>> manage_addFiveTraversableFolder(self.folder, 'ftf')
...@@ -47,9 +47,10 @@ which incorrectly ignored its 'handle_errors' argument): ...@@ -47,9 +47,10 @@ which incorrectly ignored its 'handle_errors' argument):
>>> print http(r""" >>> print http(r"""
... GET /test_folder_1_/ftf/+/protectedaddform.html HTTP/1.1 ... GET /test_folder_1_/ftf/+/protectedaddform.html HTTP/1.1
... """, handle_errors=True) ... """, handle_errors=False)
HTTP/1.1 401 Unauthorized Traceback (most recent call last):
... ...
Unauthorized: ...
Now let's add a piece of our sample content object to test more things Now let's add a piece of our sample content object to test more things
on it: on it:
...@@ -372,7 +373,11 @@ element to the list: ...@@ -372,7 +373,11 @@ element to the list:
... -----------------------------968064918930967154199105236 ... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somelist.add" ... Content-Disposition: form-data; name="field.somelist.add"
... ...
... Add ... Add Some item
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somelist.count"
...
... 0
... -----------------------------968064918930967154199105236-- ... -----------------------------968064918930967154199105236--
... """ % (wo_hen_hao, ni_hao), handle_errors=False) ... """ % (wo_hen_hao, ni_hao), handle_errors=False)
HTTP/1.1 200 OK HTTP/1.1 200 OK
...@@ -408,6 +413,10 @@ Now, let's enter some more Chinese: ...@@ -408,6 +413,10 @@ Now, let's enter some more Chinese:
... ...
... %s ... %s
... -----------------------------968064918930967154199105236 ... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somelist.count"
...
... 1
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
... ...
... Change ... Change
...@@ -572,5 +581,5 @@ Clean up ...@@ -572,5 +581,5 @@ Clean up
Finally, we need to clean up: Finally, we need to clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
...@@ -18,13 +18,13 @@ $Id: schemacontent.py 15514 2005-08-02 16:37:34Z yuppie $ ...@@ -18,13 +18,13 @@ $Id: schemacontent.py 15514 2005-08-02 16:37:34Z yuppie $
from OFS.SimpleItem import SimpleItem from OFS.SimpleItem import SimpleItem
from Globals import InitializeClass from Globals import InitializeClass
from zope.i18nmessageid import MessageIDFactory from zope.i18nmessageid import MessageFactory
from zope.interface import implements, Interface from zope.interface import implements, Interface
from zope.schema import TextLine, Text, Object, Int, List from zope.schema import TextLine, Text, Object, Int, List
from zope.app.form import CustomWidgetFactory from zope.app.form import CustomWidgetFactory
from zope.app.form.browser import ObjectWidget from Products.Five.form.objectwidget import ObjectWidget
_ = MessageIDFactory('formtest') _ = MessageFactory('formtest')
class IFieldContent(Interface): class IFieldContent(Interface):
......
...@@ -46,25 +46,26 @@ def test_get_widgets_for_schema_fields(): ...@@ -46,25 +46,26 @@ def test_get_widgets_for_schema_fields():
>>> from zope.app.form.browser.textwidgets import TextWidget >>> from zope.app.form.browser.textwidgets import TextWidget
>>> from zope.app.form.browser.itemswidgets import DropdownWidget >>> from zope.app.form.browser.itemswidgets import DropdownWidget
>>> view1 = zapi.getViewProviding(contactname, IInputWidget, request) >>> view1 = zapi.getMultiAdapter((contactname, request), IInputWidget)
>>> view1.__class__ == TextWidget >>> view1.__class__ == TextWidget
True True
>>> view2 = zapi.getViewProviding(salutation, IInputWidget, request) >>> view2 = zapi.getMultiAdapter((salutation, request), IInputWidget)
>>> view2.__class__ == DropdownWidget >>> view2.__class__ == DropdownWidget
True True
Clean up: Clean up:
>>> from zope.app.tests.placelesssetup import tearDown >>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown() >>> tearDown()
""" """
def test_suite(): def test_suite():
import unittest import unittest
from Testing.ZopeTestCase import ZopeDocTestSuite, FunctionalDocFileSuite from zope.testing.doctest import DocTestSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
return unittest.TestSuite(( return unittest.TestSuite((
ZopeDocTestSuite(), DocTestSuite(),
FunctionalDocFileSuite('forms.txt', FunctionalDocFileSuite('forms.txt',
package="Products.Five.form.tests",), package="Products.Five.form.tests",),
)) ))
......
...@@ -13,15 +13,21 @@ ...@@ -13,15 +13,21 @@
############################################################################## ##############################################################################
"""Mimick Zope3 i18n machinery for Zope 2 """Mimick Zope3 i18n machinery for Zope 2
$Id: i18n.py 14400 2005-07-07 17:55:08Z philikon $ $Id: i18n.py 19435 2005-11-02 16:34:58Z philikon $
""" """
from Acquisition import aq_acquire
from zope.interface import implements from zope.interface import implements
from zope.i18n import interpolate from zope.i18n import interpolate
from zope.i18n.interfaces import ITranslationDomain, IUserPreferredLanguages from zope.i18n.interfaces import ITranslationDomain, IUserPreferredLanguages
from zope.i18nmessageid import MessageID
from zope.app import zapi from zope.app import zapi
from zope.publisher.browser import BrowserLanguages from zope.publisher.browser import BrowserLanguages
# BBB 2005/10/10 -- MessageIDs are to be removed for Zope 3.3
import zope.deprecation
zope.deprecation.__show__.off()
from zope.i18nmessageid import MessageID, Message
zope.deprecation.__show__.on()
class FiveTranslationService: class FiveTranslationService:
"""Translation service that delegates to ``zope.i18n`` machinery. """Translation service that delegates to ``zope.i18n`` machinery.
""" """
...@@ -29,7 +35,7 @@ class FiveTranslationService: ...@@ -29,7 +35,7 @@ class FiveTranslationService:
# regarding fallback and Zope 2 compatability # regarding fallback and Zope 2 compatability
def translate(self, domain, msgid, mapping=None, def translate(self, domain, msgid, mapping=None,
context=None, target_language=None, default=None): context=None, target_language=None, default=None):
if isinstance(msgid, MessageID): if isinstance(msgid, (Message, MessageID)):
domain = msgid.domain domain = msgid.domain
default = msgid.default default = msgid.default
mapping = msgid.mapping mapping = msgid.mapping
...@@ -46,7 +52,7 @@ class FiveTranslationService: ...@@ -46,7 +52,7 @@ class FiveTranslationService:
# in Zope3, context is adapted to IUserPreferredLanguages, # in Zope3, context is adapted to IUserPreferredLanguages,
# which means context should be the request in this case. # which means context should be the request in this case.
if context is not None: if context is not None:
context = context.REQUEST context = aq_acquire(context, 'REQUEST', None)
return util.translate(msgid, mapping=mapping, context=context, return util.translate(msgid, mapping=mapping, context=context,
target_language=target_language, default=default) target_language=target_language, default=default)
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Five interfaces """Five interfaces
$Id: interfaces.py 14613 2005-07-13 10:39:05Z philikon $ $Id: interfaces.py 19283 2005-10-31 17:43:51Z philikon $
""" """
from zope.interface import Interface from zope.interface import Interface
from zope.interface.interfaces import IInterface from zope.interface.interfaces import IInterface
...@@ -33,23 +33,3 @@ class IMenuItemType(IInterface): ...@@ -33,23 +33,3 @@ class IMenuItemType(IInterface):
Menu item types are interfaces that define classes of Menu item types are interfaces that define classes of
menu items. menu items.
""" """
#
# BBB: Zope core interfaces
#
# The interfaces here are only provided for backwards compatibility and will
# be removed in Five 1.2. Please import interfaces from the corresponding Zope
# package instead.
#
from persistent.interfaces import IPersistent
from AccessControl.interfaces import *
from Acquisition.interfaces import *
from App.interfaces import *
from OFS.interfaces import *
from webdav.interfaces import *
# BBB: for old names used in Five 1.0
IAcquisition = IAcquirer
IPermissionMapping = IPermissionMappingSupport
<configure xmlns="http://namespaces.zope.org/five">
<!-- IPersistent, IPersistentExtra -->
<!-- Acquisition -->
<implements
class="Acquisition.ImplicitAcquisitionWrapper"
interface="Acquisition.interfaces.IAcquisitionWrapper"
/>
<implements
class="Acquisition.ExplicitAcquisitionWrapper"
interface="Acquisition.interfaces.IAcquisitionWrapper"
/>
<implements
class="Acquisition.Implicit"
interface="Acquisition.interfaces.IAcquirer"
/>
<implements
class="Acquisition.Explicit"
interface="Acquisition.interfaces.IAcquirer"
/>
<!-- DAV -->
<implements
class="webdav.Lockable.LockableItem"
interface="webdav.interfaces.IWriteLock"
/>
<implements
class="webdav.Resource.Resource"
interface="webdav.interfaces.IDAVResource"
/>
<implements
class="webdav.Collection.Collection"
interface="webdav.interfaces.IDAVCollection"
/>
<!-- OFS -->
<implements
class="OFS.CopySupport.CopySource"
interface="OFS.interfaces.ICopySource"
/>
<implements
class="OFS.CopySupport.CopyContainer"
interface="OFS.interfaces.ICopyContainer"
/>
<implements
class="OFS.Traversable.Traversable"
interface="OFS.interfaces.ITraversable"
/>
<implements
class="OFS.SimpleItem.Item"
interface="OFS.interfaces.IItem"
/>
<implements
class="OFS.SimpleItem.Item_w__name__"
interface="OFS.interfaces.IItemWithName"
/>
<implements
class="OFS.SimpleItem.SimpleItem"
interface="OFS.interfaces.ISimpleItem"
/>
<implements
class="OFS.ObjectManager.ObjectManager"
interface="OFS.interfaces.IObjectManager"
/>
<implements
class="OFS.PropertyManager.PropertyManager"
interface="OFS.interfaces.IPropertyManager"
/>
<implements
class="OFS.FindSupport.FindSupport"
interface="OFS.interfaces.IFindSupport"
/>
<implements
class="OFS.Folder.Folder"
interface="OFS.interfaces.IFolder"
/>
<implements
class="OFS.OrderSupport.OrderSupport"
interface="OFS.interfaces.IOrderedContainer"
/>
<implements
class="OFS.OrderedFolder.OrderedFolder"
interface="OFS.interfaces.IOrderedFolder"
/>
<implements
class="OFS.Application.Application"
interface="OFS.interfaces.IApplication"
/>
<!-- App -->
<implements
class="App.Undo.UndoSupport"
interface="App.interfaces.IUndoSupport"
/>
<implements
class="App.Management.Navigation"
interface="App.interfaces.INavigation"
/>
<!-- AccessControl -->
<implements
class="AccessControl.Owned.Owned"
interface="AccessControl.interfaces.IOwned"
/>
<implements
class="AccessControl.PermissionMapping.RoleManager"
interface="AccessControl.interfaces.IPermissionMappingSupport"
/>
<implements
class="AccessControl.Role.RoleManager"
interface="AccessControl.interfaces.IRoleManager"
/>
</configure>
...@@ -2,13 +2,10 @@ ...@@ -2,13 +2,10 @@
xmlns="http://namespaces.zope.org/zope" xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta"> xmlns:meta="http://namespaces.zope.org/meta">
<include package=".site" file="meta.zcml" />
<include package=".browser" file="meta.zcml" /> <include package=".browser" file="meta.zcml" />
<include package=".form" file="meta.zcml" /> <include package=".form" file="meta.zcml" />
<!-- load the zope:modulealias and zope:hook directives -->
<include package="zope.modulealias" file="meta.zcml" />
<include package="zope.configuration" file="meta.zcml" />
<meta:directives namespace="http://namespaces.zope.org/zope"> <meta:directives namespace="http://namespaces.zope.org/zope">
<meta:directive <meta:directive
...@@ -53,18 +50,6 @@ ...@@ -53,18 +50,6 @@
handler="zope.app.component.metaconfigure.factory" handler="zope.app.component.metaconfigure.factory"
/> />
<meta:directive
name="serviceType"
schema="zope.app.component.metadirectives.IServiceTypeDirective"
handler="zope.app.component.metaconfigure.serviceType"
/>
<meta:directive
name="service"
schema="zope.app.component.metadirectives.IServiceDirective"
handler="zope.app.component.metaconfigure.service"
/>
<meta:complexDirective <meta:complexDirective
name="content" name="content"
schema="zope.app.component.metadirectives.IClassDirective" schema="zope.app.component.metadirectives.IClassDirective"
...@@ -131,14 +116,20 @@ ...@@ -131,14 +116,20 @@
/> />
<meta:directive <meta:directive
name="sendEvents" name="containerEvents"
schema=".fivedirectives.ISendEventsDirective" schema=".fivedirectives.IContainerEventsDirective"
handler=".eventconfigure.sendEvents" handler=".eventconfigure.containerEvents"
/>
<meta:directive
name="deprecatedManageAddDelete"
schema=".fivedirectives.IDeprecatedManageAddDeleteDirective"
handler=".eventconfigure.deprecatedManageAddDelete"
/> />
<meta:directive <meta:directive
name="sizable" name="sizable"
schema=".fivedirectives.ISendEventsDirective" schema=".fivedirectives.ISizableDirective"
handler=".sizeconfigure.sizable" handler=".sizeconfigure.sizable"
/> />
...@@ -148,20 +139,18 @@ ...@@ -148,20 +139,18 @@
handler=".fiveconfigure.pagesFromDirectory" handler=".fiveconfigure.pagesFromDirectory"
/> />
<!-- viewable is deprecated, use traversable instead -->
<meta:directive
name="viewable"
schema=".fivedirectives.ITraversableDirective"
handler=".fiveconfigure.viewable"
/>
<meta:directive <meta:directive
name="bridge" name="bridge"
schema=".fivedirectives.IBridgeDirective" schema=".fivedirectives.IBridgeDirective"
handler=".fiveconfigure.bridge" handler=".fiveconfigure.bridge"
/> />
<meta:directive
name="registerClass"
schema=".fivedirectives.IRegisterClassDirective"
handler=".fiveconfigure.registerClass"
/>
</meta:directives> </meta:directives>
<meta:directive <meta:directive
...@@ -171,6 +160,9 @@ ...@@ -171,6 +160,9 @@
handler="zope.app.security.metaconfigure.redefinePermission" handler="zope.app.security.metaconfigure.redefinePermission"
/> />
<!-- load the zope:modulealias directive -->
<include package="zope.modulealias" file="meta.zcml" />
<!-- load the i18n:registerTranslations directive --> <!-- load the i18n:registerTranslations directive -->
<include package="zope.app.i18n" file="meta.zcml" /> <include package="zope.app.i18n" file="meta.zcml" />
......
...@@ -13,73 +13,25 @@ ...@@ -13,73 +13,25 @@
############################################################################## ##############################################################################
"""Generic Components ZCML Handlers """Generic Components ZCML Handlers
$Id: metaconfigure.py 12884 2005-05-30 13:10:41Z philikon $ $Id: metaconfigure.py 19283 2005-10-31 17:43:51Z philikon $
""" """
from types import ModuleType from Products.Five.security import CheckerPublic, protectName
from Globals import InitializeClass as initializeClass
from zope.interface import classImplements from zope.app.component.contentdirective import ContentDirective as \
from zope.configuration.exceptions import ConfigurationError zope_app_ContentDirective
from security import CheckerPublic
from security import protectName, initializeClass
class ContentDirective:
def __init__(self, _context, class_):
self.__class = class_
if isinstance(self.__class, ModuleType):
raise ConfigurationError('Content class attribute must be a class')
self.__context = _context
def implements(self, _context, interface):
for interface in interface:
_context.action(
discriminator = (
'five::directive:content', self.__class, object()),
callable = classImplements,
args = (self.__class, interface),
)
interface(_context, interface)
def require(self, _context, permission=None,
attributes=None, interface=None):
"""Require a the permission to access a specific aspect"""
if not (interface or attributes):
raise ConfigurationError("Nothing required")
if interface:
for i in interface:
if i:
self.__protectByInterface(i, permission)
if attributes:
self.__protectNames(attributes, permission)
def allow(self, _context, attributes=None, interface=None):
"""Like require, but with permission_id zope.Public"""
return self.require(_context, CheckerPublic, attributes, interface)
def __protectByInterface(self, interface, permission_id):
"Set a permission on names in an interface."
for n, d in interface.namesAndDescriptions(1):
self.__protectName(n, permission_id)
interface(self.__context, interface)
class ContentDirective(zope_app_ContentDirective):
def __protectName(self, name, permission_id): def __protectName(self, name, permission_id):
"Set a permission on a particular name."
self.__context.action( self.__context.action(
discriminator = ('five:protectName', self.__class, name), discriminator = ('five:protectName', self.__class, name),
callable = protectName, callable = protectName,
args = (self.__class, name, permission_id) args = (self.__class, name, permission_id)
) )
def __protectNames(self, names, permission_id):
"Set a permission on a bunch of names."
for name in names:
self.__protectName(name, permission_id)
def __call__(self): def __call__(self):
"Handle empty/simple declaration." """Handle empty/simple declaration."""
return self.__context.action( return self.__context.action(
discriminator = ('five:initialize:class', self.__class), discriminator = ('five:initialize:class', self.__class),
callable = initializeClass, callable = initializeClass,
......
<configure xmlns="http://namespaces.zope.org/zope" <configure xmlns="http://namespaces.zope.org/zope"
i18n_domain="Five"> i18n_domain="Five">
<permission
id="five.ManageSite"
title="Manage Five local sites"
/>
<!-- Give common Zope2 and CMF permissions a permission ID <!-- Give common Zope2 and CMF permissions a permission ID
The title of the permission is what Zope 2 knows it under --> The title of the permission is what Zope 2 knows it under -->
......
...@@ -24,7 +24,7 @@ from zope.app.security.interfaces import IPermission ...@@ -24,7 +24,7 @@ from zope.app.security.interfaces import IPermission
from zope.app import zapi from zope.app import zapi
from AccessControl import ClassSecurityInfo, getSecurityManager from AccessControl import ClassSecurityInfo, getSecurityManager
from Globals import InitializeClass from Globals import InitializeClass as initializeClass
from types import StringTypes from types import StringTypes
CheckerPublicId = 'zope.Public' CheckerPublicId = 'zope.Public'
...@@ -99,9 +99,6 @@ def newInteraction(): ...@@ -99,9 +99,6 @@ def newInteraction():
if getattr(thread_local, 'interaction', None) is None: if getattr(thread_local, 'interaction', None) is None:
thread_local.interaction = FiveSecurityPolicy() thread_local.interaction = FiveSecurityPolicy()
def initializeClass(klass):
InitializeClass(klass)
def _getSecurity(klass): def _getSecurity(klass):
# a Zope 2 class can contain some attribute that is an instance # a Zope 2 class can contain some attribute that is an instance
# of ClassSecurityInfo. Zope 2 scans through things looking for # of ClassSecurityInfo. Zope 2 scans through things looking for
...@@ -121,15 +118,12 @@ def protectName(klass, name, permission_id): ...@@ -121,15 +118,12 @@ def protectName(klass, name, permission_id):
"""Protect the attribute 'name' on 'klass' using the given """Protect the attribute 'name' on 'klass' using the given
permission""" permission"""
security = _getSecurity(klass) security = _getSecurity(klass)
# XXX: Sometimes, the object CheckerPublic is used instead of the
# string zope.Public. I haven't ben able to figure out why, or if
# it is correct, or a bug. So this is a workaround.
if permission_id is CheckerPublic:
security.declarePublic(name)
return
# Zope 2 uses string, not unicode yet # Zope 2 uses string, not unicode yet
name = str(name) name = str(name)
if permission_id == CheckerPublicId: if permission_id == CheckerPublicId or permission_id is CheckerPublic:
# Sometimes, we already get a processed permission id, which
# can mean that 'zope.Public' has been interchanged for the
# CheckerPublic object
security.declarePublic(name) security.declarePublic(name)
elif permission_id == CheckerPrivateId: elif permission_id == CheckerPrivateId:
security.declarePrivate(name) security.declarePrivate(name)
...@@ -142,7 +136,10 @@ def protectName(klass, name, permission_id): ...@@ -142,7 +136,10 @@ def protectName(klass, name, permission_id):
def protectClass(klass, permission_id): def protectClass(klass, permission_id):
"""Protect the whole class with the given permission""" """Protect the whole class with the given permission"""
security = _getSecurity(klass) security = _getSecurity(klass)
if permission_id == CheckerPublicId: if permission_id == CheckerPublicId or permission_id is CheckerPublic:
# Sometimes, we already get a processed permission id, which
# can mean that 'zope.Public' has been interchanged for the
# CheckerPublic object
security.declareObjectPublic() security.declareObjectPublic()
elif permission_id == CheckerPrivateId: elif permission_id == CheckerPrivateId:
security.declareObjectPrivate() security.declareObjectPrivate()
......
<configure xmlns="http://namespaces.zope.org/zope">
<serviceType
id="Utilities"
interface="zope.component.interfaces.IUtilityService" />
<service
serviceType="Utilities"
factory="zope.component.utility.GlobalUtilityService" />
<serviceType
id="Adapters"
interface="zope.component.interfaces.IAdapterService" />
<service
serviceType="Adapters"
factory="zope.component.adapter.GlobalAdapterService" />
<serviceType
id="Presentation"
interface="zope.component.interfaces.IPresentationService" />
<service
serviceType="Presentation"
factory="zope.component.presentation.GlobalPresentationService" />
</configure>
# make this directory a package
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Local sites browser views
$Id$
"""
from zope.app.component.interfaces import ISite
from zope.app.component.hooks import clearSite
from Products.Five.browser import BrowserView
from Products.Five.site.localsite import enableLocalSiteHook, \
disableLocalSiteHook
class LocalSiteView(BrowserView):
"""View for convering a possible site to a site
"""
def update(self):
form = self.request.form
if form.has_key('UPDATE_MAKESITE'):
self.makeSite()
elif form.has_key('UPDATE_UNMAKESITE'):
self.unmakeSite()
def isSite(self):
return ISite.providedBy(self.context)
def makeSite(self):
"""Convert a possible site to a site"""
if self.isSite():
raise ValueError('This is already a site')
enableLocalSiteHook(self.context)
return "This object is now a site"
def unmakeSite(self):
"""Convert a site to a possible site"""
if not self.isSite():
raise ValueError('This is not a site')
disableLocalSiteHook(self.context)
# disableLocalSiteHook circumcised our context so that it's
# not an ISite anymore. That can mean that certain things for
# it can't be found anymore. So, for the rest of this request
# (which will be over in about 20 CPU cycles), already clear
# the local site from the thread local.
clearSite()
return "This object is no longer a site"
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<adapter
for="*"
provides="zope.component.interfaces.ISiteManager"
factory=".localsite.siteManagerAdapter"
/>
<adapter
for="zope.app.site.interfaces.ISite"
provides=".interfaces.IFiveUtilityRegistry"
factory=".utility.SimpleLocalUtilityRegistry"
/>
<subscriber
for="zope.app.component.interfaces.ISite
zope.app.publication.interfaces.IBeforeTraverseEvent"
handler="zope.app.component.site.threadSiteSubscriber"
/>
<subscriber
for="zope.app.publication.interfaces.IEndRequestEvent"
handler="zope.app.component.site.clearThreadSiteSubscriber"
/>
<browser:page
for="zope.app.component.interfaces.IPossibleSite"
name="manage_site.html"
permission="five.ManageSite"
class=".browser.LocalSiteView"
template="managesite.pt"
/>
</configure>
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Five interfaces
$Id: interfaces.py 18584 2005-10-14 17:13:27Z regebro $
"""
from zope.interface import Interface, Attribute
from zope.component.interfaces import ISiteManager
class IRegisterUtilitySimply(Interface):
"""Register utilities simply
Allow local registrations of utilities, in a much simpler
manner than Zope 3 does it currently.
Note: The name of this interface is expressed as a verb
(describing the action it expresses, namely registering
utilities). The reason for that is that the names *utility
registry* (successor of the Zope 3 utility service) and *utility
registration* (object in a registration stack, part of the
complicated registration framework in Zope 3) have different
connotations in Zope 3 than we want to express here.
"""
def registerUtility(self, interface, utility, name=''):
"""Registers a utility in the local context"""
# TODO Define an exception than is to be thrown when a local
# utility of that interface and name is already registered.
next = Attribute("The next local registry in the tree. This attribute "
"represents the parent of this registry node. If the "
"value is ``None``, then this registry represents the "
"root of the tree")
class IFiveUtilityRegistry(IRegisterUtilitySimply):
"""Look up and register utilities"""
def getUtility(interface, name='', context=None):
"""Get the utility that provides interface
Returns the nearest utility to the context that implements the
specified interface. If one is not found, raises
ComponentLookupError.
"""
def queryUtility(interface, name='', default=None, context=None):
"""Look for the utility that provides interface
Returns the nearest utility to the context that implements
the specified interface. If one is not found, returns default.
"""
def getUtilitiesFor(interface, context=None):
"""Return the utilities that provide an interface
An iterable of utility name-value pairs is returned.
"""
def getAllUtilitiesRegisteredFor(interface, context=None):
"""Return all registered utilities for an interface
This includes overridden utilities.
An iterable of utility instances is returned. No names are
returned.
"""
class IFiveSiteManager(ISiteManager, IRegisterUtilitySimply):
"""Five site manager
For the sake of forward-portability, registering utilities can be
done directly on the site manager to cut out the middle man called
utility service (this corresponds to Zope 3.1's understanding of
site managers). An implementation of this interface will probably
delegate the work to an IFiveUtilityService component, though."""
# BBB 2005/11/01 -- gone in Five 1.5.
IFiveUtilityService = IFiveUtilityRegistry
import zope.deprecation
zope.deprecation.deprecated(
'IFiveUtilityService', "'IFiveUtilityService' has been renamed to "
"'IFiveUtilityRegistry' and will disappear in Five 1.5."
)
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Local sites
$Id$
"""
from zope.event import notify
from zope.interface import directlyProvides, directlyProvidedBy
from zope.interface import implements
from zope.component import getGlobalSiteManager
from zope.component.exceptions import ComponentLookupError
from zope.app.component.interfaces import ISite, IPossibleSite
from zope.app.publication.zopepublication import BeforeTraverseEvent
from ExtensionClass import Base
from Acquisition import aq_base, aq_inner, aq_parent
from Products.SiteAccess.AccessRule import AccessRule
from ZPublisher.BeforeTraverse import registerBeforeTraverse
from ZPublisher.BeforeTraverse import unregisterBeforeTraverse
from Products.Five.site.interfaces import IFiveSiteManager, IFiveUtilityRegistry
# Hook up custom component architecture calls
import zope.app.component.hooks
zope.app.component.hooks.setHooks()
def siteManagerAdapter(ob):
"""An adapter * -> ISiteManager.
This is registered in place of the one in Zope 3 so that we lookup
using acquisition instead of ILocation.
"""
current = ob
while True:
if ISite.providedBy(current):
return current.getSiteManager()
current = getattr(current, '__parent__', aq_parent(aq_inner(current)))
if current is None:
# It does not support acquisition or has no parent, so we
# return the global site
return getGlobalSiteManager()
HOOK_NAME = '__local_site_hook__'
class LocalSiteHook(Base):
def __call__(self, container, request):
notify(BeforeTraverseEvent(container, request))
def enableLocalSiteHook(obj):
"""Install __before_traverse__ hook for Local Site
"""
# We want the original object, not stuff in between, and no acquisition
obj = aq_base(obj)
if not IPossibleSite.providedBy(obj):
raise TypeError, 'Must provide IPossibleSite'
hook = AccessRule(HOOK_NAME)
registerBeforeTraverse(obj, hook, HOOK_NAME, 1)
if not hasattr(obj, HOOK_NAME):
setattr(obj, HOOK_NAME, LocalSiteHook())
directlyProvides(obj, ISite, directlyProvidedBy(obj))
def disableLocalSiteHook(obj):
"""Remove __before_traverse__ hook for Local Site
"""
# We want the original object, not stuff in between, and no acquisition
obj = aq_base(obj)
if not ISite.providedBy(obj):
raise TypeError, 'Must provide ISite'
unregisterBeforeTraverse(obj, HOOK_NAME)
if hasattr(obj, HOOK_NAME):
delattr(obj, HOOK_NAME)
directlyProvides(obj, directlyProvidedBy(obj) - ISite)
class FiveSiteManager(object):
implements(IFiveSiteManager)
def __init__(self, context):
# make {get|query}NextSiteManager() work without having to
# resort to Zope 2 acquisition
self.context = self.__parent__ = context
@property
def next(self):
obj = self.context
while obj is not None:
obj = aq_parent(aq_inner(obj))
if ISite.providedBy(obj):
return obj.getSiteManager()
# In Zope 3.1+, returning None here is understood by
# getNextSiteManager as that our next site manager is the
# global one. If we returned the global one, it would be
# understood as a lookup error. Yeah, it's weird, tell me
# about it.
return None
@property
def adapters(self):
return getGlobalSiteManager().adapters #XXX wrong
@property
def utilities(self):
return IFiveUtilityRegistry(self.context)
def queryAdapter(self, object, interface, name, default=None):
return self.adapters.queryAdapter(object, interface, name, default)
def queryMultiAdapter(self, objects, interface, name, default=None):
return self.adapters.queryMultiAdapter(objects, interface, name, default)
def getAdapters(self, objects, provided):
return self.adapters.getAdapters(objects, provided)
def subscribers(self, required, provided):
return self.adapters.subscribers(required, provided)
def queryUtility(self, interface, name='', default=None):
return self.utilities.queryUtility(interface, name, default)
def getUtilitiesFor(self, interface):
return self.utilities.getUtilitiesFor(interface)
def getAllUtilitiesRegisteredFor(self, interface):
return self.utilities.getAllUtilitiesRegisteredFor(interface)
def registerUtility(self, interface, utility, name=''):
return self.utilities.registerUtility(interface, utility, name)
class FiveSite:
implements(IPossibleSite)
def getSiteManager(self):
return FiveSiteManager(self)
def setSiteManager(self, sm):
raise NotImplementedError('This class has a fixed site manager')
<tal:tag condition="view/update"/>
<html metal:use-macro="context/@@standard_macros/view"
i18n:domain="zope">
<body>
<div metal:fill-slot="body">
<form action="." tal:attributes="action request/URL" method="POST"
enctype="multipart/form-data">
<div class="row">
<div class="controls">
<input type="submit" value="Make site" name="UPDATE_MAKESITE"
i18n:attributes="value"
tal:attributes="disabled view/isSite"/>
<input type="submit" value="Unmake site" name="UPDATE_UNMAKESITE"
i18n:attributes="value"
tal:attributes="disabled not:view/isSite"/>
</div>
</div>
</form>
</div>
</body>
</html>
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta">
<meta:directives namespace="http://namespaces.zope.org/five">
<meta:directive
name="localsite"
schema=".metadirectives.ILocalSiteDirective"
handler=".metaconfigure.installSiteHook"
/>
</meta:directives>
</configure>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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