[CMF-checkins] SVN: CMF/branches/tseaver-catalog_events/ Snapshot work to get cataloging done via events.

Tres Seaver tseaver at palladion.com
Wed Feb 15 23:44:33 EST 2006


Log message for revision 41631:
  Snapshot work to get cataloging done via events.
  

Changed:
  A   CMF/branches/tseaver-catalog_events/
  D   CMF/branches/tseaver-catalog_events/CHANGES.txt
  A   CMF/branches/tseaver-catalog_events/CHANGES.txt
  U   CMF/branches/tseaver-catalog_events/CMFCore/CMFCatalogAware.py
  D   CMF/branches/tseaver-catalog_events/CMFCore/CookieCrumbler.py
  A   CMF/branches/tseaver-catalog_events/CMFCore/CookieCrumbler.py
  D   CMF/branches/tseaver-catalog_events/CMFCore/configure.zcml
  A   CMF/branches/tseaver-catalog_events/CMFCore/configure.zcml
  U   CMF/branches/tseaver-catalog_events/CMFCore/tests/base/dummy.py
  U   CMF/branches/tseaver-catalog_events/CMFCore/tests/base/testcase.py
  U   CMF/branches/tseaver-catalog_events/CMFCore/tests/test_CMFCatalogAware.py
  A   CMF/branches/tseaver-catalog_events/CMFCore/tests/test_CookieCrumbler.py
  U   CMF/branches/tseaver-catalog_events/CMFCore/tests/test_OpaqueItems.py
  U   CMF/branches/tseaver-catalog_events/CMFCore/tests/test_PortalContent.py
  U   CMF/branches/tseaver-catalog_events/CMFCore/tests/test_PortalFolder.py
  U   CMF/branches/tseaver-catalog_events/CMFDefault/tests/test_Discussions.py

-=-
Copied: CMF/branches/tseaver-catalog_events (from rev 41511, CMF/trunk)

