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 @@
#####################################################################
# 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)
# below
TARGET="2.3.5"
TARGET="2.4.1"
# Order a list of "acceptable" python version numbers (as returned by
# sys.version) below in "best" to "worst" order, not including the
......@@ -22,7 +22,7 @@ ACCEPTABLE=""
# provide the executable names for all the acceptable versions
# (and the target version) below
EXENAMES="python python2 python2.3"
EXENAMES="python python2 python2.4"
#####################################################################
# END EDITABLE PARAMETERS #
......@@ -38,8 +38,7 @@ usage()
{
echo
echo "configure [--help] [--quiet] [--with-python=path] [--prefix=path] "
echo " [--build-base=path] [--ignore-largefile] [--ignore-zlib]"
echo " [--optimize]"
echo " [--ignore-largefile] [--ignore-zlib] [--optimize]"
echo
echo " Creates a Makefile suitable for building and installing Zope"
echo
......@@ -48,7 +47,6 @@ usage()
echo " --quiet suppress nonessential output"
echo " --with-python specify a path to a Python interpreter to use"
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-expat ignore warnings about expat/pyexpat"
echo " --ignore-zlib ignore warnings about zlib"
......
......@@ -26,6 +26,11 @@ Zope Changes
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
ZODB 3.4. As a result, the DBTab package was removed, as
ZODB 3.6 has multidatabase support that makes DBTab
......
......@@ -10,17 +10,14 @@ RELEASE_TAG=<<VERSION_RELEASE_TAG>>
PACKAGE_NAME=${NAME}-${MAJOR_VERSION}.${MINOR_VERSION}-${RELEASE_TAG}
PYTHON="<<PYTHON>>"
ZPKG=zpkg
TMPDIR=/tmp
PREFIX=<<PREFIX>>
BASE_DIR=<<BASE_DIR>>
BUILD_BASE=<<BUILD_BASE>>
DISTUTILS_OPTS=<<DISTUTILS_OPTS>>
INSTALL_FLAGS=<<INSTALL_FLAGS>>
TESTOPTS=-v
BUILD_FLAGS=--build-base="${BUILD_BASE}" \
--build-lib="${BUILD_BASE}/build-lib" \
--build-scripts="${BUILD_BASE}/build-scripts"\
--build-temp="${BUILD_BASE}/build-temp"
BUILD_FLAGS=-i
RM=rm -f
RMRF=rm -rf
......@@ -32,7 +29,7 @@ CP=cp
TAR=tar
MKDIR=mkdir -p
.PHONY : clean install instance untestinst testinst build unbuild
.PHONY : clean install instance untestinst testinst build
.PHONY : default
# default: The default step (invoked when make is called without a target)
......@@ -42,36 +39,22 @@ default: build
@echo to run a Zope instance directly from the build directory\).
@echo
# build: Do whatever 'setup.py build' implies
# build:
build:
${PYTHON} "${BASE_DIR}/setup.py" \
${DISTUTILS_OPTS} build ${BUILD_FLAGS}
# unbuild: Remove the build directory (undo the make build step)
unbuild:
${RMRF} ${BUILD_BASE}
${DISTUTILS_OPTS} build_ext ${BUILD_FLAGS}
# install: Install a software home.
install: build version_txt
${PYTHON} "${BASE_DIR}/setup.py" ${DISTUTILS_OPTS} install \
--home="${PREFIX}" ${BUILD_FLAGS} ${INSTALL_FLAGS}
install: version_txt
${PYTHON} "${BASE_DIR}/setup.py" ${DISTUTILS_OPTS} \
build_ext ${BUILD_FLAGS} \
install --skip-build --home="${PREFIX}" ${INSTALL_FLAGS}
[ -f ${PREFIX}/bin/python ] || ${LN} ${PYTHON} ${PREFIX}/bin/python
@echo
@echo Zope binaries installed successfully.
@echo Now run \'${PREFIX}/bin/mkzopeinstance.py\'
# 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: install
......@@ -101,7 +84,7 @@ test: inplace
# clean: Delete the build files and any binaries/bytecode files in
# the source directory for good measure.
clean: unbuild
clean:
${FIND} "${BASE_DIR}" \
-name '*.py[co]' -o -name '*.so' -o -name '*.o' | ${XARGS} ${RM}
......@@ -110,23 +93,9 @@ version_txt:
printf "Zope ${MAJOR_VERSION}.${MINOR_VERSION}-${RELEASE_TAG}" >\
"${BASE_DIR}/lib/python/version.txt"
# sdist: Create a source distribution file (implies clobber).
#
sdist: clobber sdist_tgz
# 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}
# Building a source distribution requires that zpkg be available:
sdist:
${ZPKG} -C ${BASE_DIR}/releases/Zope2.cfg
# clobber: Make the source tree 'pristine' again.
clobber: clean uninstance
......
......@@ -33,7 +33,6 @@ def main():
# below assumes this script is in the BASE_DIR/inst directory
global PREFIX
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
MAKEFILE=open(os.path.join(BASE_DIR, 'inst', IN_MAKEFILE)).read()
REQUIRE_LF_ENABLED = 1
......@@ -66,8 +65,6 @@ def main():
INSTALL_FLAGS = '--optimize=1 --no-compile'
if o == '--no-compile':
INSTALL_FLAGS = '--no-compile'
if o == '--build-base':
BUILD_BASE = a
if o == '--quiet':
DISTUTILS_OPTS = '-q'
global QUIET
......@@ -85,7 +82,6 @@ def main():
'<<PYTHON>>':PYTHON,
'<<PREFIX>>':PREFIX,
'<<BASE_DIR>>':BASE_DIR,
'<<BUILD_BASE>>':BUILD_BASE,
'<<INSTALL_FLAGS>>':INSTALL_FLAGS,
'<<ZOPE_MAJOR_VERSION>>':versions.ZOPE_MAJOR_VERSION,
'<<ZOPE_MINOR_VERSION>>':versions.ZOPE_MINOR_VERSION,
......
......@@ -48,8 +48,8 @@
*/
#include "ExtensionClass.h"
#include "Acquisition.h"
#include "ExtensionClass/ExtensionClass.h"
#include "Acquisition/Acquisition.h"
#include <stdio.h>
#include <stdlib.h>
......
......@@ -12,10 +12,10 @@
****************************************************************************/
#include "ExtensionClass.h"
#include "ExtensionClass/ExtensionClass.h"
#define _IN_ACQUISITION_C
#include "Acquisition.h"
#include "Acquisition/Acquisition.h"
static ACQUISITIONCAPI AcquisitionCAPI;
......
......@@ -11,7 +11,7 @@
FOR A PARTICULAR PURPOSE
****************************************************************************/
#include "ExtensionClass.h"
#include "ExtensionClass/ExtensionClass.h"
#define UNLESS(E) if(!(E))
#define OBJECT(O) ((PyObject*)(O))
......
......@@ -15,7 +15,7 @@ static char cDocumentTemplate_module_documentation[] =
"\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___call__, *py___roles__, *py_AUTHENTICATED_USER;
......
......@@ -17,7 +17,7 @@ static char _extensionclass_module_documentation[] =
"$Id$\n"
;
#include "ExtensionClass.h"
#include "ExtensionClass/ExtensionClass.h"
#define EC PyTypeObject
......
......@@ -12,7 +12,7 @@
****************************************************************************/
#include "ExtensionClass.h"
#include "ExtensionClass/ExtensionClass.h"
static PyObject *
of(PyObject *self, PyObject *args)
......
......@@ -17,7 +17,7 @@ static char Missing_module_documentation[] =
"\n$Id$"
;
#include "ExtensionClass.h"
#include "ExtensionClass/ExtensionClass.h"
/* Declarations for objects of type Missing */
......
......@@ -12,7 +12,7 @@
****************************************************************************/
#include "ExtensionClass.h"
#include "ExtensionClass/ExtensionClass.h"
#define UNLESS(E) if(!(E))
......
......@@ -459,6 +459,10 @@ class AppInitializer:
from Products.Sessions.BrowserIdManager import BrowserIdManager
bid = BrowserIdManager('browser_id_manager', 'Browser Id Manager')
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')
self.commit('Added browser_id_manager')
......@@ -475,6 +479,10 @@ class AppInitializer:
path='/temp_folder/session_data',
requestName='SESSION')
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')
self.commit('Added session_data_manager')
......@@ -523,6 +531,10 @@ class AppInitializer:
from Products.SiteErrorLog.SiteErrorLog import SiteErrorLog
error_log = SiteErrorLog()
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')
self.commit('Added site error_log at /error_log')
......
......@@ -30,6 +30,12 @@ from App.Dialogs import MessageDialog
from webdav.Lockable import ResourceLockedError
from zExceptions import Unauthorized, BadRequest
from zope.interface import implements
from zope.event import notify
from zope.app.event.objectevent import ObjectCopiedEvent
from zope.app.container.contained import ObjectMovedEvent
from OFS.event import ObjectWillBeMovedEvent
from OFS.event import ObjectClonedEvent
import OFS.subscribers
from OFS.interfaces import ICopyContainer
from OFS.interfaces import ICopySource
......@@ -154,85 +160,120 @@ class CopyContainer(ExtensionClass.Base):
If calling manage_pasteObjects from python code, pass the result of a
previous call to manage_cutObjects or manage_copyObjects as the first
argument.
Also sends IObjectCopiedEvent and IObjectClonedEvent
or IObjectWillBeMovedEvent and IObjectMovedEvent.
"""
cp=None
if cb_copy_data is not None:
cp=cb_copy_data
cp = cb_copy_data
elif REQUEST is not None and REQUEST.has_key('__cp'):
cp = REQUEST['__cp']
else:
if REQUEST and REQUEST.has_key('__cp'):
cp=REQUEST['__cp']
cp = None
if cp is None:
raise CopyError, eNoData
try: cp=_cb_decode(cp)
except: raise CopyError, eInvalid
try:
op, mdatas = _cb_decode(cp)
except:
raise CopyError, eInvalid
oblist=[]
op=cp[0]
oblist = []
app = self.getPhysicalRoot()
result = []
for mdata in cp[1]:
for mdata in mdatas:
m = Moniker.loadMoniker(mdata)
try: ob = m.bind(app)
except: raise CopyError, eNotFound
try:
ob = m.bind(app)
except ConflictError:
raise
except:
raise CopyError, eNotFound
self._verifyObjectPaste(ob, validate_src=op+1)
oblist.append(ob)
if op==0:
result = []
if op == 0:
# Copy operation
for ob in oblist:
orig_id = ob.getId()
if not ob.cb_isCopyable():
raise CopyError, eNotSupported % escape(ob.getId())
try: ob._notifyOfCopyTo(self, op=0)
except: raise CopyError, MessageDialog(
title='Copy Error',
raise CopyError, eNotSupported % escape(orig_id)
try:
ob._notifyOfCopyTo(self, op=0)
except ConflictError:
raise
except:
raise CopyError, MessageDialog(
title="Copy Error",
message=sys.exc_info()[1],
action ='manage_main')
ob=ob._getCopy(self)
orig_id=ob.getId()
id=self._get_id(ob.getId())
result.append({'id':orig_id, 'new_id':id})
action='manage_main')
id = self._get_id(orig_id)
result.append({'id': orig_id, 'new_id': id})
ob = ob._getCopy(self)
ob._setId(id)
notify(ObjectCopiedEvent(ob))
self._setObject(id, ob)
ob = self._getOb(id)
ob._postCopy(self, op=0)
ob.manage_afterClone(ob)
ob.wl_clearLocks()
ob._postCopy(self, op=0)
OFS.subscribers.maybeCallDeprecated('manage_afterClone', ob)
notify(ObjectClonedEvent(ob))
if REQUEST is not None:
return self.manage_main(self, REQUEST, update_menu=1,
cb_dataValid=1)
if op==1:
elif op == 1:
# Move operation
for ob in oblist:
id=ob.getId()
orig_id = ob.getId()
if not ob.cb_isMoveable():
raise CopyError, eNotSupported % escape(id)
try: ob._notifyOfCopyTo(self, op=1)
except: raise CopyError, MessageDialog(
title='Move Error',
raise CopyError, eNotSupported % escape(orig_id)
try:
ob._notifyOfCopyTo(self, op=1)
except ConflictError:
raise
except:
raise CopyError, MessageDialog(
title="Move Error",
message=sys.exc_info()[1],
action ='manage_main')
action='manage_main')
if not sanity_check(self, ob):
raise CopyError, 'This object cannot be pasted into itself'
raise CopyError, "This object cannot be pasted into itself"
orig_container = aq_parent(aq_inner(ob))
if aq_base(orig_container) is aq_base(self):
id = orig_id
else:
id = self._get_id(orig_id)
result.append({'id': orig_id, 'new_id': id})
notify(ObjectWillBeMovedEvent(ob, orig_container, orig_id,
self, id))
# try to make ownership explicit so that it gets carried
# along to the new location if needed.
ob.manage_changeOwnershipType(explicit=1)
aq_parent(aq_inner(ob))._delObject(id)
orig_container._delObject(orig_id, suppress_events=True)
ob = aq_base(ob)
orig_id=id
id=self._get_id(id)
result.append({'id':orig_id, 'new_id':id })
ob._setId(id)
self._setObject(id, ob, set_owner=0)
ob=self._getOb(id)
ob._postCopy(self, op=1)
self._setObject(id, ob, set_owner=0, suppress_events=True)
ob = self._getOb(id)
notify(ObjectMovedEvent(ob, orig_container, orig_id, self, id))
ob._postCopy(self, op=1)
# try to make ownership implicit if possible
ob.manage_changeOwnershipType(explicit=0)
......@@ -243,6 +284,7 @@ class CopyContainer(ExtensionClass.Base):
REQUEST['__cp'] = None
return self.manage_main(self, REQUEST, update_menu=1,
cb_dataValid=0)
return result
manage_renameForm=Globals.DTMLFile('dtml/renameForm', globals())
......@@ -259,31 +301,48 @@ class CopyContainer(ExtensionClass.Base):
return None
def manage_renameObject(self, id, new_id, REQUEST=None):
"""Rename a particular sub-object"""
try: self._checkId(new_id)
except: raise CopyError, MessageDialog(
"""Rename a particular sub-object.
"""
try:
self._checkId(new_id)
except:
raise CopyError, MessageDialog(
title='Invalid Id',
message=sys.exc_info()[1],
action ='manage_main')
ob=self._getOb(id)
ob = self._getOb(id)
if ob.wl_isLocked():
raise ResourceLockedError, 'Object "%s" is locked via WebDAV' % ob.getId()
raise ResourceLockedError, ('Object "%s" is locked via WebDAV'
% ob.getId())
if not ob.cb_isMoveable():
raise CopyError, eNotSupported % escape(id)
self._verifyObjectPaste(ob)
try: ob._notifyOfCopyTo(self, op=1)
except: raise CopyError, MessageDialog(
title='Rename Error',
try:
ob._notifyOfCopyTo(self, op=1)
except ConflictError:
raise
except:
raise CopyError, MessageDialog(
title="Rename Error",
message=sys.exc_info()[1],
action ='manage_main')
self._delObject(id)
notify(ObjectWillBeMovedEvent(ob, self, id, self, new_id))
self._delObject(id, suppress_events=True)
ob = aq_base(ob)
ob._setId(new_id)
# Note - because a rename always keeps the same context, we
# can just leave the ownership info unchanged.
self._setObject(new_id, ob, set_owner=0)
self._setObject(new_id, ob, set_owner=0, suppress_events=True)
ob = self._getOb(new_id)
notify(ObjectMovedEvent(ob, self, id, self, new_id))
ob._postCopy(self, op=1)
if REQUEST is not None:
......@@ -296,26 +355,43 @@ class CopyContainer(ExtensionClass.Base):
# Because it's still a "management" function.
manage_clone__roles__=None
def manage_clone(self, ob, id, REQUEST=None):
# Clone an object, creating a new object with the given id.
"""Clone an object, creating a new object with the given id.
"""
if not ob.cb_isCopyable():
raise CopyError, eNotSupported % escape(ob.getId())
try: self._checkId(id)
except: raise CopyError, MessageDialog(
try:
self._checkId(id)
except:
raise CopyError, MessageDialog(
title='Invalid Id',
message=sys.exc_info()[1],
action ='manage_main')
self._verifyObjectPaste(ob)
try: ob._notifyOfCopyTo(self, op=0)
except: raise CopyError, MessageDialog(
title='Clone Error',
try:
ob._notifyOfCopyTo(self, op=0)
except ConflictError:
raise
except:
raise CopyError, MessageDialog(
title="Clone Error",
message=sys.exc_info()[1],
action ='manage_main')
ob=ob._getCopy(self)
action='manage_main')
ob = ob._getCopy(self)
ob._setId(id)
notify(ObjectCopiedEvent(ob))
self._setObject(id, ob)
ob=self._getOb(id)
ob = self._getOb(id)
ob._postCopy(self, op=0)
ob.manage_afterClone(ob)
OFS.subscribers.maybeCallDeprecated('manage_afterClone', ob)
notify(ObjectClonedEvent(ob))
return ob
def cb_dataValid(self):
......
......@@ -15,6 +15,7 @@
$Id$
"""
import warnings
import marshal
import sys, fnmatch, copy, os, re
from cgi import escape
......@@ -42,6 +43,12 @@ from zope.interface import implements
import CopySupport
from interfaces import IObjectManager
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
......@@ -266,11 +273,17 @@ class ObjectManager(
raise AttributeError, id
return default
def _setObject(self, id, object, roles=None, user=None, set_owner=1):
v=self._checkId(id)
if v is not None: id=v
try: t=object.meta_type
except: t=None
def _setObject(self, id, object, roles=None, user=None, set_owner=1,
suppress_events=False):
"""Set an object into this container.
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.
for object_info in self._objects:
......@@ -278,78 +291,75 @@ class ObjectManager(
self._delObject(id)
break
self._objects=self._objects+({'id':id,'meta_type':t},)
self._setOb(id,object)
object=self._getOb(id)
if not suppress_events:
notify(ObjectWillBeAddedEvent(ob, self, id))
self._objects = self._objects + ({'id': id, 'meta_type': t},)
self._setOb(id, ob)
ob = self._getOb(id)
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
# no local roles have been set on the object yet.
if hasattr(object, '__ac_local_roles__'):
if object.__ac_local_roles__ is None:
user=getSecurityManager().getUser()
if getattr(ob, '__ac_local_roles__', _marker) is None:
user = getSecurityManager().getUser()
if user is not None:
userid=user.getId()
userid = user.getId()
if userid is not None:
object.manage_setLocalRoles(userid, ['Owner'])
ob.manage_setLocalRoles(userid, ['Owner'])
if not suppress_events:
notify(ObjectAddedEvent(ob, self, id))
OFS.subscribers.maybeCallDeprecated('manage_afterAdd', ob, self)
object.manage_afterAdd(object, self)
return id
def manage_afterAdd(self, item, container):
for object in self.objectValues():
try: s=object._p_changed
except: s=0
if hasattr(aq_base(object), 'manage_afterAdd'):
object.manage_afterAdd(item, container)
if s is None: object._p_deactivate()
# Don't do recursion anymore, a subscriber does that.
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_afterClone(self, item):
for object in self.objectValues():
try: s=object._p_changed
except: s=0
if hasattr(aq_base(object), 'manage_afterClone'):
object.manage_afterClone(item)
if s is None: object._p_deactivate()
# Don't do recursion anymore, a subscriber does that.
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
def manage_beforeDelete(self, item, container):
for object in self.objectValues():
try: s=object._p_changed
except: s=0
try:
if hasattr(aq_base(object), 'manage_beforeDelete'):
object.manage_beforeDelete(item, container)
except BeforeDeleteException, ob:
raise
except ConflictError:
raise
except:
LOG('Zope',ERROR,'manage_beforeDelete() 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
if s is None: object._p_deactivate()
def _delObject(self, id, dp=1):
object=self._getOb(id)
try:
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))
# Don't do recursion anymore, a subscriber does that.
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 _delObject(self, id, dp=1, suppress_events=False):
"""Delete an object from this container.
Also sends IObjectWillBeRemovedEvent and IObjectRemovedEvent.
"""
ob = self._getOb(id)
OFS.subscribers.maybeCallDeprecated('manage_beforeDelete', ob, self)
if not suppress_events:
notify(ObjectWillBeRemovedEvent(ob, self, id))
self._objects = tuple([i for i in self._objects
if i['id'] != id])
self._delOb(id)
# Indicate to the object that it has been deleted. This is
......@@ -357,8 +367,13 @@ class ObjectManager(
# tolerate failure here because the object being deleted could
# be a Broken object, and it is not possible to set attributes
# on Broken objects.
try: object._v__object_deleted__ = 1
except: pass
try:
ob._v__object_deleted__ = 1
except:
pass
if not suppress_events:
notify(ObjectRemovedEvent(ob, self, id))
def objectIds(self, spec=None):
# Returns a list of subobject ids of the current object.
......
......@@ -30,7 +30,7 @@ from IOrderSupport import IOrderedContainer as z2IOrderedContainer
from ObjectManager import ObjectManager
class OrderSupport:
class OrderSupport(object):
""" Ordered container mixin class.
......@@ -251,13 +251,12 @@ class OrderSupport:
# Override Inherited Method of ObjectManager Subclass
#
_old_manage_renameObject = ObjectManager.inheritedAttribute(
'manage_renameObject')
def manage_renameObject(self, id, new_id, REQUEST=None):
""" Rename a particular sub-object without changing its position.
"""
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)
return result
......
......@@ -20,6 +20,7 @@ item types.
$Id$
"""
import warnings
import marshal, re, sys, time
import AccessControl.Role, AccessControl.Owned, App.Common
......@@ -60,13 +61,28 @@ class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable,
isTopLevelPrincipiaApplicationObject=0
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):
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):
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()
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):
"""Check the global (zclass) registry for problems, which can
be caused by things like disk-based products being deleted.
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
from AccessControl.Owned import EmergencyUserCannotOwn
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManagement import noSecurityManager
from AccessControl.User import User # before SpecialUsers
from AccessControl.SpecialUsers import emergency_user, nobody, system
from AccessControl.User import User
from Acquisition import Implicit
from App.config import getConfiguration
from OFS.ObjectManager import ObjectManager
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 ):
......@@ -46,26 +50,39 @@ class ItemForDeletion(SimpleItem):
self.before_delete_called = True
if self.fail_on_delete:
raise DeleteFailed
return SimpleItem.manage_beforeDelete(self, item, container)
def manage_afterAdd(self, item, container):
pass
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( unittest.TestCase ):
class ObjectManagerTests(PlacelessSetup, unittest.TestCase):
def setUp(self):
super(ObjectManagerTests, self).setUp()
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 ):
noSecurityManager()
getConfiguration().debug_mode = self.saved_cfg_debug_mode
super(ObjectManagerTests, self).tearDown()
def setDebugMode(self, mode):
getConfiguration().debug_mode = mode
def _getTargetClass( self ):
from OFS.ObjectManager import ObjectManager
return ObjectManager
return ObjectManagerWithIItem
def _makeOne( self, *args, **kw ):
......
......@@ -14,6 +14,8 @@ class DummyObject(CopySource):
return
def manage_beforeDelete(self, item, container):
return
manage_afterAdd.__five_method__ = True
manage_beforeDelete.__five_method__ = True
def wl_isLocked(self):
return 0
......
......@@ -17,8 +17,8 @@ static char _Persistence_module_documentation[] =
"$Id$\n"
;
#include "ExtensionClass.h"
#include "cPersistence.h"
#include "ExtensionClass/ExtensionClass.h"
#include "persistent/cPersistence.h"
/* convert_name() returns a new reference to a string name
......
......@@ -37,6 +37,12 @@ from AccessControl.Permissions import access_contents_information, \
view_management_screens
from zLOG import LOG, INFO, ERROR, WARNING
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())
......@@ -404,47 +410,58 @@ class BTreeFolder2Base (Persistent):
'it is already in use.' % id)
def _setObject(self, id, object, roles=None, user=None, set_owner=1):
v=self._checkId(id)
if v is not None: id=v
def _setObject(self, id, object, roles=None, user=None, set_owner=1,
suppress_events=False):
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 self.has_key(id):
self._delObject(id)
self._setOb(id, object)
object = self._getOb(id)
if not suppress_events:
notify(ObjectWillBeAddedEvent(ob, self, id))
self._setOb(id, ob)
ob = self._getOb(id)
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
# no local roles have been set on the object yet.
if hasattr(object, '__ac_local_roles__'):
if object.__ac_local_roles__ is None:
user=getSecurityManager().getUser()
if getattr(ob, '__ac_local_roles__', _marker) is None:
user = getSecurityManager().getUser()
if user is not None:
userid=user.getId()
userid = user.getId()
if userid is not None:
object.manage_setLocalRoles(userid, ['Owner'])
ob.manage_setLocalRoles(userid, ['Owner'])
if not suppress_events:
notify(ObjectAddedEvent(ob, self, id))
OFS.subscribers.maybeCallDeprecated('manage_afterAdd', ob, self)
object.manage_afterAdd(object, self)
return id
def _delObject(self, id, dp=1):
object = self._getOb(id)
try:
object.manage_beforeDelete(object, self)
except BeforeDeleteException, ob:
raise
except ConflictError:
raise
except:
LOG('Zope', ERROR, 'manage_beforeDelete() threw',
error=sys.exc_info())
def _delObject(self, id, dp=1, suppress_events=False):
ob = self._getOb(id)
OFS.subscribers.maybeCallDeprecated('manage_beforeDelete', ob, self)
if not suppress_events:
notify(ObjectWillBeRemovedEvent(ob, self, id))
self._delOb(id)
if not suppress_events:
notify(ObjectRemovedEvent(ob, self, id))
# Aliases for mapping-like access.
__len__ = objectCount
......
......@@ -2,6 +2,95 @@
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)
=====================
......@@ -100,7 +189,7 @@ Restructuring
* The former test product, ``FiveTest``, was converted into separate
modules that provide the mock objects for the corresponding tests
and are located right next to them. Common test helpers have been
moved to the Five.testing package. Overall, the testing framework
moved to the Five.tests.testing package. Overall, the testing framework
was much simplified and the individual tests clean up after
themselves much like they do in Zope 3.
......
......@@ -9,7 +9,7 @@ Five contributors
- Lennart Regebro (regebro@nuxeo.com)
- Tres Seaver (tres@zope.com)
- Tres Seaver (tseaver@palladion.com)
- Jan-Wijbrand Kolman (jw@infrae.com)
......@@ -33,6 +33,9 @@ Five contributors
- Tarek Ziad (tziade@nuxeo.com)
- Whit Morriss (whit@longnow.org)
Thank you
---------
......
......@@ -70,7 +70,7 @@ def trustedTraverse(ob, path, ignored,):
o = get(object, name, M)
if o is M:
try: o = object[name]
except AttributeError: # better exception
except (AttributeError, TypeError): # better exception
raise AttributeError(name)
object = o
......
......@@ -13,20 +13,13 @@
##############################################################################
"""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
from AccessControl import ClassSecurityInfo
from Globals import InitializeClass
import zope.app.publisher.browser
class BrowserView(Acquisition.Explicit):
security = ClassSecurityInfo()
class BrowserView(Acquisition.Explicit, zope.app.publisher.browser.BrowserView):
"""Five browser view
def __init__(self, context, request):
self.context = context
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)
Mixes in explicit acquisition so that security can be acquired for
views"""
......@@ -52,7 +52,7 @@ class AbsoluteURL(BrowserView):
return (
{'name': name, 'url': context.absolute_url()},)
view = zapi.getViewProviding(container, IAbsoluteURL, request)
view = zapi.getMultiAdapter((container, request), IAbsoluteURL)
base = tuple(view.breadcrumbs())
base += (
{'name': name, 'url': ("%s/%s" % (base[-1]['url'], name))},)
......
......@@ -28,6 +28,8 @@ from zope.app.container.interfaces import IAdding, INameChooser
from zope.app.container.interfaces import IContainerNamesContainer
from zope.app.container.constraints import checkFactory, checkObject
from zope.app.publisher.browser.menu import getMenu
from zope.app import zapi
from zope.app.event.objectevent import ObjectCreatedEvent
from zope.event import notify
......@@ -84,8 +86,8 @@ class BasicAdding(Implicit, BrowserView):
# XXX this is definitely not right for all or even most uses
# of Five, but can be overridden by an AddView subclass, using
# the class attribute of a zcml:addform directive
return (str(zapi.getView(self.context, "absolute_url", self.request))
+ '/manage_main')
return str(zapi.getMultiAdapter((self.context, self.request),
name=u"absolute_url")) + '/manage_main'
# set in BrowserView.__init__
request = None
......@@ -104,7 +106,7 @@ class BasicAdding(Implicit, BrowserView):
if view_name.startswith('@@'):
view_name = view_name[2:]
return zapi.getView(self, view_name, request)
return zapi.getMultiAdapter((self, request), name=view_name)
if name.startswith('@@'):
view_name = name[2:]
......@@ -135,7 +137,7 @@ class BasicAdding(Implicit, BrowserView):
if zapi.queryView(self, view_name, self.request) is not None:
url = "%s/%s=%s" % (
zapi.getView(self, "absolute_url", self.request),
zapi.getMultiAdapter((self, self.request), name=u"absolute_url"),
type_name, id)
self.request.response.redirect(url)
return
......@@ -169,12 +171,11 @@ class Adding(BasicAdding):
This is sorted by title.
"""
container = self.context
menu_service = zapi.getService("BrowserMenu")
result = []
for menu_id in (self.menu_id, 'zope.app.container.add'):
if not menu_id:
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')
if extra:
factory = extra.get('factory')
......
<configure xmlns="http://namespaces.zope.org/zope"
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:page
......
......@@ -13,17 +13,15 @@
##############################################################################
"""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.app import zapi
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
class MenuAccessView(BrowserView):
implements(IMenuAccessView)
def __getitem__(self, menu_id):
browser_menu_service = zapi.getService(BrowserMenu)
return browser_menu_service.getMenu(menu_id, self.context, self.request)
return getMenu(menu_id, self.context, self.request)
......@@ -25,7 +25,7 @@
<meta:directive
name="defaultView"
schema="zope.app.publisher.browser.metadirectives.IDefaultViewDirective"
handler=".metaconfigure.defaultView"
handler="zope.app.publisher.browser.metaconfigure.defaultView"
/>
<meta:directive
......@@ -62,19 +62,19 @@
<meta:directive
name="menu"
schema="zope.app.publisher.browser.metadirectives.IMenuDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuDirective"
handler="zope.app.publisher.browser.menumeta.menuDirective"
/>
<meta:directive
name="menuItem"
schema="zope.app.publisher.browser.metadirectives.IMenuItemDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuItemDirective"
handler="zope.app.publisher.browser.menumeta.menuItemDirective"
/>
<meta:complexDirective
name="menuItems"
schema="zope.app.publisher.browser.metadirectives.IMenuItemsDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuItemsDirective"
handler="zope.app.publisher.browser.menumeta.menuItemsDirective"
>
<meta:subdirective
......
......@@ -21,18 +21,16 @@ $Id: metaconfigure.py 13257 2005-06-09 21:56:39Z philikon $
import os
from zope.interface import Interface
from zope.component import getGlobalService, ComponentLookupError
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 view as zope_app_view
from zope.app.publisher.browser.viewmeta import providesCallable
from zope.app.publisher.browser.globalbrowsermenuservice import\
menuItemDirective
from zope.app.publisher.browser.viewmeta import providesCallable, \
_handle_menu, _handle_for
from zope.app.component.metaconfigure import handler
from zope.app.component.interface import provideInterface
from zope.app.container.interfaces import IAdding
from Products.Five.browser import BrowserView
from Products.Five.browser.resource import FileResourceFactory, ImageResourceFactory
......@@ -40,22 +38,16 @@ from Products.Five.browser.resource import PageTemplateResourceFactory
from Products.Five.browser.resource import DirectoryResourceFactory
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Products.Five.metaclass import makeClass
from Products.Five.security import getSecurityInfo, protectClass, \
protectName, initializeClass
from Products.Five.security import getSecurityInfo, protectClass, protectName
import ExtensionClass
from Globals import InitializeClass as initializeClass
def page(_context, name, permission, for_,
layer='default', template=None, class_=None,
layer=IDefaultBrowserLayer, template=None, class_=None,
allowed_interface=None, allowed_attributes=None,
attribute='__call__', menu=None, title=None,
):
try:
s = getGlobalService(Presentation)
except ComponentLookupError, err:
pass
_handle_menu(_context, menu, title, [for_], name, permission)
if not (class_ or template):
......@@ -64,8 +56,7 @@ def page(_context, name, permission, for_,
allowed_attributes = []
if allowed_interface is not None:
for interface in allowed_interface:
attrs = [n for n, d in interface.namesAndDescriptions(1)]
allowed_attributes.extend(attrs)
allowed_attributes.extend(interface.names())
if attribute != '__call__':
if template:
......@@ -92,9 +83,10 @@ def page(_context, name, permission, for_,
"The provided class doesn't have the specified attribute "
)
cdict = getSecurityInfo(class_)
cdict['__name__'] = name
if template:
new_class = makeClassForTemplate(template, bases=(class_, ),
cdict=cdict)
cdict=cdict, name=name)
elif attribute != "__call__":
# we're supposed to make a page for an attribute (read:
# method) and it's not __call__. We thus need to create a
......@@ -125,16 +117,15 @@ def page(_context, name, permission, for_,
else:
# template
new_class = makeClassForTemplate(template)
new_class = makeClassForTemplate(template, name=name)
_handle_for(_context, for_)
_context.action(
discriminator = ('view', for_, name, IBrowserRequest, layer),
callable = handler,
args = (Presentation, 'provideAdapter',
IBrowserRequest, new_class, name, [for_], Interface, layer,
_context.info),
args = ('provideAdapter',
(for_, layer), Interface, name, new_class, _context.info),
)
_context.action(
discriminator = ('five:protectClass', new_class),
......@@ -165,19 +156,6 @@ class pages(zope_app_pages):
menu=menu, title=title,
**(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)
class view(zope_app_view):
......@@ -192,11 +170,6 @@ class view(zope_app_view):
pages = {}
for pname, attribute, template in self.pages:
try:
s = getGlobalService(Presentation)
except ComponentLookupError, err:
pass
if template:
cdict[pname] = ZopeTwoPageTemplateFile(template)
if attribute and attribute != name:
......@@ -255,7 +228,7 @@ class view(zope_app_view):
if class_ is not None:
bases = (class_, ViewMixinForTemplates)
else:
bases = (ViewMixinForTemplates)
bases = (ViewMixinForTemplates,)
try:
cname = str(name)
......@@ -277,37 +250,11 @@ class view(zope_app_view):
discriminator = ('view', for_, name, IBrowserRequest, layer,
self.provides),
callable = handler,
args = (Presentation, 'provideAdapter',
IBrowserRequest, newclass, name, [for_], self.provides,
layer, _context.info),
)
def _handle_for(_context, for_):
if for_ is not None:
_context.action(
discriminator = None,
callable = provideInterface,
args = ('', for_)
args = ('provideAdapter',
(for_, layer), self.provides, name, newclass,
_context.info),
)
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',
'count':0,
'factory':ImageResourceFactory},
......@@ -319,7 +266,7 @@ _factory_map = {'image':{'prefix':'ImageResource',
'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):
if ((file and image) or (file and template) or
......@@ -343,8 +290,8 @@ def resource(_context, name, layer='default', permission='zope.Public',
_context.action(
discriminator = ('resource', name, IBrowserRequest, layer),
callable = handler,
args = (Presentation, 'provideResource',
name, IBrowserRequest, factory, layer),
args = ('provideAdapter',
(layer,), Interface, name, factory, _context.info),
)
_context.action(
discriminator = ('five:protectClass', new_class),
......@@ -367,7 +314,7 @@ _rd_map = {ImageResourceFactory:{'prefix':'DirContainedImageResource',
'count':0}
}
def resourceDirectory(_context, name, directory, layer='default',
def resourceDirectory(_context, name, directory, layer=IDefaultBrowserLayer,
permission='zope.Public'):
if not os.path.isdir(directory):
......@@ -410,8 +357,8 @@ def resourceDirectory(_context, name, directory, layer='default',
_context.action(
discriminator = ('resource', name, IBrowserRequest, layer),
callable = handler,
args = (Presentation, 'provideResource',
name, IBrowserRequest, factory, layer),
args = ('provideAdapter',
(layer,), Interface, name, factory, _context.info),
)
for new_class in new_classes:
_context.action(
......@@ -456,11 +403,12 @@ class ViewMixinForTemplates(BrowserView):
return self.index(self, *args, **kw)
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?
if cdict is None:
cdict = {}
cdict.update({'index': ZopeTwoPageTemplateFile(filename, globals)})
cdict.update({'index': ZopeTwoPageTemplateFile(filename, globals),
'__name__': name})
bases += (ViewMixinForTemplates,)
class_ = makeClass("SimpleViewClass from %s" % filename, bases, cdict)
......
......@@ -18,15 +18,15 @@ $Id: resource.py 13268 2005-06-10 14:18:23Z philikon $
import os
import urllib
from Acquisition import Explicit
import Acquisition
from ComputedAttribute import ComputedAttribute
from OFS.Traversable import Traversable as OFSTraversable
from zope.exceptions import NotFoundError
from zope.interface import implements
from zope.component.interfaces import IResource
from zope.component import getViewProviding
from zope.publisher.interfaces.browser import IBrowserPublisher
from zope.app import zapi
from zope.app.traversing.browser.interfaces import IAbsoluteURL
from zope.app.datetimeutils import time as timeFromDateTimeString
from zope.app.publisher.fileresource import File, Image
......@@ -37,7 +37,7 @@ from Products.Five.browser import BrowserView
_marker = []
class Resource(Explicit):
class Resource(Acquisition.Explicit):
"""A publishable resource
"""
implements(IResource)
......@@ -49,7 +49,9 @@ class Resource(Explicit):
name = self.__name__
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)
if not isinstance(container, DirectoryResource):
name = '++resource++%s' % name
......@@ -213,7 +215,7 @@ class DirectoryResource(BrowserView, Resource, OFSTraversable):
filename = os.path.join(path, name)
if not os.path.isfile(filename):
if default is _marker:
raise NotFoundError(name)
raise KeyError(name)
return default
ext = name.split('.')[-1]
factory = self.resource_factories.get(ext, self.default_factory)
......
......@@ -7,7 +7,7 @@ ObjectManagerNameChooser
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
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
......
......@@ -3,15 +3,15 @@
xmlns:five="http://namespaces.zope.org/five">
<five:defaultViewable
class="Products.Five.testing.simplecontent.SimpleContent" />
class="Products.Five.tests.testing.simplecontent.SimpleContent" />
<browser:defaultView
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="eagledefaultview.txt"
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="eagledefaultview.txt"
class=".pages.SimpleView"
attribute="eagle"
......@@ -22,16 +22,16 @@
already provides __call__, such as our CallableSimpleContent -->
<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
already provides index_html, such as our IndexSimpleContent -->
<five:defaultViewable
class="Products.Five.testing.simplecontent.IndexSimpleContent" />
class="Products.Five.tests.testing.simplecontent.IndexSimpleContent" />
<browser:defaultView
for="Products.Five.testing.simplecontent.IIndexSimpleContent"
for="Products.Five.tests.testing.simplecontent.IIndexSimpleContent"
name="index_html"
/>
......
......@@ -3,7 +3,7 @@
<!-- mouse instead of eagle -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="mouse"
name="overridden_view"
......
......@@ -34,6 +34,11 @@ class FancyView(BrowserView):
def view(self):
return "Fancy, fancy"
class CallView(BrowserView):
def __call__(self):
return "I was __call__()'ed"
class CallableNoDocstring:
def __call__(self):
......
......@@ -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:
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
We also need to create a stub user account and login; otherwise we
......@@ -29,7 +29,7 @@ Simple pages
A browser page that is a view class's attribute (method):
>>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
>>> view != None
>>> view is not None
True
>>> from Products.Five.browser.tests.pages import SimpleView
>>> isinstance(view, SimpleView)
......@@ -196,8 +196,10 @@ high-level security tests). Let's manually look up a protected view:
>>> from Products.Five.traversable import FakeRequest
>>> from zope.app import zapi
>>> from zope.app.publication.browser import setDefaultSkin
>>> request = FakeRequest()
>>> 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,
so we get ('',) instead of ('eagle',):
......@@ -213,7 +215,7 @@ evaluated. __roles__ is a imPermissionRole object:
>>> view_roles
('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
>>> from Acquisition import aq_parent, aq_base, aq_inner
......@@ -255,8 +257,8 @@ High-level security
... 'nodoc-method', 'nodoc-function', 'nodoc-object',
... 'dirpage1', 'dirpage2']
>>> from Products.Five.testing.restricted import checkRestricted
>>> from Products.Five.testing.restricted import checkUnauthorized
>>> from Products.Five.tests.testing.restricted import checkRestricted
>>> from Products.Five.tests.testing.restricted import checkUnauthorized
As long as we're not authenticated, we should get Unauthorized for
protected views, but we should be able to view the public ones:
......@@ -312,5 +314,5 @@ Test traversal to resources from within ZPT pages:
Clean up
--------
>>> from zope.app.tests.placelesssetup import tearDown
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
......@@ -8,7 +8,7 @@
<!-- attribute page -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="eagle.txt"
......@@ -16,7 +16,7 @@
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
name="eagle.method"
permission="zope2.ViewManagementScreens"
......@@ -25,7 +25,7 @@
<!-- attribute page -->
<browser:pages
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
permission="zope2.ViewManagementScreens"
>
......@@ -41,7 +41,7 @@
<!-- template/class page -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="falcon.pt"
name="falcon.html"
......@@ -50,7 +50,7 @@
<!-- template page (with simple python expression) -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="owl.pt"
name="owl.html"
permission="zope2.ViewManagementScreens"
......@@ -59,7 +59,7 @@
<!-- template page which calls on context using python and path
expressions -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="flamingo.pt"
name="flamingo.html"
permission="zope2.ViewManagementScreens"
......@@ -67,7 +67,7 @@
<!-- template/class page which calls on context, view, views -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="condor.pt"
name="condor.html"
......@@ -76,7 +76,7 @@
<!-- template page that defines a macro page -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="birdmacro.pt"
name="bird.html"
permission="zope2.ViewManagementScreens"
......@@ -84,7 +84,7 @@
<!-- template page that uses macro page -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="seagull.pt"
name="seagull.html"
permission="zope2.ViewManagementScreens"
......@@ -92,21 +92,21 @@
<!-- test TALES -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="ostrich.pt"
name="ostrich.html"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="tales_traversal.pt"
name="tales_traversal.html"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="template_variables.pt"
name="template_variables.html"
permission="zope2.ViewManagementScreens"
......@@ -115,7 +115,7 @@
<!-- template security -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="security.pt"
name="security.html"
permission="zope2.View"
......@@ -124,7 +124,7 @@
<!-- a publicly accessible page, attribute, template, template/class -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="public_attribute_page"
......@@ -132,14 +132,14 @@
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="owl.pt"
name="public_template_page"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="falcon.pt"
name="public_template_class_page"
......@@ -147,16 +147,23 @@
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="parakeet.pt"
name="parakeet.html"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.CallView"
name="callview.html"
permission="zope2.Public"
/>
<!-- pages from methods/functions/callables that don't have docstrings -->
<browser:pages
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class="Products.Five.browser.tests.pages.NoDocstringView"
permission="zope2.Public">
<browser:page
......@@ -177,7 +184,7 @@
This is mainly used to load Zope2 skin templates so they can be used
in five skins and layers. -->
<five:pagesFromDirectory
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
module="Products.Five.browser.tests"
directory="pages"
permission="zope2.Public"
......@@ -186,7 +193,7 @@
<!-- browser:page directives with new style classes are ignored -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.NewStyleClass"
name="new_style_class"
attribute="method"
......@@ -198,7 +205,7 @@
<browser:view
name=""
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
permission="zope2.Public"
/>
......@@ -207,14 +214,14 @@
<!-- protected edit form for permission check -->
<browser:editform
schema="Products.Five.testing.simplecontent.ISimpleContent"
schema="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="protectededitform.html"
permission="zope2.ViewManagementScreens"
/>
<!-- stuff that we'll override in overrides.zcml -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="overridden_view"
......
......@@ -11,7 +11,7 @@ some:
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')
......@@ -122,8 +122,23 @@ All public views should always be accessible by anyone:
... self.failUnless(status == 200, (status, 200, view_name))
Miscellaneous
-------------
Zope 2 always wants objects in the traversal graph to have a __name__.
That is also true for views, e.g. a view constructed from a simple
class bearing only a __call__ method:
>>> print http(r'''
... GET /test_folder_1_/testoid/callview.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
I was __call__()'ed
Clean up
--------
>>> from zope.app.tests.placelesssetup import tearDown
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
......@@ -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
registered is found:
>>> from Products.Five.testing import manage_addFiveTraversableFolder
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'ftf')
Now for some actual testing... Our test page is a simple ZPT
......
......@@ -8,7 +8,7 @@ Set up the test fixtures:
>>> zcml.load_config("configure.zcml", Products.Five)
>>> 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')
>>> import os, glob
......@@ -73,8 +73,8 @@ PageTemplateResource's __call__ renders the template
Security
--------
>>> from Products.Five.testing.restricted import checkRestricted
>>> from Products.Five.testing.restricted import checkUnauthorized
>>> from Products.Five.tests.testing.restricted import checkRestricted
>>> from Products.Five.tests.testing.restricted import checkUnauthorized
>>> resource_names = ['cockatiel.html', 'style.css', 'pattern.png']
......@@ -112,5 +112,5 @@ We can now view them all:
Clean up
--------
>>> from zope.app.tests.placelesssetup import tearDown
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
......@@ -8,7 +8,7 @@ Set up the test fixtures:
>>> zcml.load_config("configure.zcml", Products.Five)
>>> 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')
>>> import os, glob
......@@ -69,5 +69,5 @@ Page templates aren't guaranteed to render, so exclude them from the test:
Clean up
--------
>>> from zope.app.tests.placelesssetup import tearDown
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
......@@ -29,7 +29,7 @@ def test_absoluteurl():
>>> from Products.Five import zcml
>>> 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')
A simple traversal will yield us the @@absolute_url view:
......@@ -86,7 +86,7 @@ def test_absoluteurl():
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
......
......@@ -33,9 +33,9 @@ def test_default_view():
Now let's add a couple of stub objects:
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.testing.simplecontent import manage_addCallableSimpleContent
>>> from Products.Five.testing.simplecontent import manage_addIndexSimpleContent
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.tests.testing.simplecontent import manage_addCallableSimpleContent
>>> from Products.Five.tests.testing.simplecontent import manage_addIndexSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> manage_addCallableSimpleContent(self.folder, 'testcall', 'TestCall')
......@@ -79,7 +79,7 @@ def test_default_view():
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
......
......@@ -33,7 +33,7 @@ def test_zpt_i18n():
... </configure>
... <configure package="Products.Five.browser.tests">
... <browser:page
... for="Products.Five.interfaces.IFolder"
... for="OFS.interfaces.IFolder"
... template="i18n.pt"
... name="i18n.html"
... permission="zope2.View"
......@@ -49,7 +49,7 @@ def test_zpt_i18n():
In order to be able to traverse to the PageTemplate view, we need
a traversable object:
>>> from Products.Five.testing import manage_addFiveTraversableFolder
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
We tell Zope to translate the messages by passing the
......@@ -80,7 +80,7 @@ def test_zpt_i18n():
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
......
......@@ -28,7 +28,8 @@ def test_menu():
>>> import Products.Five.browser.tests
>>> 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)
>>> from Products.Five.security import newInteraction
......@@ -37,12 +38,12 @@ def test_menu():
Now for some actual testing... Let's look up the menu we registered:
>>> from Products.Five.traversable import FakeRequest
>>> from zope.app.publisher.browser.globalbrowsermenuservice import \\
... globalBrowserMenuService
>>> from zope.app.publication.browser import setDefaultSkin
>>> from zope.app.publisher.browser.menu import getMenu
>>> request = FakeRequest()
>>> menu = globalBrowserMenuService.getMenu(
... 'testmenu', self.folder, request)
>>> setDefaultSkin(request)
>>> menu = getMenu('testmenu', self.folder, request)
It should have
......@@ -53,27 +54,41 @@ def test_menu():
>>> menu.sort(lambda x, y: cmp(x['title'], y['title']))
>>> from pprint import pprint
>>> pprint(menu)
[{'action': '@@cockatiel_menu_public.html',
'description': '',
>>> pprint(menu[0])
{'action': u'@@cockatiel_menu_public.html',
'description': u'',
'extra': None,
'selected': '',
'title': u'Page in a menu (public)'},
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Page in a menu (public)'}
>>> pprint(menu[1])
{'action': u'seagull.html',
'description': u'This is a test menu item',
'extra': None,
'selected': '',
'title': u'Test Menu Item'},
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item'}
>>> pprint(menu[2])
{'action': u'parakeet.html',
'description': u'This is a test menu item',
'extra': None,
'selected': '',
'title': u'Test Menu Item 2'},
'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,
'selected': '',
'title': u'Test Menu Item 3'}]
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item 3'}
Let's create a manager user account and log in.
......@@ -82,8 +97,7 @@ def test_menu():
>>> self.login('manager')
>>> newInteraction()
>>> menu = globalBrowserMenuService.getMenu(
... 'testmenu', self.folder, request)
>>> menu = getMenu('testmenu', self.folder, request)
We should get the protected menu items now:
......@@ -91,47 +105,73 @@ def test_menu():
7
>>> menu.sort(lambda x, y: cmp(x['title'], y['title']))
>>> pprint(menu)
[{'action': '@@cockatiel_menu_protected.html',
'description': '',
>>> pprint(menu[0])
{'action': u'@@cockatiel_menu_protected.html',
'description': u'',
'extra': None,
'selected': '',
'title': u'Page in a menu (protected)'},
{'action': '@@cockatiel_menu_public.html',
'description': '',
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Page in a menu (protected)'}
>>> pprint(menu[1])
{'action': u'@@cockatiel_menu_public.html',
'description': u'',
'extra': None,
'selected': '',
'title': u'Page in a menu (public)'},
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Page in a menu (public)'}
>>> pprint(menu[2])
{'action': u'seagull.html',
'description': u'This is a protected test menu item',
'extra': None,
'selected': '',
'title': u'Protected Test Menu Item'},
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Protected Test Menu Item'}
>>> pprint(menu[3])
{'action': u'falcon.html',
'description': u'This is a protected test menu item',
'extra': None,
'selected': '',
'title': u'Protected Test Menu Item 2'},
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Protected Test Menu Item 2'}
>>> pprint(menu[4])
{'action': u'seagull.html',
'description': u'This is a test menu item',
'extra': None,
'selected': '',
'title': u'Test Menu Item'},
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item'}
>>> pprint(menu[5])
{'action': u'parakeet.html',
'description': u'This is a test menu item',
'extra': None,
'selected': '',
'title': u'Test Menu Item 2'},
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item 2'}
>>> pprint(menu[6])
{'action': u'falcon.html',
'description': u'This is a test menu item',
'extra': None,
'selected': '',
'title': u'Test Menu Item 3'}]
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item 3'}
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
......
......@@ -26,7 +26,7 @@ def test_ViewAcquisitionWrapping():
>>> zcml.load_config("configure.zcml", Products.Five)
>>> 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')
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
......@@ -56,7 +56,7 @@ def test_ViewAcquisitionWrapping():
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
......@@ -65,11 +65,10 @@ def test_suite():
from Testing.ZopeTestCase import installProduct, ZopeDocTestSuite
from Testing.ZopeTestCase import ZopeDocFileSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
installProduct('PythonScripts') # for Five.testing.restricted
installProduct('PythonScripts') # for Five.tests.testing.restricted
return unittest.TestSuite((
ZopeDocTestSuite(),
ZopeDocFileSuite('pages.txt',
package='Products.Five.browser.tests'),
ZopeDocFileSuite('pages.txt', package='Products.Five.browser.tests'),
FunctionalDocFileSuite('pages_ftest.txt',
package='Products.Five.browser.tests')
))
......
......@@ -23,9 +23,6 @@ def test_recursion():
"""
Test recursion
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> setUp()
This test makes sure that recursion is avoided for view lookup.
First, we need to set up a stub interface...
......@@ -51,10 +48,10 @@ def test_recursion():
>>> from Products.Five.fiveconfigure import classDefaultViewable
>>> classDefaultViewable(Recurse)
>>> from zope.app import zapi
>>> from zope.component import provideAdapter
>>> from zope.publisher.interfaces.browser import IBrowserRequest
>>> pres = zapi.getGlobalService('Presentation')
>>> pres.setDefaultViewName(IRecurse, IBrowserRequest, 'view')
>>> from zope.component.interfaces import IDefaultViewName
>>> provideAdapter(u'view', (IRecurse, IBrowserRequest), IDefaultViewName)
Here comes the actual test:
......@@ -65,9 +62,10 @@ def test_recursion():
'foo'
Clean up:
Clean up adapter registry and monkey patches to classes:
>>> tearDown()
>>> from zope.testing.cleanup import cleanUp
>>> cleanUp()
"""
def test_suite():
......
......@@ -23,7 +23,7 @@ def test_suite():
import unittest
from Testing.ZopeTestCase import installProduct, ZopeDocFileSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
installProduct('PythonScripts') # for Five.testing.restricted
installProduct('PythonScripts') # for Five.tests.testing.restricted
return unittest.TestSuite((
ZopeDocFileSuite('resource.txt',
package='Products.Five.browser.tests'),
......
......@@ -33,7 +33,7 @@ def test_traversable():
the wrong reason: None doesn't have a docstring so BaseRequest
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')
>>> print http(r'''
... GET /test_folder_1_/testoid/doesntexist HTTP/1.1
......@@ -54,21 +54,21 @@ def test_traversable():
... <meta:redefinePermission from="zope2.Public" to="zope.Public" />
...
... <five:traversable
... class="Products.Five.testing.fancycontent.FancyContent"
... class="Products.Five.tests.testing.fancycontent.FancyContent"
... />
...
... <browser:page
... for="Products.Five.testing.fancycontent.IFancyContent"
... for="Products.Five.tests.testing.fancycontent.IFancyContent"
... class="Products.Five.browser.tests.pages.FancyView"
... attribute="view"
... name="fancy"
... name="fancyview"
... permission="zope2.Public"
... />
...
... </configure>'''
>>> 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', '')
In the following test we let the original __bobo_traverse__ method
......@@ -85,7 +85,7 @@ def test_traversable():
actually works:
>>> 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
...
......@@ -94,7 +94,7 @@ def test_traversable():
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
......
......@@ -2,13 +2,15 @@
xmlns:five="http://namespaces.zope.org/five">
<include file="meta.zcml" />
<include file="services.zcml" />
<include file="interfaces.zcml" />
<include file="permissions.zcml" />
<include file="i18n.zcml" />
<include file="event.zcml"/>
<include file="deprecated.zcml"/>
<include package=".site" />
<include package=".browser" />
<include package=".form" />
<include package=".skin" />
<include package=".utilities" />
<include package="zope.app.event" />
<include package="zope.app.traversing" />
......
<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
Redefine a permission in included ZCML as another one.
service
-------
Declare a global service
serviceType
-----------
Declare a type of service.
skin
----
......@@ -55,8 +45,24 @@ Declare a global utility.
interface
---------
Register an interface in ZCML.
factory
-------
Register an object factory.
modulealias
-----------
Provide a module under an alias name, e.g. for persistent backward
compatability.
hook
----
Install a hook on a hookable object.
browser ``http://namespaces.zope.org/browser``
==============================================
......@@ -148,16 +154,34 @@ sizable
Retrieve size information for a Zope 2 content class via a Zope 3
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
correspond to the Zope 2 methods ``manage_afterAdd`` and
``manage_beforeDelete``.
deprecatedManageAddDelete
-------------------------
Specify a class that needs its old deprecated methods like
``manage_afterAdd``, ``manage_beforeDelete`` and ``manage_afterClone``
to be called. Modern classes should use event subscribers instead.
pagesFromDirectory
------------------
Load all *.pt files in a directory as pages. Useful when you want to
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.
Loads all files with .pt extension in a directory as pages.
registerClass
-------------
Registers Five content with Zope 2.
localsite
---------
Turns a class into an implementation of ``IPossibleSite`` so that its
instances can be serve as local sites. Unless otherwise specified, a
default implementation's methods will be used to make the class comply
with the ``IPossibleSite`` interface.
......@@ -8,7 +8,7 @@ and some limitations.
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
the foundation of schemas.
......@@ -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
protect directly exposed methods on content classes (the python
scripts and the ZPublisher).
Local Sites
===========
Five supports the concept of a local sites and local site managers.
See localsite.txt_ for more information.
.. _localsite.txt: localsite.html
Object events
=============
Five supports sending Zope 3 object events when objects are added,
moved, renamed, copied and deleted. The use of ``manage_afterAdd`` & co
methods is deprecated.
===============================
Porting Five to Zope 3.1+ notes
===============================
Introduction
------------
Five needs to work in Zope 2.9. Zope 2.9 will ship with Zope 3.2. This
means Five will need to work with Zope 3.2. Since Zope 3.2 doesn't
truly exist yet we'll target Zope 3.1 for now.
A Five Roadmap
--------------
Here is a tentative Five roadmap:
Five 1.1 is to be released shortly, and its main feature is a
refactored directory structure and Zope 3 i18n for Zope 2. It's still
targeting the Zope X3.0 that's in Zope 2.8.
Five 1.2 is still targetting Zope 2.8, and its main expected feature
is support for local utilities.
Five 1.3 is targetting Zope 2.9 and thus Zope 3.2. We're talking about
this release of Five in this document.
Main problem
------------
Zope 3.1 has internal changes that Five needs to support. Five works
by reimplementing ZCML statements it supplies in the context of Zope
2. This reimplementation is hard to maintain, as for each Zope 3
upgrade we need to review all these ZCML statements and port them into
Five again.
The straightforward way to start supporting Zope 3.1+ with Five would
be to review all the ZCML statements in Five and update them to work
with Zope 3.1+.
A more ambitious but nicer solution would be if we could reuse the
Zope 3 ZCML statements directly. If we could accomplish this,
maintainability of Five would be improved by a lot. Far less review of
Five would be necessary for each Zope 3 upgrade. In the rest of this
document we'll be discussing this scenario.
Reasons for Five's modified ZCML statements
-------------------------------------------
Five ships with modified implementations of Zope 3 ZCML statements for
a number of reasons:
* could not use new-style classes that are in Zope 3 due to
ExtensionClass.
* Five views need to work with the Zope 2 publisher, and this expects
different things than the Zope 3 publisher.
* cannot use the Zope 3 security system, while the Zope 3 ZCML calls
into this to configure it.
* Five views need to work with the Zope 2 security system. This means
Five needs to issue Zope 2 style security declarations for views.
We'll go into more detail about each of these points below.
New-style ExtensionClass
========================
Five needed to be compatible with Zope 2.7, which uses old-style
ExtensionClass. This made life difficult for Five, as Zope 3 uses
new-style Python classes in many places. It's not easy to mix the two.
Zope 2.8 changed to allow new-style ExtensionClasses, which are
compatible with new-style Python classes. This means Five can
hopefully be simplified as we can forget about old-style
ExtensionClasses.
Five views need to work with the Zope 2 publisher
=================================================
The Zope 2 publisher expects something quite different than the Zope 3
publisher.
* does what is returned to the publisher need to inherit from
Acquisition.Explicit? (security reasons?)
* we may need something that calls the right methods on the Zope 3
view (such as browserDefault, __call__ and publishTraverse)
Cannot use the Zope 3 security system
=====================================
Do the Zope 3 security calls get in the way? Five currently removes
these calls, but perhaps doing the calls does not harm.
If they do interface, we could perhaps still trick things into
working harmlessly.
Five must issue Zope 2 security declarations for views
======================================================
This cannot be done by the ZCML implementation of Zope 3. We could
hopefully do this by following the following pattern::
def our_directive_implementation(...):
original_directive_implementation(...)
do_the_zope2_work(...)
Local sites in Five
===================
Intro
-----
Zope 3 has a concept of local sites and site managers. They allow one
to locally override component registrations and have components and
their configuration be persisted in the ZODB as well as managed
through the web interface.
By default, Zope 3 has a global site which is configured through ZCML.
It provides the fallback for all component look-up. Local sites are
typically set during traversal, when the traverser encounters an
``ISite`` object. The last encountered ``ISite`` wins. Component
look-up will cascade through all the sites in the hierarchy and fall
back to the global site where it can finally fail.
Five also supports local sites, however by default only local
utilities. Local adapters, such as ZODB-based views, could be
supported with a custom implementation of the local site manager and
local adapter registry. This is not the focus of local sites in Five,
though.
Turning possible sites into sites
---------------------------------
Five uses the same technique as Zope 3 for determining local sites:
sites are found during URL traversal. However, since the Zope 2
ZPublisher doesn't emit the necessary events by default, Five needs to
set a ``BeforeTraverse`` hook on site objects.
Setting this hook needs to be done an object-per-object basis and can
be performed through the ``manage_site.html`` browser page. This view
operates on ``IPossibleSite`` objects (in other words, objects that
*can* be sites but aren't yet). It sets the traversal hook on the
object and then marks it with the ``ISite`` interface to indicate that
it is a real site now, not just a possible site.
Note that unlike the Zope 3 equivalent of this view, it does not set
the site manager to site; it is assumed that the site already knows
how to get its site manager.
Also note that in order for the view to work, the object's class needs
to be Five-traversable, e.g. with the following ZCML statement:
<five:traversable class=".module.MyClass" />
Custom site implementations
---------------------------
Anything can be a site, there are no restrictions (sites don't have to
be folders, for examples). Sites can also be nested. For all the
Component Architecture cares, every object in your URL graph could be
a site.
The only requirement are two interfaces:
``IPossibleSite``
Objects that can potentially be turned into a site need to provide
this interface. That requires them to have a ``setSiteManager()``
and ``getSiteManager()`` method for setting and getting the local
site manager of that site. The site manager is the registry that
takes care of local component look-up.
``IFiveSiteManager``
This interface is a slight extension of the ``IServiceService`` or
``ISiteManager`` interface, respectively (the former in Zope X3
3.0, the latter in later versions). It defines the API of a local
site manager that is to be used in a Five environment. The site's
``getSiteManager()`` method should return an object providing this
interface.
Five's default site manager
----------------------------
If you want to instantly make your custom class an ``IPossibleSite``
implementation, you can use a default mix-in class from Five, e.g.::
class MySite(OFS.Folder, Products.Five.site.localsite.FiveSite):
pass
This default implementation of ``IPossibleSite`` features a site
manager implementation that knows how to register and look-up local
utilities. It does so by adapting the site to
``IFiveUtilityRegistry``.
The default adapter for this local utility registry simply stores the
utilities in a standard OFS Folder on called ``utilities`` on the site
object. You probably want to exchange that simple behaviour with
something that works better in your application. You can do so by
plugging in your own utility registry adapter, e.g.::
<adapter for=".interfaces.IMySite"
provides="Products.Five.site.interfaces.IFiveUtilityRegistry"
fatory=".module.MyUtilityRegistry" />
All this implementation needs to do is comply with the
``IFiveUtilityRegistry`` interface, which essentially means the
standard utility look-up methods like ``queryUtility()``,
``getUtilitiesFor()``, etc.
Turning existing classes into possible sites
--------------------------------------------
If you cannot or do not want to modify existing classes to mix in the
``FiveSite`` class, you can also use a structured monkey patch via
ZCML::
<five:localsite class=".module.MyClass" />
This makes ``MyClass`` an ``IPossibleSite`` and sticks ``FiveSite``'s
``getSiteManager()`` and ``setSiteManager()`` methods on the class as
well. You can also tell it to use a different site implementation's
methods for the monkey patch::
<five:localsite class=".module.MyClass"
site_class=".module.MySiteImpl" />
Just make sure that this class implements ``IPossibleSite``.
......@@ -5,23 +5,19 @@ What is Five?
-------------
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
following Zope 3 technologies in Zope 2:
technologies into Zope 2, today. Among others, it allows you to use
Zope 3 interfaces, ZCML-based configuration, adapters, browser pages
(including skins, layers, and resources), automated add and edit forms
based on schemas, object events, as well as Zope 3-style i18n message
catalogs.
* 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
* pages (views), including skins and layers, and edit and add forms
* 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.
Five 1.0 and 1.1 work on a straight Zope 2.7 installation, as long as
Zope 3 has been installed. Five 1.2 requires Zope 2.8 which already
ships with Zope 3, Five 1.3 is included in Zope 2.9.
We're in the process of evaluating lots more Zope 3 technologies for
integration into Zope 2. This is the right moment for interested Zope
......@@ -32,6 +28,19 @@ system for us all.
Download
--------
2005-11-02 -- We have released Five 1.2b and 1.3b! Download Five 1.2b
here:
http://codespeak.net/z3/five/release/Five-1.2b.tgz
2005-10-04 -- We have released Five 1.1! Download it here:
http://codespeak.net/z3/five/release/Five-1.1.tgz
2005-07-13 -- We have released Five 1.1b! Download it here:
http://codespeak.net/z3/five/release/Five-1.1b.tgz
2005-07-12 -- We have released Five 1.0.2! This is also the version
that will be included in Zope 2.8.1. Download it here:
......
##############################################################################
#
# 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),
)
# make this directory a package
<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
from democontent import DemoContent
class Overview:
"""View for overview.
"""
class Overview(BrowserView):
def reversedIds(self):
result = []
for id in self.context.objectIds():
......@@ -27,9 +19,28 @@ class Overview(BrowserView):
def directlyPublished(self):
return "This is directly published"
class NewExample(BrowserView):
class NewExample:
"""View for new example.
"""
def helpsWithOne(self):
return random.randrange(10)
def two(self):
return "Two got called"
class DemoContentAddView:
"""Add view for demo content.
"""
def __call__(self, add_input_name='', title='', submit_add=''):
if submit_add:
obj = DemoContent(add_input_name, title)
self.context.add(obj)
self.request.response.redirect(self.context.nextURL())
return ''
return self.index()
......@@ -3,27 +3,29 @@
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<five:traversable
class="OFS.Folder.Folder"
/>
<five:traversable class="OFS.Application.Application"/>
<!-- OFS.Folder.Folder views -->
<five:traversable class="OFS.Folder.Folder"/>
<browser:page
for="Products.Five.interfaces.IFolder"
for="OFS.interfaces.IFolder"
name="overview.html"
template="overview.pt"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.interfaces.IFolder"
for="OFS.interfaces.IFolder"
name="overview2.html"
template="overview2.pt"
permission="zope2.ViewManagementScreens"
class=".browser.Overview"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.interfaces.IFolder"
for="OFS.interfaces.IFolder"
name="test.html"
class=".browser.Overview"
attribute="directlyPublished"
......@@ -31,7 +33,7 @@
/>
<browser:pages
for="Products.Five.interfaces.IFolder"
for="OFS.interfaces.IFolder"
class=".browser.NewExample"
permission="zope2.ViewManagementScreens"
>
......@@ -45,7 +47,30 @@
/>
</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
for=".democontent.IDemoContent"
......@@ -54,10 +79,11 @@
permission="zope2.ViewManagementScreens"
/>
<five:defaultViewable class=".democontent.DemoContent" />
<five:defaultViewable class=".democontent.DemoContent"/>
<browser:defaultView
for=".democontent.IDemoContent"
name="someview.html" />
name="someview.html"
/>
</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 OFS.SimpleItem import SimpleItem
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
class IDemoContent(Interface):
def mymethod():
"Return some text"
"""Return some text.
"""
class DemoContent(SimpleItem):
implements(IDemoContent)
meta_type = 'Five Demo Content'
implements(IDemoContent)
def __init__(self, id, title):
self.id = id
......@@ -30,15 +19,3 @@ class DemoContent(SimpleItem):
def mymethod(self):
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.
#
# This software is subject to the provisions of the Zope Public License,
......@@ -15,109 +15,38 @@
Use 'structured monkey patching' to enable zope.app.container event sending for
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
_monkied = []
import warnings
from OFS.subscribers import deprecatedManageAddDeleteClasses
# ObjectAddedEvent and ObjectRemovedEvent are different in Zope 2
class ObjectAddedEvent(ObjectMovedEvent):
implements(IObjectAddedEvent)
def setContainerEvents():
warnings.warn("Using <five:containerEvents/> is deprecated (it is now "
"the default), it will be removed in Zope 2.11",
DeprecationWarning)
def __init__(self, object, newParent=None, newName=None):
if newParent is None:
newParent = object.aq_inner.aq_parent
if newName is None:
newName = object.id
ObjectMovedEvent.__init__(self, object, None, None, newParent, newName)
def setDeprecatedManageAddDelete(class_):
"""Instances of the class will still see their old methods called."""
deprecatedManageAddDeleteClasses.append(class_)
class ObjectRemovedEvent(ObjectMovedEvent):
implements(IObjectRemovedEvent)
def __init__(self, object, oldParent=None, oldName=None):
if oldParent is None:
oldParent = object.aq_inner.aq_parent
if oldName is None:
oldName = object.id
ObjectMovedEvent.__init__(self, object, oldParent, oldName, None, None)
def manage_afterAdd(self, item, container):
original_location_path = getattr(self, '__five_location_path__', None)
self.__five_location_path__ = self.getPhysicalPath()
# if there still is an object in the original location, we're copied
# we cannot rely on manage_afterClone, as this gets triggered only
# *after* a manage_afterAdd. This logic might fail in the case where
# something *is* somehow left in the original location that can
# be traversed to.
is_copied = original_location_path and (self.unrestrictedTraverse(
original_location_path, None) is not None)
if is_copied:
notify(ObjectCopiedEvent(self))
if original_location_path is None or is_copied:
notify(ObjectAddedEvent(self))
else:
original_location = self.unrestrictedTraverse(
original_location_path[:-1])
notify(ObjectMovedEvent(self,
original_location, original_location_path[-1],
container, self.id))
# call original
method = getattr(self, '__five_original_manage_afterAdd', None)
if method is not None:
self.__five_original_manage_afterAdd(item, container)
manage_afterAdd.__five_method__ = True
def manage_beforeDelete(self, item, container):
notify(ObjectRemovedEvent(self))
# call original
method = getattr(self, '__five_original_manage_beforeDelete', None)
if method is not None:
self.__five_original_manage_beforeDelete(item, container)
manage_beforeDelete.__five_method__ = True
def classSendEvents(class_):
"""Make instances of the class send Object*Event."""
# tuck away original methods if necessary
for name in ['manage_afterAdd', 'manage_beforeDelete']:
method = getattr(class_, name, None)
if not isFiveMethod(method):
# if we haven't alread overridden this, tuck away originals
setattr(class_, '__five_original_' + name, method)
def cleanUp():
deprecatedManageAddDeleteClasses[:] = []
class_.manage_afterAdd = manage_afterAdd
class_.manage_beforeDelete = manage_beforeDelete
# remember class for clean up
_monkied.append(class_)
def containerEvents(_context):
_context.action(
discriminator=None,
callable=setContainerEvents,
args=(),
)
def sendEvents(_context, class_):
def deprecatedManageAddDelete(_context, class_):
_context.action(
discriminator = ('five:sendEvents', class_),
callable = classSendEvents,
args=(class_,)
discriminator=('five:deprecatedManageAddDelete', class_),
callable=setDeprecatedManageAddDelete,
args=(class_,),
)
# clean up code
from Products.Five.fiveconfigure import killMonkey
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)
del addCleanUp
......@@ -22,12 +22,20 @@ import sys
import glob
import warnings
import App
import App.config
import Products
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.exceptions import ConfigurationError
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
from zope.app import zapi
from zope.app.component.interface import provideInterface
from zope.app.component.metaconfigure import adapter
from zope.app.security.interfaces import IPermission
from viewable import Viewable
from traversable import Traversable
......@@ -55,7 +63,7 @@ def handleBrokenProduct(product):
# in the control panel. However, all attempts to do so has failed from my
# side. //regebro
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):
products = findProducts()
......@@ -177,20 +185,6 @@ def defaultViewable(_context, 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):
# Map a Zope 2 interface into a Zope3 interface, seated within 'package'
# as 'name'.
......@@ -215,7 +209,7 @@ def bridge(_context, zope2, package, name=None):
)
def pagesFromDirectory(_context, directory, module, for_=None,
layer='default', permission='zope.Public'):
layer=IDefaultBrowserLayer, permission='zope.Public'):
if isinstance(module, basestring):
module = _context.resolve(module)
......@@ -233,6 +227,41 @@ def pagesFromDirectory(_context, directory, module, for_=None,
page(_context, name=name, permission=permission,
layer=layer, for_=for_, template=fname)
_register_monkies = []
_meta_type_regs = []
def _registerClass(class_, meta_type, permission, addview, icon, global_):
setattr(class_, 'meta_type', meta_type)
permission_obj = zapi.getUtility(IPermission, permission)
if icon:
setattr(class_, 'icon', '++resource++%s' % icon)
interfaces = tuple(implementedBy(class_))
info = {'name': meta_type,
'action': addview and ('+/%s' % addview) or '',
'product': 'Five',
'permission': str(permission_obj.title),
'visibility': global_ and 'Global' or None,
'interfaces': interfaces,
'instance': class_,
'container_filter': None}
Products.meta_types += (info,)
_register_monkies.append(class_)
_meta_type_regs.append(meta_type)
def registerClass(_context, class_, meta_type, permission, addview=None,
icon=None, global_=True):
_context.action(
discriminator = ('registerClass', meta_type),
callable = _registerClass,
args = (class_, meta_type, permission, addview, icon, global_)
)
# clean up code
def killMonkey(class_, name, fallback, attr=None):
......@@ -265,11 +294,33 @@ def undefaultViewable(class_):
killMonkey(class_, '__browser_default__', '__fallback_default__',
'__five_viewable__')
def unregisterClass(class_):
delattr(class_, 'meta_type')
try:
delattr(class_, 'icon')
except AttributeError:
pass
def cleanUp():
global _traversable_monkies
for class_ in _traversable_monkies:
untraversable(class_)
_traversable_monkies = []
global _defaultviewable_monkies
for class_ in _defaultviewable_monkies:
undefaultViewable(class_)
_defaultviewable_monkies = []
global _register_monkies
for class_ in _register_monkies:
unregisterClass(class_)
_register_monkies = []
global _meta_type_regs
Products.meta_types = tuple([ info for info in Products.meta_types
if info['name'] not in _meta_type_regs ])
_meta_type_regs = []
from zope.testing.cleanup import addCleanUp
addCleanUp(cleanUp)
......
......@@ -17,7 +17,10 @@ $Id: fivedirectives.py 12884 2005-05-30 13:10:41Z philikon $
"""
from zope.interface import Interface
from zope.app.publisher.browser.metadirectives import IBasicResourceInformation
from zope.app.security.fields import Permission
from zope.configuration.fields import GlobalObject, Tokens, PythonIdentifier
from zope.configuration.fields import Bool
from zope.schema import ASCII
from zope.schema import TextLine
class IImplementsDirective(Interface):
......@@ -56,7 +59,7 @@ class IDefaultViewableDirective(Interface):
required=True
)
class ISendEventsDirective(Interface):
class ISizableDirective(Interface):
"""Make instances of class send events.
"""
......@@ -65,6 +68,19 @@ class ISendEventsDirective(Interface):
required=True
)
class IContainerEventsDirective(Interface):
"""Global switch to enable container events
"""
class IDeprecatedManageAddDeleteDirective(Interface):
"""Call manage_afterAdd & co for these contained content classes.
"""
class_ = GlobalObject(
title=u"Class",
required=True,
)
class IBridgeDirective(Interface):
"""Bridge from a Zope 2 interface to an equivalent Zope3 interface.
"""
......@@ -104,3 +120,51 @@ class IPagesFromDirectoryDirective(IBasicResourceInformation):
description=u"The directory containing the resource data.",
required=True
)
class IRegisterClassDirective(Interface):
"""registerClass directive schema.
Register Five content with Zope 2.
"""
class_ = GlobalObject(
title=u'Instance Class',
description=u'Dotted name of the class that is registered.',
required=True
)
meta_type = ASCII(
title=u'Meta Type',
description=u'A human readable unique identifier for the class.',
required=True
)
permission = Permission(
title=u'Add Permission',
description=u'The permission for adding objects of this class.',
required=True
)
addview = ASCII(
title=u'Add View ID',
description=u'The ID of the add view used in the ZMI. Consider this '
u'required unless you know exactly what you do.',
default=None,
required=False
)
icon = ASCII(
title=u'Icon ID',
description=u'The ID of the icon used in the ZMI.',
default=None,
required=False
)
global_ = Bool(
title=u'Global scope?',
description=u'If "global" is False the class is only available in '
u'containers that explicitly allow one of its interfaces.',
default=True,
required=False
)
......@@ -33,7 +33,7 @@ from zope.app.form.interfaces import WidgetsError, MissingInputError
from zope.app.form.utility import setUpWidgets, getWidgetsData
from zope.app.form.interfaces import IInputWidget, WidgetsError
from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.app.i18n import ZopeMessageIDFactory as _
from zope.app.i18n import ZopeMessageFactory as _
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
......@@ -144,12 +144,13 @@ class EditView(BrowserView):
if changed:
self.changed()
# XXX: Needs locale support:
# formatter = self.request.locale.dates.getFormatter(
#formatter = self.request.locale.dates.getFormatter(
# 'dateTime', 'medium')
status = _("Updated on ${date_time}")
# status.mapping = {'date_time': formatter.format(
# datetime.utcnow())}
status.mapping = {'date_time': str(datetime.utcnow())}
#status = _("Updated on ${date_time}",
# mapping={'date_time':
# formatter.format(datetime.utcnow())})
status = _("Updated on ${date_time}",
mapping={'date_time': str(datetime.utcnow())})
self.update_status = status
return status
......@@ -249,9 +250,3 @@ class AddView(EditView):
def nextURL(self):
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 @@
##############################################################################
"""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
from Globals import InitializeClass as initializeClass
from zope.component import getGlobalService
from zope.component.servicenames import Presentation
from zope.interface import Interface
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.container.interfaces import IAdding
from zope.app.i18n import ZopeMessageFactory as _
from Products.Five.form import EditView, AddView
from Products.Five.metaclass import makeClass
from Products.Five.security import protectClass, initializeClass
from Products.Five.security import protectClass
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Products.Five.browser.metaconfigure import makeClassForTemplate
def EditViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_, fields,
fulledit_path=None, fulledit_label=None, menu=u''):
s = getGlobalService(Presentation)
class_ = makeClassForTemplate(template, globals(), used_for=schema,
bases=bases)
class_.schema = schema
......@@ -49,8 +50,15 @@ def EditViewFactory(name, schema, label, permission, layer,
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)
initializeClass(class_)
......@@ -58,20 +66,22 @@ class FiveFormDirective(BaseFormDirective):
def _processWidgets(self):
if self._widgets:
customWidgetsObject = makeClass('CustomWidgetsMixin', (ExtensionClass.Base,), self._widgets)
customWidgetsObject = makeClass(
'CustomWidgetsMixin', (ExtensionClass.Base,), self._widgets)
self.bases = self.bases + (customWidgetsObject,)
class EditFormDirective(FiveFormDirective):
view = EditView
default_template = 'edit.pt'
title = 'Edit'
title = _('Edit')
def _handle_menu(self):
if self.menu:
menuItemDirective(
self._context, self.menu, self.for_ or self.schema,
'@@' + self.name, self.title, permission=self.permission)
'@@' + self.name, self.title, permission=self.permission,
layer=self.layer)
def __call__(self):
self._processWidgets()
......@@ -89,8 +99,6 @@ def AddViewFactory(name, schema, label, permission, layer,
fields, content_factory, arguments,
keyword_arguments, set_before_add, set_after_add,
menu=u''):
s = getGlobalService(Presentation)
class_ = makeClassForTemplate(template, globals(), used_for=schema,
bases=bases)
......@@ -105,7 +113,15 @@ def AddViewFactory(name, schema, label, permission, layer,
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)
initializeClass(class_)
......@@ -132,7 +148,7 @@ class AddFormDirective(FiveFormDirective):
# for=self.schema.
menuItemDirective(
self._context, self.menu, self.for_, '@@' + self.name,
self.title, permission=self.permission,
self.title, permission=self.permission, layer=self.layer,
description=self.description)
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 @@
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IObject"
provides="zope.app.form.interfaces.IInputWidget"
factory="zope.app.form.browser.objectwidget.ObjectWidget"
factory="Products.Five.form.objectwidget.ObjectWidget"
permission="zope.Public"
/>
......
......@@ -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
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')
......@@ -47,9 +47,10 @@ which incorrectly ignored its 'handle_errors' argument):
>>> print http(r"""
... GET /test_folder_1_/ftf/+/protectedaddform.html HTTP/1.1
... """, handle_errors=True)
HTTP/1.1 401 Unauthorized
... """, handle_errors=False)
Traceback (most recent call last):
...
Unauthorized: ...
Now let's add a piece of our sample content object to test more things
on it:
......@@ -372,7 +373,11 @@ element to the list:
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somelist.add"
...
... Add
... Add Some item
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somelist.count"
...
... 0
... -----------------------------968064918930967154199105236--
... """ % (wo_hen_hao, ni_hao), handle_errors=False)
HTTP/1.1 200 OK
......@@ -408,6 +413,10 @@ Now, let's enter some more Chinese:
...
... %s
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="field.somelist.count"
...
... 1
... -----------------------------968064918930967154199105236
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Change
......@@ -572,5 +581,5 @@ Clean up
Finally, we need to clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
......@@ -18,13 +18,13 @@ $Id: schemacontent.py 15514 2005-08-02 16:37:34Z yuppie $
from OFS.SimpleItem import SimpleItem
from Globals import InitializeClass
from zope.i18nmessageid import MessageIDFactory
from zope.i18nmessageid import MessageFactory
from zope.interface import implements, Interface
from zope.schema import TextLine, Text, Object, Int, List
from zope.app.form import CustomWidgetFactory
from zope.app.form.browser import ObjectWidget
from Products.Five.form.objectwidget import ObjectWidget
_ = MessageIDFactory('formtest')
_ = MessageFactory('formtest')
class IFieldContent(Interface):
......
......@@ -46,25 +46,26 @@ def test_get_widgets_for_schema_fields():
>>> from zope.app.form.browser.textwidgets import TextWidget
>>> from zope.app.form.browser.itemswidgets import DropdownWidget
>>> view1 = zapi.getViewProviding(contactname, IInputWidget, request)
>>> view1 = zapi.getMultiAdapter((contactname, request), IInputWidget)
>>> view1.__class__ == TextWidget
True
>>> view2 = zapi.getViewProviding(salutation, IInputWidget, request)
>>> view2 = zapi.getMultiAdapter((salutation, request), IInputWidget)
>>> view2.__class__ == DropdownWidget
True
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
import unittest
from Testing.ZopeTestCase import ZopeDocTestSuite, FunctionalDocFileSuite
from zope.testing.doctest import DocTestSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
return unittest.TestSuite((
ZopeDocTestSuite(),
DocTestSuite(),
FunctionalDocFileSuite('forms.txt',
package="Products.Five.form.tests",),
))
......
......@@ -13,15 +13,21 @@
##############################################################################
"""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.i18n import interpolate
from zope.i18n.interfaces import ITranslationDomain, IUserPreferredLanguages
from zope.i18nmessageid import MessageID
from zope.app import zapi
from zope.publisher.browser import BrowserLanguages
# BBB 2005/10/10 -- MessageIDs are to be removed for Zope 3.3
import zope.deprecation
zope.deprecation.__show__.off()
from zope.i18nmessageid import MessageID, Message
zope.deprecation.__show__.on()
class FiveTranslationService:
"""Translation service that delegates to ``zope.i18n`` machinery.
"""
......@@ -29,7 +35,7 @@ class FiveTranslationService:
# regarding fallback and Zope 2 compatability
def translate(self, domain, msgid, mapping=None,
context=None, target_language=None, default=None):
if isinstance(msgid, MessageID):
if isinstance(msgid, (Message, MessageID)):
domain = msgid.domain
default = msgid.default
mapping = msgid.mapping
......@@ -46,7 +52,7 @@ class FiveTranslationService:
# in Zope3, context is adapted to IUserPreferredLanguages,
# which means context should be the request in this case.
if context is not None:
context = context.REQUEST
context = aq_acquire(context, 'REQUEST', None)
return util.translate(msgid, mapping=mapping, context=context,
target_language=target_language, default=default)
......
......@@ -13,7 +13,7 @@
##############################################################################
"""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.interfaces import IInterface
......@@ -33,23 +33,3 @@ class IMenuItemType(IInterface):
Menu item types are interfaces that define classes of
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 @@
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta">
<include package=".site" file="meta.zcml" />
<include package=".browser" file="meta.zcml" />
<include package=".form" file="meta.zcml" />
<!-- 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:directive
......@@ -53,18 +50,6 @@
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
name="content"
schema="zope.app.component.metadirectives.IClassDirective"
......@@ -131,14 +116,20 @@
/>
<meta:directive
name="sendEvents"
schema=".fivedirectives.ISendEventsDirective"
handler=".eventconfigure.sendEvents"
name="containerEvents"
schema=".fivedirectives.IContainerEventsDirective"
handler=".eventconfigure.containerEvents"
/>
<meta:directive
name="deprecatedManageAddDelete"
schema=".fivedirectives.IDeprecatedManageAddDeleteDirective"
handler=".eventconfigure.deprecatedManageAddDelete"
/>
<meta:directive
name="sizable"
schema=".fivedirectives.ISendEventsDirective"
schema=".fivedirectives.ISizableDirective"
handler=".sizeconfigure.sizable"
/>
......@@ -148,20 +139,18 @@
handler=".fiveconfigure.pagesFromDirectory"
/>
<!-- viewable is deprecated, use traversable instead -->
<meta:directive
name="viewable"
schema=".fivedirectives.ITraversableDirective"
handler=".fiveconfigure.viewable"
/>
<meta:directive
name="bridge"
schema=".fivedirectives.IBridgeDirective"
handler=".fiveconfigure.bridge"
/>
<meta:directive
name="registerClass"
schema=".fivedirectives.IRegisterClassDirective"
handler=".fiveconfigure.registerClass"
/>
</meta:directives>
<meta:directive
......@@ -171,6 +160,9 @@
handler="zope.app.security.metaconfigure.redefinePermission"
/>
<!-- load the zope:modulealias directive -->
<include package="zope.modulealias" file="meta.zcml" />
<!-- load the i18n:registerTranslations directive -->
<include package="zope.app.i18n" file="meta.zcml" />
......
......@@ -13,73 +13,25 @@
##############################################################################
"""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.configuration.exceptions import ConfigurationError
from zope.app.component.contentdirective import ContentDirective as \
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):
"Set a permission on a particular name."
self.__context.action(
discriminator = ('five:protectName', self.__class, name),
callable = protectName,
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):
"Handle empty/simple declaration."
"""Handle empty/simple declaration."""
return self.__context.action(
discriminator = ('five:initialize:class', self.__class),
callable = initializeClass,
......
<configure xmlns="http://namespaces.zope.org/zope"
i18n_domain="Five">
<permission
id="five.ManageSite"
title="Manage Five local sites"
/>
<!-- Give common Zope2 and CMF permissions a permission ID
The title of the permission is what Zope 2 knows it under -->
......
......@@ -24,7 +24,7 @@ from zope.app.security.interfaces import IPermission
from zope.app import zapi
from AccessControl import ClassSecurityInfo, getSecurityManager
from Globals import InitializeClass
from Globals import InitializeClass as initializeClass
from types import StringTypes
CheckerPublicId = 'zope.Public'
......@@ -99,9 +99,6 @@ def newInteraction():
if getattr(thread_local, 'interaction', None) is None:
thread_local.interaction = FiveSecurityPolicy()
def initializeClass(klass):
InitializeClass(klass)
def _getSecurity(klass):
# a Zope 2 class can contain some attribute that is an instance
# of ClassSecurityInfo. Zope 2 scans through things looking for
......@@ -121,15 +118,12 @@ def protectName(klass, name, permission_id):
"""Protect the attribute 'name' on 'klass' using the given
permission"""
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
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)
elif permission_id == CheckerPrivateId:
security.declarePrivate(name)
......@@ -142,7 +136,10 @@ def protectName(klass, name, permission_id):
def protectClass(klass, permission_id):
"""Protect the whole class with the given permission"""
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()
elif permission_id == CheckerPrivateId:
security.declareObjectPrivate()
......
<configure xmlns="http://namespaces.zope.org/zope">
<serviceType
id="Utilities"
interface="zope.component.interfaces.IUtilityService" />
<service
serviceType="Utilities"
factory="zope.component.utility.GlobalUtilityService" />
<serviceType
id="Adapters"
interface="zope.component.interfaces.IAdapterService" />
<service
serviceType="Adapters"
factory="zope.component.adapter.GlobalAdapterService" />
<serviceType
id="Presentation"
interface="zope.component.interfaces.IPresentationService" />
<service
serviceType="Presentation"
factory="zope.component.presentation.GlobalPresentationService" />
</configure>
# make this directory a package
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Local sites browser views
$Id$
"""
from zope.app.component.interfaces import ISite
from zope.app.component.hooks import clearSite
from Products.Five.browser import BrowserView
from Products.Five.site.localsite import enableLocalSiteHook, \
disableLocalSiteHook
class LocalSiteView(BrowserView):
"""View for convering a possible site to a site
"""
def update(self):
form = self.request.form
if form.has_key('UPDATE_MAKESITE'):
self.makeSite()
elif form.has_key('UPDATE_UNMAKESITE'):
self.unmakeSite()
def isSite(self):
return ISite.providedBy(self.context)
def makeSite(self):
"""Convert a possible site to a site"""
if self.isSite():
raise ValueError('This is already a site')
enableLocalSiteHook(self.context)
return "This object is now a site"
def unmakeSite(self):
"""Convert a site to a possible site"""
if not self.isSite():
raise ValueError('This is not a site')
disableLocalSiteHook(self.context)
# disableLocalSiteHook circumcised our context so that it's
# not an ISite anymore. That can mean that certain things for
# it can't be found anymore. So, for the rest of this request
# (which will be over in about 20 CPU cycles), already clear
# the local site from the thread local.
clearSite()
return "This object is no longer a site"
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<adapter
for="*"
provides="zope.component.interfaces.ISiteManager"
factory=".localsite.siteManagerAdapter"
/>
<adapter
for="zope.app.site.interfaces.ISite"
provides=".interfaces.IFiveUtilityRegistry"
factory=".utility.SimpleLocalUtilityRegistry"
/>
<subscriber
for="zope.app.component.interfaces.ISite
zope.app.publication.interfaces.IBeforeTraverseEvent"
handler="zope.app.component.site.threadSiteSubscriber"
/>
<subscriber
for="zope.app.publication.interfaces.IEndRequestEvent"
handler="zope.app.component.site.clearThreadSiteSubscriber"
/>
<browser:page
for="zope.app.component.interfaces.IPossibleSite"
name="manage_site.html"
permission="five.ManageSite"
class=".browser.LocalSiteView"
template="managesite.pt"
/>
</configure>
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Five interfaces
$Id: interfaces.py 18584 2005-10-14 17:13:27Z regebro $
"""
from zope.interface import Interface, Attribute
from zope.component.interfaces import ISiteManager
class IRegisterUtilitySimply(Interface):
"""Register utilities simply
Allow local registrations of utilities, in a much simpler
manner than Zope 3 does it currently.
Note: The name of this interface is expressed as a verb
(describing the action it expresses, namely registering
utilities). The reason for that is that the names *utility
registry* (successor of the Zope 3 utility service) and *utility
registration* (object in a registration stack, part of the
complicated registration framework in Zope 3) have different
connotations in Zope 3 than we want to express here.
"""
def registerUtility(self, interface, utility, name=''):
"""Registers a utility in the local context"""
# TODO Define an exception than is to be thrown when a local
# utility of that interface and name is already registered.
next = Attribute("The next local registry in the tree. This attribute "
"represents the parent of this registry node. If the "
"value is ``None``, then this registry represents the "
"root of the tree")
class IFiveUtilityRegistry(IRegisterUtilitySimply):
"""Look up and register utilities"""
def getUtility(interface, name='', context=None):
"""Get the utility that provides interface
Returns the nearest utility to the context that implements the
specified interface. If one is not found, raises
ComponentLookupError.
"""
def queryUtility(interface, name='', default=None, context=None):
"""Look for the utility that provides interface
Returns the nearest utility to the context that implements
the specified interface. If one is not found, returns default.
"""
def getUtilitiesFor(interface, context=None):
"""Return the utilities that provide an interface
An iterable of utility name-value pairs is returned.
"""
def getAllUtilitiesRegisteredFor(interface, context=None):
"""Return all registered utilities for an interface
This includes overridden utilities.
An iterable of utility instances is returned. No names are
returned.
"""
class IFiveSiteManager(ISiteManager, IRegisterUtilitySimply):
"""Five site manager
For the sake of forward-portability, registering utilities can be
done directly on the site manager to cut out the middle man called
utility service (this corresponds to Zope 3.1's understanding of
site managers). An implementation of this interface will probably
delegate the work to an IFiveUtilityService component, though."""
# BBB 2005/11/01 -- gone in Five 1.5.
IFiveUtilityService = IFiveUtilityRegistry
import zope.deprecation
zope.deprecation.deprecated(
'IFiveUtilityService', "'IFiveUtilityService' has been renamed to "
"'IFiveUtilityRegistry' and will disappear in Five 1.5."
)
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Local sites
$Id$
"""
from zope.event import notify
from zope.interface import directlyProvides, directlyProvidedBy
from zope.interface import implements
from zope.component import getGlobalSiteManager
from zope.component.exceptions import ComponentLookupError
from zope.app.component.interfaces import ISite, IPossibleSite
from zope.app.publication.zopepublication import BeforeTraverseEvent
from ExtensionClass import Base
from Acquisition import aq_base, aq_inner, aq_parent
from Products.SiteAccess.AccessRule import AccessRule
from ZPublisher.BeforeTraverse import registerBeforeTraverse
from ZPublisher.BeforeTraverse import unregisterBeforeTraverse
from Products.Five.site.interfaces import IFiveSiteManager, IFiveUtilityRegistry
# Hook up custom component architecture calls
import zope.app.component.hooks
zope.app.component.hooks.setHooks()
def siteManagerAdapter(ob):
"""An adapter * -> ISiteManager.
This is registered in place of the one in Zope 3 so that we lookup
using acquisition instead of ILocation.
"""
current = ob
while True:
if ISite.providedBy(current):
return current.getSiteManager()
current = getattr(current, '__parent__', aq_parent(aq_inner(current)))
if current is None:
# It does not support acquisition or has no parent, so we
# return the global site
return getGlobalSiteManager()
HOOK_NAME = '__local_site_hook__'
class LocalSiteHook(Base):
def __call__(self, container, request):
notify(BeforeTraverseEvent(container, request))
def enableLocalSiteHook(obj):
"""Install __before_traverse__ hook for Local Site
"""
# We want the original object, not stuff in between, and no acquisition
obj = aq_base(obj)
if not IPossibleSite.providedBy(obj):
raise TypeError, 'Must provide IPossibleSite'
hook = AccessRule(HOOK_NAME)
registerBeforeTraverse(obj, hook, HOOK_NAME, 1)
if not hasattr(obj, HOOK_NAME):
setattr(obj, HOOK_NAME, LocalSiteHook())
directlyProvides(obj, ISite, directlyProvidedBy(obj))
def disableLocalSiteHook(obj):
"""Remove __before_traverse__ hook for Local Site
"""
# We want the original object, not stuff in between, and no acquisition
obj = aq_base(obj)
if not ISite.providedBy(obj):
raise TypeError, 'Must provide ISite'
unregisterBeforeTraverse(obj, HOOK_NAME)
if hasattr(obj, HOOK_NAME):
delattr(obj, HOOK_NAME)
directlyProvides(obj, directlyProvidedBy(obj) - ISite)
class FiveSiteManager(object):
implements(IFiveSiteManager)
def __init__(self, context):
# make {get|query}NextSiteManager() work without having to
# resort to Zope 2 acquisition
self.context = self.__parent__ = context
@property
def next(self):
obj = self.context
while obj is not None:
obj = aq_parent(aq_inner(obj))
if ISite.providedBy(obj):
return obj.getSiteManager()
# In Zope 3.1+, returning None here is understood by
# getNextSiteManager as that our next site manager is the
# global one. If we returned the global one, it would be
# understood as a lookup error. Yeah, it's weird, tell me
# about it.
return None
@property
def adapters(self):
return getGlobalSiteManager().adapters #XXX wrong
@property
def utilities(self):
return IFiveUtilityRegistry(self.context)
def queryAdapter(self, object, interface, name, default=None):
return self.adapters.queryAdapter(object, interface, name, default)
def queryMultiAdapter(self, objects, interface, name, default=None):
return self.adapters.queryMultiAdapter(objects, interface, name, default)
def getAdapters(self, objects, provided):
return self.adapters.getAdapters(objects, provided)
def subscribers(self, required, provided):
return self.adapters.subscribers(required, provided)
def queryUtility(self, interface, name='', default=None):
return self.utilities.queryUtility(interface, name, default)
def getUtilitiesFor(self, interface):
return self.utilities.getUtilitiesFor(interface)
def getAllUtilitiesRegisteredFor(self, interface):
return self.utilities.getAllUtilitiesRegisteredFor(interface)
def registerUtility(self, interface, utility, name=''):
return self.utilities.registerUtility(interface, utility, name)
class FiveSite:
implements(IPossibleSite)
def getSiteManager(self):
return FiveSiteManager(self)
def setSiteManager(self, sm):
raise NotImplementedError('This class has a fixed site manager')
<tal:tag condition="view/update"/>
<html metal:use-macro="context/@@standard_macros/view"
i18n:domain="zope">
<body>
<div metal:fill-slot="body">
<form action="." tal:attributes="action request/URL" method="POST"
enctype="multipart/form-data">
<div class="row">
<div class="controls">
<input type="submit" value="Make site" name="UPDATE_MAKESITE"
i18n:attributes="value"
tal:attributes="disabled view/isSite"/>
<input type="submit" value="Unmake site" name="UPDATE_UNMAKESITE"
i18n:attributes="value"
tal:attributes="disabled not:view/isSite"/>
</div>
</div>
</form>
</div>
</body>
</html>
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta">
<meta:directives namespace="http://namespaces.zope.org/five">
<meta:directive
name="localsite"
schema=".metadirectives.ILocalSiteDirective"
handler=".metaconfigure.installSiteHook"
/>
</meta:directives>
</configure>
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Five-specific directive handlers
These directives are specific to Five and have no equivalents in Zope 3.
$Id: fiveconfigure.py 18581 2005-10-14 16:54:25Z regebro $
"""
from zope.interface import classImplements, classImplementsOnly, implementedBy
from zope.interface.interface import InterfaceClass
from zope.configuration.exceptions import ConfigurationError
from zope.app.component.metaconfigure import adapter
from zope.app.component.interfaces import IPossibleSite
from Products.Five.site.localsite import FiveSite
def classSiteHook(class_, site_class):
setattr(class_, 'getSiteManager',
site_class.getSiteManager.im_func)
setattr(class_, 'setSiteManager',
site_class.setSiteManager.im_func)
_localsite_monkies = []
def installSiteHook(_context, class_, site_class=None):
if site_class is None:
if not IPossibleSite.implementedBy(class_):
# This is not a possible site, we need to monkey-patch it so that
# it is.
site_class = FiveSite
else:
if not IPossibleSite.implementedBy(site_class):
raise ConfigurationError('Site class does not implement '
'IPossibleClass: %s' % site_class)
if site_class is not None:
_context.action(
discriminator = (class_,),
callable = classSiteHook,
args=(class_, site_class)
)
_context.action(
discriminator = (class_, IPossibleSite),
callable = classImplements,
args=(class_, IPossibleSite)
)
_localsite_monkies.append(class_)
# clean up code
def uninstallSiteHooks():
for class_ in _localsite_monkies:
delattr(class_, 'getSiteManager')
delattr(class_, 'setSiteManager')
classImplementsOnly(class_, implementedBy(class_)-IPossibleSite)
_localsite_monkies.remove(class_)
from zope.testing.cleanup import addCleanUp
addCleanUp(uninstallSiteHooks)
del addCleanUp
......@@ -11,30 +11,26 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test import conflicts
"""Site support ZCML directive schemas
$Id: test_import_conflicts.py 18150 2005-10-04 16:19:49Z philikon $
$Id: fivedirectives.py 18581 2005-10-14 16:54:25Z regebro $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
from zope.interface import Interface
from zope.configuration.fields import GlobalObject
def testImportConflicts():
"""
In a Five environment, importing Zope 3 packages that would use
interfaces from the Zope 3 transaction module would lead to an
error, because of the monkey patching. The zope.app.mail package
makes use of transaction interfaces, for example the following
class:
>>> from zope.app.mail.delivery import QueueProcessorThread
class ILocalSiteDirective(Interface):
"""Make instances of class hookable for Site.
Note that this only concerns Zope 2.7 and Zope X3 3.0.
site_class is an implementation of ISite, which will have it's methods
monkey_patched into the the class. If not given a default implementation
will be used.
"""
class_ = GlobalObject(
title=u"Class",
required=True
)
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
site_class = GlobalObject(
title=u"Site Class",
required=False
)
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Dummy test fixtures
$Id$
"""
from zope.interface import implements, Interface
from OFS.SimpleItem import SimpleItem
from Products.Five.tests.testing import FiveTraversableFolder
class IDummySite(Interface):
pass
class DummySite(FiveTraversableFolder):
"""A very dummy Site
"""
implements(IDummySite)
def manage_addDummySite(self, id, REQUEST=None):
"""Add the dummy site."""
id = self._setObject(id, DummySite(id))
return ''
class IDummyUtility(Interface):
pass
class ISuperDummyUtility(IDummyUtility):
pass
class DummyUtility(SimpleItem):
implements(IDummyUtility)
##############################################################################
#
# ZopeTestCase
#
# COPY THIS FILE TO YOUR 'tests' DIRECTORY.
#
# This version of framework.py will use the SOFTWARE_HOME
# environment variable to locate Zope and the Testing package.
#
# If the tests are run in an INSTANCE_HOME installation of Zope,
# Products.__path__ and sys.path with be adjusted to include the
# instance's Products and lib/python directories respectively.
#
# If you explicitly set INSTANCE_HOME prior to running the tests,
# auto-detection is disabled and the specified path will be used
# instead.
#
# If the 'tests' directory contains a custom_zodb.py file, INSTANCE_HOME
# will be adjusted to use it.
#
# If you set the ZEO_INSTANCE_HOME environment variable a ZEO setup
# is assumed, and you can attach to a running ZEO server (via the
# instance's custom_zodb.py).
#
##############################################################################
#
# The following code should be at the top of every test module:
#
# import os, sys
# if __name__ == '__main__':
# execfile(os.path.join(sys.path[0], 'framework.py'))
#
# ...and the following at the bottom:
#
# if __name__ == '__main__':
# framework()
#
##############################################################################
__version__ = '0.2.3'
# Save start state
#
__SOFTWARE_HOME = os.environ.get('SOFTWARE_HOME', '')
__INSTANCE_HOME = os.environ.get('INSTANCE_HOME', '')
if __SOFTWARE_HOME.endswith(os.sep):
__SOFTWARE_HOME = os.path.dirname(__SOFTWARE_HOME)
if __INSTANCE_HOME.endswith(os.sep):
__INSTANCE_HOME = os.path.dirname(__INSTANCE_HOME)
# Find and import the Testing package
#
if not sys.modules.has_key('Testing'):
p0 = sys.path[0]
if p0 and __name__ == '__main__':
os.chdir(p0)
p0 = ''
s = __SOFTWARE_HOME
p = d = s and s or os.getcwd()
while d:
if os.path.isdir(os.path.join(p, 'Testing')):
zope_home = os.path.dirname(os.path.dirname(p))
sys.path[:1] = [p0, p, zope_home]
break
p, d = s and ('','') or os.path.split(p)
else:
print 'Unable to locate Testing package.',
print 'You might need to set SOFTWARE_HOME.'
sys.exit(1)
import Testing, unittest
execfile(os.path.join(os.path.dirname(Testing.__file__), 'common.py'))
# Include ZopeTestCase support
#
if 1: # Create a new scope
p = os.path.join(os.path.dirname(Testing.__file__), 'ZopeTestCase')
if not os.path.isdir(p):
print 'Unable to locate ZopeTestCase package.',
print 'You might need to install ZopeTestCase.'
sys.exit(1)
ztc_common = 'ztc_common.py'
ztc_common_global = os.path.join(p, ztc_common)
f = 0
if os.path.exists(ztc_common_global):
execfile(ztc_common_global)
f = 1
if os.path.exists(ztc_common):
execfile(ztc_common)
f = 1
if not f:
print 'Unable to locate %s.' % ztc_common
sys.exit(1)
# Debug
#
print 'SOFTWARE_HOME: %s' % os.environ.get('SOFTWARE_HOME', 'Not set')
print 'INSTANCE_HOME: %s' % os.environ.get('INSTANCE_HOME', 'Not set')
sys.stdout.flush()
Functional test for local sites
===============================
Set up all of Five:
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
First we turn our DummySite class into a site in ZCML (and register
some views that will provide us with some test info),
>>> zcml_text = """
... <configure xmlns="http://namespaces.zope.org/zope"
... xmlns:meta="http://namespaces.zope.org/meta"
... xmlns:five="http://namespaces.zope.org/five"
... xmlns:browser="http://namespaces.zope.org/browser">
...
... <!-- make the zope2.Public permission work -->
... <meta:redefinePermission from="zope2.Public" to="zope.Public" />
...
... <five:localsite class="Products.Five.site.tests.dummy.DummySite" />
...
... <browser:page
... for="Products.Five.site.tests.dummy.IDummySite"
... name="checkSiteManager.html"
... class="Products.Five.site.tests.test_functional.CheckSiteManagerView"
... permission="zope2.Public"
... />
...
... <browser:page
... for="Products.Five.site.tests.dummy.IDummySite"
... name="lookupUtilities.html"
... class="Products.Five.site.tests.test_functional.LookupUtilitiesView"
... permission="zope2.Public"
... />
...
... </configure>"""
>>> zcml.load_string(zcml_text)
then we add an instance to our folder:
>>> from Products.Five.site.tests.dummy import manage_addDummySite
>>> nothing = manage_addDummySite(self.folder, 'site')
Now we check what the info view tells us about local component lookup:
>>> print http(r'''
... GET /test_folder_1_/site/@@checkSiteManager.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
{'IFiveUtilityRegistry.providedBy(utility_service)': False,
'isinstance(zapi.getSiteManager(), FiveSiteManager)': False,
'zapi.getSiteManager() is zapi.getGlobalSiteManager()': True}
We see that we have no local component lookup yet, because we haven't
set the site. Therefore, enable the traversal hook by using the view
that's provided for this task (we first need to create a manager
account in order to be able to access it):
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> print http(r'''
... POST /test_folder_1_/site/@@manage_site.html HTTP/1.1
... Authorization: Basic manager:r00t
... Content-Length: 25
...
... UPDATE_MAKESITE=Make site''')
HTTP/1.1 200 OK
...
Now we call the info view again and find that local component lookup
is working:
>>> print http(r'''
... GET /test_folder_1_/site/@@checkSiteManager.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
{'IFiveUtilityRegistry.providedBy(utility_service)': True,
'isinstance(zapi.getSiteManager(), FiveSiteManager)': True,
'zapi.getSiteManager() is zapi.getGlobalSiteManager()': False}
Of course, sites are only active *during* traversal; after traversal
they're gone:
>>> from zope.app.component.hooks import getSite
>>> getSite() is None
True
We can also register utilities now:
>>> from zope.app import zapi
>>> sm = self.folder.site.getSiteManager()
>>> from Products.Five.site.tests.dummy import IDummyUtility, DummyUtility
>>> dummy = DummyUtility()
>>> sm.registerUtility(IDummyUtility, dummy)
and find them being looked up just fine:
>>> print http(r'''
... GET /test_folder_1_/site/@@lookupUtilities.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
zapi.getUtility(IDummyUtility) == dummy: True
Of course, we can't look it up once the request has ended, because we
lose the local site setup:
>>> zapi.getUtility(IDummyUtility)
Traceback (most recent call last):
...
ComponentLookupError: (<InterfaceClass Products.Five.site.tests.dummy.IDummyUtility>, '')
At last we can "unmake" the site using the browser view provided by
Five:
>>> print http(r'''
... POST /test_folder_1_/site/@@manage_site.html HTTP/1.1
... Authorization: Basic manager:r00t
... Content-Length: 29
...
... UPDATE_UNMAKESITE=Unmake site''')
HTTP/1.1 200 OK
...
And everything is back to normal with respect to local component
lookup:
>>> print http(r'''
... GET /test_folder_1_/site/@@checkSiteManager.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
{'IFiveUtilityRegistry.providedBy(utility_service)': False,
'isinstance(zapi.getSiteManager(), FiveSiteManager)': False,
'zapi.getSiteManager() is zapi.getGlobalSiteManager()': True}
Finally, global services and the monkeys:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
Five Site Manager
=================
In this test we want to test Five's implementation of a site manager.
First, we need to set a few things up...
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("meta.zcml", Products.Five)
>>> zcml.load_config("permissions.zcml", Products.Five)
>>> zcml.load_config("configure.zcml", Products.Five.site)
>>> zcml_text = """\
... <five:localsite
... xmlns:five="http://namespaces.zope.org/five"
... class="Products.Five.site.tests.dummy.DummySite" />"""
>>> zcml.load_string(zcml_text)
...for example some sort of site object:
>>> from Products.Five.site.tests.dummy import manage_addDummySite
>>> nothing = manage_addDummySite(self.folder, 'dummysite')
>>> dummysite = self.folder.dummysite
Local vs. global sites
----------------------
Let's make the possible site a real site:
>>> from Products.Five.site.localsite import enableLocalSiteHook
>>> enableLocalSiteHook(dummysite)
and tell Zope 3 about it:
>>> from zope.app.component.hooks import setSite, setHooks
>>> setSite(dummysite)
Also hook up custom component architecture calls; we need to do this
here because zope.app.component.hooks registers a cleanup with the
testing cleanup framework, so the hooks get torn down by
placelesssetup each time.
>>> setHooks()
That seems to have worked (we test this by using the context property
of FiveSiteManager):
>>> from zope.app import zapi
>>> zapi.getSiteManager().context == dummysite
True
Since there's no other local site in between this one and the global
one, the next one should be the global one. FiveSiteManager indicates
that to us by return ``None``:
>>> from zope.app import zapi
>>> zapi.getSiteManager().next is None
True
To the the Zope 3 API, this means the next site manager should be the
global one:
>>> from zope.app.component import getNextSiteManager
>>> getNextSiteManager(dummysite.getSiteManager()) is zapi.getGlobalSiteManager()
True
ISiteManager API
----------------
Site managers are supposed to have an ``adapters`` and a ``utilities``
attribute. Five's site manager simply passes through the global
adapter registry:
>>> zapi.getSiteManager().adapters is zapi.getGlobalSiteManager().adapters
True
The utility registry, however, is an ``IFiveUtilityRegistry``:
>>> from Products.Five.site.interfaces import IFiveUtilityRegistry
>>> IFiveUtilityRegistry.providedBy(zapi.getSiteManager().utilities)
True
The methods on registering and looking up utilities are covered by the
utility tests in depth. The methods on adapter look up are indirectly
covered in the functional test; view look up, for example, is adapter
look up.
Nesting sites
-------------
Let's set up another site to test nested sites:
>>> nothing = manage_addDummySite(self.folder.dummysite, 'subsite')
>>> subsite = self.folder.dummysite.subsite
Now we set the current site to the ``subsite``:
>>> enableLocalSiteHook(subsite)
>>> setSite(subsite)
When we call getServices() now, we get the correct site manager:
>>> zapi.getSiteManager().context == subsite
True
The "next" site is the less local one:
>>> zapi.getSiteManager().next.context == dummysite
True
The Zope 3 API for this agrees with that:
>>> getNextSiteManager(subsite.getSiteManager()).context == dummysite
True
Finally, some clean up:
>>> tearDown()
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test local sites
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
import pprint
from zope.app import zapi
from Products.Five import BrowserView
from Products.Five.site.interfaces import IFiveUtilityRegistry
from Products.Five.site.localsite import FiveSiteManager
from Products.Five.site.tests.dummy import IDummyUtility
class CheckSiteManagerView(BrowserView):
def __call__(self):
sm = zapi.getSiteManager()
result = {
'zapi.getSiteManager() is zapi.getGlobalSiteManager()':
sm is zapi.getGlobalSiteManager(),
'IFiveUtilityRegistry.providedBy(utility_service)':
IFiveUtilityRegistry.providedBy(sm.utilities),
'isinstance(zapi.getSiteManager(), FiveSiteManager)':
isinstance(sm, FiveSiteManager),
}
return pprint.pformat(result)
class LookupUtilitiesView(BrowserView):
def __call__(self):
dummy = getattr(self.context.utilities, IDummyUtility.getName())
return "zapi.getUtility(IDummyUtility) == dummy: %s" % \
(zapi.getUtility(IDummyUtility) == dummy)
def test_suite():
from Testing.ZopeTestCase import FunctionalDocFileSuite
suite = FunctionalDocFileSuite('functional.txt',
package='Products.Five.site.tests')
suite.level = 2
return suite
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test local sites
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
import unittest
from Testing import ZopeTestCase
from zope.interface import implements
from zope.interface import directlyProvides, directlyProvidedBy
from zope.component import getGlobalSiteManager, getSiteManager
from zope.component.exceptions import ComponentLookupError
from zope.component.interfaces import ISiteManager
from zope.app.component.hooks import setSite, getSite, setHooks
from zope.app.component.interfaces import IPossibleSite, ISite
from zope.app.traversing.interfaces import IContainmentRoot
from zope.app.testing.placelesssetup import PlacelessSetup
from Acquisition import Implicit
from OFS.ObjectManager import ObjectManager
import Products.Five
from Products.Five import zcml
class SiteManager(Implicit):
implements(ISiteManager)
class Folder(ObjectManager):
implements(IPossibleSite)
sm = None
def getId(self):
return self.id
def getSiteManager(self, default=None):
return self.sm
def setSiteManager(self, sm):
self.sm = sm
directlyProvides(self, ISite, directlyProvidedBy(self))
class Package(Implicit):
pass
class Root(Folder):
implements(IContainmentRoot, ISite)
def getSiteManager(self):
return getGlobalSiteManager()
class SiteManagerStub(object):
implements(ISiteManager)
class SiteManagerTest(PlacelessSetup, unittest.TestCase):
def setUp(self):
super(SiteManagerTest, self).setUp()
self.root = root = Root()
self.f1 = f1 = Folder().__of__(root)
self.sm1 = sm1 = SiteManager()
f1.setSiteManager(sm1)
self.p1 = p1 = Package().__of__(sm1)
self.f2 = f2 = Folder().__of__(f1)
self.sm2 = sm2 = SiteManager()
f2.setSiteManager(sm2)
self.p2 = p2 = Package().__of__(sm2)
sm1.next = getGlobalSiteManager()
sm2.next = sm1
self.unparented_folder = Folder()
self.unrooted_subfolder = Folder().__of__(self.unparented_folder)
zcml.load_config("meta.zcml", Products.Five)
zcml.load_config("permissions.zcml", Products.Five)
zcml.load_config("configure.zcml", Products.Five.site)
zcml_text = """\
<five:localsite
xmlns:five="http://namespaces.zope.org/five"
class="Products.Five.site.tests.dummy.DummySite" />"""
zcml.load_string(zcml_text)
# Hook up custom component architecture calls; we need to do
# this here because zope.app.component.hooks registers a
# cleanup with the testing cleanup framework, so the hooks get
# torn down by placelesssetup each time.
setHooks()
def test_getSiteManager(self):
self.assertEqual(getSiteManager(None), getGlobalSiteManager())
self.assertEqual(getSiteManager(self.root), getGlobalSiteManager())
self.assertEqual(getSiteManager(self.f1), self.sm1)
self.assertEqual(getSiteManager(self.f2), self.sm2)
setSite(self.f2)
self.assertEqual(getSiteManager(None), self.sm2)
def test_queryNextSiteManager(self):
from zope.app.component import queryNextSiteManager
marker = object()
self.assert_(queryNextSiteManager(self.root, marker) is marker)
self.assert_(queryNextSiteManager(self.f1, marker) is getGlobalSiteManager())
#XXX the following used to be
#self.assertEqual(queryNextSiteManager(self.f2, marker), marker)
self.assertEqual(queryNextSiteManager(self.f2, marker), self.sm1)
self.assertEqual(queryNextSiteManager(self.sm1), getGlobalSiteManager())
self.assertEqual(queryNextSiteManager(self.sm2), self.sm1)
#XXX the following used to be
#self.assert_(queryNextSiteManager(self.p1) is getGlobalSiteManager())
self.assert_(queryNextSiteManager(self.p1, marker) is marker)
#XXX the following used to be
#self.assertEqual(queryNextSiteManager(self.p2), self.sm1)
self.assert_(queryNextSiteManager(self.p2, marker) is marker)
self.assert_(queryNextSiteManager(self.unparented_folder, marker)
is marker)
self.assert_(queryNextSiteManager(self.unrooted_subfolder, marker)
is marker)
def test_getNextSiteManager(self):
from zope.app.component import getNextSiteManager
self.assertRaises(ComponentLookupError, getNextSiteManager, self.root)
self.assertEqual(getNextSiteManager(self.f1), getGlobalSiteManager())
#XXX the following used to be
#self.assertRaises(ComponentLookupError, getNextSiteManager, self.f2)
self.assertEqual(getNextSiteManager(self.f2), self.sm1)
self.assertEqual(getNextSiteManager(self.sm1), getGlobalSiteManager())
self.assertEqual(getNextSiteManager(self.sm2), self.sm1)
#XXX the following used to be
#self.assert_(getNextSiteManager(self.p1) is getGlobalSiteManager())
self.assertRaises(ComponentLookupError, getNextSiteManager, self.p1)
#XXX the following used to be
#self.assertEqual(getNextSiteManager(self.p2), self.sm1)
self.assertRaises(ComponentLookupError, getNextSiteManager, self.p2)
self.assertRaises(ComponentLookupError,
getNextSiteManager, self.unparented_folder)
self.assertRaises(ComponentLookupError,
getNextSiteManager, self.unrooted_subfolder)
# XXX Maybe we need to test this with RestrictedPython in the context
# of Zope2? Maybe we just don't care.
#
# def test_getNextSiteManager_security(self):
# from zope.app.component import getNextSiteManager
# from zope.security.checker import ProxyFactory, NamesChecker
# sm = ProxyFactory(self.sm1, NamesChecker(('next',)))
# # Check that getGlobalSiteManager() is not proxied
# self.assert_(getNextSiteManager(sm) is getGlobalSiteManager())
def test_siteManagerAdapter(self):
from Products.Five.site.localsite import siteManagerAdapter
# If it is a site, return the service service.
sm = SiteManagerStub()
site = Folder()
site.setSiteManager(sm)
self.assertEqual(siteManagerAdapter(site), sm)
# If it has an acquisition context, "acquire" the site
# and return the service service
ob = Folder()
ob = ob.__of__(site)
self.assertEqual(siteManagerAdapter(ob), sm)
ob2 = Folder()
ob2 = ob2.__of__(ob)
self.assertEqual(siteManagerAdapter(ob2), sm)
# If it does we are unable to find a service service, raise
# ComponentLookupError
orphan = Folder()
self.failUnless(siteManagerAdapter(orphan) is getGlobalSiteManager())
def test_setThreadSite_clearThreadSite(self):
from zope.app.component.site import threadSiteSubscriber, clearSite
from zope.app.publication.zopepublication import BeforeTraverseEvent
self.assertEqual(getSite(), None)
# A site is traversed
sm = SiteManagerStub()
site = Folder()
site.setSiteManager(sm)
ev = BeforeTraverseEvent(site, object())
threadSiteSubscriber(site, ev)
self.assertEqual(getSite(), site)
clearSite()
self.assertEqual(getSite(), None)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(SiteManagerTest))
return suite
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
......@@ -11,16 +11,18 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Z2 -> Z3 bridge utilities.
"""Test Five site manager
$Id: adding.py 15515 2005-08-02 17:42:08Z yuppie $
$Id$
"""
import os, sys
# BBB: This file will be removed in future versions of Five.
from browser.adding import ContentAdding
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
import warnings
warnings.warn("\nThe Products.Five.adding module has been renamed to "
"Products.Five.browser.adding \n"
"and will be disabled starting in Five 1.2.\n",
DeprecationWarning, stacklevel=2)
def test_suite():
from Testing.ZopeTestCase import ZopeDocFileSuite
return ZopeDocFileSuite('sitemanager.txt', package="Products.Five.site.tests")
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test local sites
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
import unittest
import sets
from Testing import ZopeTestCase
from zope.interface import directlyProvides
from zope.component import provideUtility
from zope.component.exceptions import ComponentLookupError
from zope.app import zapi
from zope.app.testing.placelesssetup import setUp, tearDown
from zope.app.component import getNextUtility
from zope.app.component.hooks import setSite, clearSite, setHooks
import Products.Five
from Products.Five import zcml
from Products.Five.site.interfaces import IRegisterUtilitySimply
from Products.Five.site.localsite import enableLocalSiteHook
from Products.Five.site.tests.dummy import manage_addDummySite, \
IDummyUtility, ISuperDummyUtility, DummyUtility
class LocalUtilityServiceTest(ZopeTestCase.ZopeTestCase):
def afterSetUp(self):
setUp()
zcml.load_config("meta.zcml", Products.Five)
zcml.load_config("permissions.zcml", Products.Five)
zcml.load_config("configure.zcml", Products.Five.site)
zcml_text = """\
<five:localsite
xmlns:five="http://namespaces.zope.org/five"
class="Products.Five.site.tests.dummy.DummySite" />"""
zcml.load_string(zcml_text)
manage_addDummySite(self.folder, 'site')
enableLocalSiteHook(self.folder.site)
setSite(self.folder.site)
# Hook up custom component architecture calls; we need to do
# this here because zope.app.component.hooks registers a
# cleanup with the testing cleanup framework, so the hooks get
# torn down by placelesssetup each time.
setHooks()
def beforeTearDown(self):
tearDown()
def test_getSiteManagerHook(self):
from Products.Five.site.localsite import FiveSiteManager
from Products.Five.site.utility import SimpleLocalUtilityRegistry
local_sm = zapi.getSiteManager(None)
self.failIf(local_sm is zapi.getGlobalSiteManager())
self.failUnless(isinstance(local_sm, FiveSiteManager))
local_sm = zapi.getSiteManager(self.folder.site)
self.failIf(local_sm is zapi.getGlobalSiteManager())
self.failUnless(isinstance(local_sm, FiveSiteManager))
sm = zapi.getSiteManager()
self.failUnless(isinstance(sm.utilities, SimpleLocalUtilityRegistry))
def test_getUtilitiesNoUtilitiesFolder(self):
sm = zapi.getSiteManager()
#XXX test whether sm really is a local site...
self.failUnless(sm.queryUtility(IDummyUtility) is None)
self.assertEquals(list(sm.getUtilitiesFor(IDummyUtility)), [])
self.assertEquals(list(sm.getAllUtilitiesRegisteredFor(IDummyUtility)), [])
def test_registerUtilityOnUtilityRegistry(self):
utils = zapi.getSiteManager().utilities
dummy = DummyUtility()
utils.registerUtility(IDummyUtility, dummy, 'dummy')
self.assertEquals(zapi.getUtility(IDummyUtility, name='dummy'), dummy)
self.assertEquals(list(zapi.getUtilitiesFor(IDummyUtility)),
[('dummy', dummy)])
self.assertEquals(list(zapi.getAllUtilitiesRegisteredFor(
IDummyUtility)), [dummy])
def test_registerUtilityOnSiteManager(self):
sm = zapi.getSiteManager()
self.failUnless(IRegisterUtilitySimply.providedBy(sm))
dummy = DummyUtility()
sm.registerUtility(IDummyUtility, dummy, 'dummy')
self.assertEquals(zapi.getUtility(IDummyUtility, name='dummy'), dummy)
self.assertEquals(list(zapi.getUtilitiesFor(IDummyUtility)),
[('dummy', dummy)])
self.assertEquals(list(zapi.getAllUtilitiesRegisteredFor(
IDummyUtility)), [dummy])
def test_registerTwoUtilitiesWithSameNameDifferentInterface(self):
sm = zapi.getSiteManager()
self.failUnless(IRegisterUtilitySimply.providedBy(sm))
dummy = DummyUtility()
superdummy = DummyUtility()
directlyProvides(superdummy, ISuperDummyUtility)
sm.registerUtility(IDummyUtility, dummy, 'dummy')
sm.registerUtility(ISuperDummyUtility, superdummy, 'dummy')
self.assertEquals(zapi.getUtility(IDummyUtility, 'dummy'), dummy)
self.assertEquals(zapi.getUtility(ISuperDummyUtility, 'dummy'),
superdummy)
def test_nestedSitesDontConflictButStillAcquire(self):
# let's register a dummy utility in the dummy site
dummy = DummyUtility()
sm = zapi.getSiteManager()
sm.registerUtility(IDummyUtility, dummy)
# let's also create a subsite and make that our site
manage_addDummySite(self.folder.site, 'subsite')
enableLocalSiteHook(self.folder.site.subsite)
setSite(self.folder.site.subsite)
# we should still be able to lookup the original utility from
# the site one level above
self.assertEqual(zapi.getUtility(IDummyUtility), dummy)
# now we register a dummy utility in the subsite and see that
# its registration doesn't conflict
subdummy = DummyUtility()
sm = zapi.getSiteManager()
sm.registerUtility(IDummyUtility, subdummy)
# when we look it up we get the more local one now because the
# more local one shadows the less local one
self.assertEqual(zapi.getUtility(IDummyUtility), subdummy)
# getAllUtilitiesFor gives us both the more local and the less
# local utility (XXX not sure if this is the right semantics
# for getAllUtilitiesFor)
self.assertEqual(sets.Set(zapi.getAllUtilitiesRegisteredFor(IDummyUtility)),
sets.Set([subdummy, dummy]))
# getUtilitiesFor will only find one, because the more local
# one shadows the less local one
self.assertEqual(list(zapi.getUtilitiesFor(IDummyUtility)),
[('', subdummy)])
def test_registeringTwiceIsConflict(self):
dummy1 = DummyUtility()
dummy2 = DummyUtility()
sm = zapi.getSiteManager()
sm.registerUtility(IDummyUtility, dummy1)
self.assertRaises(ValueError, sm.registerUtility,
IDummyUtility, dummy2)
sm.registerUtility(IDummyUtility, dummy1, 'dummy')
self.assertRaises(ValueError, sm.registerUtility,
IDummyUtility, dummy2, 'dummy')
def test_utilitiesHaveProperAcquisitionContext(self):
dummy = DummyUtility()
sm = zapi.getSiteManager()
sm.registerUtility(IDummyUtility, dummy)
# let's see if we can acquire something all the way from the
# root (Application) object; we need to be careful to choose
# something that's only available from the root object
from Acquisition import aq_acquire
dummy = zapi.getUtility(IDummyUtility)
acquired = aq_acquire(dummy, 'ZopeAttributionButton', None)
self.failUnless(acquired is not None)
name, dummy = zapi.getUtilitiesFor(IDummyUtility).next()
acquired = aq_acquire(dummy, 'ZopeAttributionButton', None)
self.failUnless(acquired is not None)
dummy = zapi.getAllUtilitiesRegisteredFor(IDummyUtility).next()
acquired = aq_acquire(dummy, 'ZopeAttributionButton', None)
self.failUnless(acquired is not None)
def test_getNextUtility(self):
# test local site vs. global site
global_dummy = DummyUtility()
provideUtility(global_dummy, IDummyUtility)
local_dummy = DummyUtility()
sm = zapi.getSiteManager()
sm.registerUtility(IDummyUtility, local_dummy)
self.assertEquals(zapi.getUtility(IDummyUtility), local_dummy)
self.assertEquals(getNextUtility(self.folder.site, IDummyUtility),
global_dummy)
# test local site vs. nested local site
manage_addDummySite(self.folder.site, 'subsite')
enableLocalSiteHook(self.folder.site.subsite)
setSite(self.folder.site.subsite)
sublocal_dummy = DummyUtility()
sm = zapi.getSiteManager()
sm.registerUtility(IDummyUtility, sublocal_dummy)
self.assertEquals(zapi.getUtility(IDummyUtility), sublocal_dummy)
self.assertEquals(getNextUtility(self.folder.site.subsite, IDummyUtility),
local_dummy)
self.assertEquals(getNextUtility(self.folder.site, IDummyUtility),
global_dummy)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(LocalUtilityServiceTest))
return suite
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Local utility registration
$Id$
"""
from zope.interface import implements
from zope.component import getGlobalSiteManager
from zope.component.exceptions import ComponentLookupError
from zope.app.component import getNextSiteManager
from Acquisition import aq_base
from OFS.Folder import Folder
from Products.Five.site.interfaces import IFiveUtilityRegistry
class SimpleLocalUtilityRegistry(object):
implements(IFiveUtilityRegistry)
def __init__(self, context):
self.context = context
# make {get|query}NextSiteManager() work without having to
# resort to Zope 2 acquisition
self.__parent__ = self.context.getSiteManager()
@property
def next(self):
try:
return getNextSiteManager(self)
except ComponentLookupError:
return getGlobalSiteManager()
def getUtility(self, interface, name=''):
"""See IFiveUtilityRegistry interface
"""
c = self.queryUtility(interface, name)
if c is not None:
return c
raise ComponentLookupError(interface, name)
def queryUtility(self, interface, name='', default=None):
"""See IFiveUtilityRegistry interface
"""
if name == '':
# Singletons. Only one per interface allowed, so, let's call it
# by the interface.
id = interface.getName()
else:
id = interface.getName() + '-' + name
if getattr(aq_base(self.context), 'utilities', None) is not None:
utility = self.context.utilities._getOb(id, None)
if utility is not None:
return utility
return self.next.queryUtility(interface, name, default)
def getUtilitiesFor(self, interface):
names = []
prefix = interface.getName() + '-'
if getattr(aq_base(self.context), 'utilities', None) is not None:
for name, utility in self.context.utilities.objectItems():
if name == interface.getName():
names.append('')
yield '', utility
elif name.startswith(prefix):
name = name[len(prefix):]
names.append(name)
yield (name, utility)
for name, utility in self.next.getUtilitiesFor(interface):
if name not in names:
yield name, utility
def getAllUtilitiesRegisteredFor(self, interface):
# This also supposedly returns "overridden" utilities, but we don't
# keep them around. It also does not return the name-value pair that
# getUtilitiesFor returns.
if getattr(aq_base(self.context), 'utilities', None) is not None:
for utility in self.context.utilities.objectValues():
if interface.providedBy(utility):
yield utility
for utility in self.next.getAllUtilitiesRegisteredFor(interface):
yield utility
def registerUtility(self, interface, utility, name=''):
# I think you are *really* supposed to:
# 1. Check if there is a "registrations" object for utilities.
# 2. If not create one.
# 3. Get it.
# 4. Create a registration object for the utility.
# 5. Rgister the registration object in the registrations.
# But that is quite complex, and Jim sais he wants to change that
# anyway, and in any case the way you would normally do this in Zope3
# and Five would probably differ anyway, so, here is this new
# Five-only, easy to use method!
if getattr(aq_base(self.context), 'utilities', None) is None:
self.context._setObject('utilities', Folder('utilities'))
utilities = self.context.utilities
if name == '':
# Singletons. Only one per interface allowed, so, let's call it
# by the interface.
id = interface.getName()
else:
id = interface.getName() + '-' + name
if id in utilities.objectIds():
raise ValueError("There is already a utility registered for "
"%s with the name '%s'" % (interface.getName(),
name))
utilities._setObject(id, utility)
# BBB 2005/11/01 -- gone in Five 1.5.
SimpleLocalUtilityService = SimpleLocalUtilityRegistry
import zope.deprecation
zope.deprecation.deprecated(
'SimpleLocalUtilityService', "'SimpleLocalUtilityService' has been renamed to "
"'SimpleLocalUtilityRegistry' and will disappear in Five 1.5."
)
......@@ -13,17 +13,17 @@
##############################################################################
"""Mimick the Zope 3 skinning system in Five.
$Id: standardmacros.py 12884 2005-05-30 13:10:41Z philikon $
$Id: standardmacros.py 19283 2005-10-31 17:43:51Z philikon $
"""
from zope.interface.common.mapping import IItemMapping
from zope.interface import implements
from zope.component import getView
import zope.interface
from zope.app import zapi
from Products.Five.browser import BrowserView
# this is a verbatim copy of zope.app.basicskin except that it doesn't
# derive from ``object``
class Macros:
implements(IItemMapping)
zope.interface.implements(zope.interface.common.mapping.IItemMapping)
macro_pages = ()
aliases = {
......@@ -37,7 +37,7 @@ class Macros:
context = self.context
request = self.request
for name in self.macro_pages:
page = getView(context, name, request)
page = zapi.getMultiAdapter((context, request), name=name)
try:
v = page[key]
except KeyError:
......
......@@ -26,7 +26,7 @@ def test_standard_macros():
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.login('manager')
>>> from Products.Five.testing import manage_addFiveTraversableFolder
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> import Products.Five.skin.tests
......@@ -72,7 +72,7 @@ def test_standard_macros():
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
......
Five tests
==========
The tests require ZopeTestCase to be installed. ZopeTestCase can be
downloaded from here:
All you have to do is type::
http://zope.org/Members/shh/ZopeTestCase
it needs to be installed in your Zope software's lib/python/Testing
directory.
Then, if you have Zope 2.7.3 or better all you have to do is type::
./bin/zopectl test --dir Products/Five
$ bin/zopectl test -s Products.Five
to run the Five tests.
......@@ -21,16 +21,16 @@ if __name__ == '__main__':
def test_boilerplate():
"""
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
>>> import Products.Five.tests
>>> from Products.Five import zcml
>>> zcml.load_config('boilerplate.zcml', Products.Five.tests)
>>> from Products.Five.testing import manage_addFiveTraversableFolder
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.testing.fancycontent import manage_addFancyContent
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent
>>> tearDown()
"""
......
......@@ -11,17 +11,17 @@
once on a class; SimpleContent inherits from Traversable, so
one directive suffices here -->
<five:traversable class="Products.Five.testing.simplecontent.SimpleContent" />
<five:traversable class="Products.Five.tests.testing.simplecontent.SimpleContent" />
<!-- this is a test whether the *directive* can be called more than
once without raising a conflicting configuration exception -->
<five:traversable class="Products.Five.testing.simplecontent.SimpleContent" />
<five:traversable class="Products.Five.tests.testing.simplecontent.SimpleContent" />
<!-- this tests whether five:traversable can be called on a class that
already provides __bobo_traverse__, such as our FancyContent -->
<five:traversable class="Products.Five.testing.fancycontent.FancyContent" />
<five:traversable class="Products.Five.tests.testing.fancycontent.FancyContent" />
<!-- Testing the vocabulary directive -->
......
Test events
===========
================
Container events
================
Before we can start, we need to set up an event subscriber that allows
us to inspect events that will be thrown during the test:
Zope 3 container events are used to inform subscribers that an object is
about to be added/removed from a container, and also after it has been
done. This is used for bookkeeping and cleaning up in subobjects.
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> setUp()
Add a folder that doesn't verify objects on paste. We use it as a
test sandbox:
>>> from Products.Five.testing import manage_addNoVerifyPasteFolder
>>> manage_addNoVerifyPasteFolder(self.folder, 'npvf')
>>> folder = self.folder.npvf
Finally add a manager user login, give it the right permissions and
log in using it:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.setPermissions(standard_permissions + ['Copy or Move'], 'Manager')
>>> self.login('manager')
>>> from zope.app.event.tests.placelesssetup import getEvents, clearEvents
Sending events
--------------
Zope 2 classes need to be modified so that they send Zope 3 style
events. Our stub class here is such a case. We can add it to a
folder, for example, ...
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(folder, 'foo', 'Foo')
and no event will have been triggered:
>>> len(getEvents())
0
Clean up:
>>> folder.manage_delObjects(['foo'])
Now make the class send events:
>>> from Products.Five.eventconfigure import classSendEvents
>>> from Products.Five.testing.simplecontent import SimpleContent
>>> classSendEvents(SimpleContent)
Added event
------------
Let's add an object to a folder:
>>> manage_addSimpleContent(folder, 'foo', 'Foo')
These events replace the old Zope 2 manage_afterAdd, manage_beforeDelete
and manage_afterClone methods.
One object event should have been sent with the event's object being
our foo object:
All standard Zope containers will only call manage_afterAdd & co on
classes specified with the directive::
>>> events = getEvents()
>>> len(events)
1
>>> foo = folder.foo
>>> events[0].object == foo
True
<five:deprecatedManageAddDelete class="some.content.class"/>
That object event should have been an object added event:
Classes that don't have this directive but still have manage_afterAdd &
co methods will trigger a warning when they are called (and this is
strictly a compatibility call, behavior may not be strictly equivalent
to the original one).
>>> from zope.app.container.interfaces import IObjectAddedEvent
>>> events = getEvents(IObjectAddedEvent)
>>> len(events)
1
>>> events[0].object == foo
True
>>> events[0].newParent == foo.aq_parent
True
Test setup
==========
Check that the object's original manage_afterAdd method was also called:
A bit of setup for the tests. Because we'll test copy/paste, we need to
work inside a database::
>>> foo.afterAdd_called
True
Now clean up:
>>> clearEvents()
Moved event (I) -- Renaming
--------------------------
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
Somehow we need to at least commit a subtransaction to make renaming
succeed:
>>> import ZODB.tests.util
>>> db = ZODB.tests.util.DB()
>>> connection = db.open()
>>> root = connection.root()
We'll use a few simple classes (defined in python code for picklability)
for our tests.
>>> from Products.Five.tests.test_event import MyApp, MyContent
>>> from Products.Five.tests.test_event import MyFolder, MyBTreeFolder
>>> from Products.Five.tests.test_event import MyOrderedFolder
>>> app = MyApp('')
>>> root['app'] = app
>>> folder = MyFolder('folder')
>>> app._setObject('folder', folder) # doctest: +NORMALIZE_WHITESPACE
old manage_afterAdd folder folder
'folder'
>>> folder = app.folder
>>> btfolder = MyBTreeFolder('btfolder')
>>> app._setObject('btfolder', btfolder) # doctest: +NORMALIZE_WHITESPACE
old manage_afterAdd btfolder btfolder
'btfolder'
To observe what object events are dispatched, we'll have some
subscribers print them. We'll actually do that for a specific interface,
not for (None, IObjectEvent), and register our subscribers before the
framework's ones, so ours will be called first. This has the effect that
printed events will be in their "natural" order::
>>> from zope.app.event.interfaces import IObjectEvent
>>> from zope.app.container.interfaces import IObjectMovedEvent
>>> from OFS.interfaces import IObjectWillBeMovedEvent
>>> from OFS.interfaces import IObjectClonedEvent
>>> from OFS.interfaces import IItem
>>> def printObjectEvent(object, event):
... print event.__class__.__name__, object.getId()
>>> def printObjectEventExceptSome(object, event):
... if (IObjectMovedEvent.providedBy(event) or
... IObjectWillBeMovedEvent.providedBy(event) or
... IObjectClonedEvent.providedBy(event)):
... return
... print event.__class__.__name__, object.getId()
>>> from zope.component import provideHandler
>>> provideHandler(printObjectEvent, (IItem, IObjectMovedEvent))
>>> provideHandler(printObjectEvent, (IItem, IObjectWillBeMovedEvent))
>>> provideHandler(printObjectEvent, (IItem, IObjectClonedEvent))
>>> provideHandler(printObjectEventExceptSome, (None, IObjectEvent))
Finally we need to load the subscribers configuration::
>>> from Products.Five import zcml
>>> import Products.Five
>>> import zope.app.component
>>> zcml.load_config('meta.zcml', zope.app.component)
>>> zcml.load_config('event.zcml', Products.Five)
Old class
=========
If we use an instance of an old class for which we haven't specified
anything, events are sent and the manage_afterAdd & co methods are
called but in a "compatibility" way.
Because the bases classes of Zope have been changed to not recurse
except through the event framework, unexpected behavior may happen
(however a warning will be sent)::
>>> ob = MyContent('dog')
>>> folder._setObject('dog', ob)
ObjectWillBeAddedEvent dog
ObjectAddedEvent dog
old manage_afterAdd dog dog folder
'dog'
And when we delete the object, manage_beforeDelete is also called and
events are sent::
>>> folder.manage_delObjects('dog')
old manage_beforeDelete dog dog folder
ObjectWillBeRemovedEvent dog
ObjectRemovedEvent dog
Old class with deprecatedManageAddDelete
========================================
We specifiy that our class is deprecated (using zcml in real life)::
>>> from Products.Five.eventconfigure import setDeprecatedManageAddDelete
>>> setDeprecatedManageAddDelete(MyContent)
>>> setDeprecatedManageAddDelete(MyFolder)
>>> setDeprecatedManageAddDelete(MyOrderedFolder)
Now some events are sent but the old manage_afterAdd method is also
called correctly::
>>> ob = MyContent('lassie')
>>> folder._setObject('lassie', ob)
ObjectWillBeAddedEvent lassie
ObjectAddedEvent lassie
old manage_afterAdd lassie lassie folder
'lassie'
And when we delete the object, manage_beforeDelete is also called and
events are sent::
>>> folder.manage_delObjects('lassie')
ObjectWillBeRemovedEvent lassie
old manage_beforeDelete lassie lassie folder
ObjectRemovedEvent lassie
The old behavior happens for a move or a copy, with events too.
For a move::
>>> ob = MyContent('blueberry')
>>> folder._setObject('blueberry', ob)
ObjectWillBeAddedEvent blueberry
ObjectAddedEvent blueberry
old manage_afterAdd blueberry blueberry folder
'blueberry'
>>> cp = folder.manage_cutObjects('blueberry')
>>> folder.manage_pasteObjects(cp)
ObjectWillBeMovedEvent blueberry
old manage_beforeDelete blueberry blueberry folder
ObjectMovedEvent blueberry
old manage_afterAdd blueberry blueberry folder
[{'new_id': 'blueberry', 'id': 'blueberry'}]
Old behavior with events for a copy::
>>> cp = folder.manage_copyObjects('blueberry')
>>> folder.manage_pasteObjects(cp)
ObjectCopiedEvent copy_of_blueberry
ObjectWillBeAddedEvent copy_of_blueberry
ObjectAddedEvent copy_of_blueberry
old manage_afterAdd copy_of_blueberry copy_of_blueberry folder
ObjectClonedEvent copy_of_blueberry
old manage_afterClone copy_of_blueberry copy_of_blueberry
[{'new_id': 'copy_of_blueberry', 'id': 'blueberry'}]
Old behavior with events for a renaming::
>>> folder.manage_renameObject('copy_of_blueberry', 'myrtille')
ObjectWillBeMovedEvent copy_of_blueberry
old manage_beforeDelete copy_of_blueberry copy_of_blueberry folder
ObjectMovedEvent myrtille
old manage_afterAdd myrtille myrtille folder
Old behavior with events for a clone::
>>> res = folder.manage_clone(folder.blueberry, 'strawberry')
ObjectCopiedEvent strawberry
ObjectWillBeAddedEvent strawberry
ObjectAddedEvent strawberry
old manage_afterAdd strawberry strawberry folder
ObjectClonedEvent strawberry
old manage_afterClone strawberry strawberry
>>> res.getId()
'strawberry'
Events are also sent when we work with a BTreeFolder::
>>> ob = MyContent('luckyluke')
>>> btfolder._setObject('luckyluke', ob)
ObjectWillBeAddedEvent luckyluke
ObjectAddedEvent luckyluke
old manage_afterAdd luckyluke luckyluke btfolder
'luckyluke'
>>> btfolder.manage_delObjects('luckyluke')
ObjectWillBeRemovedEvent luckyluke
old manage_beforeDelete luckyluke luckyluke btfolder
ObjectRemovedEvent luckyluke
Here is what happens for a tree of objects. Let's create a simple one::
>>> subfolder = MyFolder('subfolder')
>>> folder._setObject('subfolder', subfolder)
ObjectWillBeAddedEvent subfolder
ObjectAddedEvent subfolder
old manage_afterAdd subfolder subfolder folder
'subfolder'
>>> subfolder = folder.subfolder
>>> ob = MyContent('donald')
>>> subfolder._setObject('donald', ob)
ObjectWillBeAddedEvent donald
ObjectAddedEvent donald
old manage_afterAdd donald donald subfolder
'donald'
Renaming a tree of objects. Note that manage_beforeDelete is called
bottom-up::
>>> folder.manage_renameObject('subfolder', 'pluto')
ObjectWillBeMovedEvent subfolder
ObjectWillBeMovedEvent donald
old manage_beforeDelete donald subfolder folder
old manage_beforeDelete subfolder subfolder folder
ObjectMovedEvent pluto
old manage_afterAdd pluto pluto folder
ObjectMovedEvent donald
old manage_afterAdd donald pluto folder
Cloning a tree of objects::
>>> res = folder.manage_clone(folder.pluto, 'mickey')
ObjectCopiedEvent mickey
ObjectWillBeAddedEvent mickey
ObjectWillBeAddedEvent donald
ObjectAddedEvent mickey
old manage_afterAdd mickey mickey folder
ObjectAddedEvent donald
old manage_afterAdd donald mickey folder
ObjectClonedEvent mickey
old manage_afterClone mickey mickey
ObjectClonedEvent donald
old manage_afterClone donald mickey
>>> res.getId()
'mickey'
New class
=========
If we use classes that don't have any manage_afterAdd & co method,
everything happens correctly::
>>> from Products.Five.tests.test_event import MyNewFolder, MyNewContent
>>> app = MyApp('')
>>> root['app'] = app
>>> folder = MyNewFolder('folder')
>>> app._setObject('folder', folder)
ObjectWillBeAddedEvent folder
ObjectAddedEvent folder
'folder'
>>> folder = app.folder
>>> ob = MyNewContent('dogbert')
>>> folder._setObject('dogbert', ob)
ObjectWillBeAddedEvent dogbert
ObjectAddedEvent dogbert
'dogbert'
>>> folder.manage_delObjects('dogbert')
ObjectWillBeRemovedEvent dogbert
ObjectRemovedEvent dogbert
Now move::
>>> ob = MyNewContent('dilbert')
>>> folder._setObject('dilbert', ob)
ObjectWillBeAddedEvent dilbert
ObjectAddedEvent dilbert
'dilbert'
>>> cp = folder.manage_cutObjects('dilbert')
>>> folder.manage_pasteObjects(cp)
ObjectWillBeMovedEvent dilbert
ObjectMovedEvent dilbert
[{'new_id': 'dilbert', 'id': 'dilbert'}]
And copy::
>>> cp = folder.manage_copyObjects('dilbert')
>>> folder.manage_pasteObjects(cp)
ObjectCopiedEvent copy_of_dilbert
ObjectWillBeAddedEvent copy_of_dilbert
ObjectAddedEvent copy_of_dilbert
ObjectClonedEvent copy_of_dilbert
[{'new_id': 'copy_of_dilbert', 'id': 'dilbert'}]
Then rename::
>>> folder.manage_renameObject('copy_of_dilbert', 'wally')
ObjectWillBeMovedEvent copy_of_dilbert
ObjectMovedEvent wally
Or copy using manage_clone::
>>> res = folder.manage_clone(folder.dilbert, 'phb')
ObjectCopiedEvent phb
ObjectWillBeAddedEvent phb
ObjectAddedEvent phb
ObjectClonedEvent phb
>>> res.getId()
'phb'
Also on a BTreeFolder::
>>> ob = MyNewContent('alice')
>>> btfolder._setObject('alice', ob)
ObjectWillBeAddedEvent alice
ObjectAddedEvent alice
'alice'
>>> btfolder.manage_renameObject('alice', 'rabbit')
ObjectWillBeMovedEvent alice
ObjectMovedEvent rabbit
>>> btfolder.manage_delObjects('rabbit')
ObjectWillBeRemovedEvent rabbit
ObjectRemovedEvent rabbit
Now for a tree of objects. Let's create a simple one::
>>> subfolder = MyNewFolder('subfolder')
>>> folder._setObject('subfolder', subfolder)
ObjectWillBeAddedEvent subfolder
ObjectAddedEvent subfolder
'subfolder'
>>> subfolder = folder.subfolder
>>> ob = MyNewContent('mel')
>>> subfolder._setObject('mel', ob)
ObjectWillBeAddedEvent mel
ObjectAddedEvent mel
'mel'
Renaming a tree of objects::
>>> folder.manage_renameObject('subfolder', 'firefly')
ObjectWillBeMovedEvent subfolder
ObjectWillBeMovedEvent mel
ObjectMovedEvent firefly
ObjectMovedEvent mel
Cloning a tree of objects::
>>> res = folder.manage_clone(folder.firefly, 'serenity')
ObjectCopiedEvent serenity
ObjectWillBeAddedEvent serenity
ObjectWillBeAddedEvent mel
ObjectAddedEvent serenity
ObjectAddedEvent mel
ObjectClonedEvent serenity
ObjectClonedEvent mel
>>> res.getId()
'serenity'
OrderedFolder has the same renaming behavior than before::
>>> ofolder = MyOrderedFolder('ofolder')
>>> app._setObject('ofolder', ofolder) # doctest: +NORMALIZE_WHITESPACE
ObjectWillBeAddedEvent ofolder
ObjectAddedEvent ofolder
old manage_afterAdd ofolder ofolder
'ofolder'
>>> ob1 = MyNewContent('ob1')
>>> ofolder._setObject('ob1', ob1)
ObjectWillBeAddedEvent ob1
ObjectAddedEvent ob1
'ob1'
>>> ob2 = MyNewContent('ob2')
>>> ofolder._setObject('ob2', ob2)
ObjectWillBeAddedEvent ob2
ObjectAddedEvent ob2
'ob2'
>>> ofolder.manage_renameObject('ob1', 'ob4')
ObjectWillBeMovedEvent ob1
ObjectMovedEvent ob4
>>> ofolder.objectIds()
['ob4', 'ob2']
Now cleanup::
>>> import transaction
>>> transaction.commit(1)
Let's rename the object we created before:
>>> folder.manage_renameObject('foo', 'bar')
We should get two events...
>>> events = getEvents()
>>> len(events)
2
the removed event...
>>> event = events[0]
>>> from zope.app.container.interfaces import IObjectRemovedEvent
>>> IObjectRemovedEvent.providedBy(event)
True
>>> event.object == foo
True
>>> event.oldName, event.newName
('foo', None)
>>> event.oldParent == folder
True
>>> event.newParent is None
True
and the moved event:
>>> event = events[1]
>>> event.object == foo
True
>>> event.oldName, event.newName
('foo', 'bar')
>>> event.oldParent == folder
True
>>> event.newParent == folder
True
Now clean up:
>>> folder.manage_delObjects(['bar'])
>>> clearEvents()
We don't delete the stub object just yet because it's being used in
the next part of the test.
Moved event (II) -- Cut and paste
---------------------------------
Let's move from one folder to another:
>>> manage_addNoVerifyPasteFolder(folder, 'folder1', 'Folder1')
>>> folder1 = folder.folder1
>>> manage_addNoVerifyPasteFolder(folder, 'folder2', 'Folder2')
>>> folder2 = folder.folder2
>>> manage_addSimpleContent(folder1, 'foo', 'Foo')
>>> foo = folder1.foo
We need to trigger a subtransaction before cut/paste can work:
>>> transaction.commit(1)
>>> cb = folder1.manage_cutObjects(['foo'])
>>> info = folder2.manage_pasteObjects(cb)
Apart from the added event we triggerred when we added the stub object
to the folder, we expect two events...
>>> events = getEvents()
>>> len(events)
3
>>> len(getEvents(IObjectAddedEvent))
1
a removed event...
>>> event = events[1]
>>> from zope.app.container.interfaces import IObjectRemovedEvent
>>> IObjectRemovedEvent.providedBy(event)
True
>>> event.oldParent == folder1
True
>>> event.newParent is None
True
and a moved event:
>>> event = events[2]
>>> event.object == foo
True
>>> event.oldName, event.newName
('foo', 'foo')
>>> event.oldParent == folder1
True
>>> event.newParent == folder2
True
Now clean up:
>>> folder.manage_delObjects(['folder1'])
>>> folder.manage_delObjects(['folder2'])
>>> clearEvents()
Copied event
------------
>>> manage_addSimpleContent(folder, 'foo', 'Foo')
>>> manage_addNoVerifyPasteFolder(folder, 'folder1')
>>> folder1 = folder.folder1
We need to trigger subtransaction before copy/paste can work
>>> transaction.commit(1)
>>> cb = folder.manage_copyObjects(['foo'])
>>> info = folder1.manage_pasteObjects(cb)
>>> foo = folder1.foo
Apart from the added event we triggerred when we added the stub object
to the folder, we expect two events...
>>> events = getEvents()
>>> len(events)
3
a copied event...
>>> event = events[1]
>>> from zope.app.event.interfaces import IObjectCopiedEvent
>>> IObjectCopiedEvent.providedBy(event)
True
>>> events[1].object == foo
True
and an added event:
>>> event = events[2]
>>> IObjectAddedEvent.providedBy(event)
True
>>> event.object == foo
True
>>> event.newName
'foo'
>>> event.newParent == folder1
True
Now clean up:
>>> folder.manage_delObjects(['folder1'])
>>> folder.manage_delObjects(['foo'])
>>> clearEvents()
Removed event
-------------
>>> manage_addSimpleContent(folder, 'foo', 'Foo')
>>> foo = folder.foo
>>> foo.beforeDelete_called
False
>>> folder.manage_delObjects(['foo'])
>>> events = getEvents()
>>> len(events)
2
>>> events[1].object.id
'foo'
Check that the object's original manage_beforeDelete method was also called:
>>> foo.beforeDelete_called
True
>>> clearEvents()
Clean up
--------
Finally, we need to put our stub class back the way it was before we
monkeyed with it:
>>> from Products.Five.eventconfigure import cleanUp
>>> cleanUp()
Now adding an object won't trigger an event anymore:
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(folder, 'foo', 'Foo')
>>> len(getEvents())
0
Finally, we need to tear down everything else (services, etc.)
>>> transaction.abort()
>>> tearDown()
......@@ -23,9 +23,6 @@ def test_directives():
"""
Test ZCML directives
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> setUp()
There isn't much to test here since the actual directive handlers
are either tested in other, more specific tests, or they're
already tested in Zope 3. We'll just do a symbolic test of
......@@ -62,9 +59,10 @@ def test_directives():
>>> dest.method()
'Overridden'
Clean up:
Clean up adapter registry and others:
>>> tearDown()
>>> from zope.testing.cleanup import cleanUp
>>> cleanUp()
"""
def test_suite():
......
......@@ -19,9 +19,64 @@ import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
# These classes aren't defined in the doctest because otherwise
# they wouldn't be picklable, and we need that to test copy/paste.
from OFS.SimpleItem import SimpleItem
from OFS.Folder import Folder
from OFS.OrderedFolder import OrderedFolder
from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2
class DontComplain(object):
def _verifyObjectPaste(self, object, validate_src=1):
pass
def cb_isMoveable(self):
return True
def cb_isCopyable(self):
return True
class NotifyBase(DontComplain):
def manage_afterAdd(self, item, container):
print 'old manage_afterAdd %s %s %s' % (self.getId(), item.getId(),
container.getId())
def manage_beforeDelete(self, item, container):
print 'old manage_beforeDelete %s %s %s' % (self.getId(), item.getId(),
container.getId())
def manage_afterClone(self, item):
print 'old manage_afterClone %s %s' % (self.getId(), item.getId())
class MyApp(Folder):
def getPhysicalRoot(self):
return self
class MyFolder(NotifyBase, Folder):
pass
class MyOrderedFolder(NotifyBase, OrderedFolder):
pass
class MyBTreeFolder(NotifyBase, BTreeFolder2):
def _verifyObjectPaste(self, object, validate_src=1):
pass
class MyContent(NotifyBase, SimpleItem):
def __init__(self, id):
self._setId(id)
# These don't have manage_beforeDelete & co methods
class MyNewContent(DontComplain, SimpleItem):
def __init__(self, id):
self._setId(id)
class MyNewFolder(DontComplain, Folder):
pass
def test_suite():
from Testing.ZopeTestCase import ZopeDocFileSuite
return ZopeDocFileSuite('event.txt', package="Products.Five.tests")
from zope.testing.doctest import DocFileSuite
return DocFileSuite('event.txt', package="Products.Five.tests")
if __name__ == '__main__':
framework()
......@@ -23,7 +23,7 @@ def test_directive():
"""
Test the i18n directive
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
First, we need to register the ZCML directive:
......@@ -44,9 +44,9 @@ def test_directive():
Now, take an arbitrary message id from that domain:
>>> from zope.i18nmessageid import MessageIDFactory
>>> from zope.i18nmessageid import MessageFactory
>>> from zope.i18n import translate
>>> _ = MessageIDFactory('fivetest')
>>> _ = MessageFactory('fivetest')
>>> msg = _(u'explicit-msg', u'This is an explicit message')
As you can see, both the default functionality and translation to
......
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Unit tests for the registerClass directive.
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_registerClass():
"""
Testing registerClass
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
>>> import Products
>>> import Products.Five
>>> from Products.Five import zcml
>>> from Products.Five.tests.testing.simplecontent import SimpleContent
>>> from Products.Five.tests.testing.simplecontent import ISimpleContent
>>> from persistent.interfaces import IPersistent
Use the five:registerClass directive::
>>> configure_zcml = '''
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:five="http://namespaces.zope.org/five"
... i18n_domain="foo">
... <permission id="foo.add" title="Add Foo"/>
... <five:registerClass
... class="Products.Five.tests.testing.simplecontent.SimpleContent"
... meta_type="Foo Type"
... permission="foo.add"
... addview="addfoo.html"
... icon="foo_icon.png"
... global="false"
... />
... </configure>'''
>>> zcml.load_config('meta.zcml', Products.Five)
>>> zcml.load_string(configure_zcml)
Make sure that the class attributes are set correctly::
>>> SimpleContent.meta_type
'Foo Type'
>>> SimpleContent.icon
'++resource++foo_icon.png'
And the meta_type is registered correctly::
>>> for info in Products.meta_types:
... if info['name'] == 'Foo Type':
... break
>>> info['product']
'Five'
>>> info['permission']
'Add Foo'
>>> ISimpleContent in info['interfaces']
True
>>> IPersistent in info['interfaces']
True
>>> info['visibility'] is None
True
>>> info['instance'] is SimpleContent
True
>>> info['action']
'+/addfoo.html'
>>> info['container_filter'] is None
True
Now reset everything and see what happens without optional parameters::
>>> tearDown()
>>> setUp()
Use the five:registerClass directive again::
>>> configure_zcml = '''
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:five="http://namespaces.zope.org/five"
... i18n_domain="bar">
... <permission id="bar.add" title="Add Bar"/>
... <five:registerClass
... class="Products.Five.tests.testing.simplecontent.SimpleContent"
... meta_type="Bar Type"
... permission="bar.add"
... />
... </configure>'''
>>> zcml.load_config('meta.zcml', Products.Five)
>>> zcml.load_string(configure_zcml)
Make sure that the class attributes are set correctly::
>>> SimpleContent.meta_type
'Bar Type'
>>> SimpleContent.icon
''
And the meta_type is registered correctly::
>>> for info in Products.meta_types:
... if info['name'] == 'Bar Type':
... break
>>> info['product']
'Five'
>>> info['permission']
'Add Bar'
>>> ISimpleContent in info['interfaces']
True
>>> IPersistent in info['interfaces']
True
>>> info['visibility']
'Global'
>>> info['instance'] is SimpleContent
True
>>> info['action']
''
>>> info['container_filter'] is None
True
Clean up:
>>> tearDown()
>>> SimpleContent.meta_type
'simple item'
>>> SimpleContent.icon
''
>>> [info for info in Products.meta_types if info['name'] == 'Bar Type']
[]
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
......@@ -45,7 +45,7 @@ def test_security_equivalence():
Zope 2 can be replaced by ZCML statements without any loss of
information.
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
We start out with two classes, ``Dummy1`` and ``Dummy2``. They
......@@ -141,7 +141,7 @@ def test_checkPermission():
"""
Test checkPermission
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
Zope 3 has a function zope.security.checkPermission which provides
......
......@@ -54,21 +54,21 @@ def test_size():
Set up:
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
>>> configure_zcml = '''
... <configure xmlns="http://namespaces.zope.org/zope"
... xmlns:five="http://namespaces.zope.org/five">
... <five:sizable class="Products.Five.testing.simplecontent.SimpleContent" />
... <five:sizable class="Products.Five.testing.fancycontent.FancyContent" />
... <five:sizable class="Products.Five.tests.testing.simplecontent.SimpleContent" />
... <five:sizable class="Products.Five.tests.testing.fancycontent.FancyContent" />
... <adapter
... for="Products.Five.testing.simplecontent.ISimpleContent"
... for="Products.Five.tests.testing.simplecontent.ISimpleContent"
... provides="zope.app.size.interfaces.ISized"
... factory="Products.Five.tests.test_size.SimpleContentSize"
... />
... <adapter
... for="Products.Five.testing.fancycontent.IFancyContent"
... for="Products.Five.tests.testing.fancycontent.IFancyContent"
... provides="zope.app.size.interfaces.ISized"
... factory="Products.Five.tests.test_size.FancyContentSize"
... />
......@@ -79,8 +79,8 @@ def test_size():
>>> zcml.load_config('meta.zcml', Products.Five)
>>> zcml.load_string(configure_zcml)
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.testing.fancycontent import manage_addFancyContent
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent
We have registered an ``ISized`` adapter for SimpleContent:
......
......@@ -23,9 +23,6 @@ def test_defaultView():
"""
Testing default view functionality
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> setUp()
Take a class Foo and an interface IFoo:
>>> class Foo:
......@@ -37,14 +34,14 @@ def test_defaultView():
Set up a default view for IFoo:
>>> from zope.app import zapi
>>> pres = zapi.getGlobalService('Presentation')
>>> from zope.component import provideAdapter
>>> from zope.component.interfaces import IDefaultViewName
>>> from zope.publisher.interfaces.browser import IBrowserRequest
and default view names for everything and IFoo objects in particular:
>>> pres.setDefaultViewName(None, IBrowserRequest, u'index.html')
>>> pres.setDefaultViewName(IFoo, IBrowserRequest, u'foo.html')
>>> provideAdapter(u'index.html', (None, IBrowserRequest), IDefaultViewName)
>>> provideAdapter(u'foo.html', (IFoo, IBrowserRequest), IDefaultViewName)
Now take a BrowserDefault for an instance of Foo::
......@@ -73,10 +70,10 @@ def test_defaultView():
>>> path
[u'foo.html']
Clean up adapter registry:
Clean up:
>>> tearDown()
>>> from zope.testing.cleanup import cleanUp
>>> cleanUp()
"""
def test_suite():
......
......@@ -13,11 +13,11 @@
##############################################################################
"""Test helpers
$Id: __init__.py 14470 2005-07-10 11:57:49Z philikon $
$Id$
"""
from Products.Five.testing.restricted import RestrictedPythonTestCase
from Products.Five.tests.testing.restricted import RestrictedPythonTestCase
from Products.Five.testing.folder import FiveTraversableFolder
from Products.Five.testing.folder import manage_addFiveTraversableFolder
from Products.Five.testing.folder import NoVerifyPasteFolder
from Products.Five.testing.folder import manage_addNoVerifyPasteFolder
from Products.Five.tests.testing.folder import FiveTraversableFolder
from Products.Five.tests.testing.folder import manage_addFiveTraversableFolder
from Products.Five.tests.testing.folder import NoVerifyPasteFolder
from Products.Five.tests.testing.folder import manage_addNoVerifyPasteFolder
......@@ -13,7 +13,7 @@
##############################################################################
"""Test content objects.
$Id: fancycontent.py 14470 2005-07-10 11:57:49Z philikon $
$Id$
"""
import Acquisition
from AccessControl import ClassSecurityInfo
......
......@@ -13,7 +13,7 @@
##############################################################################
"""Test folders
$Id: folder.py 14468 2005-07-10 11:39:32Z philikon $
$Id$
"""
from OFS.Folder import Folder
from OFS.interfaces import IFolder
......
......@@ -16,7 +16,7 @@
Based on Plone's RestrictedPythonTestCase, with kind permission by the
Plone developers.
$Id: restricted.py 14473 2005-07-10 13:02:21Z philikon $
$Id$
"""
from AccessControl import Unauthorized
from Testing.ZopeTestCase import ZopeTestCase
......
......@@ -13,7 +13,7 @@
##############################################################################
"""Simple content class(es) for browser tests
$Id: simplecontent.py 17810 2005-09-24 09:12:59Z efge $
$Id$
"""
from OFS.SimpleItem import SimpleItem
from Globals import InitializeClass
......@@ -37,19 +37,10 @@ class SimpleContent(Traversable, SimpleItem):
meta_type = 'Five SimpleContent'
security = ClassSecurityInfo()
afterAdd_called = False
beforeDelete_called = False
def __init__(self, id, title):
self.id = id
self.title = title
def manage_afterAdd(self, item, container):
self.afterAdd_called = True
def manage_beforeDelete(self, item, container):
self.beforeDelete_called = True
security.declarePublic('mymethod')
def mymethod(self):
return "Hello world"
......
......@@ -13,28 +13,29 @@
##############################################################################
"""Machinery for making things traversable through adaptation
$Id: traversable.py 18841 2005-10-23 09:57:38Z philikon $
$Id: traversable.py 19283 2005-10-31 17:43:51Z philikon $
"""
from zExceptions import NotFound
from zope.exceptions import NotFoundError
from zope.component import getView, ComponentLookupError
from zope.interface import implements
from zope.component import getMultiAdapter, ComponentLookupError
from zope.interface import implements, Interface
from zope.publisher.interfaces import ILayer
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.traversing.interfaces import ITraverser, ITraversable
from zope.app.traversing.adapters import DefaultTraversable
from zope.app.traversing.adapters import traversePathElement
from zope.app.publication.browser import setDefaultSkin
from zope.app.interface import queryType
from AccessControl import getSecurityManager
from Products.Five.security import newInteraction
_marker = object
class FakeRequest:
class FakeRequest(dict):
implements(IBrowserRequest)
def getPresentationSkin(self):
return None
def has_key(self, key):
return False
......@@ -69,16 +70,20 @@ class Traversable:
REQUEST = getattr(self, 'REQUEST', None)
if not IBrowserRequest.providedBy(REQUEST):
REQUEST = FakeRequest()
# set the default skin on the request if it doesn't have any
# layers set on it yet
if queryType(REQUEST, ILayer) is None:
setDefaultSkin(REQUEST)
# con Zope 3 into using Zope 2's checkPermission
newInteraction()
try:
return ITraverser(self).traverse(
path=[name], request=REQUEST).__of__(self)
except (ComponentLookupError, NotFoundError,
except (ComponentLookupError, LookupError,
AttributeError, KeyError, NotFound):
pass
try:
return getattr(self, name)
except AttributeError:
......@@ -100,8 +105,9 @@ class FiveTraversable(DefaultTraversable):
REQUEST = getattr(context, 'REQUEST', None)
if not IBrowserRequest.providedBy(REQUEST):
REQUEST = FakeRequest()
# Try to lookup a view first
setDefaultSkin(REQUEST)
# Try to lookup a view
try:
return getView(context, name, REQUEST)
return getMultiAdapter((context, REQUEST), Interface, name)
except ComponentLookupError:
pass
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<browser:page
for="*"
name="edit-markers.html"
template="edit_markers.pt"
class="Products.Five.utilities.browser.marker.EditView"
permission="zope2.ManageProperties"
/>
<browser:page
for="*"
name="manage_interfaces"
template="manage_interfaces.pt"
class="Products.Five.utilities.browser.marker.EditView"
permission="zope2.ManageProperties"
/>
</configure>
<html metal:use-macro="context/@@standard_macros/page">
<body>
<metal:slot metal:fill-slot="body">
<metal:macro metal:define-macro="heading">
<h1 i18n:translate="heading_edit_marker">Assign Marker Interfaces</h1>
</metal:macro>
<metal:macro metal:define-macro="main">
<p class="form-help formHelp" i18n:translate="">Change the behavior of this
object by adding or removing marker interfaces. You can choose one or more
interfaces to be added to the list of provided interfaces for this
object.</p>
<p class="form-help formHelp" i18n:translate="">A marker interface is used to
identify an instance of a piece of content. When in conjunction with Five,
this allows you to enable and disable views based on marker interfaces for
example.</p>
<form action="." method="post"
tal:attributes="action request/ACTUAL_URL">
<fieldset>
<legend i18n:translate="legend_provided">Provided Interfaces</legend>
<tal:loop tal:repeat="interface view/getInterfaceNames">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<label class="form-mono"
tal:content="interface/name" i18n:translate="">INTERFACE</label><br />
</tal:loop>
<tal:loop tal:repeat="interface view/getDirectlyProvidedNames">
<input type="checkbox" id="INTERFACE" name="remove:list"
tal:attributes="id interface/name; value interface/name" />
<label class="form-mono" for="INTERFACE" tal:attributes="for interface/name"
tal:content="interface/name" i18n:translate="">INTERFACE</label><br />
</tal:loop>
<tal:case tal:condition="view/getDirectlyProvidedNames">
<div class="formControls FormButtons">
<input class="form-element" type="submit" name="SAVE" value="Remove"
i18n:attributes="value" />
</div>
</tal:case>
</fieldset>
<fieldset>
<legend i18n:translate="legend_available_marker">Available Marker
Interfaces</legend>
<tal:loop tal:repeat="interface view/getAvailableInterfaceNames">
<input type="checkbox" id="INTERFACE" name="add:list"
tal:attributes="id interface/name; value interface/name" />
<label class="form-mono" for="INTERFACE" tal:attributes="for interface/name"
tal:content="interface/name" i18n:translate="">INTERFACE</label><br />
</tal:loop>
<div class="formControls FormButtons">
<input class="form-element" type="submit" name="SAVE" value="Add"
i18n:attributes="value" />
</div>
</fieldset>
</form>
</metal:macro>
</metal:slot>
</body>
</html>
<h1 tal:replace="structure context/manage_page_header">PAGE HEADER</h1>
<h2 tal:replace="structure context/manage_tabs">TABS</h2>
<style type="text/css">
fieldset {width:auto; float:left}
</style>
<metal:macro metal:use-macro="context/@@edit-markers.html/main" />
<h1 tal:replace="structure context/manage_page_footer">PAGE FOOTER</h1>
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Marker interfaces adapter views.
$Id$
"""
from Products.Five.utilities.interfaces import IMarkerInterfaces
class EditView:
"""Marker interface edit view.
"""
def __init__(self, context, request):
self.context = context
self.request = request
self.adapted = IMarkerInterfaces(context)
self.context_url = self.context.absolute_url()
def __call__(self, SAVE=None, add=(), remove=()):
if SAVE:
self.update(add, remove)
self.request.response.redirect(self.request.ACTUAL_URL)
return ''
return self.index()
def _getLinkToInterfaceDetailsView(self, interfaceName):
return (self.context_url +
'/views-details.html?iface=%s&type=zope.publisher.interfaces.browser.IBrowserRequest' % interfaceName)
def _getNameLinkDicts(self, interfaceNames):
return [dict(name=name,
link=self._getLinkToInterfaceDetailsView(name))
for name in interfaceNames]
def getAvailableInterfaceNames(self):
return self._getNameLinkDicts(
self.adapted.getAvailableInterfaceNames())
def getDirectlyProvidedNames(self):
return self._getNameLinkDicts(self.adapted.getDirectlyProvidedNames())
def getInterfaceNames(self):
return self._getNameLinkDicts(self.adapted.getInterfaceNames())
def update(self, add, remove):
# this could return errors
add = self.adapted.dottedToInterfaces(add)
remove = self.adapted.dottedToInterfaces(remove)
self.adapted.update(add=add, remove=remove)
##############################################################################
#
# ZopeTestCase
#
# COPY THIS FILE TO YOUR 'tests' DIRECTORY.
#
# This version of framework.py will use the SOFTWARE_HOME
# environment variable to locate Zope and the Testing package.
#
# If the tests are run in an INSTANCE_HOME installation of Zope,
# Products.__path__ and sys.path with be adjusted to include the
# instance's Products and lib/python directories respectively.
#
# If you explicitly set INSTANCE_HOME prior to running the tests,
# auto-detection is disabled and the specified path will be used
# instead.
#
# If the 'tests' directory contains a custom_zodb.py file, INSTANCE_HOME
# will be adjusted to use it.
#
# If you set the ZEO_INSTANCE_HOME environment variable a ZEO setup
# is assumed, and you can attach to a running ZEO server (via the
# instance's custom_zodb.py).
#
##############################################################################
#
# The following code should be at the top of every test module:
#
# import os, sys
# if __name__ == '__main__':
# execfile(os.path.join(sys.path[0], 'framework.py'))
#
# ...and the following at the bottom:
#
# if __name__ == '__main__':
# framework()
#
##############################################################################
__version__ = '0.2.3'
# Save start state
#
__SOFTWARE_HOME = os.environ.get('SOFTWARE_HOME', '')
__INSTANCE_HOME = os.environ.get('INSTANCE_HOME', '')
if __SOFTWARE_HOME.endswith(os.sep):
__SOFTWARE_HOME = os.path.dirname(__SOFTWARE_HOME)
if __INSTANCE_HOME.endswith(os.sep):
__INSTANCE_HOME = os.path.dirname(__INSTANCE_HOME)
# Find and import the Testing package
#
if not sys.modules.has_key('Testing'):
p0 = sys.path[0]
if p0 and __name__ == '__main__':
os.chdir(p0)
p0 = ''
s = __SOFTWARE_HOME
p = d = s and s or os.getcwd()
while d:
if os.path.isdir(os.path.join(p, 'Testing')):
zope_home = os.path.dirname(os.path.dirname(p))
sys.path[:1] = [p0, p, zope_home]
break
p, d = s and ('','') or os.path.split(p)
else:
print 'Unable to locate Testing package.',
print 'You might need to set SOFTWARE_HOME.'
sys.exit(1)
import Testing, unittest
execfile(os.path.join(os.path.dirname(Testing.__file__), 'common.py'))
# Include ZopeTestCase support
#
if 1: # Create a new scope
p = os.path.join(os.path.dirname(Testing.__file__), 'ZopeTestCase')
if not os.path.isdir(p):
print 'Unable to locate ZopeTestCase package.',
print 'You might need to install ZopeTestCase.'
sys.exit(1)
ztc_common = 'ztc_common.py'
ztc_common_global = os.path.join(p, ztc_common)
f = 0
if os.path.exists(ztc_common_global):
execfile(ztc_common_global)
f = 1
if os.path.exists(ztc_common):
execfile(ztc_common)
f = 1
if not f:
print 'Unable to locate %s.' % ztc_common
sys.exit(1)
# Debug
#
print 'SOFTWARE_HOME: %s' % os.environ.get('SOFTWARE_HOME', 'Not set')
print 'INSTANCE_HOME: %s' % os.environ.get('INSTANCE_HOME', 'Not set')
sys.stdout.flush()
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Unit tests for marker interface views.
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_editview():
"""
Set everything up:
>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()
>>> import Products.Five
>>> import Products.Five.utilities
>>> from Products.Five import zcml
>>> zcml.load_config('meta.zcml', Products.Five)
>>> zcml.load_config('permissions.zcml', Products.Five)
>>> zcml.load_config('configure.zcml', Products.Five.utilities)
>>> from Products.Five.utilities.browser.marker import EditView
>>> from Products.Five.tests.testing.simplecontent import SimpleContent
>>> obj = SimpleContent('foo', 'Foo').__of__(self.folder)
Create an EditView:
>>> view = EditView(obj, {})
>>> view.context.aq_inner is obj
True
>>> view.request
{}
>>> view.getAvailableInterfaceNames()
[]
>>> view.getDirectlyProvidedNames()
[]
>>> view.getInterfaceNames()
[...ISimpleContent...]
Try to add a marker interface that doesn't exist:
>>> view.update(('__builtin__.IFooMarker',), ())
Traceback (most recent call last):
...
ComponentLookupError...
Now create the marker interface:
>>> from Products.Five.tests.testing.simplecontent import ISimpleContent
>>> class IFooMarker(ISimpleContent): pass
>>> from zope.app.component.interface import provideInterface
>>> provideInterface('', IFooMarker)
>>> view.getAvailableInterfaceNames()
[...IFooMarker...]
>>> view.getDirectlyProvidedNames()
[]
And try again to add it to the object:
>>> view.update(('__builtin__.IFooMarker',), ())
>>> view.getAvailableInterfaceNames()
[]
>>> view.getDirectlyProvidedNames()
[...IFooMarker...]
And remove it again:
>>> view.update((), ('__builtin__.IFooMarker',))
>>> view.getAvailableInterfaceNames()
[...IFooMarker...]
>>> view.getDirectlyProvidedNames()
[]
Finally tear down:
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
<configure xmlns="http://namespaces.zope.org/zope">
<include package=".browser"/>
<adapter
for="*"
provides=".interfaces.IMarkerInterfaces"
factory=".marker.MarkerInterfacesAdapter"
permission="zope2.ManageProperties"
/>
</configure>
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Utility Interface Definitions.
$Id$
"""
from zope.interface import Interface
class IReadInterface(Interface):
def getDirectlyProvided():
"""List the interfaces directly implemented by the object.
"""
def getDirectlyProvidedNames():
"""List the names of interfaces directly implemented by the object.
"""
def getAvailableInterfaces():
"""List the marker interfaces available for the object.
"""
def getAvailableInterfaceNames():
"""List the names of marker interfaces available for the object.
"""
def getInterfaces():
"""List interfaces provided by the class of the object.
"""
def getInterfaceNames():
"""List the names of interfaces provided by the class of the object.
"""
def getProvided():
"""List interfaces provided by the object.
"""
def getDirectlyProvidedNames():
"""List the names of interfaces provided by the object.
"""
class IWriteInterface(Interface):
def update(add=(), remove=()):
"""Update directly provided interfaces of the object.
"""
def mark(interface):
"""Add interface to interfaces the object directly provides.
"""
def erase(interface):
"""Remove interfaces from interfaces the object directly provides.
"""
class IMarkerInterfaces(IReadInterface, IWriteInterface):
"""Provides methods for inspecting and assigning marker interfaces.
"""
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Marker interfaces adapter.
Allows for arbitrary application of marker interfaces to objects.
$Id$
"""
from sets import Set
from zope.interface import implements
from zope.interface import implementedBy
from zope.interface import directlyProvidedBy
from zope.interface import directlyProvides
from zope.interface import providedBy
from zope.interface.interfaces import IInterface
from zope.app.component.interface import getInterface, interfaceToName
from zope.app.component.interface import searchInterface
from interfaces import IMarkerInterfaces
def interfaceStringCheck(f):
def wrapper(ob, interface):
if isinstance(interface, str):
interface = getInterface(ob, interface)
return f(ob, interface)
return wrapper
def mark(ob, interface):
directlyProvides(ob, directlyProvidedBy(ob), interface)
def erase(ob, interface):
directlyProvides(ob, directlyProvidedBy(ob)-interface)
mark = interfaceStringCheck(mark)
erase = interfaceStringCheck(erase)
class MarkerInterfacesAdapter(object):
implements(IMarkerInterfaces)
mark = staticmethod(mark)
erase = staticmethod(erase)
def __init__(self, context):
self.context = context
def dottedToInterfaces(self, seq):
return [getInterface(self.context, dotted) for dotted in seq]
def getDirectlyProvided(self):
return directlyProvidedBy(self.context)
def getDirectlyProvidedNames(self):
return self._getInterfaceNames(self.getDirectlyProvided())
def getAvailableInterfaces(self):
results = []
todo = list(providedBy(self.context))
done = []
while todo:
interface = todo.pop()
done.append(interface)
for base in interface.__bases__:
if base not in todo and base not in done:
todo.append(base)
markers = self._getDirectMarkersOf(interface)
for interface in markers:
if (interface not in results
and not interface.providedBy(self.context)):
results.append(interface)
todo += markers
return tuple(results)
def getAvailableInterfaceNames(self):
names = self._getInterfaceNames(self.getAvailableInterfaces())
names.sort()
return names
def getInterfaces(self):
return tuple(implementedBy(self.context.__class__))
def getInterfaceNames(self):
return self._getInterfaceNames(self.getInterfaces())
def getProvided(self):
return providedBy(self.context)
def getProvidedNames(self):
return self._getInterfaceNames(self.getProvided())
def update(self, add=(), remove=()):
"""Currently update adds and then removes, rendering duplicate null.
"""
marker_ifaces = self.getAvailableInterfaces()
if len(add):
[mark(self.context, interface)
for interface in Set(marker_ifaces) & Set(add)]
direct_ifaces = self.getDirectlyProvided()
if len(remove):
[erase(self.context, interface)
for interface in Set(direct_ifaces) & Set(remove)]
def _getInterfaceNames(self, interfaces):
return [interfaceToName(self, iface) for iface in interfaces]
def _getDirectMarkersOf(self, base):
"""Get empty interfaces directly inheriting from the given one.
"""
results = []
interfaces = searchInterface(None, base=base)
for interface in interfaces:
# There are things registered with the interface service
# that are not interfaces. Yay!
if not IInterface.providedBy(interface):
continue
if base in interface.__bases__ and not interface.names():
results.append(interface)
results.sort()
return tuple(results)
......@@ -17,10 +17,10 @@ $Id: viewable.py 14595 2005-07-12 21:26:12Z philikon $
"""
import inspect
from zExceptions import NotFound
from zope.exceptions import NotFoundError
from zope.component import getView, getDefaultViewName, ComponentLookupError
from zope.component import ComponentLookupError
from zope.interface import implements
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.zapi import getDefaultViewName
from Products.Five.traversable import FakeRequest
from Products.Five.interfaces import IBrowserDefault
......
......@@ -13,11 +13,6 @@
import os, sys, unittest
#XXX utility service needed for Zope X3 3.0. This will go away before
# when we switch to Zope 3.2 and thus be removed before the release --
# philiKON
import zope.app.tests.placelesssetup
from Products.PageTemplates.tests import util
from Products.PageTemplates.PageTemplate import PageTemplate
from Products.PageTemplates.GlobalTranslationService import \
......@@ -154,22 +149,12 @@ class HTMLTests(unittest.TestCase):
self.assert_expected_unicode(self.folder.t, 'CheckUnicodeInserts.html')
def checkI18nTranslate(self):
#XXX utility service needed for Zope X3 3.0. This will go
# away before when we switch to Zope 3.2 and thus be removed
# before the release -- philiKON
zope.app.tests.placelesssetup.setUp()
self.assert_expected(self.folder.t, 'CheckI18nTranslate.html')
zope.app.tests.placelesssetup.tearDown()
def checkI18nTranslateHooked(self):
#XXX utility service needed for Zope X3 3.0. This will go
# away before when we switch to Zope 3.2 and thus be removed
# before the release -- philiKON
zope.app.tests.placelesssetup.setUp()
old_ts = setGlobalTranslationService(TestTranslationService())
self.assert_expected(self.folder.t, 'CheckI18nTranslateHooked.html')
setGlobalTranslationService(old_ts)
zope.app.tests.placelesssetup.tearDown()
def test_suite():
return unittest.makeSuite(HTMLTests, 'check')
......
......@@ -330,6 +330,14 @@ class PythonScript(Script, Historical, Cacheable):
if item is self:
self._filepath = self.get_filepath()
def manage_beforeDelete(self, item, container):
# shut up deprecation warnings
pass
def manage_afterClone(self, item):
# shut up deprecation warnings
pass
def get_filepath(self):
return self.meta_type + ':' + '/'.join(self.getPhysicalPath())
......
......@@ -26,8 +26,10 @@ class VHMRegressions(unittest.TestCase):
transaction.begin()
self.app = makerequest(Zope2.app())
try:
#self.app.manage_addProduct['SiteAccess'].manage_addVirtualHostMonster('VHM')
# now we have a VHM as virtual_hosting per default
if not hasattr(self.app, 'virtual_hosting'):
# If ZopeLite was imported, we have no default virtual host monster
from Products.SiteAccess.VirtualHostMonster import manage_addVirtualHostMonster
manage_addVirtualHostMonster(self.app, 'virtual_hosting')
self.app.manage_addFolder('folder')
self.app.folder.manage_addDTMLMethod('doc', '')
self.app.REQUEST.set('PARENTS', [self.app])
......
......@@ -22,6 +22,10 @@ class SiteErrorLogTests(unittest.TestCase):
transaction.begin()
self.app = makerequest(Zope2.app())
try:
if not hasattr(self.app, 'error_log'):
# If ZopeLite was imported, we have no default error_log
from Products.SiteErrorLog.SiteErrorLog import SiteErrorLog
self.app._setObject('error_log', SiteErrorLog())
self.app.manage_addDTMLMethod('doc', '')
except:
self.tearDown()
......@@ -41,7 +45,10 @@ class SiteErrorLogTests(unittest.TestCase):
self.assert_(self.app.__error_log__ == sel_ob)
# Right now there should not be any entries in the log
self.assertEquals(len(sel_ob.getLogEntries()), 0)
# but if another test fails and leaves something in the
# log (which belongs to app , we get a spurious error here.
# There's no real point in testing this anyway.
#self.assertEquals(len(sel_ob.getLogEntries()), 0)
def testSimpleException(self):
# Grab the Site Error Log and make sure it's empty
......
......@@ -17,7 +17,7 @@ static char Record_module_documentation[] =
"\n$Id: _Record.c,v 1.2 2003/11/28 16:46:36 jim Exp $"
;
#include "ExtensionClass.h"
#include "ExtensionClass/ExtensionClass.h"
/* ----------------------------------------------------- */
......
......@@ -22,17 +22,6 @@ from TALDefs import NAME_RE, TALESError, ErrorInfo
from ITALES import ITALESCompiler, ITALESEngine
from DocumentTemplate.DT_Util import ustr
IDomain = None
try:
from Zope2.I18n.ITranslationService import ITranslationService
from Zope2.I18n.IDomain import IDomain
except ImportError:
pass
if IDomain is None:
# Before 2.7, or not in Zope
class ITranslationService: pass
class IDomain: pass
class _Default:
pass
Default = _Default()
......@@ -234,7 +223,6 @@ class Iterator:
return 1
class DummyDomain:
__implements__ = IDomain
def translate(self, msgid, mapping=None, context=None,
target_language=None, default=None):
......@@ -246,7 +234,9 @@ class DummyDomain:
# things back together.
# simulate an unknown msgid by returning None
text = msgid
if msgid == "don't translate me":
if default is not None:
text = default
else:
text = msgid.upper()
......@@ -257,7 +247,6 @@ class DummyDomain:
return cre.sub(repl, text)
class DummyTranslationService:
__implements__ = ITranslationService
def translate(self, domain, msgid, mapping=None, context=None,
target_language=None, default=None):
......
......@@ -24,7 +24,12 @@ from StringIO import StringIO
from DocumentTemplate.DT_Util import ustr
from ZODB.POSException import ConflictError
from zope.i18nmessageid import MessageID
# BBB 2005/10/10 -- MessageIDs are to be removed for Zope 3.3
import zope.deprecation
zope.deprecation.__show__.off()
from zope.i18nmessageid import Message, MessageID
zope.deprecation.__show__.on()
from TALDefs import attrEscape, TAL_VERSION, METALError
from TALDefs import isCurrentVersion
from TALDefs import getProgramVersion, getProgramMode
......@@ -32,7 +37,7 @@ from TALGenerator import TALGenerator
from TranslationContext import TranslationContext
# This will become (MessageID, Message) when we use Zope 3.1 as a base:
I18nMessageTypes = MessageID
I18nMessageTypes = Message, MessageID
# TODO: In Python 2.4 we can use frozenset() instead of dict.fromkeys()
BOOLEAN_HTML_ATTRS = dict.fromkeys([
......
......@@ -28,7 +28,7 @@ from TAL.TALInterpreter import TALInterpreter
from TAL.DummyEngine import DummyEngine, DummyTranslationService
from TAL.TALInterpreter import interpolate
from TAL.tests import utils
from zope.i18nmessageid import MessageID
from zope.i18nmessageid import Message
class TestCaseBase(unittest.TestCase):
......@@ -67,10 +67,10 @@ class I18NCornerTestCase(TestCaseBase):
def setUp(self):
self.engine = DummyEngine()
self.engine.setLocal('foo', MessageID('FoOvAlUe', 'default'))
self.engine.setLocal('foo', Message('FoOvAlUe', 'default'))
self.engine.setLocal('bar', 'BaRvAlUe')
self.engine.setLocal('raw', ' \tRaW\n ')
self.engine.setLocal('noxlt', MessageID("don't translate me"))
self.engine.setLocal('noxlt', Message("don't translate me"))
def _check(self, program, expected):
result = StringIO()
......
......@@ -14,11 +14,13 @@
$Id$
"""
from urllib import quote
import xmlrpc
from zExceptions import Forbidden
from zope.event import notify
from zope.app.publication.interfaces import EndRequestEvent
UNSPECIFIED_ROLES=''
try:
......@@ -83,6 +85,7 @@ class BaseRequest:
def close(self):
self.other.clear()
self._held=None
notify(EndRequestEvent(None, self))
def processInputs(self):
"""Do any input processing that could raise errors
......
......@@ -1339,16 +1339,6 @@ class HTTPRequest(BaseRequest):
def taintWrapper(self, enabled=TAINTING_ENABLED):
return enabled and TaintRequestWrapper(self) or self
# Used by Five
def getPresentationSkin(self):
"""see zope.component.interfaces.IPresentationRequest"""
return getattr(self, '_presentation_skin', None)
def setPresentationSkin(self, skin):
"""see zope.publisher.interfaces.IPublicationRequest"""
self._presentation_skin = skin
def getURL(self):
return self.URL
......
# zpkg config file
#
build-application yes
collect-dependencies yes
resource-map Zope2.map
default-collection Zope2-src
# These packages are the Zope 2 components.
#
docutils ../lib/python/docutils
pytz ../lib/python/pytz
zodbcode ../lib/python/zodbcode
zope ../lib/python/zope
# Child packages of Zope are handled separately when constructing Zope
# distributions; this tells where to find all of them.
zope.* ../lib/python/zope/
# These packages are copied from the ZConfig, zdaemon, and ZODB projects:
#
BTrees ../lib/python/BTrees
persistent ../lib/python/persistent
transaction ../lib/python/transaction
ThreadedAsync ../lib/python/ThreadedAsync
ZEO ../lib/python/ZEO
ZODB ../lib/python/ZODB
RestrictedPython ../lib/python/RestrictedPython
ZConfig ../lib/python/ZConfig
zdaemon ../lib/python/zdaemon
AccessControl ../lib/python/AccessControl
Acquisition ../lib/python/Acquisition
App ../lib/python/App
ComputedAttribute ../lib/python/ComputedAttribute
DateTime ../lib/python/DateTime
DocumentTemplate ../lib/python/DocumentTemplate
ExtensionClass ../lib/python/ExtensionClass
Globals ../lib/python/Globals.py
HelpSys ../lib/python/HelpSys
ImageFile ../lib/python/ImageFile.py
Interface ../lib/python/Interface
Lifetime ../lib/python/Lifetime.py
MethodObject ../lib/python/MethodObject
Missing ../lib/python/Missing
MultiMapping ../lib/python/MultiMapping
OFS ../lib/python/OFS
Persistence ../lib/python/Persistence
Products ../lib/python/Products
Record ../lib/python/Record
Shared ../lib/python/Shared
Signals ../lib/python/Signals
StructuredText ../lib/python/StructuredText
TAL ../lib/python/TAL
Testing ../lib/python/Testing
ThreadLock ../lib/python/ThreadLock
TreeDisplay ../lib/python/TreeDisplay
ZClasses ../lib/python/ZClasses
ZPublisher ../lib/python/ZPublisher
ZServer ../lib/python/ZServer
ZTUtils ../lib/python/ZTUtils
Zope ../lib/python/Zope.py
Zope2 ../lib/python/Zope2
ZopeUndo ../lib/python/ZopeUndo
initgroups ../lib/python/initgroups
nt_svcutils ../lib/python/nt_svcutils
reStructuredText ../lib/python/reStructuredText
tempstorage ../lib/python/tempstorage
webdav ../lib/python/webdav
zExceptions ../lib/python/zExceptions
zLOG ../lib/python/zLOG
# These packages are the release collections based on the Zope 2
# project; they define what goes into the Zope 2 and related
# releases.
#
Zope2-src ../releases/Zope2
# Things listed here represent features we want to include in the
# distribution.
#
# We'll start with a micro distribution, and add the commented out
# things once we're confident the core is working.
AccessControl
Acquisition
App
ComputedAttribute
DateTime
DocumentTemplate
ExtensionClass
Globals
HelpSys
ImageFile
Interface
Lifetime
MethodObject
Missing
MultiMapping
OFS
Persistence
Products
Record
RestrictedPython
Shared
Signals
StructuredText
TAL
Testing
ThreadLock
TreeDisplay
ZClasses
ZPublisher
ZServer
ZTUtils
Zope
Zope2
ZopeUndo
docutils
initgroups
nt_svcutils
reStructuredText
tempstorage
webdav
zExceptions
zLOG
zope.app
# zope.app depends for us on:
# - ZODB
# - persistent
# - transaction
# - zdaemon
# - zodbcode
# - ZConfig (indirectly)
# - ThreadedAsync (indirectly)
# - ZConfig (indirectly)
# - zdaemon (indirectly)
# - pytz (indirectly)
<load>
README.txt svn://svn.zope.org/repos/main/Zope/tags/*/README.txt
ZopePublicLicense.txt svn://svn.zope.org/repos/main/Zope/tags/*/ZopePublicLicense.txt
bin/mkzopeinstance.py svn://svn.zope.org/repos/main/Zope/tags/*/utilities/mkzopeinstance.py
bin/mkzeoinstance.py svn://svn.zope.org/repos/main/Zope/tags/*/utilities/mkzeoinstance.py
doc svn://svn.zope.org/repos/main/Zope/tags/*/doc/
skel svn://svn.zope.org/repos/main/Zope/tags/*/skel/
</load>
<distribution>
README.txt
ZopePublicLicense.txt
test.py
</distribution>
documentation doc/*.txt
script utilities/*
script zopetest
<data-files .>
skel
</data-files>
#!/usr/bin/env python2.4
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test script for running unit tests in a distribution root.
The functional tests can't be run since we don't have enough of the
package configuration in a usable state. The functional tests can be
run from an installation.
$Id$
"""
import sys, os
from distutils.util import get_platform
PLAT_SPEC = "%s-%s" % (get_platform(), sys.version[0:3])
here = os.path.dirname(os.path.realpath(__file__))
lib = os.path.join(here, "build", "lib." + PLAT_SPEC)
sys.path.append(lib)
import zope.app.testing.test
if __name__ == '__main__':
args = sys.argv[:1] + ["-ul", lib] + sys.argv[1:]
zope.app.testing.test.process_args(args)
#!/bin/env python2.4
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test script to run the unit and functional tests in a Zope installation.
$Id$
"""
import sys, os
here = os.path.dirname(os.path.realpath(__file__))
here = os.path.dirname(here)
if sys.platform in ("win32",):
lib = os.path.join(here, "Lib", "site-packages")
else:
lib = os.path.join(here, "lib", "python")
sys.path.append(lib)
ftesting = os.path.join(here, "zopeskel", "etc", "ftesting.zcml")
import zope.app.tests.test
zope.app.tests.test.FTESTING = ftesting
if __name__ == '__main__':
args = sys.argv[:1] + ["-l", lib] + sys.argv[1:]
zope.app.tests.test.process_args(args)
#!/usr/bin/env python2.3
##############################################################################
#############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# 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
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
Distutils setup for Zope
In-place building
This builds extension modules in-place, much like build_extensions.py
does. Use 'setup.py' like this::
python setup.py build_ext -i
Installation
This builds extension modules, compiles python modules, and installs
everything needed to support Zope instances in the directory of
your choosing. For example, to use '/usr/local/lib/zope'::
python setup.py install \
--home=/usr/local/lib/zope \
--install-platlib=/usr/local/lib/zope \
--install-purelib=/usr/local/lib/zope
"""
import glob
import os
import site
import sys
import distutils.core
# Override install_data to install into module directories, and to support
# globbing on data_files.
from distutils.command.install import install
from distutils.command.install_data import install_data
from distutils.util import convert_path
class ZopeInstallData(install_data):
def finalize_options(self):
self.set_undefined_options('install',
('install_purelib', 'install_dir'),
('root', 'root'),
('force', 'force'),
)
def run(self):
self.mkpath(self.install_dir)
for f in self.data_files:
if isinstance(f, str):
# it's a simple file, so copy it
f = convert_path(f)
gl = glob.glob(f)
if len(gl) == 0:
raise distutils.core.DistutilsFileError, \
"can't copy '%s': no matching files" % f
for g in gl:
if os.path.isfile(g):
if self.warn_dir:
self.warn("setup script did not provide a "
"directory for '%s' -- installing "
"right in '%s'" %
(g, self.install_dir))
(out, _) = self.copy_file(g, self.install_dir)
self.outfiles.append(out)
else:
# it's a tuple with path to install to and a list of files
dir = convert_path(f[0])
if not os.path.isabs(dir):
dir = os.path.join(self.install_dir, dir)
elif self.root:
dir = change_root(self.root, dir)
self.mkpath(dir)
for data in f[1]:
data = convert_path(data)
gl = glob.glob(data)
if len(gl) == 0:
raise distutils.core.DistutilsFileError, \
"can't copy '%s': no matching files" % data
for g in gl:
if os.path.isfile(g):
(out, _) = self.copy_file(g, dir)
self.outfiles.append(out)
# We create a custom "install scheme" that works the same way on all
# platforms. We do this in order to prevent distutils from trying to
# guess where to put our files on a per-platform basis.
ZOPE_INSTALL_SCHEME = {
'purelib': '$base/lib/python',
'platlib': '$base/lib/python',
'headers': '$base/lib/python',
'scripts': '$base/bin',
'data' : '$base/lib/python',
}
class ZopeInstall(install):
# give distutils install step knowledge about build file placement options
user_options = install.user_options + [
('build-base=', None, 'base directory for build library'),
('build-lib=', None, 'build directory for all distribution'),
('build-scripts=', None, 'build directory for scripts'),
('build-temp=', None, 'temporary build directory'),
]
build_scripts = None
build_temp = None
def run(self):
""" Override run to pass along build location info so
we can use custom build directories """
build = self.distribution.get_command_obj('build')
build.build_base = self.build_base
build.build_lib = self.build_lib
build.build_scripts = self.build_scripts
build.build_temp = self.build_temp
install.run(self)
def select_scheme(self, name):
"""
Override the default platform installation schemes, ignoring whatever
'name' is passed in. For our purposes, we want to put all library,
header, and data into [install_base]/lib/python. Comment
this method out to achieve distutils-standard platform-specific
behavior for 'setup.py install'. This is most useful if you set the
[install-base] by using the '--prefix' or '--home' flags on the
setup.py install command line. Otherwise, all Zope software
will probably be installed to your Python's 'lib/python' directory.
"""
scheme = ZOPE_INSTALL_SCHEME
import distutils.command.install
for key in distutils.command.install.SCHEME_KEYS:
attrname = 'install_' + key
if getattr(self, attrname) is None:
setattr(self, attrname, scheme[key])
class ZopeDistribution(distutils.core.Distribution):
def __init__(self, attrs):
distutils.core.Distribution.__init__(self, attrs)
self.cmdclass["install"] = ZopeInstall
self.cmdclass["install_data"] = ZopeInstallData
# presumes this script lives in the base dir
BASE_DIR=os.path.dirname(os.path.abspath(sys.argv[0]))
AUTHOR = 'Zope Corporation and Contributors'
# Most modules are in lib/python in the source distribution
PACKAGES_ROOT = os.path.join(BASE_DIR, 'lib', 'python')
os.chdir(PACKAGES_ROOT)
# Most of this is ripped from the Zope 3 setup.py.
from distutils import dir_util
from distutils.command.build import build as buildcmd
from distutils.command.build_ext import build_ext
from distutils.command.install_lib import install_lib as installcmd
from distutils.core import setup
from distutils.dist import Distribution
from distutils.extension import Extension
# This class serves multiple purposes. It walks the file system looking for
# auxiliary files that distutils doesn't install properly, and it actually
# copies those files (when hooked into by distutils). It also walks the file
# system looking for candidate packages for distutils to install as normal.
# The key here is that the package must have an __init__.py file.
class Finder:
def __init__(self, ignore, prefix):
self._files = []
self._pkgs = {}
self._ignore = ignore
# We're finding packages in lib/python in the source dir, but we're
# copying them directly under build/lib.<plat>. So we need to lop off
# the prefix when calculating the package names from the file names.
self._plen = len(prefix) + 1
def visit(self, dir, files):
# First see if this is one of the packages we want to add, or if
# we're really skipping this package.
if '__init__.py' in files:
aspkg = dir[self._plen:].replace(os.sep, '.')
self._pkgs[aspkg] = True
ignore = ('.py',) + self._ignore
else:
ignore = self._ignore
# Add any extra files we're interested in
for file in files:
base, ext = os.path.splitext(file)
if ext not in ignore:
self._files.append(os.path.join(dir, file))
def copy_files(self, cmd, outputbase):
for file in self._files:
dest = os.path.join(outputbase, file[self._plen:])
# Make sure the destination directory exists
dir = os.path.dirname(dest)
if not os.path.exists(dir):
dir_util.mkpath(dir)
cmd.copy_file(file, dest)
def get_packages(self):
return self._pkgs.keys()
def remove_stale_bytecode(arg, dirname, names):
names = map(os.path.normcase, names)
for name in names:
if name.endswith(".pyc") or name.endswith(".pyo"):
srcname = name[:-1]
if srcname not in names:
fullname = os.path.join(dirname, name)
print "Removing stale bytecode file", fullname
os.unlink(fullname)
# Create the finder instance, which will be used in lots of places. `finder'
# is the global we're most interested in.
IGNORE_EXTS = ('.pyc', '.pyo', '.c', '.h', '.so', '.cfg')
finder = Finder(IGNORE_EXTS, PACKAGES_ROOT)
for dirpath, dirnames, filenames in os.walk(PACKAGES_ROOT):
if not '.svn' in dirpath:
finder.visit(dirpath, filenames)
packages = finder.get_packages()
# Distutils hook classes
class MyBuilder(buildcmd):
def run(self):
os.path.walk(os.curdir, remove_stale_bytecode, None)
buildcmd.run(self)
finder.copy_files(self, self.build_lib)
class MyExtBuilder(build_ext):
# Override the default build_ext to remove stale bytecodes.
# Technically, removing bytecode has nothing to do with
# building extensions, but Zope's the build_ext -i variant
# is used to build Zope in place.
def run(self):
os.path.walk(os.curdir, remove_stale_bytecode, None)
build_ext.run(self)
class MyLibInstaller(installcmd):
def run(self):
installcmd.run(self)
finder.copy_files(self, self.install_dir)
class MyDistribution(Distribution):
# To control the selection of MyLibInstaller and MyPyBuilder, we
# have to set it into the cmdclass instance variable, set in
# Distribution.__init__().
def __init__(self, *attrs):
Distribution.__init__(self, *attrs)
self.cmdclass['install'] = ZopeInstall
self.cmdclass['build'] = MyBuilder
self.cmdclass['build_ext'] = MyExtBuilder
self.cmdclass['install_lib'] = MyLibInstaller
# added . to EXTENSIONCLASS_INCLUDEDIRS in order to be able to compile
# ZODB HEAD code (which uses qualified paths to find header files). This
# should be a temporary change, thrown away once we use zpkg to package
# Zope.
EXTENSIONCLASS_INCLUDEDIRS = ['ExtensionClass', '.']
# All extension modules must be listed here.
ext_modules = [
# AccessControl
Extension(name='AccessControl.cAccessControl',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['Acquisition'],
sources=['AccessControl/cAccessControl.c'],
depends=['ExtensionClass/ExtensionClass.h',
'ExtensionClass/pickle/pickle.c',
'Acquisition/Acquisition.h']),
# BTrees
Extension(name='BTrees._OOBTree',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['persistent'],
sources=['BTrees/_OOBTree.c']),
Extension(name='BTrees._OIBTree',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['persistent'],
sources=['BTrees/_OIBTree.c']),
Extension(name='BTrees._IIBTree',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['persistent'],
define_macros=[('EXCLUDE_INTSET_SUPPORT', None)],
sources=['BTrees/_IIBTree.c']),
Extension(name='BTrees._IOBTree',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['persistent'],
define_macros=[('EXCLUDE_INTSET_SUPPORT', None)],
sources=['BTrees/_IOBTree.c']),
Extension(name='BTrees._IFBTree',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['persistent'],
define_macros=[('EXCLUDE_INTSET_SUPPORT', None)],
sources=['BTrees/_IFBTree.c']),
Extension(name='BTrees._fsBTree',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['persistent'],
define_macros=[('EXCLUDE_INTSET_SUPPORT', None)],
sources=['BTrees/_fsBTree.c']),
# DocumentTemplate
Extension(name='DocumentTemplate.cDocumentTemplate',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
sources=['DocumentTemplate/cDocumentTemplate.c']),
# ExtensionClass
Extension(name='ExtensionClass._ExtensionClass',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
sources=["ExtensionClass/_ExtensionClass.c"],
depends=["ExtensionClass/ExtensionClass.h"]),
Extension(name='Acquisition._Acquisition',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
sources=["Acquisition/_Acquisition.c"],
depends=["ExtensionClass/ExtensionClass.h",
"Acquisition/Acquisition.h"]),
Extension(name='MethodObject._MethodObject',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
sources=["MethodObject/_MethodObject.c"],
depends=["ExtensionClass/ExtensionClass.h"]),
Extension(name='MultiMapping._MultiMapping',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
sources=["MultiMapping/_MultiMapping.c"],
depends=["ExtensionClass/ExtensionClass.h"]),
Extension(name='ThreadLock._ThreadLock',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
sources=["ThreadLock/_ThreadLock.c"],
depends=["ExtensionClass/ExtensionClass.h"]),
Extension(name='Missing._Missing',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
sources=["Missing/_Missing.c"],
depends=["ExtensionClass/ExtensionClass.h"]),
Extension(name='Record._Record',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
sources=["Record/_Record.c"],
depends=["ExtensionClass/ExtensionClass.h"]),
Extension(name='ComputedAttribute._ComputedAttribute',
include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
sources=["ComputedAttribute/_ComputedAttribute.c"],
depends=["ExtensionClass/ExtensionClass.h"]),
# initgroups
Extension(name='initgroups._initgroups',
sources=['initgroups/_initgroups.c']),
# indexes
Extension(name='Products.PluginIndexes.TextIndex.Splitter.ZopeSplitter.ZopeSplitter',
sources=['Products/PluginIndexes/TextIndex/Splitter/ZopeSplitter/src/ZopeSplitter.c']),
Extension(name='Products.PluginIndexes.TextIndex.Splitter.ISO_8859_1_Splitter.ISO_8859_1_Splitter',
sources=['Products/PluginIndexes/TextIndex/Splitter/ISO_8859_1_Splitter/src/ISO_8859_1_Splitter.c']),
Extension(name='Products.PluginIndexes.TextIndex.Splitter.UnicodeSplitter.UnicodeSplitter',
sources=['Products/PluginIndexes/TextIndex/Splitter/UnicodeSplitter/src/UnicodeSplitter.c']),
Extension(name='Products.ZCTextIndex.stopper',
sources=['Products/ZCTextIndex/stopper.c']),
Extension(name='Products.ZCTextIndex.okascore',
sources=['Products/ZCTextIndex/okascore.c']),
#ZODB
Extension(name = 'persistent.cPersistence',
include_dirs = ['persistent'],
sources= ['persistent/cPersistence.c',
'persistent/ring.c'],
depends = ['persistent/cPersistence.h',
'persistent/ring.h',
'persistent/ring.c']
),
Extension(name = 'Persistence._Persistence',
include_dirs = ['persistent', 'ExtensionClass'],
sources = ['Persistence/_Persistence.c'],
depends = ['persistent/cPersistence.h',
'ExtensionClass/ExtensionClass.h']
),
Extension(name = 'persistent.cPickleCache',
include_dirs = ['persistent'],
sources= ['persistent/cPickleCache.c',
'persistent/ring.c'],
depends = ['persistent/cPersistence.h',
'persistent/ring.h',
'persistent/ring.c']
),
Extension(name = 'persistent.TimeStamp',
sources= ['persistent/TimeStamp.c']
),
Extension(name = 'ZODB.winlock',
sources = ['ZODB/winlock.c']
),
#zope
Extension("zope.proxy._zope_proxy_proxy",
["zope/proxy/_zope_proxy_proxy.c"],
include_dirs = ["zope/proxy"],
depends = ["zope/proxy/proxy.h"]),
Extension("zope.security._proxy", ["zope/security/_proxy.c"],
include_dirs = ["zope/proxy"],
depends = ["zope/proxy/proxy.h"]),
Extension("zope.security._zope_security_checker",
["zope/security/_zope_security_checker.c"],
include_dirs = [],
depends = []),
Extension("zope.interface._zope_interface_coptimizations",
["zope/interface/_zope_interface_coptimizations.c"]),
Extension("zope.hookable._zope_hookable",
["zope/hookable/_zope_hookable.c"]),
Extension("zope.thread._zope_thread",
["zope/thread/_zope_thread.c"]),
Extension("zope.app.container._zope_app_container_contained",
["zope/app/container/_zope_app_container_contained.c"],
include_dirs = ["persistent",
"zope/proxy",
"zope/app/container"],
depends = [
"persistent/cPersistence.h",
"zope/proxy/_zope_proxy_proxy.c",
]),
]
# We're using the module docstring as the distutils descriptions.
doclines = __doc__.split("\n")
setup(name='Zope',
author=AUTHOR,
version="2.8",
maintainer="Zope Corporation",
maintainer_email="zope-dev@zope.org",
url = "http://www.zope.org/",
ext_modules = ext_modules,
license = "http://www.zope.org/Resources/ZPL",
platforms = ["any"],
description = doclines[0],
long_description = "\n".join(doclines[2:]),
packages = packages,
distclass = MyDistribution,
)
# The rest of these modules live in the root of the source tree
os.chdir(BASE_DIR)
here = os.path.dirname(os.path.abspath(__file__))
buildsupport = os.path.join(here, "buildsupport")
IGNORE_NAMES = (
'CVS', '.svn', # Revision Control Directories
)
# Add 'buildsupport' to sys.path and process *.pth files from 'buildsupport':
last = len(sys.path)
site.addsitedir(buildsupport)
if len(sys.path) > last:
# Move all appended directories to the start.
# Make sure we use ZConfig shipped with the distribution
new = sys.path[last:]
del sys.path[last:]
sys.path[:0] = new
def skel_visit(skel, dirname, names):
for ignore in IGNORE_NAMES:
if ignore in names:
names.remove(ignore)
L = []
for name in names:
if os.path.isfile(os.path.join(dirname, name)):
L.append("%s/%s" % (dirname, name))
skel.append(("../../" + dirname, L))
import zpkgsetup.package
import zpkgsetup.publication
import zpkgsetup.setup
installed_data_files = [
["../../doc", ['doc/*.txt']],
["../../import", ['import/*.zexp']],
["../../bin", ['utilities/README.txt']],
]
os.path.walk("skel", skel_visit, installed_data_files)
context = zpkgsetup.setup.SetupContext(
"Zope", "2.8.42", __file__)
setup(
name='Zope',
author=AUTHOR,
context.load_metadata(
os.path.join(here, "releases", "Zope2",
zpkgsetup.publication.PUBLICATION_CONF))
data_files=installed_data_files,
scripts=["utilities/mkzeoinstance.py", "utilities/mkzopeinstance.py",
"utilities/check_catalog.py", "utilities/load_site.py",
"utilities/requestprofiler.py", "utilities/zpasswd.py",
"utilities/copyzopeskel.py", "utilities/reindex_catalog.py",
"utilities/compilezpy.py", "utilities/decompilezpy.py",
"utilities/ZODBTools/timeout.py", "utilities/ZODBTools/analyze.py",
"utilities/ZODBTools/checkbtrees.py", "utilities/ZODBTools/fsdump.py",
"utilities/ZODBTools/fsrefs.py" , "utilities/ZODBTools/fstail.py",
"utilities/ZODBTools/fstest.py", "utilities/ZODBTools/migrate.py",
"utilities/ZODBTools/netspace.py", "utilities/ZODBTools/parsezeolog.py",
"utilities/ZODBTools/repozo.py", "utilities/ZODBTools/space.py",
"utilities/ZODBTools/timeout.py", "utilities/ZODBTools/zeopack.py",
"utilities/ZODBTools/zeoqueue.py", "utilities/ZODBTools/zeoreplay.py",
"utilities/ZODBTools/zeoserverlog.py", "utilities/ZODBTools/zeoup.py",
"utilities/ZODBTools/zodbload.py",
"test.py"],
distclass=ZopeDistribution,
)
context.walk_packages("lib/python")
context.setup()
......@@ -66,6 +66,8 @@ defaults += ['-m',
'zdaemon'
'|'
'zope[.]testing'
'|'
'zope[.]app'
')[.]']
if ihome:
ihome = os.path.abspath(ihome)
......
#!/usr/bin/env python2.3
#!/usr/bin/env python2.4
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
......
#!/usr/bin/env python2.3
#!/usr/bin/env python2.4
##############################################################################
#
......
#!/usr/bin/env python2.3
#!/usr/bin/env python2.4
##############################################################################
#
......
#!/usr/bin/env python2.3
#!/usr/bin/env python2.4
##############################################################################
#
......
#!/usr/bin/env python2.3
#!/usr/bin/env python2.4
##############################################################################
#
......
#!/usr/bin/env python2.3
#!/usr/bin/env python2.4
##############################################################################
#
......
#!/usr/bin/env python2.3
#!/usr/bin/env python2.4
##############################################################################
#
......
#!/usr/bin/env python2.3
#!/usr/bin/env python2.4
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
......
#!/usr/bin/env python2.3
#!/usr/bin/env python2.4
##############################################################################
#
......
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