[Checkins] SVN: Sandbox/ulif/grok-adminui/src/grok/ New Object browser for the admin-UI. Several minor changes. Sorry for the mess.

Uli Fouquet uli at gnufix.de
Mon Aug 6 11:00:39 EDT 2007


Log message for revision 78628:
  New Object browser for the admin-UI. Several minor changes. Sorry for the mess.

Changed:
  U   Sandbox/ulif/grok-adminui/src/grok/admin/docgrok.py
  A   Sandbox/ulif/grok-adminui/src/grok/admin/inspect.txt
  A   Sandbox/ulif/grok-adminui/src/grok/admin/objectinfo.py
  A   Sandbox/ulif/grok-adminui/src/grok/admin/objectinfo.txt
  U   Sandbox/ulif/grok-adminui/src/grok/admin/static/grok.css
  U   Sandbox/ulif/grok-adminui/src/grok/admin/tests/test_grokadmin.py
  A   Sandbox/ulif/grok-adminui/src/grok/admin/utilities.py
  U   Sandbox/ulif/grok-adminui/src/grok/admin/view.py
  U   Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/applications.pt
  U   Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/docgrokpackageview.pt
  U   Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/docgrokview.pt
  U   Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/inspect.pt
  A   Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/logout.pt
  U   Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/macros.pt
  U   Sandbox/ulif/grok-adminui/src/grok/ftests/admin/apps.py
  A   Sandbox/ulif/grok-adminui/src/grok/ftests/admin/loginlogout.py
  A   Sandbox/ulif/grok-adminui/src/grok/ftests/admin/objectbrowser.py

-=-
Modified: Sandbox/ulif/grok-adminui/src/grok/admin/docgrok.py
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/docgrok.py	2007-08-06 13:09:23 UTC (rev 78627)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/docgrok.py	2007-08-06 15:00:38 UTC (rev 78628)
@@ -7,6 +7,13 @@
 
 """
 
+import os
+import sys # for sys.path
+import types
+import grok
+import inspect
+from urlparse import urlparse, urlunparse
+
 import zope.component
 from zope.app.folder.interfaces import IRootFolder
 from zope.dottedname.resolve import resolve
@@ -15,18 +22,7 @@
 from zope.security.proxy import removeSecurityProxy
 from zope.proxy import removeAllProxies
 
-import os
-import sys # for sys.path
-import types
-import grok
-import inspect
-import grok.interfaces
-from grok.interfaces import IApplication
-from martian.scan import is_package, ModuleInfo
-from martian import InstanceGrokker, ModuleGrokker
-
 from zope.app.i18n import ZopeMessageFactory as _
-
 from zope.app.apidoc.codemodule.module import Module
 from zope.app.apidoc.codemodule.class_ import Class
 from zope.app.apidoc.codemodule.text import TextFile
@@ -35,11 +31,20 @@
 from zope.app.apidoc.utilities import getPythonPath, getPermissionIds
 from zope.app.apidoc.utilities import isReferencable
 
+import grok.interfaces
+from grok.interfaces import IApplication
+from martian.scan import is_package, ModuleInfo
+from martian import InstanceGrokker, ModuleGrokker
+from grok.admin.objectinfo import ZopeObjectInfo
 
+# This is the name under which the docgrok object-browser can be
+# reached.
+DOCGROK_ITEM_NAMESPACE = 'docgrok-obj'
+
 grok.context(IRootFolder)
 grok.define_permission('grok.ManageApplications')
 
-def find_filepath( dotted_path ):
+def find_filepath(dotted_path):
     """Find the filepath for a dotted name.
 
     If a dotted name denotes a filename we try to find its path
@@ -66,8 +71,8 @@
             currname = "." + currname
         currname = name + currname
         tmp_path = ""
-        for elem in currpath.split( '.' ):
-            tmp_path = os.path.join( tmp_path, elem )
+        for elem in currpath.split('.'):
+            tmp_path = os.path.join(tmp_path, elem)
         for syspath in sys.path:
             filepath_to_check = os.path.join(syspath, tmp_path, currname)
             if os.path.isfile(filepath_to_check):
@@ -75,12 +80,12 @@
     return None
 
 
-def handle_module( dotted_path, ob=None ):
+def handle_module(dotted_path, ob=None):
     """Determine, whether the given path/obj references a Python module.
     """
     if ob is None:
         try:
-            ob = resolve( dotted_path )
+            ob = resolve(dotted_path)
         except ImportError:
             return None
     if not hasattr(ob, '__file__'):
@@ -93,12 +98,12 @@
         return None
     return DocGrokModule(dotted_path)
 
-def handle_package( dotted_path, ob=None):
+def handle_package(dotted_path, ob=None):
     """Determine, whether the given path/obj references a Python package.
     """
     if ob is None:
         try:
-            ob = resolve( dotted_path )
+            ob = resolve(dotted_path)
         except ImportError:
             return None
     if not hasattr(ob, '__file__'):
@@ -136,7 +141,7 @@
         return None
     return DocGrokClass(dotted_path)
 
-def handle_grokapplication( dotted_path, ob=None):
+def handle_grokapplication(dotted_path, ob=None):
     """Determine, whether the given path/obj references a Grok application.
     """
     if ob is None:
@@ -145,17 +150,17 @@
         except ImportError:
             None
     try:
-        if not IApplication.implementedBy( ob ):
+        if not IApplication.implementedBy(ob):
             return None
     except TypeError:
         return None
     return DocGrokGrokApplication(dotted_path)
 
-def handle_textfile( dotted_path, ob=None):
+def handle_textfile(dotted_path, ob=None):
     if ob is not None:
         # Textfiles that are objects, are not text files.
         return None
-    if os.path.splitext( dotted_path )[1] != u'.txt':
+    if os.path.splitext(dotted_path)[1] != u'.txt':
         return None
     return DocGrokTextFile(dotted_path)
 
@@ -182,11 +187,11 @@
     """Find a doctor specialized for certain things.
     """
     try:
-        ob = resolve( dotted_path )
+        ob = resolve(dotted_path)
     except ImportError:
         # There is no object of that name. Give back 404.
-        # XXX Do something more intelligent, offer a search.
-        if not find_filepath( dotted_path ):
+        # TODO: Do something more intelligent, offer a search.
+        if not find_filepath(dotted_path):
             return None
         ob = None
     except:
@@ -194,7 +199,7 @@
 
     for handler in docgrok_handlers:
         spec_handler = handler['handler']
-        doc_grok = spec_handler( dotted_path, ob )
+        doc_grok = spec_handler(dotted_path, ob)
         if doc_grok is None:
             continue
         return doc_grok
@@ -279,7 +284,7 @@
     Now we want to register this new DocGrok with the 'global
     machinery'. Easy::
     
-      >>> module_grokker.grok( 'mammoth_grokker', mammoth )
+      >>> module_grokker.grok('mammoth_grokker', mammoth)
       True
       
     Now the 'handle_mammoths' function is considered to deliver a
@@ -292,7 +297,7 @@
     mammoths (if you defined one; otherwise the default 'DocGrok'
     template will be used to show mammoth information).
 
-    XXX TODO: Show how to make a docgrok view.
+    TODO: Show how to make a docgrok view.
 
     That's it.
     
@@ -304,8 +309,7 @@
             return False
         if name in [x['name'] for x in docgrok_handlers]:
             return False
