diff --git a/IPython/Extensions/ipy_profile_zope.py b/IPython/Extensions/ipy_profile_zope.py
new file mode 100644
index 0000000..d413129
--- /dev/null
+++ b/IPython/Extensions/ipy_profile_zope.py
@@ -0,0 +1,320 @@
+# -*- coding: utf-8 -*-
+#
+# File: ipy_profile_zope.py
+#
+# Copyright (c) InQuant GmbH
+#
+# An ipython profile for zope and plone. Some ideas
+# stolen from http://www.tomster.org.
+#
+# German Free Software License (D-FSL)
+#
+# This Program may be used by anyone in accordance with the terms of the 
+# German Free Software License
+# The License may be obtained under <http://www.d-fsl.org>.
+
+__author__ = """Stefan Eletzhofer <stefan.eletzhofer@inquant.de>"""
+__docformat__ = 'plaintext'
+__revision__ = "$Revision$"
+
+from IPython import ipapi
+from IPython import Release
+from types import StringType
+import sys
+import os
+import textwrap
+
+# The import below effectively obsoletes your old-style ipythonrc[.ini],
+# so consider yourself warned!
+# import ipy_defaults
+
+_marker = []
+def shasattr(obj, attr, acquire=False):
+    """ See Archetypes/utils.py
+    """
+    if not acquire:
+        obj = obj.aq_base
+    return getattr(obj, attr, _marker) is not _marker
+
+class ZopeDebug(object):
+    def __init__(self):
+
+        self.instancehome = os.environ.get( "INSTANCE_HOME" )
+
+        configfile = os.environ.get( "CONFIG_FILE" )
+        if configfile is None and self.instancehome is not None:
+            configfile = os.path.join( self.instancehome, "etc", "zope.conf" )
+
+        if configfile is None:
+            raise RuntimeError( "CONFIG_FILE env not set" )
+
+        print "CONFIG_FILE=", configfile
+        print "INSTANCE_HOME=", self.instancehome
+
+        self.configfile = configfile
+
+        try:
+            from Zope2 import configure
+        except ImportError:
+            from Zope import configure
+
+        configure( configfile )
+
+        try:
+            import Zope2
+            app = Zope2.app()
+        except ImportError:
+            import Zope
+            app = Zope.app()
+
+        from Testing.makerequest import makerequest
+        self.app = makerequest( app )
+
+        try:
+            self._make_permissive()
+            print "Permissive security installed"
+        except:
+            print "Permissive security NOT installed"
+
+        self._pwd = self.portal or self.app
+
+        try:
+            from zope.component import getSiteManager
+            from zope.component import getGlobalSiteManager
+            from zope.app.component.hooks import setSite
+
+            if self.portal is not None:
+                setSite( self.portal )
+
+                gsm = getGlobalSiteManager()
+                sm = getSiteManager()
+
+                if sm is gsm:
+                    print "ERROR SETTING SITE!"
+        except:
+            pass
+
+
+    @property
+    def utils(self):
+        class Utils(object):
+            commit = self.commit
+            sync = self.sync
+            objectInfo = self.objectInfo
+            ls = self.ls
+            pwd = self.pwd
+            cd = self.cd
+            su = self.su
+            getCatalogInfo = self.getCatalogInfo
+
+            @property
+            def cwd(self):
+                return self.pwd()
+
+        return Utils()
+
+    @property
+    def namespace(self):
+        return dict( utils=self.utils, app=self.app, portal=self.portal )
+
+    @property
+    def portal(self):
+        portals = self.app.objectValues( "Plone Site" )
+        if len(portals):
+            return portals[0]
+        else:
+            raise KeyError( "No Plone Site found.")
+
+    def pwd(self):
+        return self._pwd
+
+    def _make_permissive(self):
+        """
+        Make a permissive security manager with all rights. Hell,
+        we're developers, aren't we? Security is for whimps. :)
+        """
+        from Products.CMFCore.tests.base.security import PermissiveSecurityPolicy
+        import AccessControl
+        from AccessControl.SecurityManagement import newSecurityManager
+        from AccessControl.SecurityManager import setSecurityPolicy
+
+        _policy = PermissiveSecurityPolicy()
+        self.oldpolicy = setSecurityPolicy(_policy)
+        newSecurityManager(None, AccessControl.User.system)
+
+    def su(self, username):
+        """ Change to named user.
+        """
+        # TODO Make it easy to change back to permissive security.
+        user = self.portal.acl_users.getUser(username)
+        if not user:
+            print "Can't find %s in %s" % (username, self.portal.acl_users)
+            return
+
+        from AccessControl import ZopeSecurityPolicy
+        import AccessControl
+        from AccessControl.SecurityManagement import newSecurityManager, getSecurityManager
+        from AccessControl.SecurityManager import setSecurityPolicy
+
+        _policy = ZopeSecurityPolicy
+        self.oldpolicy = setSecurityPolicy(_policy)
+        wrapped_user = user.__of__(self.portal.acl_users)
+        newSecurityManager(None, user)
+        print 'User changed.'
+        return getSecurityManager().getUser()
+
+    def getCatalogInfo(self, obj=None, catalog='portal_catalog', query=None, sort_on='created', sort_order='reverse' ):
+        """ Inspect portal_catalog. Pass an object or object id for a
+        default query on that object, or pass an explicit query.
+        """
+        if obj and query:
+            print "Ignoring %s, using query." % obj
+
+        catalog = self.portal.get(catalog)
+        if not catalog:
+            return 'No catalog'
+
+        indexes = catalog._catalog.indexes
+        if not query:
+            if type(obj) is StringType:
+                cwd = self.pwd()
+                obj = cwd.unrestrictedTraverse( obj )
+            # If the default in the signature is mutable, its value will
+            # persist across invocations.
+            query = {}
+            if indexes.get('path'):
+                from string import join
+                path = join(obj.getPhysicalPath(), '/') 
+                query.update({'path': path})
+            if indexes.get('getID'):
+                query.update({'getID': obj.id, })
+            if indexes.get('UID') and shasattr(obj, 'UID'):
+                query.update({'UID': obj.UID(), })
+        if indexes.get(sort_on):
+            query.update({'sort_on': sort_on, 'sort_order': sort_order})
+        if not query:
+            return 'Empty query'
+        results = catalog(**query)
+
+        result_info = []
+        for r in results:
+            rid = r.getRID()
+            if rid:
+                result_info.append(
+                        {'path': catalog.getpath(rid),
+                        'metadata': catalog.getMetadataForRID(rid),
+                        'indexes': catalog.getIndexDataForRID(rid), }
+                        )
+            else:
+                result_info.append({'missing': rid})
+
+        if len(result_info) == 1:
+            return result_info[0]
+        return result_info
+
+    def commit(self):
+        """
+        Commit the transaction.
+        """
+        try:
+            import transaction
+            transaction.get().commit()
+        except ImportError:
+            get_transaction().commit()
+
+    def sync(self):
+        """
+        Sync the app's view of the zodb.
+        """
+        self.app._p_jar.sync()
+
+    def objectInfo( self, o ):
+        """
+        Return a descriptive string of an object
+        """
+        Title = ""
+        t = getattr( o, 'Title', None )
+        if t:
+            Title = t()
+        return {'id': o.getId(),
+                'Title': Title,
+                'portal_type': getattr( o, 'portal_type', o.meta_type),
+                'folderish': o.isPrincipiaFolderish
+                }
+
+    def cd( self, path ):
+        """
+        Change current dir to a specific folder.
+
+         cd( ".." )
+         cd( "/plone/Members/admin" )
+         cd( portal.Members.admin )
+         etc.
+        """
+        if type(path) is not StringType:
+            path = '/'.join(path.getPhysicalPath())
+        cwd = self.pwd()
+        x = cwd.unrestrictedTraverse( path )
+        if x is None:
+            raise KeyError( "Can't cd to %s" % path )
+
+        print "%s -> %s" % ( self.pwd().getId(), x.getId() )
+        self._pwd = x
+
+    def ls( self, x=None ):
+        """
+        List object(s)
+        """
+        if type(x) is StringType:
+            cwd = self.pwd()
+            x = cwd.unrestrictedTraverse( x )
+        if x is None:
+            x = self.pwd()
+        if x.isPrincipiaFolderish:
+            return [self.objectInfo(o) for id, o in x.objectItems()]
+        else:
+            return self.objectInfo( x )
+
+zope_debug = None
+
+def ipy_set_trace():
+    import IPython; IPython.Debugger.Pdb().set_trace()
+
+def main():
+    global zope_debug
+    ip = ipapi.get()
+    o = ip.options
+    # autocall to "full" mode (smart mode is default, I like full mode)
+    
+    SOFTWARE_HOME = os.environ.get( "SOFTWARE_HOME" )
+    sys.path.append( SOFTWARE_HOME )
+    print "SOFTWARE_HOME=%s\n" % SOFTWARE_HOME
+
+    zope_debug = ZopeDebug()
+
+    # <HACK ALERT>
+    import pdb;
+    pdb.set_trace = ipy_set_trace
+    # </HACK ALERT>
+
+    # I like my banner minimal.
+    o.banner = "ZOPE Py %s IPy %s\n" % (sys.version.split('\n')[0],Release.version)
+
+    print textwrap.dedent("""\
+        ZOPE mode iPython shell.
+
+          Bound names:
+           app
+           portal
+           utils.{ %s }
+  
+        Uses the $SOFTWARE_HOME and $CONFIG_FILE environment
+        variables.
+        """ % ( ",".join([ x for x in dir(zope_debug.utils) if not x.startswith("_") ] ) ) )
+
+
+    ip.user_ns.update( zope_debug.namespace )
+
+
+main()
+# vim: set ft=python ts=4 sw=4 expandtab :
diff --git a/doc/ChangeLog b/doc/ChangeLog
index 2c7136f..6629ae9 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -1,3 +1,7 @@
+2007-09-04  Ville Vainio  <vivainio@gmail.com>
+
+	* ipy_profile_zope.py: add zope profile, by Stefan Eletzhofer.
+
 2007-09-03  Ville Vainio  <vivainio@gmail.com>
 
 	* Magic.py: %time now passes expression through prefilter,