Deleted: CMF/branches/tseaver-catalog_events/CHANGES.txt
===================================================================
--- CMF/trunk/CHANGES.txt	2006-01-31 15:34:23 UTC (rev 41511)
+++ CMF/branches/tseaver-catalog_events/CHANGES.txt	2006-02-16 04:44:31 UTC (rev 41631)
@@ -1,297 +0,0 @@
-CMF 2.0.0-alpha (2006/01/22)
-
-  New Features
-
-    - CMFCore.FSPythonScript:  Customized versions now track the "original"
-      source from which they were customized, and can present a diff between
-      that version and their current source text.
-
-    - CMFDefault and CMFCalendar: Added locales directories with .pot files.
-      A modified i18nextract.py script from Zope 3 is used to extract
-      translatable strings from .py, .pt, .html and .xml files.
-
-    - FSFile: autodetect the encoding of UTF-8 text files with a
-      suitable Byte Order Mark (0xEF 0xBB 0xBF).
-
-    - CMFDefault.MetadataTool:  support arbitrary additional schemas.
-      The "stock" DublinCore-specific API is still accessible, implemented
-      via a special "DCMI" subobject.
-
-    - WorkflowTool and DCWorkflow: Improved add form for workflow objects.
-      Presettings can now be loaded from workflow settings in setup profiles.
-      This replaces the feature that did allow to load presettings from the
-      oldstyle workflow factories registry.
-
-    - WorkflowTool: Switched to generic plug-in mechanism for workflows.
-      Any class registered for IWorkflowDefinition can now be used in the
-      WorkflowTool.
-
-    - DCWorkflow: Added 'revision2' profile.
-      This replaces the hardcoded 'Revision 2' default workflow.
-
-    - CMFActionIcons, CMFCalendar, CMFDefault, CMFTopic, CMFUid:
-      use the new 'for_' argument in GenericSetup's profile registry API
-      to indicate that profiles are intended for CMFCore's ISiteRoot sites.
-
-    - CMFTopic:  added specialized GenericSetup support for topics, to
-      allow capturing criteria in a single XML file.
-
-    - CMFDefault and CMFTopic: Split off CMFTopic profile.
-      CMFTopic support is now configured by an optional extension profile.
-      CMFDefault no longer depends on CMFTopic.
-
-    - TypesTool: Improved add form for type info objects.
-      Presettings can now be loaded from type info settings in setup profiles.
-      This replaces the feature that did allow to load presettings from
-      registered (oldstyle) fti data.
-
-    - CMFCore.CachingPolicyManager: Caching policies can now control all the 
-      Cache-Control tokens defined in the HTTP 1.1 spec (s-maxage, public, 
-      private, no-transform).  When no-cache is enabled, a Pragma: no-cache 
-      header is also sent for HTTP 1.0 clients. Thanks go to Geoff Davis
-      for contributing the necessary patches.
-
-    - ActionsTool: Improved add form for 'CMF Action' objects.
-      Presettings can now be loaded from Action settings in setup profiles.
-
-    - CMFCore and GenericSetup: Added catalog tool setup handlers.
-      This includes node adapters for PluginIndexes, ZCTextIndex and ZCatalog.
-      Support for additional indexes can be added by providing INode adapters.
-      All indexes are cleared by this handler, so please make sure to
-      re-catalog existing content if necessary.
-
-    - GenericSetup.utils: Added new sub-framework for XML im- and export.
-      Instead of using ConfiguratorBase configurators should now implement
-      IBody or INode. These adapters should subclass from XMLAdapterBase or
-      NodeAdapterBase and mix in ObjectManagerHelpers and / or
-      PropertyManagerHelpers if needed.
-
-    - CMFCore.exportimport:  Added framework and interfaces for exporting
-      and importing content using the export / import contexts provided by
-      GenericSetup.
-
-    - CMFSetup: Split off GenericSetup.
-      GenericSetup allows to use CMFSetup functionality outside CMF. See the
-      README.txt of GenericSetup for details.
-
-    - Interfaces: Converted all interfaces to zope 3 style interfaces.
-      Most interfaces are bridged back to zope 2 style interfaces to provide
-      backwards compatibility.
-
-    - The features of CMFonFive have been folder into CMFCore and
-      CMFDefault:
-
-      * Zope 3 menus (browser:menu) are bridged to CMF actions using the
-        portal_fiveactions tool. Any menuItem registered will be
-        accessible though the portal_actions tool, where the menu for
-        which the item was registered will be used as the action
-        category.
-
-      * The Zope 3 'cmf' skin layer provides integration between the
-        Zope3 standard macros and the CMF main template, by redefining
-        five_template.pt.
-
-    - TypeInformation and newstyle Actions: Added i18n support.
-      If 'i18n_domain' is specified, 'title' and 'description' are returned as
-      MassageID objects instead of strings. This allows to use different i18n
-      domains for different TypeInfos and Actions.
-
-    - Replaced user messages by Message objects to improve the i18n support.
-
-    - CMFDefault GenericSetup profile: Added CMF BTree Folder to the list of
-      automatically instantiated types in the types tool.
-      (http://www.zope.org/Collectors/CMF/371)
-
-    - CMFDefault skins: Added members_delete_form.
-      This adds a confirmation step to avoid accidental deletion of members.
-
-    - DirectoryView and derived classes can now have metadata associated with
-      them just like regular FSObject-derived objects can.
-
-    - DirectoryView and derived classes: It is now possible to customize what 
-      gets created to represent directories inside the directory view.
-      Previously, the code had a fixed assumption that all directories on the 
-      file system must turn into instances of 
-      CMFCore.DirectoryView.DirectoryView(Surrogate). It is now possible to 
-      register a class deriving from DirectoryView and have that be 
-      instantiated instead.
-
-    - ActionsTool: Added new way to define Actions.
-      'CMF Action Category' objects can now be added to the portal_actions
-      tool and 'CMF Action' objects to categories or subcategories. To
-      migrate oldstyle Actions (ActionInformation objects) you can create a
-      snapshot and re-import Actions using the portal_setup tool.
-
-    - TypesTool: TypeInformation classes are now pluggable.
-      Any class registered for ITypeInformation can now be used in the
-      TypesTool.
-
-  Bug Fixes
-
-    - CMFDefault skins: Refactored and improved discussion_reply_form.
-
-    - CMFDefault utils: Fixed html_marshal function.
-      The return values are no longer escaped to avoid double quoting and no
-      longer stringified. The page templates take care of these steps.
-
-    - PortalFolder: Synced _verifyObjectPaste code with OFS.CopySupport.
-      The behavior is almost the same as before, but the code is less tolerant
-      if content types are not registered properly.
-
-    - ActionProviderBase: getActionObject did stumble over newstyle Actions.
-
-    - CMFCore.exportimport.content:  Ensure that BODYFILE in our "faux"
-      request is a file-like object, FBO objects which expect to call its
-      'read' method.
-
-    - Got rid of the "CMF Site" and "Configured CMF Site" duality in the ZMI
-      add list by removing the "CMF Site" class registration in CMFDefault
-      and moving the "Configured CMF Site" registration from CMFSetup into
-      CMFDefault, renaming it to "CMF Site". 
-      (http://www.zope.org/Collectors/CMF/364)
-
-    - Updated RELEASE.txt and the slurp_release script to now use Subversion
-      instead of CVS, and to reflect the new tag/branch naming conventions
-      used in the CMF repository.
-
-    - Added testing framework to suppress / examine output from warnings
-      module and from zLOG.
-
-    - CMFUid/UniqueIdGeneratorTool.py: Replaced the old BTree.Length.Length
-      implementation by a simple counter. Using a BTree.Length.Length object
-      as counter may have caused setting the same unique id to multiple 
-      objects under high load. The tools counter gets automigrated on the 
-      first access. This is a forward port from CMF-1_5-branch before the 
-      CMF 1.5.2 release.
-
-    - CMFCore.utils.ToolInit: For icon registration to work with ToolInit
-      you would have to have the same product_name parameter as the actual
-      product name of the product. Now we just pick up that product name from
-      the product context instead, and ignore the product_name parameter (with
-      a deprecation warning).
-
-    - CMFSetup: Merged the registerClass and registerIcon call since 
-      registerClass is capable of registering icons.
-
-    - DublinCore and PortalFolder: Changed fallback in 'Type' method.
-      The fallback is only necessary if the related type info is missing.
-
-    - CMFCore.PortalContent: Wrong variable name in __call__ would blow up
-      if no default view could be found for a piece of content.
-
-  Others
-
-    - Moved GenericSetup out of the CMF package, it is now a standalone
-      product, but still distributed as part of the CMF package.
-
-    - Replaced use of deprecated 'zLOG' module with standard Python
-      'logging' module.
-
-    - CMFCore utils: Made _checkPermission depend on Zope's checkPermission.
-      There is no longer a need to modify the checkPermission behavior in CMF.
-
-    - TypeInformation: Removed support for old setting formats.
-      If TypeInformation objects are initialized with keyword arguments,
-      'actions' and 'aliases' keys have to use the format introduced in
-      CMF 1.5.
-
-    - CMFSetup and GenericSetup: Removed obsolete CMFSetup product.
-      Added __module_aliases__ to support setup tools created with CMFSetup. 
-
-    - DCWorkflow: Removed hardcoded default workflows.
-
-    - Workflow: Removed deprecated WorkflowInformation and getActionsFor.
-
-    - CMFCore and GenericSetup: Moved mechanisms for content export / import
-      to GenericSetup/content.py, and made more generic.
-
-    - CMFDefault: Removed PortalGenerator and manage_addCMFSite.
-
-    - Portal Types: Removed factory_type_information data.
-      TypesTool.listDefaultTypeInformation was removed, the 'fti' argument of
-      utils.ContentInit and the 'typeinfo_name' argument of
-      TypesTool.manage_addTypeInformation are ignored.
-
-    - CatalogTool: A new portal_catalog is now empty.
-      Removed enumerateIndexes, enumerateLexicons, enumerateColumns and
-      _initIndexes. Please use the setup tool to populate the catalog.
-
-    - CMFActionIcons, CMFCalendar and CMFTopic: Removed old install scripts.
-
-    - Refactored and extended CMFDefault.tests.test_join so it can be easily 
-      subclassed and reused for alternative membership implementations. All
-      that is needed is to ovverride _createPortal to return a portal with
-      the desired non-default membership-related tools installed.
-
-    - Remove all "old-style" actions from tools that still carried them 
-      (CMFDefault.MembershipTool, CMFDefault.PropertiesTool, 
-      CMFDefault.RegistrationTool, CMFDefault.SyndicationTool,
-      CMFDefault.DiscussionTool, CMFCore.UndoTool). These have been
-      superceded by "new-style" action information objects stored inside
-      the Actions Tool.
-
-    - Some simplifications to the slurp_release release helper script and
-      updates to the RELEASE.txt release instructions.
-
-    - The CMF now depends on Zope 2.9.0 including Five.
-
-    - Non-release packages moved out of the /CMF/ repository package:
-
-      o hotfixes moved to /CMF_Hotfixes
-
-      o others (CMFCollector, CMFStaging, CMFTracker, CMFWorkspaces)
-        moved to /CMF_Extras/
-
-    - To document how to create a CMF release from CMF a description is now
-      included in RELEASE.txt at the root of the CMF package. This text can
-      guide release managers or volunteers with the appropriate release
-      privileges.
-
-    - Added DeprecationWarning to the all_cmf_tests.py script. The canonical
-      way to run the tests is using "zopectl test".
-      (http://www.zope.org/Collectors/CMF/272)
-
-    - CMFDefault: Oldstyle DefaultWorkflowDefinition is no longer registered.
-      So you can't accidentally add this deprecated workflow.
-
-    - The "Access future portal content" was not used anywhere and has been
-      removed. (http://www.zope.org/Collectors/CMF/240)
-
-    - CMFDefault: Marked 'manage_addCMFSite' as deprecated.
-      To add a new CMF site, select 'Configured CMF Site'. The 'CMFDefault
-      Site' profile creates a new site like those you know from 'CMF Site'.
-
-    - CMFDefault: Cleaned out some super-ancient code for migrating
-      or fixing PTK content so it would work with CMF.
-
-    - CatalogTool: Removed deprecated indexes and metadata.
-      'Creator' was replaced by 'listCreators', 'portal_type' should be used
-      instead of 'meta_type'.
-
-    - CMFCore: Removed some deprecated aliases and constants.
-
-    - Permissions: Removed deprecated oldstyle permission modules.
-
-    - TypeInformation: Removed deprecated Type method.
-
-    - PortalFolder: Removed deprecated _morphSpec() and spec arguments.
-
-    - Portal Types: Removed deprecated _getViewFor / getActionById machinery.
-      PortalContent objects might be used as methods, so __call__() still
-      returns their default view.
-
-    - CMFDefault and CMFTopic skins: Removed deprecated DTML skins and layers.
-
-    - ActionsTool: Removed deprecated support for oldstyle Action Providers.
-      If Action Providers don't implement the ActionProvider interface they
-      are now ignored.
-
-    - Workflow: Removed deprecated WorkflowMethod machinery.
-
-    - Made unit tests close the request properly.
-
-    - Reordered base classes of File and Image, to allow use of super().
-
-CMF 1.5.x and previous
-
-  For a complete list of changes see HISTORY.txt.

Copied: CMF/branches/tseaver-catalog_events/CHANGES.txt (from rev 41514, CMF/trunk/CHANGES.txt)

Modified: CMF/branches/tseaver-catalog_events/CMFCore/CMFCatalogAware.py
===================================================================
--- CMF/trunk/CMFCore/CMFCatalogAware.py	2006-01-31 15:34:23 UTC (rev 41511)
+++ CMF/branches/tseaver-catalog_events/CMFCore/CMFCatalogAware.py	2006-02-16 04:44:31 UTC (rev 41631)
@@ -22,7 +22,15 @@
 from ExtensionClass import Base
 from Globals import DTMLFile
 from Globals import InitializeClass
+from OFS.interfaces import IObjectWillBeMovedEvent
+from OFS.interfaces import IObjectClonedEvent
 
+from zope.interface import implements
+from zope.component import adapts
+from zope.app.container.interfaces import IObjectAddedEvent
+from zope.app.container.interfaces import IObjectMovedEvent
+from zope.app.location.interfaces import ISublocations
+
 from permissions import AccessContentsInformation
 from permissions import ManagePortal
 from permissions import ModifyPortalContent
@@ -31,8 +39,8 @@
 from utils import getToolByName
 
 from interfaces import ICallableOpaqueItem
-from interfaces.IOpaqueItems \
-        import ICallableOpaqueItem as z2ICallableOpaqueItem
+from interfaces import ICallableOpaqueItemEvents
+from interfaces import IContentish
 
 class CMFCatalogAware(Base):
     """Mix-in for notifying portal_catalog and portal_workflow
@@ -165,8 +173,7 @@
         self_base = aq_base(self)
         for name in self_base.__dict__.keys():
             obj = getattr(self_base, name)
-            if ICallableOpaqueItem.providedBy(obj) \
-                    or z2ICallableOpaqueItem.isImplementedBy(obj):
+            if ICallableOpaqueItem.providedBy(obj):
                 items.append((obj.getId(), obj))
 
         return tuple(items)
@@ -190,22 +197,7 @@
     # Hooks
     # -----
 
-    def manage_afterAdd(self, item, container):
-        """
-            Add self to the catalog.
-            (Called when the object is created or moved.)
-        """
-        self.indexObject()
-        self.__recurse('manage_afterAdd', item, container)
-
-    def manage_afterClone(self, item):
-        """
-            Add self to the workflow.
-            (Called when the object is cloned.)
-        """
-        self.notifyWorkflowCreated()
-        self.__recurse('manage_afterClone', item)
-
+    def _clearLocalRolesAfterClone(self):
         # Make sure owner local role is set after pasting
         # The standard Zope mechanisms take care of executable ownership
         current_user = _getAuthenticatedUser(self)
@@ -214,27 +206,23 @@
             self.manage_delLocalRoles(local_role_holders)
             self.manage_setLocalRoles(current_user.getId(), ['Owner'])
 
-    def manage_beforeDelete(self, item, container):
+    def _recurseOpaques(self, name, container=None):
         """
-            Remove self from the catalog.
-            (Called when the object is deleted or moved.)
+            Recurse in both opaque subobjects.
         """
-        self.__recurse('manage_beforeDelete', item, container)
-        self.unindexObject()
+        for opaque in  self.opaqueValues():
+            s = getattr(opaque, '_p_changed', 0)
+            try:
+                if container is None:
+                    getattr(opaque, name)(opaque)
+                else:
+                    getattr(opaque, name)(opaque, container)
+            except AttributeError:
+                pass
+            else:
+                if s is None:
+                    opaque._p_deactivate()
 
-    def __recurse(self, name, *args):
-        """
-            Recurse in both normal and opaque subobjects.
-        """
-        values = self.objectValues()
-        opaque_values = self.opaqueValues()
-        for subobjects in values, opaque_values:
-            for ob in subobjects:
-                s = getattr(ob, '_p_changed', 0)
-                if hasattr(aq_base(ob), name):
-                    getattr(ob, name)(*args)
-                if s is None: ob._p_deactivate()
-
     # ZMI
     # ---
 
@@ -279,3 +267,59 @@
             manage_tabs_message=manage_tabs_message)
 
 InitializeClass(CMFCatalogAware)
+
+class ContentishSublocations(object):
+    """ Get the sublocations for content, including "opaque" subitems.
+    """
+    adapts(IContentish)
+    implements(ISublocations)
+
+    def __init__(self, context):
+        self.context = context
+
+    def sublocations(self):
+
+        for ob in self.context.objectValues():
+            yield ob
+
+        for opaque in  self.context.opaqueValues():
+            s = getattr(opaque, '_p_changed', 0)
+            yield opaque
+            if s is None:
+                opaque._p_deactivate()
+
+def handleObjectEvent(ob, event):
+    """ Event subscriber for (IContentish, IObjectEvent) events.
+
+    o XXX:  the nasty '_recurseOpaques' propagates notification  to opaque
+            items, which are ignored by the stock ObjectManager propagation.
+    """
+    if not IContentish.providedBy(ob):
+        return
+
+    if IObjectAddedEvent.providedBy(event):
+        if event.newParent is not None:
+            ob.indexObject()
+            if ICallableOpaqueItemEvents.providedBy(ob):
+                ob.manage_afterAdd(ob, event.newParent)
+            #ob._recurseOpaques('manage_afterAdd', ob)
+
+    elif IObjectClonedEvent.providedBy(event):
+        ob.notifyWorkflowCreated()
+        ob._clearLocalRolesAfterClone()
+        if ICallableOpaqueItemEvents.providedBy(ob):
+            ob.manage_afterClone(ob)
+        #ob._recurseOpaques('manage_afterClone')
+
+    elif IObjectMovedEvent.providedBy(event):
+        if event.newParent is not None:
+            ob.reindexObject()
+            #ob._recurseOpaques('manage_afterAdd', ob)
+
+    elif IObjectWillBeMovedEvent.providedBy(event):
+        if event.oldParent is not None:
+            ob.unindexObject()
+            if ICallableOpaqueItemEvents.providedBy(ob):
+                ob.manage_beforeDelete(ob, event.oldParent)
+            #ob._recurseOpaques('manage_beforeDelete', ob)
+

Deleted: CMF/branches/tseaver-catalog_events/CMFCore/CookieCrumbler.py
===================================================================
--- CMF/trunk/CMFCore/CookieCrumbler.py	2006-01-31 15:34:23 UTC (rev 41511)
+++ CMF/branches/tseaver-catalog_events/CMFCore/CookieCrumbler.py	2006-02-16 04:44:31 UTC (rev 41631)
@@ -1,441 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001 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.
-# 
-##############################################################################
-""" Cookie Crumbler: Enable cookies for non-cookie user folders.
-
-$Id$
-"""
-
-from base64 import encodestring, decodestring
-from urllib import quote, unquote
-
-from Acquisition import aq_inner, aq_parent
-from DateTime import DateTime
-from AccessControl import ClassSecurityInfo, Permissions
-from ZPublisher import BeforeTraverse
-import Globals
-from Globals import HTMLFile
-from ZPublisher.HTTPRequest import HTTPRequest
-from OFS.Folder import Folder
-from zExceptions import Redirect
-from zope.interface import implements
-
-from interfaces import ICookieCrumbler
-
-
-# Constants.
-ATTEMPT_NONE = 0       # No attempt at authentication
-ATTEMPT_LOGIN = 1      # Attempt to log in
-ATTEMPT_RESUME = 2     # Attempt to resume session
-
-ModifyCookieCrumblers = 'Modify Cookie Crumblers'
-ViewManagementScreens = Permissions.view_management_screens
-
-
-class CookieCrumblerDisabled(Exception):
-
-    """Cookie crumbler should not be used for a certain request.
-    """
-
-
-class CookieCrumbler(Folder):
-
-    """Reads cookies during traversal and simulates the HTTP auth headers.
-    """
-
-    implements(ICookieCrumbler)
-
-    meta_type = 'Cookie Crumbler'
-
-    security = ClassSecurityInfo()
-    security.declareProtected(ModifyCookieCrumblers, 'manage_editProperties')
-    security.declareProtected(ModifyCookieCrumblers, 'manage_changeProperties')
-    security.declareProtected(ViewManagementScreens, 'manage_propertiesForm')
-
-    # By default, anonymous users can view login/logout pages.
-    _View_Permission = ('Anonymous',)
-
-
-    _properties = ({'id':'auth_cookie', 'type': 'string', 'mode':'w',
-                    'label':'Authentication cookie name'},
-                   {'id':'name_cookie', 'type': 'string', 'mode':'w',
-                    'label':'User name form variable'},
-                   {'id':'pw_cookie', 'type': 'string', 'mode':'w',
-                    'label':'User password form variable'},
-                   {'id':'persist_cookie', 'type': 'string', 'mode':'w',
-                    'label':'User name persistence form variable'},
-                   {'id':'auto_login_page', 'type': 'string', 'mode':'w',
-                    'label':'Login page ID'},
-                   {'id':'logout_page', 'type': 'string', 'mode':'w',
-                    'label':'Logout page ID'},
-                   {'id':'unauth_page', 'type': 'string', 'mode':'w',
-                    'label':'Failed authorization page ID'},
-                   {'id':'local_cookie_path', 'type': 'boolean', 'mode':'w',
-                    'label':'Use cookie paths to limit scope'},
-                   {'id':'cache_header_value', 'type': 'string', 'mode':'w',
-                    'label':'Cache-Control header value'},
-                   {'id':'log_username', 'type':'boolean', 'mode': 'w',
-                    'label':'Log cookie auth username to access log'}
-                   )
-
-    auth_cookie = '__ac'
-    name_cookie = '__ac_name'
-    pw_cookie = '__ac_password'
-    persist_cookie = '__ac_persistent'
-    auto_login_page = 'login_form'
-    unauth_page = ''
-    logout_page = 'logged_out'
-    local_cookie_path = False
-    cache_header_value = 'private'
-    log_username = True
-
-    security.declarePrivate('delRequestVar')
-    def delRequestVar(self, req, name):
-        # No errors of any sort may propagate, and we don't care *what*
-        # they are, even to log them.
-        try: del req.other[name]
-        except: pass
-        try: del req.form[name]
-        except: pass
-        try: del req.cookies[name]
-        except: pass
-        try: del req.environ[name]
-        except: pass
-
-    security.declarePublic('getCookiePath')
-    def getCookiePath(self):
-        if not self.local_cookie_path:
-            return '/'
-        parent = aq_parent(aq_inner(self))
-        if parent is not None:
-            return '/' + parent.absolute_url(1)
-        else:
-            return '/'
-
-    # Allow overridable cookie set/expiration methods.
-    security.declarePrivate('getCookieMethod')
-    def getCookieMethod(self, name, default=None):
-        return getattr(self, name, default)
-
-    security.declarePrivate('defaultSetAuthCookie')
-    def defaultSetAuthCookie(self, resp, cookie_name, cookie_value):
-        kw = {}
-        req = getattr(self, 'REQUEST', None)
-        if req is not None and req.get('SERVER_URL', '').startswith('https:'):
-            # Ask the client to send back the cookie only in SSL mode
-            kw['secure'] = 'y'
-        resp.setCookie(cookie_name, cookie_value,
-                       path=self.getCookiePath(), **kw)
-
-    security.declarePrivate('defaultExpireAuthCookie')
-    def defaultExpireAuthCookie(self, resp, cookie_name):
-        resp.expireCookie(cookie_name, path=self.getCookiePath())
-    
-    def _setAuthHeader(self, ac, request, response):
-        """Set the auth headers for both the Zope and Medusa http request
-        objects.
-        """
-        request._auth = 'Basic %s' % ac
-        response._auth = 1
-        if self.log_username:
-            # Set the authorization header in the medusa http request
-            # so that the username can be logged to the Z2.log
-            try:
-                # Put the full-arm latex glove on now...
-                medusa_headers = response.stdout._request._header_cache
-            except AttributeError:
-                pass
-            else:
-                medusa_headers['authorization'] = request._auth
-
-    security.declarePrivate('modifyRequest')
-    def modifyRequest(self, req, resp):
-        """Copies cookie-supplied credentials to the basic auth fields.
-
-        Returns a flag indicating what the user is trying to do with
-        cookies: ATTEMPT_NONE, ATTEMPT_LOGIN, or ATTEMPT_RESUME.  If
-        cookie login is disabled for this request, raises
-        CookieCrumblerDisabled.
-        """
-        if (req.__class__ is not HTTPRequest
-            or not req['REQUEST_METHOD'] in ('HEAD', 'GET', 'PUT', 'POST')
-            or req.environ.has_key('WEBDAV_SOURCE_PORT')):
-            raise CookieCrumblerDisabled
-
-        # attempt may contain information about an earlier attempt to
-        # authenticate using a higher-up cookie crumbler within the
-        # same request.
-        attempt = getattr(req, '_cookie_auth', ATTEMPT_NONE)
-
-        if attempt == ATTEMPT_NONE:
-            if req._auth:
-                # An auth header was provided and no cookie crumbler
-                # created it.  The user must be using basic auth.
-                raise CookieCrumblerDisabled
-
-            if req.has_key(self.pw_cookie) and req.has_key(self.name_cookie):
-                # Attempt to log in and set cookies.
-                attempt = ATTEMPT_LOGIN
-                name = req[self.name_cookie]
-                pw = req[self.pw_cookie]
-                ac = encodestring('%s:%s' % (name, pw)).rstrip()
-                self._setAuthHeader(ac, req, resp)
-                if req.get(self.persist_cookie, 0):
-                    # Persist the user name (but not the pw or session)
-                    expires = (DateTime() + 365).toZone('GMT').rfc822()
-                    resp.setCookie(self.name_cookie, name,
-                                   path=self.getCookiePath(),
-                                   expires=expires)
-                else:
-                    # Expire the user name
-                    resp.expireCookie(self.name_cookie,
-                                      path=self.getCookiePath())
-                method = self.getCookieMethod( 'setAuthCookie'
-                                             , self.defaultSetAuthCookie )
-                method( resp, self.auth_cookie, quote( ac ) )
-                self.delRequestVar(req, self.name_cookie)
-                self.delRequestVar(req, self.pw_cookie)
-
-            elif req.has_key(self.auth_cookie):
-                # Attempt to resume a session if the cookie is valid.
-                # Copy __ac to the auth header.
-                ac = unquote(req[self.auth_cookie])
-                if ac and ac != 'deleted':
-                    try:
-                        decodestring(ac)
-                    except:
-                        # Not a valid auth header.
-                        pass
-                    else:
-                        attempt = ATTEMPT_RESUME
-                        self._setAuthHeader(ac, req, resp)
-                        self.delRequestVar(req, self.auth_cookie)
-                        method = self.getCookieMethod(
-                            'twiddleAuthCookie', None)
-                        if method is not None:
-                            method(resp, self.auth_cookie, quote(ac))
-
-        req._cookie_auth = attempt
-        return attempt
-
-
-    def __call__(self, container, req):
-        '''The __before_publishing_traverse__ hook.'''
-        resp = self.REQUEST['RESPONSE']
-        try:
-            attempt = self.modifyRequest(req, resp)
-        except CookieCrumblerDisabled:
-            return
-        if req.get('disable_cookie_login__', 0):
-            return
-
-        if (self.unauth_page or
-            attempt == ATTEMPT_LOGIN or attempt == ATTEMPT_NONE):
-            # Modify the "unauthorized" response.
-            req._hold(ResponseCleanup(resp))
-            resp.unauthorized = self.unauthorized
-            resp._unauthorized = self._unauthorized
-        if attempt != ATTEMPT_NONE:
-            # Trying to log in or resume a session
-            if self.cache_header_value:
-                # we don't want caches to cache the resulting page
-                resp.setHeader('Cache-Control', self.cache_header_value)
-                # demystify this in the response.
-                resp.setHeader('X-Cache-Control-Hdr-Modified-By',
-                               'CookieCrumbler')
-            phys_path = self.getPhysicalPath()
-            if self.logout_page:
-                # Cookies are in use.
-                page = getattr(container, self.logout_page, None)
-                if page is not None:
-                    # Provide a logout page.
-                    req._logout_path = phys_path + ('logout',)
-            req._credentials_changed_path = (
-                phys_path + ('credentialsChanged',))
-
-    security.declarePublic('credentialsChanged')
-    def credentialsChanged(self, user, name, pw):
-        ac = encodestring('%s:%s' % (name, pw)).rstrip()
-        method = self.getCookieMethod( 'setAuthCookie'
-                                       , self.defaultSetAuthCookie )
-        resp = self.REQUEST['RESPONSE']
-        method( resp, self.auth_cookie, quote( ac ) )
-
-    def _cleanupResponse(self):
-        resp = self.REQUEST['RESPONSE']
-        # No errors of any sort may propagate, and we don't care *what*
-        # they are, even to log them.
-        try: del resp.unauthorized
-        except: pass
-        try: del resp._unauthorized
-        except: pass
-        return resp
-
-    security.declarePrivate('unauthorized')
-    def unauthorized(self):
-        resp = self._cleanupResponse()
-        # If we set the auth cookie before, delete it now.
-        if resp.cookies.has_key(self.auth_cookie):
-            del resp.cookies[self.auth_cookie]
-        # Redirect if desired.
-        url = self.getUnauthorizedURL()
-        if url is not None:
-            raise Redirect, url
-        # Fall through to the standard unauthorized() call.
-        resp.unauthorized()
-
-    def _unauthorized(self):
-        resp = self._cleanupResponse()
-        # If we set the auth cookie before, delete it now.
-        if resp.cookies.has_key(self.auth_cookie):
-            del resp.cookies[self.auth_cookie]
-        # Redirect if desired.
-        url = self.getUnauthorizedURL()
-        if url is not None:
-            resp.redirect(url, lock=1)
-            # We don't need to raise an exception.
-            return
-        # Fall through to the standard _unauthorized() call.
-        resp._unauthorized()
-
-    security.declarePublic('getUnauthorizedURL')
-    def getUnauthorizedURL(self):
-        '''
-        Redirects to the login page.
-        '''
-        req = self.REQUEST
-        resp = req['RESPONSE']
-        attempt = getattr(req, '_cookie_auth', ATTEMPT_NONE)
-        if attempt == ATTEMPT_NONE:
-            # An anonymous user was denied access to something.
-            page_id = self.auto_login_page
-            retry = ''
-        elif attempt == ATTEMPT_LOGIN:
-            # The login attempt failed.  Try again.
-            page_id = self.auto_login_page
-            retry = '1'
-        else:
-            # An authenticated user was denied access to something.
-            page_id = self.unauth_page
-            retry = ''
-        if page_id:
-            page = self.restrictedTraverse(page_id, None)
-            if page is not None:
-                came_from = req.get('came_from', None)
-                if came_from is None:
-                    came_from = req.get('VIRTUAL_URL', None)
-                    if came_from is None:
-                        came_from = '%s%s%s' % ( req['SERVER_URL'].strip(),
-                                                 req['SCRIPT_NAME'].strip(),
-                                                 req['PATH_INFO'].strip() )
-                    query = req.get('QUERY_STRING')
-                    if query:
-                        # Include the query string in came_from
-                        if not query.startswith('?'):
-                            query = '?' + query
-                        came_from = came_from + query
-                url = '%s?came_from=%s&retry=%s&disable_cookie_login__=1' % (
-                    page.absolute_url(), quote(came_from), retry)
-                return url
-        return None
-
-    # backward compatible alias
-    getLoginURL = getUnauthorizedURL
-
-    security.declarePublic('logout')
-    def logout(self):
-        '''
-        Logs out the user and redirects to the logout page.
-        '''
-        req = self.REQUEST
-        resp = req['RESPONSE']
-        method = self.getCookieMethod( 'expireAuthCookie'
-                                     , self.defaultExpireAuthCookie )
-        method( resp, cookie_name=self.auth_cookie )
-        if self.logout_page:
-            page = self.restrictedTraverse(self.logout_page, None)
-            if page is not None:
-                resp.redirect('%s?disable_cookie_login__=1'
-                              % page.absolute_url())
-                return ''
-        # We should not normally get here.
-        return 'Logged out.'
-
-    # Installation and removal of traversal hooks.
-
-    def manage_beforeDelete(self, item, container):
-        if item is self:
-            handle = self.meta_type + '/' + self.getId()
-            BeforeTraverse.unregisterBeforeTraverse(container, handle)
-
-    def manage_afterAdd(self, item, container):
-        if item is self:
-            handle = self.meta_type + '/' + self.getId()
-            container = container.this()
-            nc = BeforeTraverse.NameCaller(self.getId())
-            BeforeTraverse.registerBeforeTraverse(container, nc, handle)
-
-    security.declarePublic('propertyLabel')
-    def propertyLabel(self, id):
-        """Return a label for the given property id
-        """
-        for p in self._properties:
-            if p['id'] == id:
-                return p.get('label', id)
-        return id
-
-Globals.InitializeClass(CookieCrumbler)
-
-
-class ResponseCleanup:
-    def __init__(self, resp):
-        self.resp = resp
-
-    def __del__(self):
-        # Free the references.
-        #
-        # No errors of any sort may propagate, and we don't care *what*
-        # they are, even to log them.
-        try: del self.resp.unauthorized
-        except: pass
-        try: del self.resp._unauthorized
-        except: pass
-        try: del self.resp
-        except: pass
-
-
-manage_addCCForm = HTMLFile('dtml/addCC', globals())
-manage_addCCForm.__name__ = 'addCC'
-
-def _create_forms(ob):
-    ''' Create default forms inside ob '''
-    import os
-    from OFS.DTMLMethod import addDTMLMethod
-    dtmldir = os.path.join(os.path.dirname(__file__), 'dtml')
-    for fn in ('index_html', 'logged_in', 'logged_out', 'login_form',
-                'standard_login_footer', 'standard_login_header'):
-        filename = os.path.join(dtmldir, fn + '.dtml')
-        f = open(filename, 'rt')
-        try: data = f.read()
-        finally: f.close()
-        addDTMLMethod(ob, fn, file=data)
-
-def manage_addCC(dispatcher, id, create_forms=0, REQUEST=None):
-    ' '
-    ob = CookieCrumbler()
-    ob.id = id
-    dispatcher._setObject(ob.getId(), ob)
-    ob = getattr(dispatcher.this(), ob.getId())
-    if create_forms:
-        _create_forms(ob)
-    if REQUEST is not None:
-        return dispatcher.manage_main(dispatcher, REQUEST)

Copied: CMF/branches/tseaver-catalog_events/CMFCore/CookieCrumbler.py (from rev 41522, CMF/trunk/CMFCore/CookieCrumbler.py)

Deleted: CMF/branches/tseaver-catalog_events/CMFCore/configure.zcml
===================================================================
--- CMF/trunk/CMFCore/configure.zcml	2006-01-31 15:34:23 UTC (rev 41511)
+++ CMF/branches/tseaver-catalog_events/CMFCore/configure.zcml	2006-02-16 04:44:31 UTC (rev 41631)
@@ -1,42 +0,0 @@
-<configure
-    xmlns="http://namespaces.zope.org/zope"
-    xmlns:five="http://namespaces.zope.org/five"
-    >
-
-  <include package=".browser"/>
-
-  <include package=".exportimport"/>
-
-  <five:registerClass
-      class=".ActionInformation.ActionCategory"
-      meta_type="CMF Action Category"
-      addview="addActionCategory.html"
-      permission="cmf.ManagePortal"
-      global="False"
-      />
-
-  <five:registerClass
-      class=".ActionInformation.Action"
-      meta_type="CMF Action"
-      addview="addAction.html"
-      permission="cmf.ManagePortal"
-      global="False"
-      />
-
-  <five:registerClass
-      class=".TypesTool.FactoryTypeInformation"
-      meta_type="Factory-based Type Information"
-      addview="addFactoryTypeInformation.html"
-      permission="cmf.ManagePortal"
-      global="False"
-      />
-
-  <five:registerClass
-      class=".TypesTool.ScriptableTypeInformation"
-      meta_type="Scriptable Type Information"
-      addview="addScriptableTypeInformation.html"
-      permission="cmf.ManagePortal"
-      global="False"
-      />
-
-</configure>

Copied: CMF/branches/tseaver-catalog_events/CMFCore/configure.zcml (from rev 41514, CMF/trunk/CMFCore/configure.zcml)

Modified: CMF/branches/tseaver-catalog_events/CMFCore/tests/base/dummy.py
===================================================================
--- CMF/trunk/CMFCore/tests/base/dummy.py	2006-01-31 15:34:23 UTC (rev 41511)
+++ CMF/branches/tseaver-catalog_events/CMFCore/tests/base/dummy.py	2006-02-16 04:44:31 UTC (rev 41631)
@@ -17,9 +17,13 @@
 
 from Acquisition import Implicit, aq_base, aq_inner, aq_parent
 from OFS.SimpleItem import Item
+from OFS.interfaces import IObjectManager
+from zope.interface import implements
 
+from Products.CMFCore.interfaces import IContentish
 from Products.CMFCore.ActionProviderBase import ActionProviderBase
 from Products.CMFCore.PortalContent import PortalContent
+
 from security import OmnipotentUser
 
 
@@ -80,6 +84,7 @@
     """
     A Dummy piece of PortalContent
     """
+    implements(IContentish)
     meta_type = 'Dummy'
     portal_type = 'Dummy Content'
     url = 'foo_url'
@@ -166,9 +171,10 @@
 
 
 class DummyFolder(DummyObject):
+    """ Dummy Container for testing
     """
-        Dummy Container for testing
-    """
+    implements(IObjectManager)
+
     def __init__( self, id='dummy', fake_product=0, prefix='' ):
         self._prefix = prefix
         self._id = id
@@ -186,17 +192,31 @@
         return getattr(self, id)
 
     def _setObject(self, id, object):
+        from zope.event import notify
+        from zope.app.container.contained import ObjectAddedEvent
+        from zope.app.container.contained import notifyContainerModified
+        from OFS.event import ObjectWillBeAddedEvent
+        notify(ObjectWillBeAddedEvent(object, self, id))
         self._setOb(id, object)
         object = self._getOb(id)
         if hasattr(aq_base(object), 'manage_afterAdd'):
             object.manage_afterAdd(object, self)
+        notify(ObjectAddedEvent(object, self, id))
+        notifyContainerModified(self)
         return object
 
     def _delObject(self, id):
+        from zope.event import notify
+        from zope.app.container.contained import ObjectRemovedEvent
+        from zope.app.container.contained import notifyContainerModified
+        from OFS.event import ObjectWillBeRemovedEvent
         object = self._getOb(id)
+        notify(ObjectWillBeRemovedEvent(object, self, id))
         if hasattr(aq_base(object), 'manage_beforeDelete'):
             object.manage_beforeDelete(object, self)
         self._delOb(id)
+        notify(ObjectRemovedEvent(object, self, id))
+        notifyContainerModified(self)
 
     def getPhysicalPath(self):
         p = aq_parent(aq_inner(self))

Modified: CMF/branches/tseaver-catalog_events/CMFCore/tests/base/testcase.py
===================================================================
--- CMF/trunk/CMFCore/tests/base/testcase.py	2006-01-31 15:34:23 UTC (rev 41511)
+++ CMF/branches/tseaver-catalog_events/CMFCore/tests/base/testcase.py	2006-02-16 04:44:31 UTC (rev 41631)
@@ -168,6 +168,29 @@
 _prefix = abspath(join(_prefix,'..'))
 
 
+class ContentEventAwareTests(PlacelessSetup):
+    """ Mix-in for test case classes which need to get object events handled.
+    """
+
+    def _registerObjectEventHandler(self):
+        from zope.component import provideAdapter
+        from zope.component import provideHandler
+        from zope.app.container.interfaces import IObjectEvent
+        from zope.app.location.interfaces import ISublocations
+        from Products.CMFCore.interfaces import IContentish
+        from Products.CMFCore.interfaces import IFolderish
+        from Products.CMFCore.CMFCatalogAware import ContentishSublocations
+        from Products.CMFCore.CMFCatalogAware import handleObjectEvent
+        import Products.Five
+        from Products.Five import zcml
+        #   First, set up "stock" OFS event propagation
+        zcml.load_config('meta.zcml', Products.Five)
+        zcml.load_config('event.zcml', Products.Five)
+        #   Now, register the CMF-specific handler
+        provideHandler(handleObjectEvent, adapts=(IContentish, IObjectEvent))
+        provideAdapter(ContentishSublocations, adapts=(IContentish,),
+                       provides=ISublocations)
+
 class FSDVTest( TestCase, WarningInterceptor ):
     # Base class for FSDV test, creates a fake skin
     # copy that can be edited.

Modified: CMF/branches/tseaver-catalog_events/CMFCore/tests/test_CMFCatalogAware.py
===================================================================
--- CMF/trunk/CMFCore/tests/test_CMFCatalogAware.py	2006-01-31 15:34:23 UTC (rev 41511)
+++ CMF/branches/tseaver-catalog_events/CMFCore/tests/test_CMFCatalogAware.py	2006-02-16 04:44:31 UTC (rev 41631)
@@ -17,14 +17,20 @@
 
 import unittest
 import Testing
+import transaction
 
 from OFS.Folder import Folder
 from OFS.SimpleItem import SimpleItem
+from zope.interface import implements
 
 from Products.CMFCore.CMFCatalogAware import CMFCatalogAware
 from Products.CMFCore.exceptions import NotFound
+from Products.CMFCore.interfaces import IContentish
+from Products.CMFCore.tests.test_PortalFolder import _AllowedUser
+from Products.CMFCore.tests.test_PortalFolder import _SensitiveSecurityPolicy
 from Products.CMFCore.tests.base.testcase import LogInterceptor
-from Products.CMFCore.WorkflowTool import WorkflowTool
+from Products.CMFCore.tests.base.testcase import ContentEventAwareTests
+from Products.CMFCore.tests.base.testcase import SecurityTest
 
 CMF_SECURITY_INDEXES = CMFCatalogAware._cmf_security_indexes
 
@@ -84,7 +90,14 @@
             res.append(self.brain_class(ob, obpath))
         return res
 
+class DummyWorkflowTool(SimpleItem):
+    def __init__(self):
+        self.log = []
+    def notifyCreated(self, obj):
+        self.log.append('created %s' % physicalpath(obj))
+
 class TheClass(CMFCatalogAware, Folder):
+    implements(IContentish)
     def __init__(self, id):
         self._setId(id)
         self.notified = False
@@ -92,14 +105,15 @@
         self.notified = True
 
 
-class CMFCatalogAwareTests(unittest.TestCase, LogInterceptor):
-
+class CMFCatalogAwareTests(unittest.TestCase,
+                           LogInterceptor,
+                          ):
     def setUp(self):
         self.root = DummyRoot('')
         self.root.site = SimpleFolder('site')
         self.site = self.root.site
         self.site._setObject('portal_catalog', DummyCatalog())
-        self.site._setObject('portal_workflow', WorkflowTool())
+        self.site._setObject('portal_workflow', DummyWorkflowTool())
         self.site.foo = TheClass('foo')
 
     def tearDown(self):
@@ -184,12 +198,161 @@
     def test_workflow_tool(self):
         foo = self.site.foo
         self.assertEqual(foo._getWorkflowTool(), self.site.portal_workflow)
-
+        
     # FIXME: more tests needed
 
+class CMFCatalogAware_CopySupport_Tests(SecurityTest, ContentEventAwareTests):
+
+    def setUp(self):
+        SecurityTest.setUp(self)
+        ContentEventAwareTests.setUp(self)
+
+    def tearDown(self):
+        ContentEventAwareTests.tearDown(self)
+        SecurityTest.tearDown(self)
+
+
+    def _makeSite(self):
+        import cStringIO
+        from OFS.Application import Application
+        from OFS.tests.testCopySupport import makeConnection
+        from Products.CMFCore.PortalFolder import PortalFolder
+        from Testing.makerequest import makerequest
+
+        self.connection = makeConnection()
+        try:
+            r = self.connection.root()
+            a = Application()
+            r['Application'] = a
+            self.root = a
+            responseOut = self.responseOut = cStringIO.StringIO()
+            self.app = makerequest(self.root, stdout=responseOut)
+            site = SimpleFolder('site')
+            self.app._setObject('site', site)
+            site = self.app._getOb('site')
+            site._setObject('portal_catalog', DummyCatalog())
+            site._setObject('portal_workflow', DummyWorkflowTool())
+            # Hack, we need a _p_mtime for the file, so we make sure that it
+            # has one. We use a subtransaction, which means we can rollback
+            # later and pretend we didn't touch the ZODB.
+            transaction.savepoint(optimistic=True)
+        except:
+            self.connection.close()
+            raise
+        else:
+            return site
+
+    def _initPolicyAndUser( self
+                          , a_lambda=None
+                          , v_lambda=None
+                          , c_lambda=None
+                          ):
+        from AccessControl import SecurityManager
+        from Products.CMFCore.tests.base.testcase import newSecurityManager
+
+        def _promiscuous( *args, **kw ):
+            return 1
+
+        if a_lambda is None:
+            a_lambda = _promiscuous
+
+        if v_lambda is None:
+            v_lambda = _promiscuous
+
+        if c_lambda is None:
+            c_lambda = _promiscuous
+
+        scp = _SensitiveSecurityPolicy( v_lambda, c_lambda )
+        SecurityManager.setSecurityPolicy( scp )
+        newSecurityManager( None
+                          , _AllowedUser( a_lambda ).__of__( self.root ) )
+
+    def test_object_indexed_after_adding(self):
+
+        site = self._makeSite()
+        self._registerObjectEventHandler()
+        bar = TheClass('bar')
+        site._setObject('bar', bar)
+        cat = site.portal_catalog
+        self.assertEquals(cat.log, ["index /site/bar"])
+
+    def test_object_unindexed_after_removing(self):
+
+        site = self._makeSite()
+        self._registerObjectEventHandler()
+        bar = TheClass('bar')
+        site._setObject('bar', bar)
+        cat = site.portal_catalog
+        cat.log = []
+        site._delObject('bar')
+        self.assertEquals(cat.log, ["unindex /site/bar"])
+
+    def test_object_indexed_after_copy_and_pasting(self):
+
+        self._initPolicyAndUser() # allow copy/paste operations
+        site = self._makeSite()
+        site.folder1 = SimpleFolder('folder1')
+        folder1 = site.folder1
+        site.folder2 = SimpleFolder('folder2')
+        folder2 = site.folder2
+
+        self._registerObjectEventHandler()
+        bar = TheClass('bar')
+        folder1._setObject('bar', bar)
+        cat = site.portal_catalog
+        cat.log = []
+
+        transaction.savepoint(optimistic=True)
+
+        cookie = folder1.manage_copyObjects(ids=['bar'])
+        folder2.manage_pasteObjects(cookie)
+
+        self.assertEquals(cat.log, ["index /site/folder2/bar"])
+
+    def test_object_reindexed_after_cut_and_paste(self):
+
+        self._initPolicyAndUser() # allow copy/paste operations
+        site = self._makeSite()
+        site.folder1 = SimpleFolder('folder1')
+        folder1 = site.folder1
+        site.folder2 = SimpleFolder('folder2')
+        folder2 = site.folder2
+
+        self._registerObjectEventHandler()
+        bar = TheClass('bar')
+        folder1._setObject('bar', bar)
+        cat = site.portal_catalog
+        cat.log = []
+
+        transaction.savepoint(optimistic=True)
+
+        cookie = folder1.manage_cutObjects(ids=['bar'])
+        folder2.manage_pasteObjects(cookie)
+
+        self.assertEquals(cat.log, ["unindex /site/folder1/bar",
+                                    "reindex /site/folder2/bar []"])
+
+    def test_object_reindexed_after_moving(self):
+
+        self._initPolicyAndUser() # allow copy/paste operations
+        site = self._makeSite()
+
+        self._registerObjectEventHandler()
+        bar = TheClass('bar')
+        site._setObject('bar', bar)
+        cat = site.portal_catalog
+        cat.log = []
+
+        transaction.savepoint(optimistic=True)
+
+        site.manage_renameObject(id='bar', new_id='baz')
+        self.assertEquals(cat.log, ["unindex /site/bar",
+                                    "reindex /site/baz []"])
+
 def test_suite():
     return unittest.TestSuite((
         unittest.makeSuite(CMFCatalogAwareTests),
+        unittest.makeSuite(CMFCatalogAware_CopySupport_Tests),
         ))
 
 if __name__ == '__main__':

Copied: CMF/branches/tseaver-catalog_events/CMFCore/tests/test_CookieCrumbler.py (from rev 41522, CMF/trunk/CMFCore/tests/test_CookieCrumbler.py)

Modified: CMF/branches/tseaver-catalog_events/CMFCore/tests/test_OpaqueItems.py
===================================================================
--- CMF/trunk/CMFCore/tests/test_OpaqueItems.py	2006-01-31 15:34:23 UTC (rev 41511)
+++ CMF/branches/tseaver-catalog_events/CMFCore/tests/test_OpaqueItems.py	2006-02-16 04:44:31 UTC (rev 41631)
@@ -18,17 +18,17 @@
 import Testing
 
 from zope.interface import implements
+from zope.interface import implementedBy
 
 from Products.CMFCore.interfaces import ICallableOpaqueItem
 from Products.CMFCore.interfaces import ICallableOpaqueItemEvents
-from Products.CMFCore.interfaces.IOpaqueItems \
-        import ICallableOpaqueItem as z2ICallableOpaqueItem
-from Products.CMFCore.interfaces.IOpaqueItems \
-        import ICallableOpaqueItemEvents as z2ICallableOpaqueItemEvents
+from Products.CMFCore.interfaces import IContentish
+
 from Products.CMFCore.PortalFolder import PortalFolder
 from Products.CMFCore.tests.base.dummy \
     import DummyContent as OriginalDummyContent
 from Products.CMFCore.tests.base.testcase import SecurityTest
+from Products.CMFCore.tests.base.testcase import ContentEventAwareTests
 from Products.CMFCore.TypesTool import TypesTool
 
 
@@ -48,6 +48,7 @@
 class DummyContent(OriginalDummyContent):
     """ A Dummy piece of PortalContent with additional attributes
     """
+    implements(IContentish)
 
     def __init__(self, id='dummy', opaqueItem=None, *args, **kw):
         OriginalDummyContent.__init__(self, id, *args, **kw)
@@ -95,18 +96,12 @@
 class Marker(OpaqueBase):
     """ Opaque item without manage_after/before hookes but marked as callable
     """
-    implements(ICallableOpaqueItem)
-    __implements__ = (
-        z2ICallableOpaqueItem,
-    )
+    implements(IContentish, ICallableOpaqueItem)
 
 class Hooks(OpaqueBase):
     """ Opaque item with manage_after/before hooks but not marked as callable
     """
-    implements(ICallableOpaqueItemEvents)
-    __implements__ = (
-        z2ICallableOpaqueItemEvents,
-    )
+    implements(IContentish, ICallableOpaqueItemEvents)
     
     def manage_afterAdd(self, item, container):
         self.addCount = self.addCounter
@@ -124,17 +119,18 @@
 class MarkerAndHooks(Marker, Hooks):
     """ Opaque item with manage_after/before hookes and marked as callable
     """
-    __implements__ = Marker.__implements__ + Hooks.__implements__
+    implements(implementedBy(Marker), implementedBy(Hooks))
 
 
 # -------------------------------------------
 # Unit Tests
 # -------------------------------------------
 
-class ManageBeforeAfterTests(SecurityTest):
+class ManageBeforeAfterTests(SecurityTest, ContentEventAwareTests):
 
     def setUp(self):
         SecurityTest.setUp(self)
+        ContentEventAwareTests.setUp(self)
 
         root = self.root
 
@@ -163,6 +159,12 @@
         try: sub._delObject('dummy')
         except AttributeError: pass
 
+        self._registerObjectEventHandler()
+
+    def tearDown(self):
+        ContentEventAwareTests.tearDown(self)
+        SecurityTest.tearDown(self)
+
     def test_nonCallableItem(self):
         # no exception should be raised
         folder = self.folder

Modified: CMF/branches/tseaver-catalog_events/CMFCore/tests/test_PortalContent.py
===================================================================
--- CMF/trunk/CMFCore/tests/test_PortalContent.py	2006-01-31 15:34:23 UTC (rev 41511)
+++ CMF/branches/tseaver-catalog_events/CMFCore/tests/test_PortalContent.py	2006-02-16 04:44:31 UTC (rev 41631)
@@ -24,6 +24,7 @@
 from Products.CMFCore.tests.base.dummy import DummySite
 from Products.CMFCore.tests.base.dummy import DummyUserFolder
 from Products.CMFCore.tests.base.testcase import SecurityRequestTest
+from Products.CMFCore.tests.base.testcase import ContentEventAwareTests
 
 
 class PortalContentTests(unittest.TestCase):
@@ -49,18 +50,24 @@
         verifyClass(IDynamicType, PortalContent)
 
 
-class TestContentCopyPaste(SecurityRequestTest):
+class TestContentCopyPaste(SecurityRequestTest, ContentEventAwareTests):
 
     # Tests related to http://www.zope.org/Collectors/CMF/205
     # Copy/pasting a content item must set ownership to pasting user
 
     def setUp(self):
         SecurityRequestTest.setUp(self)
+        ContentEventAwareTests.setUp(self)
 
         self.root._setObject('site', DummySite('site'))
         self.site = self.root.site
         self.acl_users = self.site._setObject('acl_users', DummyUserFolder())
+        self._registerObjectEventHandler()
 
+    def tearDown(self):
+        ContentEventAwareTests.tearDown(self)
+        SecurityRequestTest.tearDown(self)
+
     def _initContent(self, folder, id):
         from Products.CMFCore.PortalContent import PortalContent
 

Modified: CMF/branches/tseaver-catalog_events/CMFCore/tests/test_PortalFolder.py
===================================================================
--- CMF/trunk/CMFCore/tests/test_PortalFolder.py	2006-01-31 15:34:23 UTC (rev 41511)
+++ CMF/branches/tseaver-catalog_events/CMFCore/tests/test_PortalFolder.py	2006-02-16 04:44:31 UTC (rev 41631)
@@ -40,6 +40,7 @@
 from Products.CMFCore.tests.base.testcase import newSecurityManager
 from Products.CMFCore.tests.base.testcase import noSecurityManager
 from Products.CMFCore.tests.base.testcase import SecurityTest
+from Products.CMFCore.tests.base.testcase import ContentEventAwareTests
 from Products.CMFCore.tests.base.tidata import FTIDATA_CMF15
 from Products.CMFCore.tests.base.tidata import FTIDATA_DUMMY
 from Products.CMFCore.tests.base.utils import has_path
@@ -53,11 +54,12 @@
              'permission': 'View'}]
 
 
-class PortalFolderFactoryTests( SecurityTest ):
+class PortalFolderFactoryTests( SecurityTest, ContentEventAwareTests ):
 
     def setUp( self ):
         from Products.CMFCore.PortalFolder import PortalFolder
         SecurityTest.setUp( self )
+        ContentEventAwareTests.setUp( self )
 
         self.root._setObject( 'portal_types', TypesTool() )
         types_tool = self.root.portal_types
@@ -73,6 +75,12 @@
         fti = FTIDATA_DUMMY[0].copy()
         types_tool._setObject( 'Dummy Content', FTI(**fti) )
 
+        self._registerObjectEventHandler()
+
+    def tearDown( self ):
+        ContentEventAwareTests.tearDown( self )
+        SecurityTest.tearDown( self )
+
     def _makeOne( self, id ):
         from Products.CMFCore.PortalFolder import PortalFolder
         return PortalFolder( id ).__of__( self.root )
@@ -111,12 +119,20 @@
                          , type_name='Dummy Content', id='foo' )
 
 
-class PortalFolderTests(SecurityTest):
+class PortalFolderTests(SecurityTest, ContentEventAwareTests):
 
     def setUp(self):
+
         SecurityTest.setUp(self)
+        ContentEventAwareTests.setUp( self )
         self.site = DummySite('site').__of__(self.root)
 
+        self._registerObjectEventHandler()
+
+    def tearDown( self ):
+        ContentEventAwareTests.tearDown( self )
+        SecurityTest.tearDown( self )
+
     def _makeOne(self, id, *args, **kw):
         from Products.CMFCore.PortalFolder import PortalFolder
 
@@ -248,9 +264,7 @@
         wftool.notifyCreated(test)
         self.assertEqual( len(ctool), 0 )
 
-
     def test_tracker261(self):
-
         #
         #   Tracker issue #261 says that content in a deleted folder
         #   is not being uncatalogued.  Try creating a subfolder with
@@ -274,7 +288,7 @@
         self.assertEqual( len(ctool), 1 )
 
         foo.reset()
-        test.manage_delObjects( ids=['sub'] )
+        test._delObject( 'sub' )
         self.failIf( foo.after_add_called )
         self.failUnless( foo.before_delete_called )
         self.assertEqual( len(ctool), 0 )
@@ -423,13 +437,19 @@
 
         self.assert_(sub.checkIdAvailable('.foo'))
 
-class PortalFolderMoveTests(SecurityTest):
+class PortalFolderMoveTests(SecurityTest, ContentEventAwareTests):
 
     def setUp(self):
         SecurityTest.setUp(self)
+        ContentEventAwareTests.setUp( self )
         self.root._setObject( 'site', DummySite('site') )
         self.site = self.root.site
+        self._registerObjectEventHandler()
 
+    def tearDown( self ):
+        ContentEventAwareTests.tearDown( self )
+        SecurityTest.tearDown( self )
+
     def _makeOne(self, id, *args, **kw):
         from Products.CMFCore.PortalFolder import PortalFolder
 

Modified: CMF/branches/tseaver-catalog_events/CMFDefault/tests/test_Discussions.py
===================================================================
--- CMF/trunk/CMFDefault/tests/test_Discussions.py	2006-01-31 15:34:23 UTC (rev 41511)
+++ CMF/branches/tseaver-catalog_events/CMFDefault/tests/test_Discussions.py	2006-02-16 04:44:31 UTC (rev 41631)
@@ -25,6 +25,7 @@
 from Products.CMFCore.tests.base.dummy import DummySite
 from Products.CMFCore.tests.base.dummy import DummyTool
 from Products.CMFCore.tests.base.testcase import SecurityTest
+from Products.CMFCore.tests.base.testcase import ContentEventAwareTests
 from Products.CMFCore.tests.base.tidata import FTIDATA_DUMMY
 from Products.CMFCore.tests.base.utils import has_path
 from Products.CMFCore.TypesTool import FactoryTypeInformation as FTI
@@ -95,15 +96,21 @@
         verifyClass(IDiscussable, DiscussionItemContainer)
 
 
-class DiscussionTests( SecurityTest ):
+class DiscussionTests( SecurityTest, ContentEventAwareTests ):
 
     def setUp( self ):
         SecurityTest.setUp(self)
+        ContentEventAwareTests.setUp( self )
         self.site = DummySite('site').__of__(self.root)
         self.site._setObject( 'portal_discussion', DiscussionTool() )
         self.site._setObject( 'portal_membership', DummyTool() )
         self.site._setObject( 'portal_types', TypesTool() )
+        self._registerObjectEventHandler()
 
+    def tearDown( self ):
+        ContentEventAwareTests.tearDown( self )
+        SecurityTest.tearDown( self )
+
     def _makeDummyContent(self, id, *args, **kw):
         return self.site._setObject( id, DummyContent(id, *args, **kw) )
 



More information about the CMF-checkins mailing list