-        #docgrok_handlers[name] = obj
-        docgrok_handlers.insert( 0, {'name':name,
+        docgrok_handlers.insert(0, {'name':name,
                                      'handler':obj})
         return True
 
@@ -321,24 +325,18 @@
     DocGrok offers a minimum of information but can easily be extended in
     derived classes.
     """
-    msg = "I am Dr. Grok. How can I help you?"
     path = None
     _traversal_root = None
-    #_children = {}
 
-    def __init__(self, dotted_path ):
-        #super( DocGrok, self ).__init__()
+    def __init__(self, dotted_path):
         self.path = dotted_path
 
     def getPath(self):
         return self.path
 
-    def getMsg(self):
-        return self.msg
-
-    def getFilePath( self ):
+    def getFilePath(self):
         try:
-            ob = resolve( self.path )
+            ob = resolve(self.path)
             return hasattr(ob, __file__) and os.path.dirname(ob.__file__) or None
         except ImportError:
             pass
@@ -347,8 +345,8 @@
     def getDoc(self, heading_only=False):
         """Get the doc string of the module STX formatted.
         """
-        if hasattr( self, "apidoc") and hasattr(
-            self.apidoc, "getDocString" ):
+        if hasattr(self, "apidoc") and hasattr(
+            self.apidoc, "getDocString"):
             text = self.apidoc.getDocString()
         else:
             return None
@@ -384,14 +382,12 @@
         else:
             newpath = '.'.join([self.path, patient])
 
-        #doctor = getDocGrokForDottedPath( newpath )
-        doctor = handle( newpath )
+        doctor = handle(newpath)
 
         if doctor is None:
             # There is nothing of that name. Give back 404.
             # XXX Do something more intelligent, offer a search.
             return None
-        #doctor.msg = "Do more grokking!"
         doctor.__parent__ = self
         doctor.__name__ = patient
         doctor._traversal_root = self._traversal_root
@@ -399,6 +395,30 @@
         return doctor
     pass
 
+def getItemLink(name, baseurl):
+    """Get a link to a docgrok item out of an item name and a base URL.
+
+    A docgrok item is any object, which is a 'subobject' of another
+    object, for example an attribute, a memberfunction, an annotation
+    or a sequence item (if the parent object is a sequence). Those
+    objects are not neccessarily directly accessible, but need to be
+    denoted for the object browser. We put 'docgrok-item' as marker at
+    the beginning of the URL to enable the docgrok traverser and to
+    handle those URLs in a special way. Such the objectbrowser can
+    handle those 'unaccessible' items.
+    """
+    url = list(urlparse(baseurl))
+    path = url[2]
+    if path.startswith('/' + DOCGROK_ITEM_NAMESPACE + '/'):
+        path = path[len(DOCGROK_ITEM_NAMESPACE) + 2:]
+    if path.endswith('/@@inspect.html') or path.endswith('/inspect.html'):
+        path = path.rsplit('/', 1)
+    path = "/%s/%s%s/@@inspect.html" % (DOCGROK_ITEM_NAMESPACE, path, name)
+    path = path.replace('//', '/')
+    url[2] = path 
+    return urlunparse(url)
+
+
 class DocGrokTraverser(grok.Traverser):
     """If first URL element is 'docgrok', handle over to DocGrok.
 
@@ -410,6 +430,13 @@
     grok.context(IRootFolder)
 
     def traverse(self,path):
+        if path == DOCGROK_ITEM_NAMESPACE:
+            # The objectbrowser is called...
+            obj_info = ZopeObjectInfo(self.context)
+            obj_info.__parent__ = self.context
+            obj_info.__name__ = DOCGROK_ITEM_NAMESPACE
+            return obj_info
+
         if path == "docgrok":
             doctor = DocGrok(None)
             # Giving a __parent__ and a __name__, we make things
@@ -424,7 +451,6 @@
 class DocGrokPackage(DocGrok):
     """This doctor cares for python packages.
     """
-    msg = "I am a Package of the Doc"
     path=None
     apidoc = None
     _traversal_root = None
@@ -433,65 +459,64 @@
         self.path = dotted_path
         self._module = resolve(self.path)
         # In apidoc packages are handled like modules...
-        self.apidoc = Module( None, None, self._module, True)
+        self.apidoc = Module(None, None, self._module, True)
 
-    def getDocString( self ):
+    def getDocString(self):
         return self.apidoc.getDocString()
 
-    def getFilePath( self ):
-        ob = resolve( self.path )
-        return os.path.dirname( ob.__file__ ) + '/'
+    def getFilePath(self):
+        ob = resolve(self.path)
+        return os.path.dirname(ob.__file__) + '/'
 
-    def _getModuleInfos( self, filter_func=lambda x:x ):
+    def _getModuleInfos(self, filter_func=lambda x:x):
         """Get modules and packages of a package.
 
         The filter function will be applied to a list of modules and
         packages of type grok.scan.ModuleInfo.
         """
-        ob = resolve( self.path )
+        ob = resolve(self.path)
         filename = ob.__file__
-        module_info = ModuleInfo( filename, self.path )
+        module_info = ModuleInfo(filename, self.path)
         infos = module_info.getSubModuleInfos()
         if filter_func is not None:
-            infos = filter( filter_func, infos)
-        #infos = [x for x in infos if not x.isPackage()]
+            infos = filter(filter_func, infos)
         result = []
         for info in infos:
             subresult = {}
             # Build a url string from dotted path...
             mod_path = "docgrok"
             for path_part in info.dotted_name.split('.'):
-                mod_path = os.path.join( mod_path, path_part )
+                mod_path = os.path.join(mod_path, path_part)
             subresult = {
                 'url' : mod_path,
                 'name' : info.name,
                 'dotted_name' : info.dotted_name
                 }
-            result.append( subresult )
+            result.append(subresult)
         return result
         
 
-    def getModuleInfos( self ):
+    def getModuleInfos(self):
         """Get the modules inside a package.
         """
         filter_func = lambda x: not x.isPackage()
-        return self._getModuleInfos( filter_func )
+        return self._getModuleInfos(filter_func)
 
-    def getSubPackageInfos( self ):
+    def getSubPackageInfos(self):
         """Get the subpackages inside a package.
         """
         filter_func = lambda x: x.isPackage()
-        return self._getModuleInfos( filter_func )
+        return self._getModuleInfos(filter_func)
 
-    def getTextFiles( self ):
+    def getTextFiles(self):
         """Get the text files inside a package.
         """
-        filter_func = lambda x: x.isinstance( TextFile )
-        return self._getModuleInfos( filter_func )
+        filter_func = lambda x: x.isinstance(TextFile)
+        return self._getModuleInfos(filter_func)
 
-    def getChildren( self ):
+    def getChildren(self):
         result = self.apidoc.items()
-        result.sort( lambda x,y:cmp(x[0], y[0]) )
+        result.sort(lambda x,y:cmp(x[0], y[0]))
         return result
 
 
@@ -500,8 +525,8 @@
     """This doctor cares for python modules.
     """
 
-    def getFilePath( self ):
-        ob = resolve( self.path )
+    def getFilePath(self):
+        ob = resolve(self.path)
         filename = ob.__file__
         if filename.endswith('o') or filename.endswith('c'):
             filename = filename[:-1]
@@ -515,12 +540,12 @@
         self.path = dotted_path
         self.klass = resolve(self.path)
         self.module_path, self.name = dotted_path.rsplit('.',1)
-        self.module = resolve( self.module_path )
-        mod_apidoc = Module( None, None, self.module, False)
-        self.apidoc = Class( mod_apidoc, self.name, self.klass)
+        self.module = resolve(self.module_path)
+        mod_apidoc = Module(None, None, self.module, False)
+        self.apidoc = Class(mod_apidoc, self.name, self.klass)
 
     def getFilePath(self):
-        if not hasattr( self.module, "__file__" ):
+        if not hasattr(self.module, "__file__"):
             return None
         filename = self.module.__file__
         if filename.endswith('o') or filename.endswith('c'):
@@ -578,12 +603,12 @@
         self.path = dotted_path
         self.klass = resolve(self.path)
         self.module_path, self.name = dotted_path.rsplit('.',1)
-        self.module = resolve( self.module_path )
-        mod_apidoc = Module( None, None, self.module, False)
-        self.apidoc = Class( mod_apidoc, self.name, self.klass)
+        self.module = resolve(self.module_path)
+        mod_apidoc = Module(None, None, self.module, False)
+        self.apidoc = Class(mod_apidoc, self.name, self.klass)
 
-    def getFilePath( self ):
-        if not hasattr( self.module, "__file__" ):
+    def getFilePath(self):
+        if not hasattr(self.module, "__file__"):
             return None
         filename = self.module.__file__
         if filename.endswith('o') or filename.endswith('c'):
@@ -601,14 +626,13 @@
 
     def __init__(self,dotted_path):
         self.path = dotted_path
-        self.filepath = find_filepath( self.path )
-        self.filename = os.path.basename( self.filepath )
+        self.filepath = find_filepath(self.path)
+        self.filename = os.path.basename(self.filepath)
 
 
     def getPackagePath(self):
         """Return package path as dotted name.
         """
-        #return os.path.dirname( self.filepath )
         dot_num_in_filename = len([x for x in self.filename if x == '.'])
         parts = self.path.rsplit('.', dot_num_in_filename + 1)
         return parts[0]

Added: Sandbox/ulif/grok-adminui/src/grok/admin/inspect.txt
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/inspect.txt	                        (rev 0)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/inspect.txt	2007-08-06 15:00:38 UTC (rev 78628)
@@ -0,0 +1,101 @@
+=======
+Inspect
+=======
+
+Grok's own object inspector.
+
+The Grok object browser is basically simply a view on objects.
+
+.. contents::
+
+
+How to use it
+-------------
+
+You can use the object inspector through-the-web (TTW) or using
+Python. It was primarily designed for TTW-use. This is explained
+here.
+
+
+Simple usage: Inspecting easy reachable objects
+-----------------------------------------------
+
+The inspector is called by appending ``/@@inspect.html`` to an URL
+describing an object in the ZODB. For example, to watch the object
+description of your Zopes root folder, you can use something like:
+
+	    http://localhost:8080/@@inspect.html
+
+Note, that not all URLs you type into your browser denote
+objects. They often describe only certain views of certain
+objects. Currently the browsing of views with the object inspector is
+not supported.
+
+If you want to examine another object, which is reachable 'via URL',
+just get the path you would normally enter into the browser and append
+``@@inspect.html``.
+
+Assuming you once created an object, which is reachable by::
+
+	 http://localhost:8080/my_object
+
+then you can examine it via the object browser, using the following
+URL::
+
+         http://localhost:8080/my_object/@@inspect.html
+
+Easy, isn't it?
+
+In most cases you can also skip the 'eyes', the strange ``@@`` symbol
+in front of the view name (``inspect.html``). The 'eyes' are only
+neccessary, when you examine an object, which has got an own attribute
+called ``inspect.html``.
+
+
+Extended usage: Inspecting 'hidden' objects
+-------------------------------------------
+
+All objects we exmined so far, are 'easy reachable'. This means, they
+are reachable by entering a simple URL. However, some (to tell the
+truth: many) objects cannot be reached this way. Such objects might be
+'subobjects' of others, they might be handled by a special traverser,
+simply have no view or whatever. We call those objects here 'hidden
+objects'. What, if we want to know more about
+those hidden objects?
+
+For example, we might have the attribute of an object (which is an
+object as well), which is not directly callable::
+
+  http://localhost:8080/my_object/hidden_attribute
+
+might so generate an error. It does not help to append simply
+``@@inspect.html`` to the URL. Instead we have to call a special
+traverser, which is able to look up the object for us and helps
+out. 
+
+This traverser is called by inserting ``docgrok-obj/`` as root of
+the path in the URL. The above mentioned hidden_attribute can
+therefore be reached using::
+
+  http://localhost:8080/docgrok_obj/my_object/hidden_attribute/@@inspect.html
+
+Such, you can reach even deeply nested subobjects of subobjects. 
+
+Just think of ``docgrok-obj`` as a special URL-namespace like
+``++annotations++``, ``++view++`` or similar (in fact it is not a
+URL-namespace), which can examine the complete object tree stored in
+the ZODB. In fact this 'namespace' can be used with every object, also
+with 'visible' ones. The above call to see ``my_object`` could
+therefore be reached (in a more complicated way) as::
+
+  http://localhost:8080/docgrok_obj/my_object/@@inspect.html
+
+This is the most general form to call the object browser:
+
+  http://<hostname>[:<port>]/docgrok_obj/<object-path>/@@inspect.html
+
+where 'docgrok-obj/' and '@@inspect.html' are the object browser
+specific parts.
+
+
+

Added: Sandbox/ulif/grok-adminui/src/grok/admin/objectinfo.py
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/objectinfo.py	                        (rev 0)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/objectinfo.py	2007-08-06 15:00:38 UTC (rev 78628)
@@ -0,0 +1,805 @@
+"""Information about objects.
+
+This module provides wrappers/proies to give information about
+objects at runtime. See inspect.txt to learn more about retrieving
+object information using ObjectInfos.
+"""
+import inspect
+import re
+import types
+from types import MethodType
+
+import grok
+from grok.admin.utilities import isContainingEvilRegExpChars
+
+import zope
+from zope.interface import Interface, implementedBy
+from zope.interface.common.sequence import IExtendedReadSequence
+from zope.interface.common.mapping import IEnumerableMapping
+from zope.annotation.interfaces import IAnnotatable, IAnnotations
+from zope.app.apidoc import utilities
+from zope.location import location
+from zope.schema import getFields
+from zope.security.checker import getCheckerForInstancesOf
+from zope.security.proxy import removeSecurityProxy
+from zope.traversing.api import getParent, getRoot
+from zope.traversing.interfaces import IPhysicallyLocatable
+from zope.dublincore.interfaces import IZopeDublinCore
+from zope.app.folder import rootFolder
+
+
+
+class ObjectInfo(grok.Model):
+    """An inspect proxy to provide object specific data.
+
+    This is a base proxy, which can handle all (and only)
+    Python-related data. See objectinfo.txt to learn more about
+    ObjectInfos.
+    """
+    obj = None
+
+    def __init__(self, obj):
+        self.obj = obj
+
+    def getmembers(self, predicate=None):
+        """Return all the members of an object in a list of (name,
+        value) pairs sorted by name.
+
+        If the optional predicate argument is supplied, only members
+        for which the predicate returns a true value are included.
+        """
+        return inspect.getmembers(self.obj, predicate)
+
+    def getmoduleinfo(self):
+        """Get information about modules.
+
+        Return a tuple of values that describe how Python will
+        interpret the file identified by path if it is a module, or
+        None if it would not be identified as a module. The return
+        tuple is (name, suffix, mode, mtype), where name is the name
+        of the module without the name of any enclosing package,
+        suffix is the trailing part of the file name (which may not be
+        a dot-delimited extension), mode is the open() mode that would
+        be used ('r' or 'rb'), and mtype is an integer giving the type
+        of the module. mtype will have a value which can be compared
+        to the constants defined in the imp module; see the
+        documentation for that module for more information on module
+        types.
+        """
+        path = getattr(self.obj, '__file__', None)
+        if path is None:
+            return None
+        return inspect.getmoduleinfo(path)
+
+    def getmodulename(self):
+        """Return the name of the module named by the file path,
+        without including the names of enclosing packages. This uses
+        the same algorithm as the interpreter uses when searching for
+        modules. If the name cannot be matched according to the
+        interpreter's rules, None is returned.
+        """
+        path = getattr(self.obj, '__file__', None)
+        if path is None:
+            return None
+        return inspect.getmodulename(path)
+
+    def ismodule(self):
+        """Return true if the object is a module.
+        """
+        return inspect.ismodule(self.obj)
+
+    def isclass(self):
+        """Return true if the object is a class.
+        """
+        return inspect.isclass(self.obj)
+
+    def ismethod(self):
+        """Return true if the object is a method.
+        """
+        return inspect.ismethod(self.obj)
+
+    def isfunction(self):
+        """Return true if the object is a Python function or unnamed
+        (lambda) function.
+        """
+        return inspect.isfunction(self.obj)
+
+    def istraceback(self):
+        """Return true if the object is a traceback.
+        """
+        return inspect.istraceback(self.obj)
+
+    def isframe(self):
+        """Return true if the object is a frame.
+        """
+        return inspect.isframe(self.obj)
+
+    def iscode(self):
+        """Return true if the object is a code.
+        """
+        return inspect.iscode(self.obj)
+
+    def isbuiltin(self):
+        """Return true if the object is a built-in function.
+        """
+        return inspect.isbuiltin(self.obj)
+
+    def isroutine(self):
+        """Return true if the object is a user-defined or built-in
+        function or method.
+        """
+        return inspect.isroutine(self.obj)
+
+    def ismethoddescriptor(self):
+        """Return true if the object is a method descriptor, but not
+        if ismethod() or isclass() or isfunction() are true.
+
+        This is new as of Python 2.2, and, for example, is true of
+        int.__add__. An object passing this test has a __get__
+        attribute but not a __set__ attribute, but beyond that the set
+        of attributes varies. __name__ is usually sensible, and
+        __doc__ often is.
+
+        Methods implemented via descriptors that also pass one of the
+        other tests return false from the ismethoddescriptor() test,
+        simply because the other tests promise more - you can, e.g.,
+        count on having the im_func attribute (etc) when an object
+        passes ismethod().
+        """
+        return inspect.ismethoddescriptor(self.obj)
+
+    def isdatadescriptor(self):
+        """Return true if the object is a data descriptor.
+
+        Data descriptors have both a __get__ and a __set__
+        attribute. Examples are properties (defined in Python),
+        getsets, and members. The latter two are defined in C and
+        there are more specific tests available for those types, which
+        is robust across Python implementations. Typically, data
+        descriptors will also have __name__ and __doc__ attributes
+        (properties, getsets, and members have both of these
+        attributes), but this is not guaranteed. New in version 2.3.
+        """
+        return inspect.isdatadescriptor(self.obj)
+
+    def isgetsetdescriptor(self):
+        """Return true if the object is a getset descriptor.
+
+        getsets are attributes defined in extension modules via
+        PyGetSetDef structures. For Python implementations without
+        such types, this method will always return False.
+
+        New in version 2.5.
+        """
+        return inspect.isgetsetdescriptor(self.obj)
+
+    def ismemberdescriptor(self):
+        """Return true if the object is a member descriptor.
+
+        Member descriptors are attributes defined in extension modules
+        via PyMemberDef structures. For Python implementations without
+        such types, this method will always return False.
+
+        New in version 2.5.
+        """
+        return inspect.ismemberdescriptor(self.obj)
+
+    #
+    # Source code related...
+    #
+    def getdoc(self):
+        """Get the documentation string for an object.
+
+        All tabs are expanded to spaces. To clean up docstrings that
+        are indented to line up with blocks of code, any whitespace
+        than can be uniformly removed from the second line onwards is
+        removed.
+        """
+        return inspect.getdoc(self.obj)
+
+    def getcomments(self):
+        """Get comments for an object.
+
+        Return in a single string any lines of comments immediately
+        preceding the object's source code (for a class, function, or
+        method), or at the top of the Python source file (if the
+        object is a module).
+
+        Due to a bug in ``inspect.getsource()`` no objects can be
+        handled, which contain a regular expression specific char in
+        their string representation.
+
+        """
+        if isContainingEvilRegExpChars(str(self.obj)):
+            return None
+        return inspect.getcomments(self.obj)
+
+    def getfile(self):
+        """Return the name of the (text or binary) file in which an
+        object was defined.
+
+        If the object is a built-in module, class or function,
+        ``None`` will be returned.
+        """
+        try:
+            return inspect.getfile(self.obj)
+        except TypeError:
+            return
+
+    def getmodule(self):
+        """Try to guess which module an object was defined in.
+        """
+        return inspect.getmodule(self.obj)
+
+    def getsourcefile(self):
+        """Return the name of the Python source file in which an
+        object was defined.
+
+        If the object is a built-in module, class or function,
+        ``None`` will be returned.
+        """
+        try:
+            return inspect.getsourcefile(self.obj)
+        except TypeError:
+            return
+
+    def getsourcelines(self):
+        """Return a list of source lines and starting line number for
+        an object.
+
+        The argument may be a module, class, method, function,
+        traceback, frame, or code object. The source code is returned
+        as a list of the lines corresponding to the object and the
+        line number indicates where in the original source file the
+        first line of code was found. An IOError is raised if the
+        source code cannot be retrieved.
+        """
+        try:
+            return inspect.getsourcelines(self.obj)
+        except TypeError:
+            return
+        return 
+
+    def getsource(self):
+        """Return the text of the source code for an object.
+
+        The argument may be a module, class, method, function,
+        traceback, frame, or code object. The source code is returned
+        as a single string. An IOError is raised if the source code
+        cannot be retrieved.
+
+        Due to a bug in ``inspect.getsource()`` no objects can be
+        handled, which contain a regular expression specific char in
+        their string representation.
+        """
+        if isContainingEvilRegExpChars(str(self.obj)):
+            return None
+
+        try:
+            return inspect.getsource(self.obj)
+        except TypeError:
+            return
+        return
+
+    def traverse(self, name):
+        new_obj = None
+
+        # Try to get name as dict entry...
+        keygetter = getattr(self.obj, 'keys', None)
+        if inspect.ismethod(keygetter):
+            if name in keygetter():
+                new_obj = self.obj[name]
+
+        # Try to get name as sequence entry...
+        if not new_obj:
+            # This is not the appropriate way to handle iterators. We
+            # must find somehing to handle them too.
+            try:
+                name_int = int(name)
+                if name_int in range(0, len(self.obj)):
+                    new_obj = self.obj[name_int]
+            except ValueError:
+                pass
+
+        # Get name as obj attribute...
+        if not new_obj and hasattr(self.obj, name):
+            new_obj = getattr(self.obj, name, None)
+
+        # Get name as annotation...
+        if not new_obj:
+            naked = zope.security.proxy.removeSecurityProxy(self.obj)
+            try:
+                annotations = IAnnotations(naked)
+                new_obj = name and name in annotations and annotations[name]
+                if not new_obj:
+                    new_obj = annotations
+            except TypeError:
+                pass
+
+        # Give obj a location...
+        if new_obj:
+            if not IPhysicallyLocatable(new_obj, False):
+                new_obj = location.LocationProxy(
+                    new_obj, self.obj, name)
+
+            new_info = ZopeObjectInfo(new_obj)
+            new_info.__parent__ = self
+            new_info.__name__ = name
+            return new_info
+
+        # name not found...
+        return
+
+
+
+
+
+class ZopeObjectInfo(ObjectInfo):
+    """Zope specific data.
+    """
+
+    def __init__(self, obj):
+        self.obj = obj
+        self.__klass = getattr(obj, '__class__', None) or type(obj)
+        self.__interfaces = tuple(implementedBy(self.__klass))
+
+    def getTypeLink(self, obj_type):
+        if obj_type is types.NoneType:
+            return None
+        path = utilities.getPythonPath(obj_type)
+        return path.replace('.', '/')
+
+    def isLinkable(self, obj):
+        """We consider all but some basic types to be linkable for docgrok.
+
+        Although even simple strings can be displayed by a docgrok, it
+        does not make much sense. We therefore simply forbid such
+        links, filtering objects of basic types out.
+        """
+        for typespec in [types.NoneType, types.TypeType, types.BooleanType,
+                         types.IntType, types.LongType, types.FloatType,
+                         types.ComplexType, types.StringTypes,
+                         types.MethodType, types.BuiltinFunctionType,
+                         types.LambdaType, types.GeneratorType, types.CodeType,
+                         types.FileType, types.TracebackType, types.FrameType,
+                         types.BufferType, types.NotImplementedType]:
+            if isinstance(obj, typespec):
+                return False
+        return True
+
+    def getParent(self):
+        return getParent(self.obj)
+
+    def getPythonPath(self):
+        return utilities.getPythonPath(self.obj)
+
+    def getDirectlyProvidedInterfaces(self):
+        # This comes from apidoc...
+        # Getting the directly provided interfaces works only on naked objects
+        naked = removeSecurityProxy(self.obj)
+        return [utilities.getPythonPath(iface)
+                for iface in zope.interface.directlyProvidedBy(naked)]
+
+    def getProvidedInterfaces(self):
+        return self.__interfaces
+
+    def getBases(self):
+        """Get base classes.
+        """
+        klass = getattr(self.obj, '__class__', None)
+        return getattr(klass, '__bases__', None)
+
+    def getAttributes(self):
+        """Get all attributes of an object.
+
+        See objectinfo.txt to learn more.
+        """
+        klass = removeSecurityProxy(self.__klass)
+        obj = removeSecurityProxy(self.obj)
+        for name in dir(obj):
+            value = getattr(obj, name, None)
+            if value is None:
+                continue
+            if inspect.ismethod(value) or inspect.ismethoddescriptor(value):
+                continue
+            entry = {
+                'name': name,
+                'value': `value`,
+                'value_linkable': self.isLinkable(value),
+                'type' : type(value),
+                # type_link is a very browser oriented data
+                # element. Move it to a view?
+                'type_link': self.getTypeLink(type(value)),
+                'interface': utilities.getInterfaceForAttribute(
+                                 name, klass=klass)
+                }
+            entry.update(utilities.getPermissionIds(
+                name, getCheckerForInstancesOf(klass)))
+            yield entry
+
+    def getMethods(self):
+        """Get all methods of an object.
+
+        Get a list of dicts, describing the methods of an object. The
+        dicts support keys ``name`` (name of method), ``signature``,
+        ``doc`` (method's docstring or ``None``) and ``interface``
+        (the interface, where the method was defined or ``None``).
+        """
+        klass = removeSecurityProxy(self.__klass)
+        obj = removeSecurityProxy(self.obj)
+        for name in dir(obj):
+            value = getattr(obj, name, None)
+            if value is None:
+                continue
+            if not (inspect.ismethod(value)
+                    and not inspect.ismethoddescriptor(value)):
+                continue
+            if inspect.ismethod(value):
+                signature = utilities.getFunctionSignature(value)
+            else:
+                signature = '(...)'
+
+            entry = {
+                'name': name,
+                'signature' : signature,
+                'doc' : getattr(value, '__doc__', None),
+                'interface': utilities.getInterfaceForAttribute(
+                                 name, klass=klass),
+                'value_linkable': self.isLinkable(value),
+                }                
+            entry.update(utilities.getPermissionIds(
+                name, getCheckerForInstancesOf(klass)))
+            yield entry
+        
+    def isSequence(self):
+        """Is the object observed a sequence?
+        """
+        if isinstance(self.obj, types.ListType):
+            return True
+        if isinstance(self.obj, types.TupleType):
+            return True
+        return IExtendedReadSequence.providedBy(self.obj)
+
+    def getSequenceItems(self):
+        """Get the items of a sequence.
+
+        Returns a list of dicts, each representing one element of the
+        sequence.
+        """
+        if not self.isSequence():
+            return
+        
+        elems = []
+        naked = removeSecurityProxy(self.obj)
+        for index in xrange(0, len(self.obj)):
+            value = naked[index]
+            elems.append({
+                'index' : index,
+                'value' : value,
+                'value_type' : type(value),
+                'value_type_link' : self.getTypeLink(type(value)),
+                'value_linkable': self.isLinkable(value),
+                })
+        return elems
+
+    def isMapping(self):
+        """Is the object observed a mapping?
+
+        Mappings are those objects, which are dicts or provide
+        IEnumerableMapping.
+        """
+        if isinstance(self.obj, types.DictType):
+            return True
+        return IEnumerableMapping.providedBy(self.obj)
+
+    def getMappingItems(self):
+        """Get the elements of a mapping.
+
+        The elements are delivered as a list of dicts, each dict
+        containing the keys ``key``, ``key_string`` (the key as a
+        string), ``value``, ``value_type`` and ``value_type_link``.
+        """
+        elems = []
+        naked = removeSecurityProxy(self.obj)
+        if not hasattr(naked, 'items'):
+            return []
+        for key, value in naked.items():
+            elems.append({
+                'key' : key,
+                'key_string' : `key`,
+                'value' : value,
+                'value_type' : type(value),
+                'value_type_link' : self.getTypeLink(type(value)),
+                'value_linkable': self.isLinkable(value),
+                })
+        return elems
+
+    def isAnnotatable(self):
+        """Does the object observed expect to be annotated?
+        """
+        return IAnnotatable.providedBy(self.obj)
+
+
+    def getAnnotationsInfo(self):
+        """Get all annotations associated with an object.
+
+        If no annotations are associated with the object, ``None`` is
+        returned. Otherwise we get a list of dicts, each containing
+        keys ``key``, ``key_string`` (textual representation of key),
+        ``value``, ``value_type`` and ``value_type_link''.
+        """
+        if not self.isAnnotatable():
+            return []
+        naked = removeSecurityProxy(self.obj)
+        annotations = IAnnotations(naked)
+        if not hasattr(annotations, 'items'):
+            return []
+        elems = []
+        for key, value in annotations.items():
+            elems.append({
+                'key' : key,
+                'key_string' : `key`,
+                'value' : value,
+                'value_type' : type(value),
+                'value_type_link' : self.getTypeLink(type(value)),
+                'value_linkable': self.isLinkable(value),
+                'obj' : annotations[key]
+                })
+        return elems
+
+    def getId(self):
+        """Try to determine some kind of name.
+        """
+        return (getattr(self.obj, '__name__', None)
+               or getattr(self.obj, 'id', None)
+               or getattr(self.obj, '_o_id', None))
+
+
+from zope.traversing.interfaces import ITraversable
+from zope.app.folder.interfaces import IRootFolder
+from zope.location import LocationProxy
+class AnnotationsTraverser(grok.Traverser):
+    """If first URL element is '++anno++', handle over to
+    ObjectBrowser.
+
+    This traverser binds to the RootFolder, which means, it is only
+    asked, when the publisher looks for elements in the Zope root (or
+    another IRootFolder). The further traversing is done by the Docs'
+    own traverser in it's model. See method `traverse()` in DocGrok.
+    """
+    grok.context(Interface)
+    #grok.name('anno')
+    #grok.provides(ITraversable)
+
+    def traverse(self,path):
+        namespace = 'anno'
+        print "TRAVERSE", path
+        if path.startswith(namespace):
+            name = path[len(namespace):]
+            naked = removeSecurityProxy(self.context)
+            annotations = IAnnotations(naked)
+            print annotations.items()
+            #obj = name and annotations[name] or annotations
+            #obj = path and annotations[name] or annotations
+            obj = ObjectInfo("Hello")
+            if not IPhysicallyLocatable(obj, False):
+                #obj = LocationProxy(
+                #    obj, self.context, namespace + name)
+                obj = LocationProxy(
+                    obj, self.context, 'anno' + name)
+            return obj
+        return
+
+    def render(self):
+        pass
+
+##
+## This comes from Dieter Maurer:
+##
+
+def determineClass(o):
+  return hasattr(o, '__class__') and o.__class__ or type(o)
+
+class Recorder(object):
+  def __init__(self): self._account = {}
+  def __call__(self, o):
+    a = self._account
+    class_ = determineClass(o)
+    if class_ in a: a[class_] += 1
+    else: a[class_] = 1
+  def account(self): return self._account
+
+def sortRecords(recorder):
+  return sorted(recorder.account().items(), key=lambda r: r[1], reverse=True)
+
+def fix((ty, no)):
+  '''some types apparently have a broken 'str'. Fix this.'''
+  try: tyn = str(ty)
+  except TypeError:
+    try: tyn = 'BROKEN-STR: %r' % ty
+    except TypeError: tyn = 'BROKEN-STR-AND-REPR'
+  return tyn, no
+
+def analyseObjects(limit=100):
+  '''analyses live objects and garbage.
+
+  The result is a pair with information for live and garbage objects, respectively.
+  The information is a dict with keys 'count' and 'sorted'.
+  'count' is the total number of objects, 'sorted' a list with pairs
+  'type' and 'instanceCount', inverse sorted by 'instanceCount'.
+  '''
+  result = []
+  import gc
+  for objs in gc.get_objects(), gc.garbage:
+    r = Recorder()
+    for o in objs: r(o)
+    result.append({
+      'count':len(objs),
+      'sorted':map(fix, sortRecords(r)[:limit]),
+      })
+  return result
+
+
+
+
+
+
+
+
+
+
+
+
+class ObjectInfo_obsolete(object):
+    """A wrapper to provide object specific information.
+
+    This is the base wrapper.
+    
+    See inspect.txt to learn more about this kind of thing.
+    """
+
+    def __init__(self, obj):
+        self.obj = obj
+
+    def getName(self):
+        """Try to determine the name of an object.
+        """
+        for key in ['__name__', 'id', 'title', '_o_id']:
+            name = getattr(self.obj, key, None)
+            if name is not None:
+                break
+        return name
+
+    def getDoc(self):
+        """Fetch any doc strings from the wrapped object.
+        """
+        if hasattr(self.obj, '__class__'):
+            return getattr(self.obj.__class__, '__doc__', None)
+        return
+
+    def getType(self):
+        """Get the wrapped objects' type as a string in dotted path
+        notation.
+        """
+        return type(self.obj)
+
+    def getParent(self):
+        """Get the parent of the wrapped object.
+
+        This might result in a `TypeError` if the wrapped object is
+        not locatable in sense of ILocation, i.e. if it doesn't
+        provide ``__parent__`` attribute.
+        """
+        return getParent(self.obj)
+
+    def getChildren(self):
+        """Returns a list of children objects.
+        """
+        return dir(self.obj)
+
+    def getRoot(self):
+        """Get the root obj of the wrapped object.
+        """
+        try:
+            return getRoot(self.obj)
+        except TypeError:
+            tmp = self.obj
+            while getattr(tmp, '__parent__', None):
+                tmp = tmp.__parent__
+            return tmp
+        return
+
+    def isRoot(self):
+        """Check, whether the wrapped object is the root within its branch.
+
+        This does of course not necessarily mean, it is also the root
+        of the whole instance.
+        """
+        try:
+            return self.getRoot() == self.obj
+        except TypeError:
+            if getattr(self.obj, '__parent__', True) is None:
+                return True
+        return False
+
+    def is_broken(self):
+        """Check, whether the wrapped object is broken.
+        """
+        # XXX to be implemented.
+        return
+
+
+class DCObjectInfo(ObjectInfo_obsolete):
+    """An object wrapper, that provides DublinCore related information.
+
+    If such information is not available (for instance, because the
+    type of object can't be adapted), None is returned for every
+    value. It is therefore safe to use this info type also for objects
+    not supporting DC.
+    """
+    obj = None
+    supports_dc = False
+    _metadata = None
+    _covered_keys = [ 'created', 'modified', 'effective', 'expires',
+                      'publisher', 'creators', 'subjects', 'contributors',
+                      'description', 'title']
+
+
+    def __init__(self, obj):
+
+        super(ObjectInfo, self).__init__(obj)
+        self.obj = obj
+        try:
+            dc = IZopeDublinCore(self.obj)
+            self._metadata = dict((field, getattr(dc, field))
+                                  for field in getFields(IZopeDublinCore) if hasattr(dc, field))
+            self.supports_dc = True
+        except TypeError:
+            # A type error occurs, when obj can't be adapted to
+            # IZopeDublinCore.
+            self._metadata = dict([(x,None) for x in self._covered_keys])
+                
+
+    def _getDCEntry(self, category):
+        if category in self._metadata.keys() and self._metadata[category]:
+            return self._metadata[category]
+        return
+
+
+
+    def getDCMisc(self):
+        """Get dict of DC metadata not covered by other methods.
+        """
+        return dict([x for x in self._metadata.items()
+                     if x[0] not in self.covered_keys])
+
+    def getDCTitle(self):
+        return self._getDCEntry('title') or u''
+
+    def getDCDescription(self):
+        return self._getDCEntry('description') or u''
+
+    def getDCCreators(self):
+        return self._getDCEntry('creators') or ()
+
+    def getDCContributors(self):
+        return self._getDCEntry('contributors') or ()
+
+    def getDCSubjects(self):
+        return self._getDCEntry('subjects') or ()
+
+    def getDCPublisher(self):
+        return self._getDCEntry('publisher') or u''
+
+    def getDCCreationDate(self):
+        return self._getDCEntry('created') or u''
+
+    def getDCModificationDate(self):
+        return self._getDCEntry('modified') or u''
+
+    def getDCEffectiveDate(self):
+        return self._getDCEntry('effective') or u''
+
+    def getDCExpiringDate(self):
+        return self._getDCEntry('expires') or u''
+

Added: Sandbox/ulif/grok-adminui/src/grok/admin/objectinfo.txt
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/objectinfo.txt	                        (rev 0)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/objectinfo.txt	2007-08-06 15:00:38 UTC (rev 78628)
@@ -0,0 +1,650 @@
+============
+Object Infos
+============
+
+Retrieve information about objects. 
+
+ObjectInfos are used by the Grok object browser to retrieve the data
+displayed in the ``inspect.html`` view. See inspect.txt to learn more
+about using the inspector.
+
+.. contents::
+
+
+ObjectInfo:
+-----------
+
+Object infos are in fact wrappers for the functionality offered by the
+Python standard library ``inspect``.
+
+Let's create a simple class:
+
+  >>> class Mammoth(object):
+  ...    """A mammoth."""
+  ...    pass
+
+Now create an object. Meet Manfred:
+
+  >>> manfred = Mammoth()
+  >>> manfred
+  <Mammoth object at ...>
+
+An ObjectInfo's job is it, to find out as much as possible about
+Manfred. We create an ObjectInfo by giving the object to examine as
+parameter: 
+
+  >>> from grok.admin.objectinfo import ObjectInfo
+  >>> info = ObjectInfo(manfred)
+  >>> info
+  <grok.admin.objectinfo.ObjectInfo object at ...>
+
+What can we know about Manfred now?
+
+  >>> members = info.getmembers()
+  >>> ('__class__', Mammoth) in members
+  True
+
+  >>> info.getmoduleinfo() is None
+  True
+
+A Mammoth is not a module. Let's see, what happens when we examine a
+real module:
+
+  >>> import grok.admin.objectinfo
+  >>> ObjectInfo(grok.admin).getmoduleinfo()
+  ('__init__', '.pyc', 'rb', 2)
+
+  >>> info.getmodulename() is None
+  True
+
+  >>> ObjectInfo(grok.admin.objectinfo).getmodulename()
+  'objectinfo'
+
+  >>> info.isclass()
+  False
+
+manfred is not a class, but an instance of a class. This information
+is correct.
+
+  >>> ObjectInfo(Mammoth).isclass()
+  True
+
+  >>> info.ismethod()
+  False
+
+  >>> ObjectInfo(info.ismethod).ismethod()
+  True
+
+  >>> info.isfunction()
+  False
+
+  >>> def f():
+  ...    pass
+
+  >>> ObjectInfo(f).isfunction()
+  True
+
+  >>> info.istraceback()
+  False
+
+  >>> info.isframe()
+  False
+
+  >>> info.iscode()
+  False
+
+  >>> info.isbuiltin()
+  False
+
+  >>> ObjectInfo(dir).isbuiltin()
+  True
+
+  >>> info.isroutine()
+  False
+
+  >>> info.ismethoddescriptor()
+  False
+
+  >>> info.isdatadescriptor()
+  False
+
+  >>> info.getdoc()
+  'A mammoth.'
+
+  >>> info.getcomments() is None
+  True
+
+We have a comment in the sources of ObjectInfo:
+
+  >>> ObjectInfo(grok.admin.objectinfo.ObjectInfo.getdoc).getcomments()
+  '# Source code related...\n'
+
+A bug in ``inspect.getcomments()`` causes objects with regular
+expression chars ('+', '*', etc.) in their name to fail. A workaround
+should check this and give ``None`` instead:
+
+  >>> evil_obj = Mammoth()
+  >>> evil_obj.__str__ = '+++evil+++'
+  >>> ObjectInfo(evil_obj).getcomments() is None
+  True
+
+``getfile()`` gives the name of the file the object was defined
+in. Contrary to the inspect method, this one returns None, if the
+object is a built-in module, class or function.
+
+  >>> info.getfile() is None
+  True
+
+The filename can be a compiled file ('.pyc') or a Python source file
+('.py'):
+
+  >>> filename = ObjectInfo(grok.admin.objectinfo.ObjectInfo).getfile()
+  >>> filename = filename[-1] == 'c' and filename or filename + 'c'
+  >>> filename
+  '.../grok/admin/objectinfo.pyc'
+
+
+  >>> info.getmodule()
+  <module '__builtin__' (built-in)>
+
+
+Here the same applies as for getfile().
+
+  >>> info.getsourcefile() is None
+  True
+
+  >>> ObjectInfo(grok.admin.objectinfo.ObjectInfo).getsourcefile()
+  '.../grok/admin/objectinfo.py'
+
+  >>> info.getsourcelines() is None
+  True
+
+  >>> ObjectInfo(grok.admin.objectinfo.ObjectInfo.isclass).getsourcelines()
+  (['    def isclass(self):\n',...)
+
+  >>> ObjectInfo(len).getsourcefile() is None
+  True
+
+  >>> info.getsource() is None
+  True
+
+  >>> ObjectInfo(grok.admin.objectinfo.ObjectInfo.isclass).getsource()
+  '    def isclass(self):\n...'
+
+A bug in ``inspect.getsource()`` causes objects with regular
+expression chars ('+', '*', etc.) in their name to fail. This is true
+for objects like '+++etc++site' and friends. A workaround should check
+this and give ``None`` instead:
+
+  >>> evil_obj = Mammoth()
+  >>> evil_obj.__str__ = '+++evil+++'
+  >>> ObjectInfo(evil_obj).getsource() is None
+  True
+
+
+ZopeObjectInfo
+--------------
+
+All information, which is special for Zope objects, can be digged
+using ``ZopeObjectInfo``. Zope specific are especially things related
+to the ZODB or interfaces.
+
+
+Preliminaries
++++++++++++++
+
+We first setup some objects, that we can examine thereafter. Let's
+create a ZopeObjectInfo for a typical Zope object, the root
+folder. First get the root folder:
+
+  >>> from zope.app.folder import rootFolder
+  >>> root = rootFolder()
+  >>> root
+  <zope.app.folder.folder.Folder object at ...>
+
+Then get an associated ZopeObjectInfo:
+
+  >>> from grok.admin.objectinfo import ZopeObjectInfo
+  >>> root_info = ZopeObjectInfo(root)
+  >>> root_info
+  <grok.admin.objectinfo.ZopeObjectInfo object at ...>
+
+We create a folder in the root:
+
+  >>> from zope.app.folder.folder import Folder
+  >>> subfolder = Folder()
+  >>> root['Savannah'] = subfolder
+
+and get a ZopeObjectInfo for it as well:
+
+  >>> subfolder_info = ZopeObjectInfo(subfolder)
+  >>> subfolder_info
+  <grok.admin.objectinfo.ZopeObjectInfo object at ...>
+
+Finally, we create some content in the subfolder. A cave, where
+Manfred can stay
+
+  >>> class Cave(grok.Container):
+  ...    """A home, sweet home."""
+  ...    pass
+
+  >>> import grok
+  >>> sweethome = Cave()
+  >>> subfolder['SweetHome'] = sweethome
+  >>> sweethome_info = ZopeObjectInfo(sweethome)
+  >>> sweethome_info
+  <grok.admin.objectinfo.ZopeObjectInfo object at ...>
+
+and put Manfred into the cave:
+
+  >>> sweethome['Owner'] = manfred
+  >>> manfred_info = ZopeObjectInfo(manfred)
+  >>> manfred_info
+   <grok.admin.objectinfo.ZopeObjectInfo object at ...>
+
+Now we can examine the objects further.
+
+getId()
++++++++
+
+Objects' names can be stored in a variety of attributes. It is
+therefore a bit difficult to get the right name (if any). ``getId``
+looks currently for ``__name__``, ``id`` and ``_o_id``. If all fails,
+``None`` is returned.
+
+  >>> root_info.getId() is None
+  True
+
+  >>> subfolder_info.getId()
+  u'Savannah'
+
+  >>> manfred_info.getId() is None
+  True
+
+getParent()
++++++++++++
+
+What is the parent object of the
+root?
+
+  >>> root_info.getParent() is None
+  True
+
+  >>> subfolder_info.getParent()
+  <zope.app.folder.folder.Folder object at ...>
+
+Oh, a folder. Is it the root?
+
+  >>> subfolder_info.getParent() == root
+  True
+
+What about the sweet home?
+
+  >>> sweethome_info.getParent()
+  <zope.app.folder.folder.Folder object at ...>
+
+  >>> sweethome_info.getParent() == subfolder
+  True
+
+Last not least, Manfred:
+
+  >>> manfred_info.getParent()
+  Traceback (most recent call last):
+  ...
+  TypeError: ('Not enough context information to get parent', <Mammoth object at ...>)
+
+Oops! Well, mammoths are not too smart. They wander around all the
+time and so sometimes they forget where they are. Technically,
+Mammoths are not locatable in sense of ILocatable. We can make them
+locatable setting two attributes:
+
+  >>> manfred.__parent__ = sweethome
+  >>> manfred.__name__ = u'Manfred'
+
+Afterwards we can tell, where Manfred lives:
+
+  >>> manfred_info.getParent()
+  <Cave object at ...>
+
+
+getDirectlyProvidedInterfaces()
++++++++++++++++++++++++++++++++
+
+Gives a list of interfaces provided *directly* by the objects in
+dotted-path notation.
+
+  >>> root_info.getDirectlyProvidedInterfaces()
+  ['zope.app.folder.interfaces.IRootFolder']
+
+  >>> subfolder_info.getDirectlyProvidedInterfaces()
+  []
+
+  >>> sweethome_info.getDirectlyProvidedInterfaces()
+  []
+
+  >>> manfred_info.getDirectlyProvidedInterfaces()
+  []
+
+
+getInterfaces()
++++++++++++++++
+
+Gives a tuple of all interfaces provided by the object, not only the
+directly provided ones.
+
+  >>> root_info.getProvidedInterfaces()
+  (<InterfaceClass zope.app.folder.interfaces.IFolder>, <InterfaceClass persistent.interfaces.IPersistent>, <InterfaceClass zope.app.component.interfaces.IPossibleSite>, <InterfaceClass zope.app.container.interfaces.IContained>)
+
+  >>> sweethome_info.getProvidedInterfaces()
+  (<InterfaceClass zope.annotation.interfaces.IAttributeAnnotatable>, <InterfaceClass zope.app.container.interfaces.IContainer>, <InterfaceClass zope.app.container.interfaces.IContained>, <InterfaceClass persistent.interfaces.IPersistent>)
+
+Manfred again, is a bit too plain to give us interesting information:
+
+  >>> manfred_info.getProvidedInterfaces()
+  ()
+
+
+getBases()
+++++++++++
+
+What bases built our objects?
+
+  >>> root_info.getBases()
+  (<type 'persistent.Persistent'>, <class 'zope.app.component.site.SiteManagerContainer'>, <class 'zope.app.container.contained.Contained'>)
+
+  >>> sweethome_info.getBases()
+  (<class 'grok.components.Container'>,)
+
+  >>> manfred_info.getBases()
+  (<type 'object'>,)
+
+
+getAttributes()
++++++++++++++++
+
+Attributes are members, which are not methods nor methoddescriptors
+(according to ``inspect`` package). This method gives (contrary to the
+apidoc method with same name) *all* attributes, also the 'private'
+ones, because some private attributes can be of interest, when
+debugging happens.
+
+Attributes are given as an iterator over dicts, each dict containing
+the keys 
+
+- ``name``: name of attribute.
+
+- ``value``: value of attribute as string.
+
+- ``value_linkable``: 
+
+  a boolean indicating, whether the attribute is reachable directly,
+  for instance calling a special URL. Linkable values can be examined
+  further by calling the current URL and inserting the name of
+  attribute. Example: an object examined by
+
+	     http://localhost:8080/someobj/inspect.html
+
+  which owns a linkable attribute ``myattr`` can be examined by
+
+             http://localhost:8080/someobj/myattr/inspect.html
+
+- ``type``: type of value as string.
+
+- ``type_link``: link to the type documentation as str.
+
+- ``interface``: name of the interface, this attribute was defined in
+  or ``None``.
+
+- ``read_perm`` and ``write_perm``: permissions to read/write the
+  attribute. 
+
+To get the attributes as a list, you can use ``list()`` as shown
+below.
+
+We now look for the attributes of the root folder:
+
+  >>> attrs = list(root_info.getAttributes())
+
+There should be a ``data`` attribute available:
+
+  >>> attr_names = [x['name'] for x in attrs]
+  >>> 'data' in attr_names
+  True
+
+And the ``data`` attribute should have eight entries (as described
+above): 
+
+  >>> data_attr = [x for x in attrs if x['name'] == 'data'][0]
+  >>> len(data_attr)
+  8
+
+First, let's determine the name of the attribute:
+
+  >>> data_attr['name']
+  'data'
+
+The value of the attribute:
+
+  >>> data_attr['value']
+  '<BTrees.OOBTree.OOBTree object at ...>'
+
+The value is directly reachable using the traverser:
+
+  >>> data_attr['value_linkable']
+  True
+
+We can get some information about the type of the value:
+
+  >>> data_attr['type']
+  <type 'BTrees.OOBTree.OOBTree'>
+
+  >>> data_attr['type_link']
+  'BTrees/OOBTree/OOBTree'
+
+Is there an interface this attribute was defined in?
+
+  >>> data_attr['interface'] is None
+  True
+
+There are no special permissions defined to read or write the 'data'
+attribute. 
+
+  >>> data_attr['read_perm'] is None
+  True
+
+  >>> data_attr['write_perm'] is None
+  True
+
+
+getMethods()
+++++++++++++
+
+This method gives (contrary to the apidoc method with same name) *all*
+methods, also the 'private' ones, because some private attributes can
+be of interest, when debugging happens.
+
+Methods are given as an iterator over dicts, each dict containing
+the keys 
+
+- ``name``: the name of the method.
+
+- ``signature``: the signature of the methods as string.
+
+- ``doc``: an doc string of the method (if one exists)
+
+- ``interface``: the interface this method was implemented at (if
+  any).
+
+- ``read_perm`` and ``write_perm``: permissions to read/write the
+  method. 
+
+We first get the methods of the root object. Because ``getMethods``
+returns an iterable, we form a list, using ``list()``:
+
+  >>> methods = list(root_info.getMethods())
+
+  >>> len(methods)
+  13
+
+Okay, there are 13 methods defined in this object. We pick one to
+examine it further:
+
+  >>> items_method = [x for x in methods if x['name'] == 'items'][0]
+  >>> len(items_method)
+  7
+
+There are six keys in the dict describing the ``items()`` method of
+the root folder.
+
+  >>> items_method['name']
+  'items'
+
+The ``items()`` method takes no parameters:
+
+  >>> items_method['signature']
+  '()'
+
+The method is documented:
+
+  >>> items_method['doc']
+  'Return a sequence-like object containing tuples of the form\n           (name, object) for the objects that appear in the folder.\n        '
+
+This is the raw documentation string, obviously. It can be formatted
+using render methods defined in the inspect view class.
+
+The method has no special interface where it was defined:
+
+  >>> items_method['interface'] is None
+  True
+
+And there are no special permissions set on this method:
+
+  >>> items_method['read_perm'] is None
+  True
+
+  >>> items_method['write_perm'] is None
+  True
+
+
+isSequence()
+++++++++++++
+
+Sequences are those objects, which provide the IExtendedReadSequence
+interface, are of tuple type or of list type.
+
+  >>> root_info.isSequence()
+  False
+
+  >>> ZopeObjectInfo(['a', 'list', 'of', 'values']).isSequence()
+  True
+
+  >>> ZopeObjectInfo(('a', 'tuple', 'of', 'values')).isSequence()
+  True
+
+  >>> ZopeObjectInfo({'a': 'dict', 'of': 'values'}).isSequence()
+  False
+
+
+getSequenceItems()
+++++++++++++++++++
+
+  >>> testlist = ['a', 'list', 'of', 'values']
+  >>> list_info = ZopeObjectInfo(testlist).getSequenceItems()
+  >>> len(list_info)
+  4
+
+  >>> first_elem = list_info[0]
+  >>> first_elem['index']
+  0
+
+  >>> first_elem['value_type']
+  <type 'str'>
+
+  >>> first_elem['value_type_link']
+  '__builtin__/str'
+
+  >>> first_elem['value']
+  'a'
+
+
+isMapping()
++++++++++++
+
+  >>> root_info.isMapping()
+  True
+
+  >>> ZopeObjectInfo(['a', 'list', 'of', 'values']).isMapping()
+  False
+
+  >>> ZopeObjectInfo(('a', 'tuple', 'of', 'values')).isMapping()
+  False
+
+  >>> ZopeObjectInfo({'a': 'dict', 'of': 'values'}).isMapping()
+  True
+
+  >>> ZopeObjectInfo(root.data).isMapping()
+  False
+
+
+getMappingItems()
++++++++++++++++++
+
+  >>> map_elems = root_info.getMappingItems()
+  >>> u'Savannah' in [x['key'] for x in map_elems]
+  True
+
+  >>> map_elem = [x for x in map_elems if x['key'] == u'Savannah'][0]
+  >>> map_elem['key']
+  u'Savannah'
+
+  >>> map_elem['key_string']
+  "u'Savannah'"
+
+  >>> map_elem['value']
+  <zope.app.folder.folder.Folder object at ...>
+
+  >>> map_elem['value_type']
+  <class 'zope.app.folder.folder.Folder'>
+
+  >>> map_elem['value_type_link']
+  'zope/app/folder/folder/Folder'
+
+Objects, which are not mappings, should return the empty list:
+
+  >>> ZopeObjectInfo('a string').getMappingItems()
+  []
+
+isAnnotatable()
++++++++++++++++
+
+Checks for the interface IAnnotatable. Most 'usual' Zope objects are
+annotatable...
+
+  >>> root_info.isAnnotatable()
+  True
+
+  >>> sweethome_info.isAnnotatable()
+  True
+
+...but some very simple ones are not (or have not declared to be
+annotatable although they are):
+
+  >>> manfred_info.isAnnotatable()
+  False
+
+
+getAnnotationsInfo()
+++++++++++++++++++++
+
+  >>> root_info.getAnnotationsInfo()
+  []
+
+  >>> sweethome_info.getAnnotationsInfo()
+  []
+
+Manfred is not annotatable, but instead of an error we get an empty
+list:
+
+  >>> manfred_info.getAnnotationsInfo()
+  []
+

Modified: Sandbox/ulif/grok-adminui/src/grok/admin/static/grok.css
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/static/grok.css	2007-08-06 13:09:23 UTC (rev 78627)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/static/grok.css	2007-08-06 15:00:38 UTC (rev 78628)
@@ -208,6 +208,10 @@
 margin-bottom:0.5em;
 }
 
+.docgrok-value { 
+margin-left:2em;
+}
+
 .docgrok-pathvalue {
 font-family:courier;
 }
@@ -236,6 +240,23 @@
 background-color:#fff;
 }
 
+.docgrok-table { 
+font-size:1.0em;
+empty-cells:show;
+border-collapse:separate;
+}
+.docgrok-table th { 
+padding-left:3px;
+padding-right:3px;
+border:1px solid #eee;
+}
+.docgrok-table td {
+min-height:1.0em;
+padding-left:5px;
+padding-right:5px;
+
+}
+
 /* --- system proces related --- */
 div#server-processes {
 background-color:#f9f9D4;
@@ -292,3 +313,10 @@
 padding-top:1em;
 padding-left:0.8em;
 }
