Thanks largely to Michael Thornhill's instructions I'm really hooked on using the ipython shell for plone debugging.
To make life even easier I like putting a file in the python path for the zope instance to get ipython running so you can fire up ipython simply by importing it:
my ipzope.py just contains:
I also keep an ipythonrc file in the root directory of each zope instance I'm running with something like the following towards the end:
execute app = __IP.internal_ns['app']
execute from Acquisition import aq_inner, aq_parent, aq_base, aq_chain, aq_get
execute from Products.CMFCore.utils import getToolByName
execute membership_tool = getToolByName(app.plonesite, 'portal_membership')
execute from AccessControl.SecurityManagement import newSecurityManager, getSecurityManager
execute user = app.acl_users.getUser('username').__of__(app.plonesite.acl_users)
execute newSecurityManager(None, user)
execute sitename = app.plonesite
so when the ipython shell is open you can access your plone instance with sitename which lets you do nice ipython things like use tab completion.
sitename.objectIds() returns a list of all the items in the root.
If you change something through the ZMI you won't see the changes in the shell until you run sitename._p_jar.sync() and if you change something on the shell you won't see the changes on the site until you run get_transaction().commit().
I think it's well worth the effort to set it up.