Commit 3adf2740 authored by Florent Guillaume's avatar Florent Guillaume

Updated to use Five 1.3b2, and merged efge-five-events-work:

Fixed bug that broke WebDAV access for five:defaultViewable objects. The
__browser_default__ now modifies only GET and POST requests.

Fixed some event recursion compatibility modes.
parent 0fdad843
...@@ -222,7 +222,7 @@ class CopyContainer(ExtensionClass.Base): ...@@ -222,7 +222,7 @@ class CopyContainer(ExtensionClass.Base):
ob._postCopy(self, op=0) ob._postCopy(self, op=0)
OFS.subscribers.maybeCallDeprecated('manage_afterClone', ob) OFS.subscribers.compatibilityCall('manage_afterClone', ob, ob)
notify(ObjectClonedEvent(ob)) notify(ObjectClonedEvent(ob))
...@@ -388,7 +388,7 @@ class CopyContainer(ExtensionClass.Base): ...@@ -388,7 +388,7 @@ class CopyContainer(ExtensionClass.Base):
ob._postCopy(self, op=0) ob._postCopy(self, op=0)
OFS.subscribers.maybeCallDeprecated('manage_afterClone', ob) OFS.subscribers.compatibilityCall('manage_afterClone', ob, ob)
notify(ObjectClonedEvent(ob)) notify(ObjectClonedEvent(ob))
......
...@@ -50,7 +50,6 @@ from OFS.event import ObjectWillBeAddedEvent ...@@ -50,7 +50,6 @@ from OFS.event import ObjectWillBeAddedEvent
from OFS.event import ObjectWillBeRemovedEvent from OFS.event import ObjectWillBeRemovedEvent
import OFS.subscribers import OFS.subscribers
# the name BadRequestException is relied upon by 3rd-party code # the name BadRequestException is relied upon by 3rd-party code
BadRequestException = BadRequest BadRequestException = BadRequest
...@@ -315,35 +314,23 @@ class ObjectManager( ...@@ -315,35 +314,23 @@ class ObjectManager(
if not suppress_events: if not suppress_events:
notify(ObjectAddedEvent(ob, self, id)) notify(ObjectAddedEvent(ob, self, id))
OFS.subscribers.maybeCallDeprecated('manage_afterAdd', ob, self) OFS.subscribers.compatibilityCall('manage_afterAdd', ob, ob, self)
return id return id
def manage_afterAdd(self, item, container): def manage_afterAdd(self, item, container):
# Don't do recursion anymore, a subscriber does that. # Don't do recursion anymore, a subscriber does that.
warnings.warn( pass
"%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 manage_afterAdd.__five_method__ = True
def manage_afterClone(self, item): def manage_afterClone(self, item):
# Don't do recursion anymore, a subscriber does that. # Don't do recursion anymore, a subscriber does that.
warnings.warn( pass
"%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 manage_afterClone.__five_method__ = True
def manage_beforeDelete(self, item, container): def manage_beforeDelete(self, item, container):
# Don't do recursion anymore, a subscriber does that. # Don't do recursion anymore, a subscriber does that.
warnings.warn( pass
"%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 manage_beforeDelete.__five_method__ = True
def _delObject(self, id, dp=1, suppress_events=False): def _delObject(self, id, dp=1, suppress_events=False):
...@@ -353,7 +340,7 @@ class ObjectManager( ...@@ -353,7 +340,7 @@ class ObjectManager(
""" """
ob = self._getOb(id) ob = self._getOb(id)
OFS.subscribers.maybeCallDeprecated('manage_beforeDelete', ob, self) OFS.subscribers.compatibilityCall('manage_beforeDelete', ob, ob, self)
if not suppress_events: if not suppress_events:
notify(ObjectWillBeRemovedEvent(ob, self, id)) notify(ObjectWillBeRemovedEvent(ob, self, id))
......
...@@ -61,27 +61,15 @@ class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable, ...@@ -61,27 +61,15 @@ class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable,
isTopLevelPrincipiaApplicationObject=0 isTopLevelPrincipiaApplicationObject=0
def manage_afterAdd(self, item, container): def manage_afterAdd(self, item, container):
warnings.warn( pass
"%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 manage_afterAdd.__five_method__ = True
def manage_beforeDelete(self, item, container): def manage_beforeDelete(self, item, container):
warnings.warn( pass
"%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 manage_beforeDelete.__five_method__ = True
def manage_afterClone(self, item): def manage_afterClone(self, item):
warnings.warn( pass
"%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 manage_afterClone.__five_method__ = True
# Direct use of the 'id' attribute is deprecated - use getId() # Direct use of the 'id' attribute is deprecated - use getId()
......
...@@ -21,6 +21,7 @@ import warnings ...@@ -21,6 +21,7 @@ import warnings
import sys import sys
from zLOG import LOG, ERROR from zLOG import LOG, ERROR
from Acquisition import aq_base
from App.config import getConfiguration from App.config import getConfiguration
from AccessControl import getSecurityManager from AccessControl import getSecurityManager
from ZODB.POSException import ConflictError from ZODB.POSException import ConflictError
...@@ -35,36 +36,42 @@ from zope.app.location.interfaces import ISublocations ...@@ -35,36 +36,42 @@ from zope.app.location.interfaces import ISublocations
deprecatedManageAddDeleteClasses = [] deprecatedManageAddDeleteClasses = []
def hasDeprecatedMethods(ob): def compatibilityCall(method_name, *args):
"""Do we need to call the deprecated methods? """Call a method if events have not been setup yet.
"""
for class_ in deprecatedManageAddDeleteClasses:
if isinstance(ob, class_):
return True
return False
def maybeCallDeprecated(method_name, ob, *args): This is the case for some unit tests that have not been converted to
"""Call a deprecated method, if the framework doesn't call it already. use the component architecture.
""" """
if hasDeprecatedMethods(ob): if deprecatedManageAddDeleteClasses:
# Already deprecated through zcml # Events initialized, don't do compatibility call
return return
method = getattr(ob, method_name) if method_name == 'manage_afterAdd':
if getattr(method, '__five_method__', False): callManageAfterAdd(*args)
elif method_name == 'manage_beforeDelete':
callManageBeforeDelete(*args)
else:
callManageAfterClone(*args)
def maybeWarnDeprecated(ob, method_name):
"""Send a warning if a method is deprecated.
"""
if not deprecatedManageAddDeleteClasses:
# Directives not fully loaded
return
for cls in deprecatedManageAddDeleteClasses:
if isinstance(ob, cls):
# Already deprecated through zcml
return
if getattr(getattr(ob, method_name), '__five_method__', False):
# Method knows it's deprecated # Method knows it's deprecated
return return
if deprecatedManageAddDeleteClasses: class_ = ob.__class__
# Not deprecated through zcml and directives fully loaded warnings.warn(
class_ = ob.__class__ "%s.%s.%s is deprecated and will be removed in Zope 2.11, "
warnings.warn( "you should use event subscribers instead, and meanwhile "
"Calling %s.%s.%s is deprecated when using Five, " "mark the class with <five:deprecatedManageAddDelete/>"
"instead use event subscribers or " % (class_.__module__, class_.__name__, method_name),
"mark the class with <five:deprecatedManageAddDelete/>" DeprecationWarning)
% (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)
################################################## ##################################################
...@@ -98,16 +105,13 @@ def dispatchObjectWillBeMovedEvent(ob, event): ...@@ -98,16 +105,13 @@ def dispatchObjectWillBeMovedEvent(ob, event):
if OFS.interfaces.IObjectManager.providedBy(ob): if OFS.interfaces.IObjectManager.providedBy(ob):
dispatchToSublocations(ob, event) dispatchToSublocations(ob, event)
# Next, do the manage_beforeDelete dance # Next, do the manage_beforeDelete dance
#import pdb; pdb.set_trace() callManageBeforeDelete(ob, event.object, event.oldParent)
if hasDeprecatedMethods(ob):
callManageBeforeDelete(ob, event)
def dispatchObjectMovedEvent(ob, event): def dispatchObjectMovedEvent(ob, event):
"""Multi-subscriber for IItem + IObjectMovedEvent. """Multi-subscriber for IItem + IObjectMovedEvent.
""" """
# First, do the manage_afterAdd dance # First, do the manage_afterAdd dance
if hasDeprecatedMethods(ob): callManageAfterAdd(ob, event.object, event.newParent)
callManageAfterAdd(ob, event)
# Next, dispatch to sublocations # Next, dispatch to sublocations
if OFS.interfaces.IObjectManager.providedBy(ob): if OFS.interfaces.IObjectManager.providedBy(ob):
dispatchToSublocations(ob, event) dispatchToSublocations(ob, event)
...@@ -116,32 +120,33 @@ def dispatchObjectClonedEvent(ob, event): ...@@ -116,32 +120,33 @@ def dispatchObjectClonedEvent(ob, event):
"""Multi-subscriber for IItem + IObjectClonedEvent. """Multi-subscriber for IItem + IObjectClonedEvent.
""" """
# First, do the manage_afterClone dance # First, do the manage_afterClone dance
if hasDeprecatedMethods(ob): callManageAfterClone(ob, event.object)
callManageAfterClone(ob, event)
# Next, dispatch to sublocations # Next, dispatch to sublocations
if OFS.interfaces.IObjectManager.providedBy(ob): if OFS.interfaces.IObjectManager.providedBy(ob):
dispatchToSublocations(ob, event) dispatchToSublocations(ob, event)
def callManageAfterAdd(ob, event): def callManageAfterAdd(ob, item, container):
"""Compatibility subscriber for manage_afterAdd. """Compatibility subscriber for manage_afterAdd.
""" """
container = event.newParent
if container is None: if container is None:
# this is a remove
return return
ob.manage_afterAdd(event.object, container) if getattr(aq_base(ob), 'manage_afterAdd', None) is None:
return
maybeWarnDeprecated(ob, 'manage_afterAdd')
ob.manage_afterAdd(item, container)
def callManageBeforeDelete(ob, event): def callManageBeforeDelete(ob, item, container):
"""Compatibility subscriber for manage_beforeDelete. """Compatibility subscriber for manage_beforeDelete.
""" """
import OFS.ObjectManager # avoid circular imports
container = event.oldParent
if container is None: if container is None:
# this is an add
return return
if getattr(aq_base(ob), 'manage_beforeDelete', None) is None:
return
maybeWarnDeprecated(ob, 'manage_beforeDelete')
import OFS.ObjectManager # avoid circular imports
try: try:
ob.manage_beforeDelete(event.object, container) ob.manage_beforeDelete(item, container)
except OFS.ObjectManager.BeforeDeleteException: except OFS.ObjectManager.BeforeDeleteException:
raise raise
except ConflictError: except ConflictError:
...@@ -153,7 +158,10 @@ def callManageBeforeDelete(ob, event): ...@@ -153,7 +158,10 @@ def callManageBeforeDelete(ob, event):
if not getSecurityManager().getUser().has_role('Manager'): if not getSecurityManager().getUser().has_role('Manager'):
raise raise
def callManageAfterClone(ob, event): def callManageAfterClone(ob, item):
"""Compatibility subscriber for manage_afterClone. """Compatibility subscriber for manage_afterClone.
""" """
ob.manage_afterClone(event.object) if getattr(aq_base(ob), 'manage_afterClone', None) is None:
return
maybeWarnDeprecated(ob, 'manage_afterClone')
ob.manage_afterClone(item)
...@@ -444,7 +444,7 @@ class BTreeFolder2Base (Persistent): ...@@ -444,7 +444,7 @@ class BTreeFolder2Base (Persistent):
if not suppress_events: if not suppress_events:
notify(ObjectAddedEvent(ob, self, id)) notify(ObjectAddedEvent(ob, self, id))
OFS.subscribers.maybeCallDeprecated('manage_afterAdd', ob, self) OFS.subscribers.compatibilityCall('manage_afterAdd', ob, ob, self)
return id return id
...@@ -452,7 +452,7 @@ class BTreeFolder2Base (Persistent): ...@@ -452,7 +452,7 @@ class BTreeFolder2Base (Persistent):
def _delObject(self, id, dp=1, suppress_events=False): def _delObject(self, id, dp=1, suppress_events=False):
ob = self._getOb(id) ob = self._getOb(id)
OFS.subscribers.maybeCallDeprecated('manage_beforeDelete', ob, self) OFS.subscribers.compatibilityCall('manage_beforeDelete', ob, ob, self)
if not suppress_events: if not suppress_events:
notify(ObjectWillBeRemovedEvent(ob, self, id)) notify(ObjectWillBeRemovedEvent(ob, self, id))
......
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
Five Changes Five Changes
============ ============
Five 1.3b2 (2005-11-10)
=======================
This version is included in Zope 2 trunk as of this date.
Five 1.3b (2005-11-02) Five 1.3b (2005-11-02)
====================== ======================
...@@ -36,6 +41,17 @@ Restructuring ...@@ -36,6 +41,17 @@ Restructuring
components has been removed as that functionality is now in the Zope components has been removed as that functionality is now in the Zope
2 core as of Zope 2.9. 2 core as of Zope 2.9.
Five 1.2 (unreleased)
=====================
Bugfixes
--------
* Fixed bug that broke WebDAV access for five:defaultViewable objects. The
__browser_default__ now modifies only GET and POST requests.
* Fixed some event recursion compatibility modes.
Five 1.2b (2005-11-02) Five 1.2b (2005-11-02)
====================== ======================
...@@ -74,9 +90,9 @@ Restructuring ...@@ -74,9 +90,9 @@ Restructuring
* Removed backwards compatibility for Zope 2.7 and 2.8.0. * Removed backwards compatibility for Zope 2.7 and 2.8.0.
* Added a (temporarily) forked copy of the "new-and-improved" test * Added a (temporarily) forked copy of the "new-and-improved" test
runner and supporting 'zope.testing' package, lifted from runner and supporting ``zope.testing`` package, lifted from
http://svn.zope.org/zope.testing. This code should be removed for 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 Five 1.3, which will use the updated version of ``zope.testing`` in
the Zope 2.9 / Zope 3.2 tree. the Zope 2.9 / Zope 3.2 tree.
There is a test runner invoking script in the ``Five`` package. For There is a test runner invoking script in the ``Five`` package. For
...@@ -85,8 +101,8 @@ Restructuring ...@@ -85,8 +101,8 @@ Restructuring
$ bin/zopectl run Products/Five/runtests.py -v -s Products.Five $ bin/zopectl run Products/Five/runtests.py -v -s Products.Five
* Moved the 'Five.testing' package down to 'Five.tests.testing', in * Moved the ``Five.testing`` package down to ``Five.tests.testing``,
order to make room for the 'zope.testing' code. in order to make room for the 'zope.testing' code.
* Removed backwards compatibility for some moved classes (AddForm, * Removed backwards compatibility for some moved classes (AddForm,
EditForm, ContentAdding) EditForm, ContentAdding)
...@@ -189,7 +205,7 @@ Restructuring ...@@ -189,7 +205,7 @@ Restructuring
* The former test product, ``FiveTest``, was converted into separate * The former test product, ``FiveTest``, was converted into separate
modules that provide the mock objects for the corresponding tests modules that provide the mock objects for the corresponding tests
and are located right next to them. Common test helpers have been and are located right next to them. Common test helpers have been
moved to the Five.tests.testing package. Overall, the testing framework moved to the Five.testing package. Overall, the testing framework
was much simplified and the individual tests clean up after was much simplified and the individual tests clean up after
themselves much like they do in Zope 3. themselves much like they do in Zope 3.
......
Five is distributed under the provisions of the Zope Public License
(ZPL) v2.1. See doc/ZopePublicLicense.txt for the license text.
Copyright (C) 2005 Five Contributors. See CREDITS.txt for a list of
Five contributors.
Five contains source code derived from:
- Zope 3, copyright (C) 2001-2005 by Zope Corporation.
- metaclass.py is derived from PEAK, copyright (C) 1996-2004 by
Phillip J. Eby and Tyler C. Sarna. PEAK may be used under the same
terms as Zope.
- TrustedExecutables. Dieter Mauer kindly allow licensing this under the
ZPL 2.1.
How to install Five
-------------------
Requirements for Five 1.3
=========================
* Zope 2.9+ with Python 2.4.1+
Note that Five 1.3 is already part of Zope 2.9. You can still install
a newer Five version in your instance, if you like. It will override
the Five product inside the Zope tree.
Running the tests
=================
For information on how to install the automatic Five tests, please see
``tests/README.txt``.
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.
Events in Zope 2.9
==================
Zope 2.9 (and Zope 2.8 when using Five 1.2) introduces a big change:
Zope 3 style container events.
With container events, you finally have the ability to react to things
happening to objects without have to subclass ``manage_afterAdd``,
``manage_beforeDelete`` or ``manage_afterClone``. Instead, you just have
to register a subscriber for the appropriate event, for instance
IObjectAddedEvent, and make it do the work.
Indeed, the old methods like ``manage_afterAdd`` are now deprecated, you
shouldn't use them anymore.
Let's see how to migrate your products.
Old product
-----------
Suppose that in an old product you have code that needs to register
through a central tool whenever a document is created. Or it could be
indexing itself. Or it could initialize an attribute according to its
current path. Code like::
class CoolDocument(...):
...
def manage_afterAdd(self, item, container):
self.mangled_path = mangle('/'.join(self.getPhysicalPath()))
getToolByName(self, 'portal_cool').registerCool(self)
super(CoolDocument, self).manage_afterAdd(item, container)
def manage_afterClone(self, item):
self.mangled_path = mangle('/'.join(self.getPhysicalPath()))
getToolByName(self, 'portal_cool').registerCool(self)
super(CoolDocument, self).manage_afterClone(item)
def manage_beforeDelete(self, item, container):
super(CoolDocument, self).manage_beforeDelete(item, container)
getToolByName(self, 'portal_cool').unregisterCool(self)
This would be the best practice in Zope 2.8. Note the use of ``super()``
to call the base class, which is often omitted because people "know"
that SimpleItem for instance doesn't do anything in these methods.
If you run this code in Zope 2.9, you will get deprecation warnings,
telling you that::
Calling Products.CoolProduct.CoolDocument.CoolDocument.manage_afterAdd
is deprecated when using Five, instead use event subscribers or mark
the class with <five:deprecatedManageAddDelete/>
Using five:deprecatedManageAddDelete
------------------------------------
The simplest thing you can do to deal with the deprecation warnings, and
have correct behavior, is to add in your products a ``configure.zcml``
file containing::
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<five:deprecatedManageAddDelete
class="Products.CoolProduct.CoolDocument.CoolDocument"/>
</configure>
This tells Zope that you acknowledge that your class contains deprecated
methods, and ask it to still call them in the proper manner. So Zope
will be sending events when an object is added, for instance, and in
addition call your old ``manage_afterAdd`` method.
One subtlety here is that you may have to modify you methods to just do
their work, and not call their super class. This is necessary because
proper events are already dispatched to all relevant classes, and the
work of the super class will be done trough events, you must not redo it
by hand. If you call the super class, you will get a warning, saying for
instance::
CoolDocument.manage_afterAdd is deprecated and will be removed in
Zope 2.11, you should use an IObjectAddedEvent subscriber instead.
The fact that you must "just do your work" is especially important for
the rare cases where people subclass the ``manage_afterAdd`` of object
managers like folders, and decided to reimplement recursion into the
children themselves. If you do that, then there will be two recursions
going on in parallel, the one done by events, and the one done by your
code. This would be bad.
Using subscribers
-----------------
In the long run, and before Zope 2.11 where ``manage_afterAdd`` and
friends will be removed, you will want to use proper subscribers.
First, you'll have to write a subscriber that "does the work", for
instance::
def addedCoolDocument(ob, event):
"""A Cool Document was added to a container."""
self.mangled_path = mangle('/'.join(self.getPhysicalPath()))
Note that we're not calling the ``portal_cool`` tool anymore, because
presumably this tool will also be modified to do its work through
events, and will have a similar subscriber doing the necessary
``registerCool``. Note also that here we don't care about the event, but
in more complex cases we would.
Now we have to register our subscriber for our object. To do that, we
need to "mark" our object through an interface. We can define in our
product's ``interfaces.py``::
from zope.interface import Interface, Attribute
class ICoolDocument(Interface):
"""Cool Document."""
mangled_path = Attribute("Our mangled path.")
...
Then the class CoolDocument is marked with this interface::
from zope.interface import implements
from Products.CoolProduct.interfaces import ICoolDocument
class CoolDocument(...):
implements(ICoolDocument)
...
Finally we must link the event and the interface to the subscriber using
zcml, so in ``configure.zcml`` we'll add::
...
<subscriber
for="Products.CoolProduct.interfaces.ICoolDocument
zope.app.container.interfaces.IObjectAddedEvent"
handler="Products.CoolProduct.CoolDocument.addedCoolDocument"
/>
...
And that's it, everything is plugged. Note that IObjectAddedEvent takes
care of both ``manage_afterAdd`` and ``manage_afterClone``, as it's sent
whenever a new object is placed into a container. However this won't
take care of moves and renamings, we'll see below how to do that.
Event dispatching
-----------------
When an IObjectEvent (from which all the events we're talking here
derive) is initially sent, it concerns one object. For instance, a
specific object is removed. The ``event.object`` attribute is this
object.
To be able to know about removals, we could just subscribe to the
appropriate event using a standard event subscriber. In that case, we'd
have to filter "by hand" to check if the object removed is of the type
we're interested in, which would be a chore. In addition, any subobjects
of the removed object wouldn't know what happens to them, and for
instance they wouldn't have any way of doing some cleanup before they
disappear.
To solve these two problems, Zope 3 has an additional mechanism by which
any IObjectEvent is redispatched using multi-adapters of the form ``(ob,
event)``, so that a subscriber can be specific about the type of object
it's interested in. Furthermore, this is done recursively for all
sublocations ``ob`` of the initial object. The ``event`` won't change
though, and ``event.object`` will still be the original object for which
the event was initially sent (this corresponds to ``self`` and ``item``
in the ``manage_afterAdd`` method -- ``self`` is ``ob``, and ``item`` is
``event.object``).
Understanding the hierarchy of events is important to see how to
subscribe to them.
* IObjectEvent is the most general. Any event focused on an object
derives from this.
* IObjectMovedEvent is sent when an object changes location or is
renamed. It is quite general, as it also encompasses the case where
there's no old location (addition) or no new location (removal).
* IObjectAddedEvent and IObjectRemovedEvent both derive from
IObjectMovedEvent.
* IObjectCopiedEvent is sent just after an object copy is made, but
this doesn't mean the object has been put into its new container yet,
so it doesn't have a location.
There are only a few basic use cases about what one wants to do with
respect to events (but you might want to read the full story in
Five/tests/event.txt).
The first use case is the one where the object has to be aware of its
path, like in the CoolDocument example above. That's strictly a Zope 2
concern, as Zope 3 has others ways to deal with this.
In Zope 2 an object has a new path through creation, copy or move
(rename is a kind of move). The events sent during these three
operations are varied: creation sends IObjectAddedEvent, copy sends
IObjectCopiedEvent then IObjectAddedEvent, and move sends
IObjectMovedEvent.
So to react to new paths, we have to subscribe to IObjectMovedEvent, but
this will also get us any IObjectRemovedEvent, which we'll have to
filter out by hand (this is unfortunate, and due to the way the Zope 3
interface hierarchy is organized). So to fix the CoolDocument
configuration we have to add::
def movedCoolDocument(ob, event):
"""A Cool Document was moved."""
if not IObjectRemovedEvent.providedBy(event):
addedCoolDocument(ob, event)
And replace the subscriber with::
...
<subscriber
for="Products.CoolProduct.interfaces.ICoolDocument
zope.app.container.interfaces.IObjectMovedEvent"
handler="Products.CoolProduct.CoolDocument.movedCoolDocument"
/>
...
The second use case is when the object has to do some cleanup when it is
removed from its parent. This used to be in ``manage_beforeDelete``, now
we can do the work in a ``removedCoolDocument`` method and just
subscribe to IObjectRemovedEvent. But wait, this won't take into account
moves... So in the same vein as above, we would have to write::
def movedCoolDocument(ob, event):
"""A Cool Document was moved."""
if not IObjectRemovedEvent.providedBy(event):
addedCoolDocument(ob, event)
if not IObjectAddedEvent.providedBy(event):
removedCoolDocument(ob, event)
The third use case is when your object has to stay registered with some
tool, for instance indexed in a catalog, or as above registered with
``portal_cool``. Here we have to know the old object's path to
unregister it, so we have to be called *before* it is removed. We'll use
``IObjectWillBe...`` events, that are sent before the actual operations
take place::
from OFS.interfaces import IObjectWillBeAddedEvent
def beforeMoveCoolDocument(ob, event):
"""A Cool Document will be moved."""
if not IObjectWillBeAddedEvent.providedBy(event):
getToolByName(ob, 'portal_cool').unregisterCool(ob)
def movedCoolDocument(ob, event):
"""A Cool Document was moved."""
if not IObjectRemovedEvent.providedBy(event):
getToolByName(ob, 'portal_cool').registerCool(ob)
...
And use an additional subscriber::
...
<subscriber
for="Products.CoolProduct.interfaces.ICoolDocument
OFS.interfaces.IObjectWillBeMovedEvent"
handler="Products.CoolProduct.CoolDocument.beforeMoveCoolDocument"
/>
...
This has to be done if the tool cannot react by itself to objects being
added and removed, which obviously would be better as it's ultimately
the tool's responsibility and not the object's.
Note that if having tests like::
if not IObjectWillBeAddedEvent.providedBy(event):
if not IObjectRemovedEvent.providedBy(event):
seems cumbersome (and backwards), it is also possible to check what kind
of event you're dealing with using::
if event.oldParent is not None:
if event.newParent is not None:
(However be careful, the ``oldParent`` and ``newParent`` are the old and
new parents *of the original object* for which the event was sent, not
of the one to which the event was redispatched using the
multi-subscribers we have registered.)
The ``IObjectWillBe...`` events are specific to Zope 2 (and imported
from ``OFS.interfaces``). Zope 3 doesn't really need them, as object
identity is often enough.
Internationalization
====================
Translation
-----------
Five registers its own translation service mockup with the Page
Templates machinery and prevents any other product from also doing so.
That means, Five always assumes control over ZPT i18n. When a certain
domain has not been registered the Zope 3 way, Five's translation
service will see that the utility lookup fails and use the next
available fallback translation service. In case of no other
translation service installed, that is just a dummy fallback. In case
you have Localizer and PTS installed, it falls back to that.
To register Zope 3 style translation domains, use the following ZCML
statement::
<i18n:registerTranslations directory="locales" />
where the 'i18n' prefix is bound to the
http://namespaces.zope.org/i18n namespace identifier. The directory
(in this case 'locales') should conform to the `standard gettext
locale directory layout`__.
.. __: http://www.gnu.org/software/gettext/manual/html_chapter/gettext_10.html#SEC148
Preferred languages and negotiation
-----------------------------------
Fallback translation services such as PTS and Localizer have their own
way of determining the user-preferred languages and negotiating that
with the available languages in the respective domain. Zope 3
translation domains typically adapt the request to
IUserPreferredLanguages to get a list of preferred languages; then
they use the INegotiator utility to negotiate between the preferred
and available languages.
The goal of the sprint was to allow both fallback translation services
(PTS, Localizer) and Zope 3 translation domains come to the same
conclusion regarding which language should be chosen. The use case is
that you have a site running Localizer or PTS and a bunch of "old"
products using either one of those for translation. Now you have an
additional, "new" Five-based product using Zope 3 translation domains.
Most of the time, a page contains user messages from more than one
domain, so you would all domains be translated to the same language.
Adjusting behaviour to your environment
---------------------------------------
The default behaviour for choosing languages in Five is the one of
Zope 3: analyze the Accept-Language HTTP header and nothing more. In
addition, Five providees ``IUserPreferredLanguages`` adapters for
Localizer and PTS that choose languages the exact same way Localizer
or PTS would. So, if you're using Five in a Localizer-environment,
you need this in your product's ``overrides.zcml``:
<adapter
for="zope.publisher.interfaces.http.IHTTPRequest"
provides="zope.i18n.interfaces.IUserPreferredLanguages"
factory="Products.Five.i18n.LocalizerLanguages"
/>
If you're using PTS:
<adapter
for="zope.publisher.interfaces.http.IHTTPRequest"
provides="zope.i18n.interfaces.IUserPreferredLanguages"
factory="Products.Five.i18n.PTSLanguages"
/>
That way Zope 3 translation domains will always come to the same
conclusion regarding the language as your original translation service
would.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
%page
Motto
It was the dawn of the third age of Zope. The Five project was a dream given form. Its goal: to use Zope 3 technologies in Zope 2.7 by creating a Zope 2 product where Zope 3 and Zope 2 could work out their differences peacefully.
(Babylon 5 season 1 intro, creatively quoted)
%page
Motto 2
The Law of Fives states simply that: ALL THINGS HAPPEN IN FIVES, OR ARE DIVISIBLE BY OR ARE MULTIPLES OF FIVE, OR ARE SOMEHOW DIRECTLY OR INDIRECTLY RELATED TO FIVE.
THE LAW OF FIVES IS NEVER WRONG.
(Principia Discordia)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
The problem
We're using Zope 2 in production
Zope 2 is showing its age
Zope 3 has better ways to do things
But can't just switch, we have customers!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Benefits of using Zope 3 in Zope 2
Able to use Zope 3 technologies right away
Don't reinvent the wheel/APIs
Better prepared for Zope 3 transition
Evolution, not revolution
Convergence, not divergence
%page
What works now?
Interfaces (zope.interface)
Schema (zope.schema)
ZCML (zope.configuration)
Adapters (zope.component)
Views, including layers, skins (zope.component)
%page
Brief demo
Show ZCML, adapters and views in action
%page
Next?
Utilities (global ones should work)
Forms
Views (improve the current system)
Who knows?
%page
Plans
Relicense from BSD to generic ZPL 2.1
Move from CVS at Infrae into SVN at codespeak.net
Convergence; join us!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
Five future directions
What might happen
%page
Unique id service support
Foundation is there in form of events
Unfortunately implementation can not be the same
Objects are referenced differently in Zope 3
Local services/utilities are difficult in Zope 2
Could lead to Zope 3 catalog support
%page
Page template engine improvements
Right now we already use Zope 3 page templates
These are unicode-only (and work with plain ascii)
To support Zope 2 content, need classic non-ascii string support
Issue in Plone, not in Silva (heh heh)
%page
Zope 2.8 and ZODB 3.3
Should be able to work much better with new-style objects
Things like local services/utilities might be doable
%page
Integration with Plone, CMF, Silva, UnionCMS etc
Sharing a common base is good
Zope 3 is good
That base should be Zope 3
Five can help us start sharing today
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
Interfaces, adapters
What are interfaces?
What are adapters?
Why?
A very quick introduction
%page
Actually
This tutorial applies to Zope 3 as much as to Five
Indication Five reached its goal in this area
%page
Interface example
%size 4, fore "blue"
from zope.interface import Interface
class IElephant(Interface):
"""An elephant is a big grey animal.
"""
def getAngerLevel():
"Return anger level on scale 0 (placid) to 10 (raging)"
def trample(target):
"Trample the target."
def trumpet():
"Make loud noise with trunk."
%page
Interface example, continued
%size 4, fore "blue"
from zope.interface import implements
class AfricanElephant:
implements(IElephant)
def getAngerLevel(self):
return 5 # always pretty stroppy
def trample(self, target):
target.flatten()
def trumpet(self):
return "A terrible racket"
%page
Interfaces
Interfaces are about the what, not the how
Interfaces don't do anything, they just describe
Code can state what interfaces objects provide
Code can introspect whether objects provide interfaces
%page
Why interfaces?
They are documentation
Make multiple implementations of same interface easier
Allows you to program against published APIs
Allow glueing by interface
%page
Component architecture
zope.component part of Zope 3
allows glueing together of components in various ways
a component is an object which provides an interface
a Zope 2 object with a Zope 3 interface is a component
%page
Adapters, example
%size 4, fore "blue"
class INoiseMaker(Interface):
"""Something that makes noise.
"""
def makeNoise():
"Returns the noise that's made."
%page
Adapters, example continued
%size 4, fore "blue"
class ElephantNoiseMaker:
"""Adapts elephant to noise maker.
"""
implements(INoiseMaker)
def __init__(self, context):
self.context = context
def makeNoise(self):
return self.context.trumpet()
%page
Adapters, example continued 2
%size 4, fore "blue"
>>> elephant = AfricanElephant()
>>> noise_maker = ElephantNoiseMaker(elephant)
>>> print noise_maker.makeNoise()
'A terrible racket'
%page
Adapters
Add behavior to object without changing its class
More manageable than mixins
Define new behavior in terms of other behavior
%page
Adapters, continued
Less framework burden on adapted objects
They only need to be a component
Adapted doesn't know about the adapter
Adapter is a component itself
%page
Adapter lookup
We just manually glued the adapter to the adapted
What if we had INoiseMaker adapters for other objects?
We want a universal way to say: give me a INoiseMaker for this object
This allows use to write more generic code
%page
Adapter lookup, example
%size 4, fore "blue"
for animal in animal_farm:
noise_maker = INoiseMaker(animal)
print noise_maker.makeNoise()
%page
Adapter glueing
System need to be informed what can adapt what
Zope Configuration Markup Language (ZCML) is used for that
%page
ZCML example
%size 4, fore "blue"
<configure xmlns="http://namespaces.zope.org/zope">
<adapter
for=".module.IElephant"
provides=".module.INoiseMaker"
factory=".module.ElephantNoiseMaker" />
<adapter
for=".other.IChicken"
provides=".module.INoiseMaker"
factory=".other.ChickenNoiseMaker" />
</configure>
%page
ZCML, what we just said
The adapter ElephantNoiseMaker adapts any object that provides IElephant to a INoiseMaker
The adapter ChickenNoiseMaker adapts any object that provides IChicken to a INoiseMaker
%page
This works in Zope 2 with Five
This works in Zope 2 with Five
Your objects just need to be components (provide Zope 3 interfaces)
Your ZCML goes into configure.zcml in your product
That's it
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
An Introduction to Five
Why Five?
What is Five?
Where are we, where are we going?
%page
Motto
It was the dawn of the third age of Zope. The Five project was a dream given form. Its goal: to use Zope 3 technologies in Zope 2.7 by creating a Zope 2 product where Zope 3 and Zope 2 could work out their differences peacefully.
(Babylon 5 season 1 intro, creatively quoted)
%page
Motto 2
The Law of Fives states simply that: ALL THINGS HAPPEN IN FIVES, OR ARE DIVISIBLE BY OR ARE MULTIPLES OF FIVE, OR ARE SOMEHOW DIRECTLY OR INDIRECTLY RELATED TO FIVE.
THE LAW OF FIVES IS NEVER WRONG.
(Principia Discordia)
%page
The problem
We're using Zope 2 in production
Zope 2 is showing its age
Zope 3 has better ways to do things
But can't just switch, we have codebases, customers!
%page
Benefits of using Zope 3 in Zope 2
Able to use Zope 3 technologies right away
Don't reinvent the wheel/APIs
Better prepared for Zope 3 transition
Evolution, not revolution
Convergence, not divergence (this is important)
%page
Divergence
Infrae created Silva, Nuxeo CPS, etc
Everybody else started using Plone (why?!)
I want to use cool Plone technology
Silva is cool too, you may want to use it
I don't want to have to reinvent every wheel (just some)
%page
What's stopping us from sharing?
Zope 2 components are hard to share between apps
Even CMF components need work to share
Especially if you don't use CMF... (Silva)
Zope 2 framework burden is making it hard
Clean Python code is easier to share
%page
Convergence
Unify our diverse efforts
Zope 3 allows you to write Python, less framework sacrifices
Zope 3 allows the glueing of components
Zope 3 is the future
Five makes some of the future available today
%page
What works now? - an overview
Interfaces
Schema
ZCML
Adapters
Views, including layers, skins
%page
What works now, continued
Zope 3 page template engine
Traversal, resources
Zope 2 security from ZCML
Events
Beginnings of forms machinery
%page
Progress made since June
Initial announcement at Europython
As promised, moved to SVN at codespeak.net
Got website, mailing list
People joined the project
%page
Progress made since June, continued
Lots of excellent contributions!
Much better view infrastructure (traversal)
ZCML's interaction with Zope 2 products much improved
UnionCMS and other projects are starting to use it!
%page
The Zope 3 Base
Five is part of the Zope 3 Base
Zope 3 Base - All Your Bobobase Are Belong To Us
Possibly the cutest Zope 3 website anywhere
http://codespeak.net/z3
%page
Zope 3 Base
%center
%image "z3-banner.png"
%page
Zope 3 Base, continued
Second area of Zope 3 related development
Equivalent of Plone collective, for Zope 3
More freewheeling than dev.zope.org
Less freewheeling than Plone collective, however
Cuter than both
%page
Evolution: Five-ification
Five is not just for new Zope 2 projects
Five can interoperate with existing Zope 2 applications
Five in Plone - Flon
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
Five Misc Topics
A number of as-yet uncategorized Five-related topics
%page
Resources
Various kinds of resources available
File, image, page template resource
Accessible through ++resource++ namespace
%page
Resources, example
%size 4, fore "blue"
<browser:resource
image="z3base.png"
name="z3base.png"
permission="zope2.ViewManagementScreens"
/>
%page
Resources
Any Five traversable object now has can be used to get to resource
url: path/to/object/++resource++z3base.png
Jim says this is not exactly Zope 3 as it ruins caching
%page
ZCML
ZCML can optionally be put in etc/site.zcml
If not, Five will automatically use included zcml
This zcml is in skel
%page
ZCML continued
ZCML loads any configure.zcml in all products
This is driven by five:loadProducts in site.zcml
Overrides are possible in override.zcml
This is driven by five:loadProductsOverrides in site.zcml
%page
Bridging interfaces
"Bride of Frankenzope"
Utility functions in bridge.py
Can convert Zope 2 interface to Zope 3 interface
%page
Events
Can instruct Zope 2 object to send Zope 3 style events using five:sendEvents
These events sent upon copy/move/rename in Zope 2
IObjectMovedEvent, IObjectAddedEvent, IObjectCopiedEvent, IObjectRemovedEvent
Can set up functions to subscribe to these events
%page
Content directive and permissions
Use content directive to declare Zope 2 permissions Zope 3 style
Declare permissions from ZCML, no more declareProtected()
Your classes look cleaner as a result
%page
Macros
Zope 3 way to aggregate macros into single object
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
Views with Five
What are views?
Why?
How to make them work?
%page
Actually
This tutorial contains only a few Five specific bits
Otherwise it applies to Zope 3 as much as to Five
The Five specific bits are mainly some extra ZCML directives
These are in their own ZCML namespace
%page
Page example: overview.pt
%size 4, fore "blue"
<html>
<body>
<p tal:content="context/objectIds"></p>
</body>
</html>
%page
Page example: configure.zcml
%size 4, fore "blue"
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<five:traversable
class="OFS.Folder.Folder"
/>
<browser:page
for="OFS.interfaces.IFolder"
name="overview.html"
template="overview.pt"
permission="zope2.ViewManagementScreens"
/>
</configure>
%page
What works now
some/folder/overview.html
%page
Hooking up the page, explanation
Much like hooking up an adapter
Adapter provides new interface (API) for developer
View provides new interface (UI) for user
Only five-specific thing is making Folder Zope-3 traversable
Well, and the Zope 2 permission.
%page
Hooking up a page, with class
We need some helper methods
Very similar to the way you'd use Python scripts in Zope 2
%page
View class example: overview2.pt
%size 4, fore "blue"
<html>
<body>
<p tal:content="view/reversedIds"></p>
</body>
</html>
%page
View class example: browser.py
%size 4, fore "blue"
from Products.Five import BrowserView
class Overview(BrowserView):
def reversedIds(self):
result = []
for id in self.context.objectIds():
l = list(id)
l.reverse()
reversed_id = ''.join(l)
result.append(reversed_id)
return result
%page
Example: configure.zcml
%size 4, fore "blue"
<browser:page
for="OFS.interfaces.IFolder"
name="overview2.html"
template="overview2.pt"
permission="zope2.ViewManagementScreens"
class=".browser.Overview"
/>
%page
A note on security
There is none: both python code and ZPT are trusted
Only checks are happening on the outside
Performance benefit
Advantage of simplicity
%page
Publishing an attribute
Expose python method on view directly to the web
%page
Attribute example: browser.py
%size 4, fore "blue"
def directlyPublished(self):
return "This is directly published"
%page
Attribute example: configure zcml
%size 4, fore "blue"
<browser:page
for="OFS.interfaces.IFolder"
name="test.html"
class=".browser.Overview"
attribute="directlyPublished"
permission="zope2.ViewManagementScreens"
/>
%page
Publishing multiple pages
Convenience directive: browser:pages
%page
Multiple pages example
%size 4, fore "blue"
<browser:pages
for="OFS.interfaces.IFolder"
class=".browser.NewExample"
permission="zope2.ViewManagementScreens"
>
<browser:page
name="one.html"
template="one.pt"
/>
<browser:page
name="two.html"
attribute="two"
/>
</browser:pages>
%page
Default view for object
We can now set views that are named
What if we traverse to the object itself?
Use five:defaultViewable and browser:defaultView
%page
Uh oh
This doesn't seem to work yet with Zope folders. Sidnei, help!
So we'll try it with a custom SimpleItem-based object
%page
DefaultView example
%size 4, fore "blue"
<five:defaultViewable class=".democontent.DemoContent" />
<browser:defaultView
for=".democontent.IDemoContent"
name="someview.html" />
%page
Conclusions
This works much the same way as Zope 3 does too
Can supplement existing view systems in Zope 2
Five specific code is mostly isolated in five:traversable and five:defaultViewable
\ No newline at end of file
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="form_title string:Add Demo Content"
tal:replace="structure here/manage_form_title">Form Title</h2>
<p class="form-help">
Add Demo Content
</p>
<form action="manage_addDemoContent" method="post">
<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="id" 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 here/manage_page_footer">Footer</h1>
NAME=Five
PYTHON="/usr/bin/python"
TMPDIR=~/tmp
CURDIR=~/src/projects/Five
BASE_DIR=${CURDIR}/..
SOFTWARE_HOME=~/src/zope/2_7/lib/python
INSTANCE_HOME=~/src/instance/five
ZOPE_CONFIG=~/src/instance/five/etc/zope.conf
Z3=~/src/z3/five
.PHONY : clean test reindent reindent_clean
.PHONY : default
# default: The default step (invoked when make is called without a target)
default: clean test
clean :
find . \( -name '*~' -o -name '*.py[co]' -o -name '*.bak' -o -name '\.#*' \) -exec rm {} \; -print
reindent :
~/src/reindent.py -r -v .
test :
export INSTANCE_HOME=${INSTANCE_HOME}; \
export SOFTWARE_HOME=${SOFTWARE_HOME}; \
export PYTHONPATH=${Z3}; \
export ZOPE_CONFIG=${ZOPE_CONFIG}; \
cd ${CURDIR}/tests && ${PYTHON} -O runalltests.py
...@@ -86,38 +86,69 @@ Finally we need to load the subscribers configuration:: ...@@ -86,38 +86,69 @@ Finally we need to load the subscribers configuration::
>>> zcml.load_config('meta.zcml', zope.app.component) >>> zcml.load_config('meta.zcml', zope.app.component)
>>> zcml.load_config('event.zcml', Products.Five) >>> zcml.load_config('event.zcml', Products.Five)
We need at least one fake deprecated method to tell the compatibility
framework that component architecture is initialized::
>>> from Products.Five.eventconfigure import setDeprecatedManageAddDelete
>>> class C(object): pass
>>> setDeprecatedManageAddDelete(C)
Old class Old class
========= =========
If we use an instance of an old class for which we haven't specified 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 anything, events are sent and the manage_afterAdd & co methods are
called but in a "compatibility" way. called, but with a deprecation warning::
Because the bases classes of Zope have been changed to not recurse >>> sub = MyFolder('sub')
except through the event framework, unexpected behavior may happen >>> folder._setObject('sub', sub)
(however a warning will be sent):: ObjectWillBeAddedEvent sub
ObjectAddedEvent sub
old manage_afterAdd sub sub folder
'sub'
>>> sub = folder.sub
>>> ob = MyContent('dog') >>> ob = MyContent('dog')
>>> folder._setObject('dog', ob) >>> sub._setObject('dog', ob)
ObjectWillBeAddedEvent dog ObjectWillBeAddedEvent dog
ObjectAddedEvent dog ObjectAddedEvent dog
old manage_afterAdd dog dog folder old manage_afterAdd dog dog sub
'dog' 'dog'
And when we delete the object, manage_beforeDelete is also called and And when we rename the subfolder, manage_beforeDelete is also called
events are sent:: bottom-up and events are sent::
>>> folder.manage_delObjects('dog') >>> folder.manage_renameObject('sub', 'marine')
old manage_beforeDelete dog dog folder ObjectWillBeMovedEvent sub
ObjectWillBeRemovedEvent dog ObjectWillBeMovedEvent dog
ObjectRemovedEvent dog old manage_beforeDelete dog sub folder
old manage_beforeDelete sub sub folder
ObjectMovedEvent marine
old manage_afterAdd marine marine folder
ObjectMovedEvent dog
old manage_afterAdd dog marine folder
Same thing for clone::
>>> res = folder.manage_clone(folder.marine, 'tank')
ObjectCopiedEvent tank
ObjectWillBeAddedEvent tank
ObjectWillBeAddedEvent dog
ObjectAddedEvent tank
old manage_afterAdd tank tank folder
ObjectAddedEvent dog
old manage_afterAdd dog tank folder
ObjectClonedEvent tank
old manage_afterClone tank tank
ObjectClonedEvent dog
old manage_afterClone dog tank
>>> res.getId()
'tank'
Old class with deprecatedManageAddDelete Old class with deprecatedManageAddDelete
======================================== ========================================
We specifiy that our class is deprecated (using zcml in real life):: We specifiy that our class is deprecated (using zcml in real life)::
>>> from Products.Five.eventconfigure import setDeprecatedManageAddDelete
>>> setDeprecatedManageAddDelete(MyContent) >>> setDeprecatedManageAddDelete(MyContent)
>>> setDeprecatedManageAddDelete(MyFolder) >>> setDeprecatedManageAddDelete(MyFolder)
>>> setDeprecatedManageAddDelete(MyOrderedFolder) >>> setDeprecatedManageAddDelete(MyOrderedFolder)
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Test events triggered by Five """Test events triggered by Five
$Id: test_event.py 14595 2005-07-12 21:26:12Z philikon $ $Id: test_event.py 19706 2005-11-10 14:05:16Z efge $
""" """
import os, sys import os, sys
if __name__ == '__main__': if __name__ == '__main__':
...@@ -40,11 +40,17 @@ class NotifyBase(DontComplain): ...@@ -40,11 +40,17 @@ class NotifyBase(DontComplain):
def manage_afterAdd(self, item, container): def manage_afterAdd(self, item, container):
print 'old manage_afterAdd %s %s %s' % (self.getId(), item.getId(), print 'old manage_afterAdd %s %s %s' % (self.getId(), item.getId(),
container.getId()) container.getId())
super(NotifyBase, self).manage_afterAdd(item, container)
manage_afterAdd.__five_method__ = True # Shut up deprecation warnings
def manage_beforeDelete(self, item, container): def manage_beforeDelete(self, item, container):
super(NotifyBase, self).manage_beforeDelete(item, container)
print 'old manage_beforeDelete %s %s %s' % (self.getId(), item.getId(), print 'old manage_beforeDelete %s %s %s' % (self.getId(), item.getId(),
container.getId()) container.getId())
manage_beforeDelete.__five_method__ = True # Shut up deprecation warnings
def manage_afterClone(self, item): def manage_afterClone(self, item):
print 'old manage_afterClone %s %s' % (self.getId(), item.getId()) print 'old manage_afterClone %s %s' % (self.getId(), item.getId())
super(NotifyBase, self).manage_afterClone(item)
manage_afterClone.__five_method__ = True # Shut up deprecation warnings
class MyApp(Folder): class MyApp(Folder):
def getPhysicalRoot(self): def getPhysicalRoot(self):
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Unit tests for the viewable module. """Unit tests for the viewable module.
$Id: test_viewable.py 14595 2005-07-12 21:26:12Z philikon $ $Id: test_viewable.py 19646 2005-11-08 15:46:36Z yuppie $
""" """
import os, sys import os, sys
if __name__ == '__main__': if __name__ == '__main__':
...@@ -77,8 +77,14 @@ def test_defaultView(): ...@@ -77,8 +77,14 @@ def test_defaultView():
""" """
def test_suite(): def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite import unittest
return ZopeDocTestSuite() from zope.testing.doctest import DocTestSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
return unittest.TestSuite((
DocTestSuite(),
FunctionalDocFileSuite('viewable.txt',
package="Products.Five.tests",),
))
if __name__ == '__main__': if __name__ == '__main__':
framework() framework()
Testing defaultViewable
=======================
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config('configure.zcml', package=Products.Five)
PROPFIND without defaultViewable
--------------------------------
>>> print http(r"""
... PROPFIND /test_folder_1_ HTTP/1.1
... Authorization: Basic test_user_1_:secret
... Content-Length: 250
... Content-Type: application/xml
... Depth: 1
...
... <?xml version="1.0" encoding="utf-8"?>
... <propfind xmlns="DAV:"><prop>
... <getlastmodified xmlns="DAV:"/>
... <creationdate xmlns="DAV:"/>
... <resourcetype xmlns="DAV:"/>
... <getcontenttype xmlns="DAV:"/>
... <getcontentlength xmlns="DAV:"/>
... </prop></propfind>
... """, handle_errors=False)
HTTP/1.1 207 Multi-Status
Connection: close
Content-Length: ...
Content-Location: http://localhost/test_folder_1_/
Content-Type: text/xml; charset="utf-8"
Date: ...
<BLANKLINE>
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/test_folder_1_/</d:href>
<d:propstat>
<d:prop>
<n:getlastmodified xmlns:n="DAV:">...
<n:creationdate xmlns:n="DAV:">...
<n:resourcetype xmlns:n="DAV:"><n:collection/></n:resourcetype>
<n:getcontenttype xmlns:n="DAV:"></n:getcontenttype>
<n:getcontentlength xmlns:n="DAV:"></n:getcontentlength>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<d:response>
<d:href>/test_folder_1_/acl_users</d:href>
<d:propstat>
<d:prop>
<n:getlastmodified xmlns:n="DAV:">...
<n:creationdate xmlns:n="DAV:">...
<n:resourcetype xmlns:n="DAV:"></n:resourcetype>
<n:getcontenttype xmlns:n="DAV:"></n:getcontenttype>
<n:getcontentlength xmlns:n="DAV:"></n:getcontentlength>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
PROPFIND with defaultViewable
-----------------------------
Now make the class default viewable:
>>> from Products.Five.fiveconfigure import classDefaultViewable
>>> from OFS.Folder import Folder
>>> classDefaultViewable(Folder)
And try it again:
>>> print http(r"""
... PROPFIND /test_folder_1_ HTTP/1.1
... Authorization: Basic test_user_1_:secret
... Content-Length: 250
... Content-Type: application/xml
... Depth: 1
...
... <?xml version="1.0" encoding="utf-8"?>
... <propfind xmlns="DAV:"><prop>
... <getlastmodified xmlns="DAV:"/>
... <creationdate xmlns="DAV:"/>
... <resourcetype xmlns="DAV:"/>
... <getcontenttype xmlns="DAV:"/>
... <getcontentlength xmlns="DAV:"/>
... </prop></propfind>
... """, handle_errors=False)
HTTP/1.1 207 Multi-Status
Connection: close
Content-Length: ...
Content-Location: http://localhost/test_folder_1_/
Content-Type: text/xml; charset="utf-8"
Date: ...
<BLANKLINE>
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/test_folder_1_/</d:href>
<d:propstat>
<d:prop>
<n:getlastmodified xmlns:n="DAV:">...
<n:creationdate xmlns:n="DAV:">...
<n:resourcetype xmlns:n="DAV:"><n:collection/></n:resourcetype>
<n:getcontenttype xmlns:n="DAV:"></n:getcontenttype>
<n:getcontentlength xmlns:n="DAV:"></n:getcontentlength>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<d:response>
<d:href>/test_folder_1_/acl_users</d:href>
<d:propstat>
<d:prop>
<n:getlastmodified xmlns:n="DAV:">...
<n:creationdate xmlns:n="DAV:">...
<n:resourcetype xmlns:n="DAV:"></n:resourcetype>
<n:getcontenttype xmlns:n="DAV:"></n:getcontenttype>
<n:getcontentlength xmlns:n="DAV:"></n:getcontentlength>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
Clean up
--------
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Machinery for making things viewable """Machinery for making things viewable
$Id: viewable.py 14595 2005-07-12 21:26:12Z philikon $ $Id: viewable.py 19646 2005-11-08 15:46:36Z yuppie $
""" """
import inspect import inspect
from zExceptions import NotFound from zExceptions import NotFound
...@@ -48,6 +48,8 @@ class Viewable: ...@@ -48,6 +48,8 @@ class Viewable:
def __browser_default__(self, request): def __browser_default__(self, request):
obj = self obj = self
path = None path = None
if request['REQUEST_METHOD'] not in ('GET', 'POST'):
return obj, [request['REQUEST_METHOD']]
try: try:
obj, path = IBrowserDefault(self).defaultView(request) obj, path = IBrowserDefault(self).defaultView(request)
except ComponentLookupError: except ComponentLookupError:
......
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