+
+#logout { 
+padding:1px;
+padding-right:150px;
+background-color:#ddd;
+text-align:right;
+}
\ No newline at end of file

Modified: Sandbox/ulif/grok-adminui/src/grok/admin/tests/test_grokadmin.py
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/tests/test_grokadmin.py	2007-08-06 13:09:23 UTC (rev 78627)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/tests/test_grokadmin.py	2007-08-06 15:00:38 UTC (rev 78628)
@@ -2,9 +2,11 @@
 from pkg_resources import resource_listdir
 from zope.testing import doctest, cleanup
 import zope.component.eventtesting
+from zope.annotation.attribute import AttributeAnnotations
 
 def setUpZope(test):
     zope.component.eventtesting.setUp(test)
+    zope.component.provideAdapter(AttributeAnnotations)
 
 def cleanUpZope(test):
     cleanup.cleanUp()
@@ -34,7 +36,7 @@
     suite = unittest.TestSuite()
     for name in []:
         suite.addTest(suiteFromPackage(name))
-    for name in ['docgrok.txt']:
+    for name in ['docgrok.txt','objectinfo.txt', 'utilities.py']:
         suite.addTest(doctest.DocFileSuite(name,
                                            package='grok.admin',
                                            setUp=setUpZope,

Added: Sandbox/ulif/grok-adminui/src/grok/admin/utilities.py
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/utilities.py	                        (rev 0)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/utilities.py	2007-08-06 15:00:38 UTC (rev 78628)
@@ -0,0 +1,240 @@
+"""Helper functions for grok admin.
+"""
+import re
+from zope.tal.taldefs import attrEscape
+from urlparse import urlparse, urlunparse
+
+def getPathLinksForObject(obj, root_url=''):
+    """Given an object, this function returns HTML code with links to
+    documentation.
+
+    The object must provide a string representation like 'foo.blah
+    object at 0x9999999'. Returned is then a string, where 'foo' and
+    'blah' are embedded in HTML links to docgrok documentation for foo
+    and foo.blah.
+
+    The (optional) ``root_url`` is used to create the links to docgrok
+    documentation. It is expected to be the URL, which can generate
+    docgrok documentation by appending '/docgrok' to the URL.
+
+    We can use ObjectInfo objects to check this:
+
+      >>> from grok.admin.objectinfo import ObjectInfo
+      >>> obj = ObjectInfo(None)
+      >>> obj
+      <grok.admin.objectinfo.ObjectInfo object at ...>
+
+    Obviously we have a string representation of the required form
+    here. So we can get HTML with links to the documentation for
+    ``grok``, ``grok.admin`` and so on.
+    
+      >>> from grok.admin.utilities import getPathLinksForObject
+      >>> link = getPathLinksForObject(obj)
+      >>> link
+      "&lt;<a href='/docgrok/grok/'>grok</a>... object at ..."
+
+    We got a link to the ``grok`` documentation. Also links to
+    ``grok.admin``, ``grok.admin.objectinfo`` and
+    ``grok.admin.objectinfo.ObjectInfo`` are provided:
+
+      >>> link
+      "&lt;...<a href='/docgrok/grok/admin/'>admin</a>... object at ..."
+
+    If we provide a root_url, we will find it in the links:
+
+      >>> link = getPathLinksForObject(obj, 'http://localhost:8080')
+      >>> link
+      "&lt;<a href='http://localhost:8080/docgrok/grok/'>grok</a>..."
+
+    If no dotted path is included in objects strings representation, a
+    simple string without links is returned:
+    
+      >>> getPathLinksForObject(None)
+      "'None'"
+
+    HTML entities should be encoded. We set up a site-manager to get
+    an 'illegal' object representation including regular expression
+    chars ('+') and no dotted path:
+
+      >>> from zope.app.folder import rootFolder
+      >>> root = rootFolder()
+      >>> from zope.app.component import site
+      >>> sm = site.LocalSiteManager(root)
+      >>> root.setSiteManager(sm)
+      >>> sm
+      <LocalSiteManager ++etc++site>
+
+    This is a strange object identifier. Anyway:
+
+      >>> getPathLinksForObject(sm)
+      "'&lt;LocalSiteManager ++etc++site&gt;'"
+      
+    """
+    r_exp = re.compile("'<(.+)( object at .*)>'")
+    
+    raw = `str(obj)`
+    match = r_exp.match(raw)
+    if match is None:
+        return attrEscape(raw)
+
+    result = "&lt;"
+    url = root_url + '/docgrok/'
+    for part in match.group(1).split('.'):
+        url = url + part + '/'
+        result += "<a href='%s'>%s</a>." % (url, part)
+    if len(result) and result[-1] == '.':
+        result = "%s%s&gt;" % (result[:-1], match.group(2))
+        return result
+    return raw
+
+def getPathLinksForClass(klass, root_url=''):
+    """Given a class or classlike object, this function returns HTML
+    code with links to documentation.
+
+    The klass object must provide a string representation like '<class
+    foo.Bar>'. Returned is then a string, where 'foo' and
+    'Bar' are embedded in HTML links to docgrok documentation for foo
+    and foo.Bar.
+
+    The (optional) ``root_url`` is used to create the links to docgrok
+    documentation. It is expected to be the URL, which can generate
+    docgrok documentation by appending '/docgrok' to the URL.
+
+    We can use class ObjectInfo to check this:
+
+      >>> from grok.admin.objectinfo import ObjectInfo
+      >>> ObjectInfo
+      <class 'grok.admin.objectinfo.ObjectInfo'>
+
+      >>> from grok.admin.utilities import getPathLinksForClass
+      >>> htmlcode = getPathLinksForClass(ObjectInfo)
+      >>> htmlcode
+      "&lt;class '<a href='/docgrok/grok/'>grok</a>...'&gt;"
+
+    When we provide a root_url the link will include it in the
+    href-attribute:
+
+      >>> getPathLinksForClass(ObjectInfo, 'http://localhost')
+      "&lt;class '<a href='http://localhost/docgrok/grok/'>grok</a>...'&gt;"
+
+    If the class does not provide an appropriate string
+    representation, we will get the representation without any links:
+
+      >>> getPathLinksForClass(None, 'http://localhost')
+      "'None'"
+
+    This also works with 'class-like' objects, for instance interfaces
+    and their interface-classes:
+
+      >>> from zope.app.folder import rootFolder
+      >>> from zope.interface import providedBy
+      >>> root = rootFolder()
+      >>> iface = list(providedBy(root))[0]
+      >>> iface
+      <InterfaceClass zope.app.folder.interfaces.IRootFolder>
+
+      >>> getPathLinksForClass(iface)
+      "&lt;InterfaceClass '<a href='/docgrok/zope/'>zope</a>...'&gt;"
+
+    HTML entities should be encoded. We set up a site-manager to get
+    an 'illegal' object representation including regular expression
+    chars ('+') and no dotted path:
+
+      >>> from zope.app.folder import rootFolder
+      >>> root = rootFolder()
+      >>> from zope.app.component import site
+      >>> sm = site.LocalSiteManager(root)
+      >>> root.setSiteManager(sm)
+      >>> sm
+      <LocalSiteManager ++etc++site>
+
+    This is a strange object identifier. Anyway:
+
+      >>> getPathLinksForClass(sm)
+      "&lt;LocalSiteManager '<a href='/docgrok/++etc++site/'>...</a>'&gt;"
+
+    """
+    r_exp = re.compile(".*<(.*) '?(.+)'?(.*)>.*")
+    raw = `str(klass)`
+    match = r_exp.match(raw)
+    if match is None:
+        return attrEscape(raw)
+
+    result = "&lt;%s '" % (match.group(1),)
+    url = root_url + '/docgrok/'
+    for part in match.group(2).split('.'):
+        url = "%s%s/" % (url, part)
+        result += "<a href='%s'>%s</a>." % (url, part)
+    if len(result) and result[-1] == '.':
+        result = "%s'%s&gt;" % (result[:-1], match.group(3))
+        return result
+    return raw
+
+def getPathLinksForDottedName(name, root_url=''):
+    """
+    """
+    if name is None:
+        return ''
+    result = ''
+    url = root_url + '/docgrok/'
+    for part in name.split('.'):
+        url = "%s%s/" % (url, part)
+        result += "<a href='%s'>%s</a>." % (url, part)
+    if len(result) and result.endswith('.'):
+        result = result[:-1]
+        return result
+    return name
+
+def isContainingEvilRegExpChars(strval):
+    """Check whether a string contains evil chars.
+
+    'Evil' with respect to regular expressions is a string, that
+    contains chars, with a special meaning in regular expressions.
+
+    We indeed must provide a string:
+
+       >>> from grok.admin.utilities import isContainingEvilRegExpChars
+       >>> isContainingEvilRegExpChars(None)
+       Traceback (most recent call last):
+       ...
+       TypeError: expected string or buffer
+
+       >>> isContainingEvilRegExpChars('foo')
+       False
+
+       >>> isContainingEvilRegExpChars('foo++etc++bar')
+       True
+
+       >>> isContainingEvilRegExpChars('foo*bar')
+       True
+
+    """
+    evil_chars = re.compile('.*(\*|\+|\(|\)|\{|\}).*')
+    if evil_chars.match(strval):
+        return True
+    return False
+
+
+def getParentURL(url):
+    """Compute the parent URL for an object described by URL.
+
+       >>> from grok.admin.utilities import getParentURL
+       >>> getParentURL('http://foo:8080/myobj')
+       'http://foo:8080/'
+
+       >>> getParentURL('http://foo:8080/myfoo/mybar')
+       'http://foo:8080/myfoo/'
+
+    We want an URL always to end with a slash:
+
+       >>> getParentURL('http://foo:8080')
+       'http://foo:8080/'
+
+    """
+    url_list = list(urlparse(url))
+    path = url_list[2]
+    if path.endswith('/'):
+        path = path[:-1]
+    path = path.rsplit('/', 1)[0] + '/'
+    url_list[2] = path
+    return urlunparse(url_list)

Modified: Sandbox/ulif/grok-adminui/src/grok/admin/view.py
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/view.py	2007-08-06 13:09:23 UTC (rev 78627)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/view.py	2007-08-06 15:00:38 UTC (rev 78628)
@@ -1,22 +1,25 @@
 import grok
 import os
 import inspect
+from urllib import urlencode
+
 from grok.admin import docgrok
 from grok.admin.docgrok import DocGrok, DocGrokPackage, DocGrokModule
-from grok.admin.docgrok import DocGrokClass, DocGrokInterface, DocGrokGrokApplication
-from grok.admin.docgrok import DocGrokTextFile
+from grok.admin.docgrok import DocGrokTextFile, DocGrokGrokApplication
+from grok.admin.docgrok import DocGrokClass, DocGrokInterface, getItemLink
 
+from grok.admin.objectinfo import ZopeObjectInfo
+from grok.admin.utilities import getPathLinksForObject, getPathLinksForClass
+from grok.admin.utilities import getPathLinksForDottedName, getParentURL
+
 import zope.component
 from zope.interface import Interface
-from zope.app.folder.interfaces import IRootFolder
-
 from zope.app import zapi
+from zope.interface.interface import InterfaceClass
 from zope.app.applicationcontrol.interfaces import IServerControl
 from zope.app.applicationcontrol.applicationcontrol import applicationController
 from zope.app.applicationcontrol.runtimeinfo import RuntimeInfo
 from zope.app.applicationcontrol.browser.runtimeinfo import RuntimeInfoView
-
-from zope.interface.interface import InterfaceClass
 from zope.app.apidoc import utilities, codemodule
 from zope.app.apidoc.utilities import getPythonPath, renderText, columnize
 from zope.app.apidoc.codemodule.module import Module
@@ -24,10 +27,11 @@
 from zope.app.apidoc.codemodule.function import Function
 from zope.app.apidoc.codemodule.text import TextFile
 from zope.app.apidoc.codemodule.zcml import ZCMLFile
-
+from zope.app.folder.interfaces import IRootFolder
+from zope.app.security.interfaces import ILogout, IAuthentication
 from zope.app.security.interfaces import IUnauthenticatedPrincipal
-
 from zope.proxy import removeAllProxies
+from zope.tal.taldefs import attrEscape
 
 import z3c.flashmessage.interfaces
 
@@ -44,7 +48,7 @@
 
     def update(self, inspectapp=None, application=None):
         if inspectapp is not None:
-            self.redirect( self.url("docgrok") + "/%s/index"%(application.replace('.','/'),))
+            self.redirect(self.url("docgrok") + "/%s/index"%(application.replace('.','/'),))
         return 
 
     def render(self, application, name, inspectapp=None):
@@ -72,6 +76,7 @@
             items = [items]
         for name in items:
             del self.context[name]
+            self.flash(u'Application %s was successfully deleted.' % (name,))
         self.redirect(self.url(self.context))
 
 
@@ -94,100 +99,152 @@
         raise ValueError("No application nor root element found.")
 
     def in_docgrok(self):
-        return '/docgrok/' in self.url()
+        return '/docgrok/' in self.url() or 'inspect.html' in self.url()
 
+    def is_authenticated(self):
+        """Check, wether we are authenticated.
+        """
+        return not IUnauthenticatedPrincipal.providedBy(self.request.principal)
 
-def getDottedPathDict(dotted_path):
-    """Get a dict containing parts of a dotted path as links.
-    """
-    if dotted_path is None:
-        return {}
-    
-    result = []
-    part_path = ""
-    for part in dotted_path.split( '.' ):
-        name = part
-        if part_path != "":
-            name = "." + part
-        part_path += part
-        result.append( {
-            'name':name,
-            'url':"/docgrok/%s" % (part_path,)
-            })
-        part_path += "/"
-    return result
 
+class Macros(GAIAView):
+    """Provides the o-wrap layout."""
 
+    grok.context(Interface)
 
+
+
 class Inspect(GAIAView):
     """Basic object browser.
     """
+
     grok.context(Interface)
+    grok.name(u'inspect.html')
+    grok.require('grok.ManageApplications')
 
-    def __init__(self, context, request):
-        # Leave out the Introspector init, because it requires
-        # ++apidoc++ to be enabled and setups skin-related stuff we
-        # don't want.
-        super(GAIAView, self).__init__(context,request)
+    _metadata = None
 
- 
-    def getPathParts(self,dotted_path):
-        return getDottedPathDict(dotted_path)
+    def update(self, show_private=False, *args, **kw):
+        obj = self.context
+        if isinstance(self.context, ZopeObjectInfo):
+            # When the docgrok-object traverser delivers content, then
+            # we get a wrapped context: the meant object is wrapped
+            # into a ZopeObjectInfo.
+            obj = self.context.obj
+            
+        self.ob_info = ZopeObjectInfo(obj)
+        ob_info = self.ob_info
+        self.show_private = show_private
+        root_url = self.root_url()
+        parent = ob_info.getParent()
+        parent = {'class_link':
+                      parent and getPathLinksForObject(parent) or '',
+                  'obj_link' : getItemLink('',getParentURL(self.url(''))),
+                  'obj' : parent
+                  }
+        bases = [getPathLinksForClass(x) for x in ob_info.getBases()]
+        bases.sort()
+        
+        ifaces = [getPathLinksForClass(x) for x in
+                  ob_info.getProvidedInterfaces()]
+        ifaces.sort()
 
-    def getId(self):
-        if hasattr( self.context, '__name__'):
-            return self.context.__name__
-        if hasattr( self.context, 'id' ):
-            return self.context.id
-        return
+        methods = [x for x in list(ob_info.getMethods())
+                   if self.show_private or not x['name'].startswith('_')]
+        for method in methods:
+            if method['interface']:
+                method['interface'] = getPathLinksForDottedName(
+                    method['interface'], root_url)
+            if method['doc']:
+                method['doc'] = renderText(method['doc'], getattr(obj,'__module__', None))
 
-    def getZODBPath(self):
-        # XXX To be implemented.
-        return
+        attrs = [x for x in list(ob_info.getAttributes())
+                 if self.show_private or not x['name'].startswith('_')
+                 ]
+        for attr in attrs:
+            if '.' in str(attr['type']):
+                attr['type'] = getPathLinksForClass(attr['type'], root_url)
+            else:
+                attr['type'] = attrEscape(str(attr['type']))
+            if attr['interface']:
+                attr['interface'] = getPathLinksForDottedName(
+                    attr['interface'], root_url)
+            attr['obj'] = getattr(obj, attr['name'], None)
+            attr['docgrok_link'] = getItemLink(attr['name'], self.url(''))
+        attrs.sort(lambda x,y: x['name']>y['name']) 
 
-    def getDottedPath(self):
-        if hasattr(self.context, '__class__'):
-            klassname = str(self.context.__class__)
-            return klassname.rsplit("'", 2)[1]
-        return
+        seqitems = ob_info.getSequenceItems() or []
+        for item in seqitems:
+            if '.' in str(item['value_type']):
+                item['value_type'] = getPathLinksForClass(item['value_type'],
+                                                          root_url)
+            else:
+                item['value_type'] = attrEscape(str(item['value_type']))
+            item['obj'] = obj[item['index']]
+            item['docgrok_link'] = getItemLink(item['index'], self.url(''))
+        seqitems.sort()
 
+        mapitems = [x for x in ob_info.getMappingItems()
+                    if self.show_private or not x['key'].startswith('_')]
+        for item in mapitems:
+            if '.' in str(item['value_type']):
+                item['value_type'] = getPathLinksForClass(item['value_type'],
+                                                          root_url)
+            else:
+                item['value_type'] = attrEscape(str(item['value_type']))
+            item['obj'] = obj[item['key']]
+            item['docgrok_link'] = getItemLink(item['key'], self.url(''))
+        mapitems.sort(lambda x,y: x['key']>y['key'])
 
-    def getSize(self):
-        # XXX To be implemented.
-        return
+        annotations = [x for x in ob_info.getAnnotationsInfo()
+                    if self.show_private or not x['key'].startswith('_')]
+        for item in annotations:
+            if '.' in str(item['value_type']):
+                item['value_type'] = getPathLinksForClass(item['value_type'],
+                                                          root_url)
+            else:
+                item['value_type'] = attrEscape(str(item['value_type']))
+            item['docgrok_link'] = getItemLink(item['key'], self.url(''))
+        annotations.sort(lambda x,y: x['key']>y['key'])
 
-    def getCreationDate(self):
-        # XXX To be implemented.
-        return
+        
+        self.info = {
+            'name' : ob_info.getId() or u'<unnamed object>',
+            'type' : getPathLinksForClass((getattr(obj,
+                                                   '__class__',
+                                                   None)
+                                           or type(obj)), root_url),
+            'obj_link' : getPathLinksForObject(obj, root_url),
+            'moduleinfo' : ob_info.getmoduleinfo(),
+            'modulename' : ob_info.getmodulename(),
+            'ismodule' : ob_info.ismodule(),
+            'isclass' : ob_info.isclass(),
+            'ismethod' : ob_info.ismethod(),
+            'isfunction' : ob_info.isfunction(),
+            'iscode' : ob_info.iscode(),
+            'isbuiltin' : ob_info.isbuiltin(),
+            'isroutine' : ob_info.isroutine(),
+            'issequence' : ob_info.isSequence(),
+            'ismapping' : ob_info.isMapping(),
+            'isannotatable' : ob_info.isAnnotatable(),
+            'doc' : renderText(ob_info.getdoc(),None),
+            'comments' : ob_info.getcomments(),
+            'module' : ob_info.getmodule(),
+            'sourcefile' : ob_info.getsourcefile(),
+            'source' : ob_info.getsource(),
+            'parent' : parent,
+            'dotted_path' : ob_info.getPythonPath(),
+            'provided_interfaces' : ob_info.getDirectlyProvidedInterfaces(),
+            'interfaces' : ifaces,
+            'bases' : bases,
+            'attributes' : attrs,
+            'methods' : methods,
+            'sequenceitems' : seqitems,
+            'mappingitems' : mapitems,
+            'annotations' : annotations
+            }
+    
 
-    def getModificationDate(self):
-        # XXX To be implemented.
-        return
-
-    def isBroken(self):
-        # XXX To be implemented.
-        return
-
-    def getSecurityInfo(self):
-        # XXX To be implemented.
-        return
-
-    def getParent(self):
-        if hasattr( self.context, '__parent__'):
-            return self.context.__parent__
-        return
-
-    def getChildren(self):
-        # XXX To be implemented.
-        return
-
-    def getType(self):
-        if hasattr(self.context, '__class__'):
-            klassname = str(self.context.__class__)
-            return klassname.rsplit("'", 2)[1]
-        return str(self.context)
-
-
 class Index(GAIAView):
     """A redirector to the real frontpage."""
 
@@ -203,7 +260,7 @@
         self.redirect(self.url('applications'))
 
 
-class loginForm(GAIAView):
+class LoginForm(GAIAView):
     """A login screen for session based authentication.
 
     To activate loginForm, i.e. session based authentication, an
@@ -224,7 +281,19 @@
             self.redirect(camefrom)
         return
 
+class Logout(GAIAView):
+    """Log out screen."""
 
+    grok.name('logout')
+
+    def update(self):
+        auth = zope.component.getUtility(IAuthentication)
+        logout = ILogout(auth)
+        logout.logout(self.request)
+        pass
+
+
+
 class Applications(GAIAView):
     """View for application management."""
 
@@ -245,7 +314,7 @@
                      if hasattr(x, '__class__') and x.__class__ in apps]
         self.applications = (
           {'name': "%s.%s" % (x.__module__, x.__name__),
-           'docurl':("%s.%s" % (x.__module__, x.__name__)).replace( '.', '/')}
+           'docurl':("%s.%s" % (x.__module__, x.__name__)).replace('.', '/')}
           for x in apps)
         self.installed_applications = inst_apps
 
@@ -326,10 +395,26 @@
         self.redirect(self.url())
 
 
-class Macros(GAIAView):
-    """Provides the o-wrap layout."""
 
-    grok.context(Interface)
+def getDottedPathDict(dotted_path):
+    """Get a dict containing parts of a dotted path as links.
+    """
+    if dotted_path is None:
+        return {}
+    
+    result = []
+    part_path = ""
+    for part in dotted_path.split('.'):
+        name = part
+        if part_path != "":
+            name = "." + part
+        part_path += part
+        result.append({
+            'name':name,
+            'url':"/docgrok/%s" % (part_path,)
+            })
+        part_path += "/"
+    return result
 
 
 class DocGrokView(GAIAView):
@@ -340,7 +425,8 @@
     """
 
     grok.context(DocGrok)
-    grok.name( 'index' )
+    grok.name('index')
+    grok.require('grok.ManageApplications')
 
     def getDoc(self, text=None, heading_only=False):
         """Get the doc string of the module STX formatted."""
@@ -364,8 +450,8 @@
         lines = [line for line in lines if not line.startswith('$Id')]
         return renderText('\n'.join(lines), self.context.getPath())
 
-    def getDocHeading( self, text=None):
-        return self.getDoc( text, True)
+    def getDocHeading(self, text=None):
+        return self.getDoc(text, True)
 
     def getPathParts(self, path=None):
         """Get parts of a dotted name as url and name parts.
@@ -377,19 +463,19 @@
         return getDottedPathDict(path)
         result = []
         part_path = ""
-        for part in path.split( '.' ):
+        for part in path.split('.'):
             name = part
             if part_path != "":
                 name = "." + part
             part_path += part
-            result.append( {
+            result.append({
                 'name':name,
                 'url':"/docgrok/%s" % (part_path,)
                 })
             part_path += "/"
         return result
 
-    def getEntries( self, columns=True ):
+    def getEntries(self, columns=True):
         """Return info objects for all modules and classes in the
         associated apidoc container.
 
@@ -451,21 +537,21 @@
     """A view for packages handled by DocGrok."""
 
     grok.context(DocGrokPackage)
-    grok.name( 'index' )
+    grok.name('index')
 
 
 class DocGrokModuleView(DocGrokView):
     """A view for modules handled by DocGrok."""
 
     grok.context(DocGrokModule)
-    grok.name( 'index' )
+    grok.name('index')
 
 
 class DocGrokClassView(DocGrokView):
     """A view for classes handled by DocGrok."""
 
     grok.context(DocGrokClass)
-    grok.name( 'index' )
+    grok.name('index')
 
     def getBases(self):
         return self._listClasses(self.context.apidoc.getBases())
@@ -496,12 +582,12 @@
             if not fullpath:
                 continue
             path, name = fullpath.rsplit('.', 1)
-            info.append( {
+            info.append({
                 'path': path or None,
-                'path_parts' : self.getPathParts( path ) or None,
+                'path_parts' : self.getPathParts(path) or None,
                 'name': name,
                 'url': fullpath and fullpath.replace('.','/') or None,
-                'doc': self.getDocHeading( cls.__doc__ ) or None
+                'doc': self.getDocHeading(cls.__doc__) or None
                 })
         return info
 
@@ -509,19 +595,19 @@
 class DocGrokInterfaceView(DocGrokClassView):
 
     grok.context(DocGrokInterface)
-    grok.name( 'index' )
+    grok.name('index')
 
 
 class DocGrokGrokApplicationView(DocGrokClassView):
 
     grok.context(DocGrokGrokApplication)
-    grok.name( 'index' )
+    grok.name('index')
 
 
 class DocGrokTextFileView(DocGrokView):
 
     grok.context(DocGrokTextFile)
-    grok.name( 'index' )
+    grok.name('index')
 
     def getContent(self):
         lines = self.context.getContent()

Modified: Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/applications.pt
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/applications.pt	2007-08-06 13:09:23 UTC (rev 78627)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/applications.pt	2007-08-06 15:00:38 UTC (rev 78628)
@@ -17,6 +17,9 @@
 	    (<span tal:replace="app/__class__/__name__"/>)
 	  </a>
 	  &nbsp;&nbsp;
+	  [<a href=""
+	      tal:attributes="href string:${context/@@absolute_url}/${app/__name__}/@@inspect.html"
+	  >object browser</a>]
 	</div>
 
 	<p>

Modified: Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/docgrokpackageview.pt
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/docgrokpackageview.pt	2007-08-06 13:09:23 UTC (rev 78627)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/docgrokpackageview.pt	2007-08-06 15:00:38 UTC (rev 78628)
@@ -37,15 +37,15 @@
 	  </div>
 	</div>
 
-	<h2>Subpackages:</h2>
+	<h2>Textfiles:</h2>
 
 	<div class="docgrok-entry" tal:repeat="item view/getEntries">
-	  <div tal:condition="item/ispackage">
+	  <div tal:condition="item/istextfile">
 	    <div class="docgrok-pathvalue">
-	      package
+	      
 	      <a href=""
 		 tal:attributes="href string:${view/root_url}/docgrok/${item/url}" 
-		 tal:content="string: ${context/path}.${item/name}">
+		 tal:content="string: ${item/name}">
 		moduleName
 	      </a>
 	    </div>
@@ -55,21 +55,17 @@
 	    </div>
 	    <div class="docgrok-annotation2"
 		 tal:condition="not: item/doc">
-	      You can use <span class="docgrok-pycode1">import <span
-	      tal:replace="string: ${context/path}.${item/name}">a.b</span>
-	    </span>
-	    to make the elements of this package available in your 
-	    application or component.
 	    </div>
 	  </div>
 	</div>
 
-	<h2>Modules:</h2>
 
+	<h2>Subpackages:</h2>
+
 	<div class="docgrok-entry" tal:repeat="item view/getEntries">
-	  <div tal:condition="item/ismodule">
+	  <div tal:condition="item/ispackage">
 	    <div class="docgrok-pathvalue">
-	      module
+	      package
 	      <a href=""
 		 tal:attributes="href string:${view/root_url}/docgrok/${item/url}" 
 		 tal:content="string: ${context/path}.${item/name}">
@@ -85,21 +81,21 @@
 	      You can use <span class="docgrok-pycode1">import <span
 	      tal:replace="string: ${context/path}.${item/name}">a.b</span>
 	    </span>
-	    to make the elements of this module available in your 
+	    to make the elements of this package available in your 
 	    application or component.
 	    </div>
 	  </div>
 	</div>
 
-	<h2>Textfiles:</h2>
+	<h2>Modules:</h2>
 
 	<div class="docgrok-entry" tal:repeat="item view/getEntries">
-	  <div tal:condition="item/istextfile">
+	  <div tal:condition="item/ismodule">
 	    <div class="docgrok-pathvalue">
-	      
+	      module
 	      <a href=""
 		 tal:attributes="href string:${view/root_url}/docgrok/${item/url}" 
-		 tal:content="string: ${item/name}">
+		 tal:content="string: ${context/path}.${item/name}">
 		moduleName
 	      </a>
 	    </div>
@@ -109,21 +105,15 @@
 	    </div>
 	    <div class="docgrok-annotation2"
 		 tal:condition="not: item/doc">
-<!--
 	      You can use <span class="docgrok-pycode1">import <span
 	      tal:replace="string: ${context/path}.${item/name}">a.b</span>
 	    </span>
-	    to make the elements of this package available in your 
+	    to make the elements of this module available in your 
 	    application or component.
--->
 	    </div>
 	  </div>
 	</div>
 
-<!--
-	<div tal:content="context/msg" />
--->
-
       </div>
     </div>
   </body>

Modified: Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/docgrokview.pt
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/docgrokview.pt	2007-08-06 13:09:23 UTC (rev 78627)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/docgrokview.pt	2007-08-06 15:00:38 UTC (rev 78628)
@@ -8,21 +8,99 @@
 	<h1 >
 	  Welcome to DocGrok...
 	</h1>
-	<div>
-	  you might want to discover the following trails...
-	  <ul>
-	    <li>
+	<div class="emph">
+	  DocGrok is Grok's run-time documentation system.
+
+	</div>
+
+	<h2>
+	  DocGrok Package Browser
+	</h2>
+
+	<div class="docgrok-annotation1">
+	  <p>
+	    Use the package browser to browse the locally installed
+	    Python packages, their classes, members and included text
+	    documentation. You can, for example,...
+	  </p>
+	  <div class="docgrok-annotation1">
+	    <div>
 	      <a href=""
+		 class="emph"
 		 tal:attributes="href string:${view/root_url}/docgrok/zope">
-	      The zope package</a>
-	    </li>
-	    <li>
+	      browse the zope package</a>
+	    </div>
+	    <div>
 	      <a href=""
+		 class="emph"
 		 tal:attributes="href string:${view/root_url}/docgrok/grok">
-	      The Grok package</a>
-	    </li>
-	  </ul>
+	      browse the grok package</a>
+	    </div>
+	  </div>
+	  <p>
+	    See
+	    <a href=""
+	       class="emph"
+	       tal:attributes="href string: 
+			      ${view/root_url}/docgrok/grok/admin/docgrok.txt">
+	      docgrok documentation</a> to learn more
+	      about this feature of Grok.
+	  </p>
 	</div>
+
+	<h2>
+	  DocGrok Object Browser
+	</h2>
+
+	<div class="docgrok-annotation1">
+	  <p>
+	    The DocGrok object browser supports discovering of objects
+	    available in the runtime system. You can for example
+	    examine the
+	  </p>
+	  <div class="docgrok-annotation1">
+	    <div>
+	      <a href=""
+		 class="emph"
+		 tal:attributes="href string:${view/root_url}/@@inspect.html">
+	      ZODB root folder</a>
+	    </div>
+	  </div>
+          <p>
+	    See <a href="" class="emph"
+	    tal:attributes="href string:
+	    ${view/root_url}/docgrok/grok/admin/inspect.txt">object
+	    browsers documentation</a> to learn more about this
+	    feature of Grok.
+	  </p>
+	</div>
+
+	<h2>
+	  External Documentation
+	</h2>
+
+	<div class="docgrok-annotation1">
+	  <p>
+	    Grok has the privilege to be supported by a very vivid
+	    community, which is contributing also documentation and
+	    help. To get you started with Grok, it is highly
+	    recommended first to do the <a class="emph"
+	    href="http://grok.zope.org/tutorial.html">Grok
+	    Tutorial</a>.  Afterwards you might find the special <a
+	    class="emph"
+	    href="http://grok.zope.org/minitutorials/index.html">Grok
+	    HOWTOs</a> of value for your work.
+	  </p>
+	  <p>
+	    If you need some more personal advice or want to get
+	    involved into Grok core development, have a look at the <a
+	    class="emph"
+	    href="http://mail.zope.org/mailman/listinfo/grok-dev">Grok-dev
+	    Mailinglist</a>. The Grok's headquarter is <a class="emph"
+	    href="http://grok.zope.org/">Grok's Home Page</a>.
+	  </p>
+	  GROK SAY: Have fun!
+	</div>
       </div>
 
       <div tal:condition="view/getPathParts">

Modified: Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/inspect.pt
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/inspect.pt	2007-08-06 13:09:23 UTC (rev 78627)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/inspect.pt	2007-08-06 15:00:38 UTC (rev 78628)
@@ -1,23 +1,216 @@
 <html metal:use-macro="context/@@macros/gaia-page">
-  <head>
-    <title></title>
-  </head>
-  <body>
-    <div metal:fill-slot="content">
-      <h1>
-	'<span tal:content="view/getId"
-	     tal:on-error="default">Unnamed Object</span>'
-	(object of type 
-	<span tal:repeat="urlpart python: view.getPathParts(view.getDottedPath())">
+  <div metal:fill-slot="content">
+    <h1>
+      <span tal:content="view/info/name" />
+      <span tal:content="structure view/info/obj_link" />
+    </h1>
+
+    <div class="docgrok-entry">
+      <div class="emph" 
+	   tal:content="structure view/info/doc" />
+    </div>
+
+    <div>
+      <form method="post" action="">
+	<p>
+	  <input type="checkbox" name="show_private"
+		 id="show_private"
+		 tal:attributes="checked view/show_private"/>
+	  <label for="show_private">Show private attributes</label>
+	  <input type="submit" value="update" />
+	</p>
+      </form>
+    </div>
+
+    <div class="docgrok-entry">
+      <h2 class="docgrok-description">
+	Parent:
+      </h2>
+      <div class="docgrok-annotation1">
+	<div tal:define="parent view/info/parent/obj"
+	     tal:condition="parent"
+	     i18n:translate="">
 	  <a href=""
-	     tal:attributes="href urlpart/url"
-	     tal:content="urlpart/name">dotted_path_part here...</a>
-	</span>)
-      </h1>
-      <h2>Parent</h2>
-      <div tal:content="view/getParent">
-	My Parent
+	     i18n:name="parent"
+	     tal:attributes="href view/info/parent/obj_link">
+	    <span tal:replace="parent/zope:name"
+		  tal:condition="parent/zope:name" />
+	    <span tal:condition="not: parent/zope:name"
+		  i18n:translate="">&lt;unnamed object&gt;</span></a>
+	  <span tal:content="structure view/info/parent/class_link" />
+	</div>
+
       </div>
+
+      <div class="docgrok-entry"
+	   tal:condition="not: view/info/parent/obj">
+	<div class="docgrok-annotation1">
+	  No parent object
+	</div>
+      </div>
     </div>
-  </body>
-</html>
\ No newline at end of file
+
+    <div class="docgrok-entry">
+      <h2 class="docgrok-description">
+	Base classes:
+      </h2>
+      <div class="docgrok-annotation1"
+	   tal:repeat="klass view/info/bases">
+	<span tal:content="structure klass">Class</span>
+      </div>
+    </div>
+    
+    <div class="docgrok-entry">
+      <h2 class="docgrok-description">
+	Interfaces provided:
+      </h2>
+      <div class="docgrok-annotation1"
+	   tal:repeat="iface view/info/interfaces">
+	<span tal:content="structure iface">Interface</span>
+      </div>
+    </div>
+
+    <div class="docgrok-entry">
+      <h2 class="docgrok-description">
+	Attributes and Properties:
+      </h2>
+      <h3 class="docgrok-description">
+	Attributes
+      </h3>
+      <div class="docgrok-annotation1"
+	   tal:repeat="attr view/info/attributes">
+        <span class="emph">
+	  <span tal:content="attr/name">Name</span>
+	</span>
+	
+	<div class="docgrok-annotation1">
+	  <div>
+	    type: <span tal:content="structure python: attr['type']">
+	    Type</span>
+	  </div>
+	  <div>
+	    value: 
+	    <span tal:condition="attr/value_linkable">
+	      <a href=""
+		 tal:attributes="href attr/docgrok_link"
+		 tal:content="attr/value">Value</a>
+	    </span>
+	    <span tal:condition="not: attr/value_linkable"
+		  tal:content="attr/value">Value</span>
+	  </div>
+	  <div tal:condition="attr/interface">
+	    interface: <span tal:content="structure attr/interface">
+	    Interface
+	  </span>
+	  </div>
+	</div>
+      </div>
+
+
+      <h3 class="docgrok-description">
+	Mappings
+      </h3>
+      <div class="docgrok-annotation1"
+	   tal:repeat="item view/info/mappingitems">
+	<span class="emph"
+	      tal:content="item/key"> 
+	  mapping-key
+	</span>
+	<div class="docgrok-annotation1">
+	  <div>
+	    type: <span tal:content="structure python: item['value_type']">
+	    Type</span>
+	  </div>
+	  <div>
+	    value:
+	    <span tal:condition="item/value_linkable">
+	      <a href=""
+		 tal:attributes="href item/docgrok_link"
+		 tal:content="item/value">Value</a>
+	    </span>
+	    <span tal:condition="not:item/value_linkable"
+		  tal:content="item/value" />
+	  </div>
+	</div>
+      </div>
+
+      <h3 class="docgrok-description">
+	Sequences
+      </h3>
+      <div class="docgrok-annotation1"
+	   tal:repeat="item view/info/sequenceitems">
+	<span class="emph"
+	      tal:content="item/index"> 
+	  sequence-index
+	</span>
+	<div class="docgrok-annotation1">
+	  <div>
+	    type: <span tal:content="structure python: item['value_type']">
+	    Type</span>
+	  </div>
+	  <div>
+	    value:
+	    <span tal:condition="item/value_linkable">
+	      <a href=""
+		 tal:attributes="href item/docgrok_link"
+		 tal:content="item/value">Value</a>
+	    </span>
+	    <span tal:condition="not:item/value_linkable"
+		  tal:content="item/value" />
+	  </div>
+	</div>
+      </div>
+
+      <h3 class="docgrok-description">
+	Annotations
+      </h3>
+      <div class="docgrok-annotation1"
+	   tal:repeat="item view/info/annotations">
+	<span class="emph"
+	      tal:content="item/key">
+	  annotation-key
+	</span>
+	<div class="docgrok-annotation1">
+	  <div>
+	    type: <span tal:content="structure item/value_type">
+	    Type</span>
+	  </div>
+	  <div>
+	    value:
+	    <span tal:condition="item/value_linkable">
+	      <a href=""
+		 tal:attributes="href item/docgrok_link"
+		 tal:content="item/value">value</a>
+	    </span>
+	    <span tal:condition="not: item/value_linkable"
+		  tal:content="item/value" />
+	  </div>
+	</div>
+	<p></p>
+      </div>
+    </div>
+
+    <div class="docgrok-entry">
+      <h2 class="docgrok-description">
+	Methods:
+      </h2>
+      <div class="docgrok-annotation1"
+	   tal:repeat="method view/info/methods">
+	<span tal:content="method/name"
+	      class="emph"
+	      >method</span><span tal:content="method/signature"
+	                          class="emph"
+	      >method</span>
+	<div tal:condition="method/doc"
+	     tal:content="structure method/doc">Method documentation</div>
+	<div class="docgrok-annotation1"
+	     tal:condition="method/interface">
+	  interface: <span tal:content="structure method/interface">interface</span>
+	</div>
+	<p></p>
+      </div>
+    </div>
+
+  </div>
+  <div metal:fill-slot="footer">asda</div>
+</html>

Added: Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/logout.pt
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/logout.pt	                        (rev 0)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/logout.pt	2007-08-06 15:00:38 UTC (rev 78628)
@@ -0,0 +1,8 @@
+<html>
+<head>
+  <title>Logged out</title>
+</head>
+<body>
+  You have been logged out.
+</body>
+</html>
\ No newline at end of file

Modified: Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/macros.pt
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/macros.pt	2007-08-06 13:09:23 UTC (rev 78627)
+++ Sandbox/ulif/grok-adminui/src/grok/admin/view_templates/macros.pt	2007-08-06 15:00:38 UTC (rev 78628)
@@ -1,5 +1,6 @@
 <html xmlns="http://www.w3.org/1999/xhtml"
-  metal:define-macro="gaia-page">
+      i18n:domain="zope"
+      metal:define-macro="gaia-page">
   <head>
     <title
       metal:define-slot="title"
@@ -19,6 +20,18 @@
             tal:attributes="src view/static/grok-admin.jpg" />
         </a>
       </div>
+
+      <div id="logout" metal:define-macro="logged_user">
+	<span tal:condition="view/is_authenticated">
+	  <span i18n:translate="">User:
+	  <span tal:replace="request/principal/title"
+		i18n:name="user_title">User</span>
+	  </span>&nbsp;
+	  [<a href=""
+	  tal:attributes="href string:${view/root_url}/logout">log out</a>]
+	</span>
+      </div>
+
       <div id="breadcrumbs">
         <div id="banner-shadow">
           &nbsp;

Modified: Sandbox/ulif/grok-adminui/src/grok/ftests/admin/apps.py
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/ftests/admin/apps.py	2007-08-06 13:09:23 UTC (rev 78627)
+++ Sandbox/ulif/grok-adminui/src/grok/ftests/admin/apps.py	2007-08-06 15:00:38 UTC (rev 78628)
@@ -3,58 +3,9 @@
   >>> import grok
   >>> grok.grok('grok.ftests.admin.apps')
 
-First setup the pluggable authentication system for session based
-authentication. This is normaly invoked by an event
-handler. Unfortunately the event handler seems not to be called, if
-the ftesting setup is set up. We therefore set up the PAU manually.
-
-  >>> root = getRootFolder()
-  >>> root is not None
-  True
-
-  >>> import grok.admin
-  >>> principal_credentials = grok.admin.getPrincipalCredentialsFromZCML()
-  >>> principal_credentials
-  [{u'login': u'mgr', u'password': u'mgrpw', u'id': u'zope.mgr', u'title': u'Manager'}]
-
-  >>> grok.admin.setupSessionAuthentication(root_folder = root, principal_credentials = principal_credentials)
-
-We should get a login page if trying to get something unauthenticated.
-
   >>> from zope.testbrowser.testing import Browser
   >>> browser = Browser()
-  >>> browser.handleErrors = True
-  >>> browser.open("http://localhost/")
-
-  >>> print browser.contents
-  <html xmlns="http://www.w3.org/1999/xhtml">
-  ... <title>Grok Login</title>
-  ...
-
-Now try to log in using *wrong* credentials
-
-  >>> browser.getControl(name='login').value = 'dumbtry'
-  >>> browser.getControl('Login').click()
-  >>> print browser.contents
-  <html xmlns="http://www.w3.org/1999/xhtml">
-  ... <title>Grok Login</title>
-  ...
-
-Okay, we got the login screen again. What about the correct credentials?
-
-  >>> browser.getControl(name='login').value = 'mgr'
-  >>> browser.getControl(name='password').value = 'mgrpw'
-  >>> browser.getControl('Login').click()
-  >>> print browser.contents
-  <html xmlns="http://www.w3.org/1999/xhtml">
-  ... <title>grok administration interface</title>
-  ...
-
-Fine. Now we are authorized and can do, whatever we want. To stay
-authenticated, we set a header here.
-  
   >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-
   
 We fetch the standard page, which should provide us a menu to get all
 installable grok applications/components.
@@ -66,6 +17,15 @@
   ...      <legend>Add application</legend>
   ...
 
+The opening screen should inform us, that there are no applications
+installed yet:
+
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ...
+  ... <p ...>Currently no applications are installed.</p>
+  ...
+
 We are able to add a mammoth manager...
 
   >>> browser.getControl('Name your new app:',index=13).value = 'my-mammoth-manager'
@@ -93,9 +53,19 @@
   >>> print browser.url
   http://localhost/my-mammoth-manager
 
+We can go to the object browser for every installed application:
+
+  >>> browser.open("http://localhost/applications")
+  >>> browser.getLink('object browser').click()
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ...
+  ...<span ...>...<a href=...>MammothManager</a> object at ...></span>
+  ... 
+
 We are able to delete installed mammoth-managers
 
-  >>> browser.open("http://localhost/")
+  >>> browser.open("http://localhost/applications")
   >>> print browser.contents
   <html xmlns="http://www.w3.org/1999/xhtml">
   ...
@@ -107,6 +77,8 @@
   >>> print browser.contents
   <html xmlns="http://www.w3.org/1999/xhtml">
   ...
+  ... <p ...>Currently no applications are installed.</p>
+  ...
   ...<legend>Add application</legend>
   ...
 

Added: Sandbox/ulif/grok-adminui/src/grok/ftests/admin/loginlogout.py
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/ftests/admin/loginlogout.py	                        (rev 0)
+++ Sandbox/ulif/grok-adminui/src/grok/ftests/admin/loginlogout.py	2007-08-06 15:00:38 UTC (rev 78628)
@@ -0,0 +1,92 @@
+"""
+
+  >>> import grok
+  >>> grok.grok('grok.ftests.admin.loginlogout')
+
+First setup the pluggable authentication system for session based
+authentication. This is normaly invoked by an event
+handler. Unfortunately the event handler seems not to be called, if
+the ftesting setup is set up. We therefore set up the PAU manually.
+
+  >>> root = getRootFolder()
+  >>> root is not None
+  True
+
+  >>> import grok.admin
+  >>> principal_credentials = grok.admin.getPrincipalCredentialsFromZCML()
+  >>> principal_credentials
+  [{u'login': u'mgr', u'password': u'mgrpw', u'id': u'zope.mgr', u'title': u'Manager'}]
+
+  >>> grok.admin.setupSessionAuthentication(root_folder = root, principal_credentials = principal_credentials)
+
+We should get a login page if trying to get something unauthenticated.
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+  >>> browser.handleErrors = True
+  >>> browser.open("http://localhost/")
+
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ... <title>Grok Login</title>
+  ...
+
+Now try to log in using *wrong* credentials
+
+  >>> browser.getControl(name='login').value = 'dumbtry'
+  >>> browser.getControl('Login').click()
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ... <title>Grok Login</title>
+  ...
+
+Okay, we got the login screen again. What about the correct credentials?
+
+  >>> browser.getControl(name='login').value = 'mgr'
+  >>> browser.getControl(name='password').value = 'mgrpw'
+  >>> browser.getControl('Login').click()
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ... <title>grok administration interface</title>
+  ...
+
+The new screen should contain a link for logging out:
+
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ... <span>User:
+  ...Manager
+  ...[<a href="http://localhost/logout">log out</a>]
+  ...
+  
+Fine. Now we are authorized and can do, whatever we want. Let's log out:
+
+  >>> outlink = browser.getLink('log out')
+  >>> outlink
+  <Link text='log out' url='http://localhost/logout'>
+
+  >>> outlink.click()
+  >>> print browser.contents
+  <html>
+  ... You have been logged out.
+  ...
+
+Looks okay. But are we really logged out? Let's try to fetch a page:
+
+  >>> browser.open("http://localhost/")
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ... <title>Grok Login</title>
+  ...
+  ... <td><input id="login" type="text" name="login" /></td>
+  ...
+
+Yes, we are.
+
+  ...
+  ...      <legend>Add application</legend>
+  ...
+
+
+"""
+

Added: Sandbox/ulif/grok-adminui/src/grok/ftests/admin/objectbrowser.py
===================================================================
--- Sandbox/ulif/grok-adminui/src/grok/ftests/admin/objectbrowser.py	                        (rev 0)
+++ Sandbox/ulif/grok-adminui/src/grok/ftests/admin/objectbrowser.py	2007-08-06 15:00:38 UTC (rev 78628)
@@ -0,0 +1,137 @@
+"""
+
+  >>> import grok
+  >>> grok.grok('grok.ftests.admin.objectbrowser')
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+  >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+  
+We fetch the documentation page, which should give us a tiny overview
+over documentation:
+
+  >>> browser.open("http://localhost/docgrok")
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ...
+  ... Welcome to DocGrok...
+  ...
+
+On the documentation page there should be a link to the ZODB root
+folder:
+
+  >>> root_link = browser.getLink('ZODB root folder')
+  >>> root_link
+  <Link text='ZODB root folder' url='http://localhost/@@inspect.html'>
+
+The root folder got no name:
+
+  >>> root_link.click()
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ...
+  ... <span>&lt;unnamed object&gt;</span>
+  ...
+
+and is of type Folder.
+
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ...
+  ... <span ...>...<a ...>Folder</a> object at ...&gt;</span>
+  ...
+
+It's class documentation should be linked in the head of page:
+
+  >>> browser.getLink('Folder').url
+  'http://localhost/docgrok/zope/app/folder/folder/Folder/'
+
+We also get the docstring of the root folder:
+
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ...
+  ...<p>The standard Zope Folder implementation.</p>
+  ...
+  
+A checkbox gives us control over private members and attributes of the
+object displayed:
+
+  >>> checkbox = browser.getControl('Show private attributes')
+  >>> checkbox
+  <ItemControl name='show_private' type='checkbox' optionValue='on' selected=False>
+
+By default the checkbox is not selected. Therefore we check for an
+arbitrary private method to be displayed or not. For example the
+``__dict__`` method. By default no __dict__ method will be displayed:
+
+  >>> '__dict__' in browser.contents
+  False
+
+Now let's tick the checkbox and update the view:
+
+  >>> checkbox.selected = True
+  >>> checkbox.selected
+  True
+
+  >>> browser.getControl('update').click()
+
+Now the private method should be displayed:
+
+  >>> '__dict__' in browser.contents
+  True
+
+Here we go :-)
+
+Okay, now let's examine the displayed data a bit. We are currently the
+object browser's view for the root folder. The root folder got no
+parent, which should be displayed:
+
+  >>> 'No parent object' in browser.contents
+  True
+
+One of the base classes of the root folder is the class
+``persistent.Persistent``. We not only want that displayed but also a
+link to the class documentation of that class:
+
+  >>> link = browser.getLink("Persistent'")
+  >>> link.url
+  'http://localhost/docgrok/persistent/Persistent'
+
+The same for interfaces. The root folder implements
+``persistent.interfaces.IPersistent``:w
+
+  >>> link = browser.getLink('IPersistent')
+  >>> link.url
+  'http://localhost/docgrok/persistent/interfaces/IPersistent/'
+
+Now the attributes and properties. The root folder got an attribute
+``data``:
+
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ...
+  ...<h3 ...>
+  ...Attributes
+  ...</h3>
+  ... <span>data</span>
+  ... <div>
+  ...  value:
+  ...   <a href="http://localhost/docgrok-obj/data/@@inspect.html">&lt;BTrees.OOBTree.OOBTree object at ...&gt;</a>
+  ... </div>
+  ...
+  
+
+
+"""
+
+import grok
+
+class MammothManager(grok.Application, grok.Container):
+    """A mammoth manager"""
+    pass
+
+class Index(grok.View):#
+
+    def render(self):
+        return u"Let's manage some mammoths!"



More information about the Checkins mailing list