[Zope3-checkins] SVN: Zope3/trunk/ Implemented the Introspector NG
proposal. I think it came out okay, but it
Stephan Richter
srichter at cosmos.phy.tufts.edu
Sat Oct 15 19:27:58 EDT 2005
Log message for revision 39468:
Implemented the Introspector NG proposal. I think it came out okay, but it
could use some more love, especially with regard to the UI. The Rotterdam
skin is just butt ugly. On the technical side, the browsing of annotation
values could be improved. But overall, I think this improves the
documentation situation a lot.
Changed:
U Zope3/trunk/doc/CHANGES.txt
U Zope3/trunk/src/zope/app/apidoc/codemodule/browser/README.txt
U Zope3/trunk/src/zope/app/apidoc/codemodule/browser/configure.zcml
U Zope3/trunk/src/zope/app/apidoc/codemodule/browser/ftests.py
U Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.pt
U Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.py
U Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.txt
A Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.zcml
U Zope3/trunk/src/zope/app/apidoc/codemodule/browser/tests.py
U Zope3/trunk/src/zope/app/file/browser/file_add.pt
-=-
Modified: Zope3/trunk/doc/CHANGES.txt
===================================================================
--- Zope3/trunk/doc/CHANGES.txt 2005-10-15 17:45:11 UTC (rev 39467)
+++ Zope3/trunk/doc/CHANGES.txt 2005-10-15 23:27:58 UTC (rev 39468)
@@ -10,6 +10,12 @@
New features
+ - Implemented a new object introspector. Instead of just providing
+ information of the object's class, the new introspector focuses on
+ providing information that is specific to the instance, such as
+ directly provided interfaces and data, for example attribute values
+ and annotation values.
+
- Implemented the `devmode` switch for `zope.conf`. When turned on a
ZCML feature called `devmode` is provided. Packages can then register
functionality based on this feature. In Zope 3 itself, the devmode is
Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/README.txt 2005-10-15 17:45:11 UTC (rev 39467)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/README.txt 2005-10-15 23:27:58 UTC (rev 39468)
@@ -351,7 +351,7 @@
>>> details.context = details.context.subs[0]
>>> pprint(details.attributes())
[{'name': u'class',
- 'url':
+ 'url':
'http://127.0.0.1/zope/app/apidoc/codemodule/module/Module/index.html',
'value': u'.module.Module',
'values': []}]
@@ -362,7 +362,7 @@
Returns `True`, if the directive has subdirectives; otherwise `False` is
returned.
- >>> details.hasSubDirectives()
+ >>> details.hasSubDirectives()
True
`getElements()`
@@ -372,42 +372,230 @@
>>> details.getElements()
[<Directive (u'http://namespaces.zope.org/zope', u'allow')>]
-
-The Introspector View
----------------------
-In order to allow the quick lookup of documentation from the content
-components themselves, a special "Introspector" tab is added for all content
-types. When clicked, it will forward you to the appropriate code browser
-documentation screen.
+The Introspector
+----------------
-So for a given content type:
+There are several tools that are used to support the introspector.
- >>> class Content(object):
- ... pass
+ >>> from zope.app.apidoc.codemodule.browser import introspector
- >>> Content.__module__ = 'module.name.here'
+`getTypeLink(type)`
+~~~~~~~~~~~~~~~~~~
-we can generate the introspector redirector like this:
+This little helper function returns the path to the type class:
- >>> from zope.app.apidoc.codemodule.browser import introspector
+ >>> from zope.app.apidoc.apidoc import APIDocumentation
+ >>> introspector.getTypeLink(APIDocumentation)
+ 'zope/app/apidoc/apidoc/APIDocumentation'
+
+ >>> introspector.getTypeLink(dict)
+ '__builtin__/dict'
+
+ >>> introspector.getTypeLink(type(None)) is None
+ True
+
+`++annootations++` Namespace
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This namespace is used to traverse into the annotations of an object.
+
+ >>> import zope.interface
+ >>> from zope.app.annotation.interfaces import IAttributeAnnotatable
+
+ >>> class Sample(object):
+ ... zope.interface.implements(IAttributeAnnotatable)
+
+ >>> sample = Sample()
+ >>> sample.__annotations__ = {'zope.my.namespace': 'Hello there!'}
+
+ >>> ns = introspector.annotationsNamespace(sample)
+ >>> ns.traverse('zope.my.namespace', None)
+ 'Hello there!'
+
+ >>> ns.traverse('zope.my.unknown', None)
+ Traceback (most recent call last):
+ ...
+ KeyError: 'zope.my.unknown'
+
+Mapping `++items++` namespace
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This namespace allows us to traverse the items of any mapping:
+
+ >>> ns = introspector.mappingItemsNamespace({'mykey': 'myvalue'})
+ >>> ns.traverse('mykey', None)
+ 'myvalue'
+
+ >>> ns.traverse('unknown', None)
+ Traceback (most recent call last):
+ ...
+ KeyError: 'unknown'
+
+
+Sequence `++items++` namespace
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This namespace allows us to traverse the items of any sequence:
+
+ >>> ns = introspector.sequenceItemsNamespace(['value1', 'value2'])
+ >>> ns.traverse('0', None)
+ 'value1'
+
+ >>> ns.traverse('2', None)
+ Traceback (most recent call last):
+ ...
+ IndexError: list index out of range
+
+ >>> ns.traverse('text', None)
+ Traceback (most recent call last):
+ ...
+ ValueError: invalid literal for int(): text
+
+Introspector View
+~~~~~~~~~~~~~~~~~
+
+The main contents of the introspector view comes from the introspector view
+class. In the following section we are going to demonstrate the methods used
+to collect the data. First we need to create an object though; let's use a
+root folder:
+
+ >>> rootFolder
+ <zope.app.folder.folder.Folder object at ...>
+
+Now we instantiate the view
+
+ >>> from zope.publisher.browser import TestRequest
>>> request = TestRequest()
- >>> view = introspector.Introspector(Content(), request)
- >>> view.class_name()
- 'module.name.here.Content'
- >>> view.class_url()
- 'http://127.0.0.1/++apidoc++/Code/module/name/here/Content/index.html'
- >>> view.direct_interfaces()
- []
+ >>> inspect = introspector.Introspector(rootFolder, request)
-If the instance directly provides any interfaces, these are reported
-as well:
+so that we can start looking at the methods. First we should note that the
+class documentation view is directly available:
- >>> import zope.interface
- >>> zope.interface.directlyProvides(view.context,
- ... IDocumentationModule)
- >>> pprint(view.direct_interfaces())
- [{'module': 'zope.app.apidoc.interfaces',
- 'name': 'IDocumentationModule',
- 'url': 'http://127.0.0.1/++apidoc++/Interface/zope.app.apidoc.interfaces.IDocumentationModule/apiindex.html'}]
+ >>> inspect.klassView
+ <zope.app.apidoc.codemodule.browser.tests.Details object at ...>
+ >>> inspect.klassView.context
+ <zope.app.apidoc.codemodule.class_.Class object at ...>
+
+You can get the parent of the inspected object, which is ``None`` for the root
+folder:
+
+ >>> inspect.parent() is None
+ True
+
+You can also get the base URL of the request:
+
+ >>> inspect.getBaseURL()
+ 'http://127.0.0.1'
+
+Next you can get a list of all directly provided interfaces:
+
+ >>> ifaces = inspect.getDirectlyProvidedInterfaces()
+ >>> ifaces.sort()
+ >>> ifaces
+ ['zope.app.component.interfaces.ISite',
+ 'zope.app.folder.interfaces.IRootFolder']
+
+The ``getProvidedInterfaces()`` and ``getBases()`` method simply forwards its
+request to the class documentation view. Thus the next method is
+``getAttributes()``, which collects all sorts of useful information about the
+object's attributes:
+
+ >>> pprint(list(inspect.getAttributes()))
+ [{'interface': None,
+ 'name': 'data',
+ 'read_perm': None,
+ 'type': 'OOBTree',
+ 'type_link': 'BTrees/_OOBTree/OOBTree',
+ 'value': '<BTrees._OOBTree.OOBTree object at ...>',
+ 'value_linkable': True,
+ 'write_perm': None}]
+
+Of course, the methods are listed as well:
+
+ >>> pprint(list(inspect.getMethods()))
+ [...
+ {'doc': u'',
+ 'interface': 'zope.app.component.interfaces.IPossibleSite',
+ 'name': 'getSiteManager',
+ 'read_perm': None,
+ 'signature': '()',
+ 'write_perm': None},
+ ...
+ {'doc': u'<dl class="docutils">\n<dt>Return a sequence-like...',
+ 'interface': 'zope.interface.common.mapping.IEnumerableMapping',
+ 'name': 'keys',
+ 'read_perm': None,
+ 'signature': '()',
+ 'write_perm': None},
+ {'doc': u'',
+ 'interface': 'zope.app.component.interfaces.IPossibleSite',
+ 'name': 'setSiteManager',
+ 'read_perm': None,
+ 'signature': '(sm)',
+ 'write_perm': None},
+ ...]
+
+The final methods deal with inspecting the objects data further. For exmaple,
+if we inspect a sequence,
+
+ >>> from persistent.list import PersistentList
+ >>> list = PersistentList(['one', 'two'])
+
+ >>> from zope.interface.common.sequence import IExtendedReadSequence
+ >>> zope.interface.directlyProvides(list, IExtendedReadSequence)
+
+ >>> inspect2 = introspector.Introspector(list, request)
+
+we can first determine whether it really is a sequence
+
+ >>> inspect2.isSequence()
+ True
+
+and then get the sequence items:
+
+ >>> pprint(inspect2.getSequenceItems())
+ [{'index': 0,
+ 'value': "'one'",
+ 'value_type': 'str',
+ 'value_type_link': '__builtin__/str'},
+ {'index': 1,
+ 'value': "'two'",
+ 'value_type': 'str',
+ 'value_type_link': '__builtin__/str'}]
+
+Similar functionality exists for a mapping. But we first have to add an item:
+
+ >>> rootFolder['list'] = list
+
+Now let's have a look:
+
+ >>> inspect.isMapping()
+ True
+
+ >>> pprint(inspect.getMappingItems())
+ [{'key': u'list',
+ 'key_string': "u'list'",
+ 'value': "['one', 'two']",
+ 'value_type': 'ContainedProxy',
+ 'value_type_link': 'zope/app/container/contained/ContainedProxy'}]
+
+The final two methods doeal with the introspection of the annotations. If an
+object is annotatable,
+
+ >>> inspect.isAnnotatable()
+ True
+
+then we can get an annotation mapping:
+
+ >>> rootFolder.__annotations__ = {'my.list': list}
+
+ >>> pprint(inspect.getAnnotationsInfo())
+ [{'key': 'my.list',
+ 'key_string': "'my.list'",
+ 'value': "['one', 'two']",
+ 'value_type': 'PersistentList',
+ 'value_type_link': 'persistent/list/PersistentList'}]
+
+And that's it. Fur some browser-based demonstration see ``introspector.txt``.
\ No newline at end of file
Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/configure.zcml 2005-10-15 17:45:11 UTC (rev 39467)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/configure.zcml 2005-10-15 23:27:58 UTC (rev 39468)
@@ -1,6 +1,5 @@
<configure
xmlns="http://namespaces.zope.org/browser"
- xmlns:zope="http://namespaces.zope.org/zope"
i18n_domain="zope">
<page
@@ -59,14 +58,6 @@
class=".zcml.DirectiveDetails"
permission="zope.ManageContent"/>
- <!-- Introspector -->
- <page
- name="introspector.html"
- for="*"
- class=".introspector.Introspector"
- permission="zope.ManageContent"
- menu="zmi_views" title="Introspector"
- template="introspector.pt"
- />
+ <include file="introspector.zcml" />
</configure>
Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/ftests.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/ftests.py 2005-10-15 17:45:11 UTC (rev 39467)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/ftests.py 2005-10-15 23:27:58 UTC (rev 39468)
@@ -17,6 +17,7 @@
"""
import unittest
+from zope.testing import doctest
from zope.app.testing.functional import BrowserTestCase
from zope.app.testing.functional import FunctionalDocFileSuite
@@ -98,7 +99,9 @@
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(CodeModuleTests),
- FunctionalDocFileSuite("introspector.txt"),
+ FunctionalDocFileSuite(
+ "introspector.txt",
+ optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE),
))
if __name__ == '__main__':
Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.pt 2005-10-15 17:45:11 UTC (rev 39467)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.pt 2005-10-15 23:27:58 UTC (rev 39468)
@@ -1,31 +1,318 @@
-<html metal:use-macro="views/standard_macros/view"
- i18n:domain="zope">
+<html metal:use-macro="context/@@standard_macros/view">
+<head>
+ <metal:block fill-slot="extrahead">
+ <link type="text/css" rel="stylesheet" href="introspector.css"
+ tal:attributes="href context/++resource++apidoc.css" />
+ </metal:block>
+</head>
<body>
<div metal:fill-slot="body">
+ <h1 i18n:translate="">
+ Object Introspector:
+ <a href=""
+ tal:attributes="href view/klassView/@@absolute_url"
+ tal:content="view/klassView/context/getPath"
+ i18n:name="class-name" />
+ (
+ <span tal:replace="context/zope:name"
+ tal:condition="context/zope:name"
+ i18n:name="object-name"/>
+ <i tal:condition="not: context/zope:name"
+ i18n:name="object-name"><no name></i>
+ )
+ </h1>
+ <em tal:define="parent view/parent"
+ tal:condition="parent">
+ Parent:
+ <a href=""
+ tal:attributes="
+ href string: ${parent/@@absolute_url}/@@introspector.html">
+ <span tal:replace="parent/zope:name"
+ tal:condition="parent/zope:name" />
+ <span tal:condition="not: parent/zope:name"><No Name></span>
+ </a>
+ </em>
-<div class="row">
- <div class="label" i18n:translate="">Class</div>
- <div class="field">
- <a tal:attributes="href view/class_url"
- tal:content="view/class_name"/>
+
+ <h2 i18n:translate="">Directly Provided Interfaces</h2>
+
+ <div class="indent"
+ tal:define="ifaces view/getDirectlyProvidedInterfaces">
+
+ <ul class="attr-list" tal:condition="ifaces">
+ <li tal:repeat="iface ifaces">
+ <a href=""
+ tal:attributes="href
+ string:${view/getBaseURL}/Interface/$iface/apiindex.html"
+ tal:content="iface" />
+ </li>
+ </ul>
+
+ <div tal:condition="not:ifaces" i18n:translate="">
+ No interfaces are directly provided.
+ </div>
+
</div>
-</div>
-<div class="row"
- tal:define="interfaces view/direct_interfaces"
- tal:condition="interfaces">
- <div class="label" i18n:translate="">Directly provided interfaces in the provided order</div>
- <div class="field">
- <ul>
- <li tal:repeat="iface interfaces"><a tal:attributes="href iface/url"
- ><tal:span replace="iface/module"
- />.<tal:span replace="iface/name"/></a></li>
+ <h2 i18n:translate="">Provided Interfaces</h2>
+
+ <div class="indent"
+ tal:define="ifaces view/getProvidedInterfaces">
+
+ <ul class="attr-list" tal:condition="ifaces">
+ <li tal:repeat="iface ifaces">
+ <a href=""
+ tal:attributes="href
+ string:${view/getBaseURL}/Interface/$iface/apiindex.html"
+ tal:content="iface" />
+ </li>
</ul>
+
</div>
-</div>
-<div class="row"></div>
+ <h2 i18n:translate="">Bases</h2>
+ <div class="indent"
+ tal:define="bases view/getBases">
+
+ <ul class="attr-list" tal:condition="bases">
+ <li tal:repeat="base bases">
+ <a href=""
+ tal:attributes="href string:${base/url}/index.html"
+ tal:content="base/path"
+ tal:condition="base/url" />
+ <div tal:condition="not: base/url">
+ <span tal:replace="base/path" />
+ <span i18n:translate="">(C-based class)</span>
+ </div>
+ </li>
+ </ul>
+
+ <p tal:condition="not: bases">
+ <em i18n:translate="">There are no base classes.</em>
+ </p>
+
+ </div>
+
+ <h2 i18n:translate="">Attributes/Properties</h2>
+
+ <div class="indent"
+ tal:define="attributes view/getAttributes">
+
+ <ul class="attr-list" tal:condition="attributes">
+
+ <li tal:repeat="attr attributes">
+ <b><code tal:content="attr/name">attr</code></b>
+ <tal:omit-tag condition="not: attr/type_link">
+ (type: <code tal:content="attr/type" />)
+ </tal:omit-tag>
+ <tal:omit-tag condition="attr/type_link">
+ (<span i18n:translate="">type:</span>
+ <a href=""
+ tal:attributes="href
+ string:${view/getBaseURL}/Code/${attr/type_link}/index.html">
+ <code tal:content="attr/type" /></a>)
+ </tal:omit-tag>
+ <br/>
+ <i i18n:translate="">Value:</i>
+ <a href=""
+ tal:attributes="href
+string:${context/@@absolute_url}/++attribute++${attr/name}/@@introspector.html"
+ tal:condition="attr/value_linkable">
+ <code tal:content="attr/value">u''</code>
+ </a>
+ <code tal:condition="not: attr/value_linkable"
+ tal:content="attr/value">u''</code>
+ <br />
+
+ <span class="small" tal:condition="attr/interface">
+ <i i18n:translate="">Interface:</i>
+ <a href=""
+ tal:attributes="href
+ string:${view/getBaseURL}/Interface/${attr/interface}/apiindex.html"
+ tal:content="attr/interface">Iface</a><br />
+ </span>
+ <span class="small"
+ tal:condition="python: attr['read_perm'] and attr['write_perm']">
+ <i i18n:translate="">Permissions:</i>
+ <span tal:replace="attr/read_perm" i18n:translate="">zope.View</span>
+ <span i18n:translate="">(read)</span>,
+ <span tal:replace="attr/write_perm" i18n:translate="">zope.View</span>
+ <span i18n:translate="">(write)</span>
+ </span>
+ </li>
+
+ </ul>
+
+ <p tal:condition="not: attributes">
+ <em i18n:translate="">There are no attributes in this class.</em>
+ </p>
+
+ </div>
+
+ <h2 i18n:translate="">Methods</h2>
+
+ <div class="indent"
+ tal:define="methods view/getMethods">
+
+ <ul class="attr-list" tal:condition="methods">
+
+ <li tal:repeat="method view/getMethods">
+ <b><code
+ tal:content="string:${method/name}${method/signature}" />
+ </b><br>
+ <div class="inline documentation" tal:content="structure method/doc">
+ method desc
+ </div>
+
+ <span class="small" tal:condition="method/interface">
+ <i i18n:translate="">Interface:</i>
+ <a href=""
+ tal:attributes="href
+ string:${view/getBaseURL}/Interface/${method/interface}/apiindex.html"
+ tal:content="method/interface">Iface</a><br/>
+ </span>
+
+ <span class="small"
+ tal:condition="python: method['read_perm'] and method['write_perm']">
+ <i i18n:translate="">Permissions:</i>
+ <span tal:replace="method/read_perm" i18n:translate="">zope.View</span>
+ <span i18n:translate="">(read)</span>,
+ <span tal:replace="method/write_perm" i18n:translate="">zope.View</span>
+ <span i18n:translate="">(write)</span>
+ </span>
+ </li>
+
+ </ul>
+
+ <p tal:condition="not: methods">
+ <em i18n:translate="">There are no methods in this class.</em>
+ </p>
+
+ </div>
+
+
+ <div tal:condition="view/isMapping">
+ <h2 i18n:translate="">Mapping Items</h2>
+
+ <div class="indent"
+ tal:define="items view/getMappingItems">
+
+ <p tal:condition="not:items">
+ <em i18n:translate="">
+ There are no items.
+ </em>
+ </p>
+
+ <ul class="attr-list"
+ tal:condition="items">
+ <li tal:repeat="item items">
+
+ <b>
+ <code tal:content="item/key_string">'key'</code>
+ </b>
+ <br />
+ <a href=""
+ tal:attributes="
+ href string:++items++${item/key}/@@introspector.html">
+ <code tal:content="item/value">'value'</code>
+ </a>
+ <tal:omit-tag condition="not: item/value_type_link">
+ (type: <code tal:content="item/value_type" />)
+ </tal:omit-tag>
+ <tal:omit-tag condition="item/value_type_link">
+ (<span i18n:translate="">type:</span>
+ <a href=""
+ tal:attributes="href
+ string:${view/getBaseURL}/Code/${item/value_type_link}/index.html">
+ <code tal:content="item/value_type" /></a>)
+ </tal:omit-tag>
+
+ </li>
+ </ul>
+ </div>
+ </div>
+
+
+ <div tal:condition="view/isSequence">
+ <h2 i18n:translate="">Sequence Items</h2>
+
+ <div class="indent"
+ tal:define="items view/getSequenceItems">
+
+ <p tal:condition="not:items">
+ <em i18n:translate="">
+ There are no items.
+ </em>
+ </p>
+
+ <ol class="attr-list" start="0"
+ tal:condition="items">
+ <li tal:repeat="item items">
+
+ <a href=""
+ tal:attributes="
+ href string:++items++${item/index}/@@introspector.html">
+ <code tal:content="item/value">'value'</code>
+ </a>
+ <tal:omit-tag condition="not: item/value_type_link">
+ (type: <code tal:content="item/value_type" />)
+ </tal:omit-tag>
+ <tal:omit-tag condition="item/value_type_link">
+ (<span i18n:translate="">type:</span>
+ <a href=""
+ tal:attributes="href
+ string:${view/getBaseURL}/Code/${item/value_type_link}/index.html">
+ <code tal:content="item/value_type" /></a>)
+ </tal:omit-tag>
+
+ </li>
+ </ol>
+ </div>
+ </div>
+
+
+ <div tal:condition="view/isAnnotatable">
+ <h2 i18n:translate="">Annotations</h2>
+
+ <div class="indent"
+ tal:define="annotations view/getAnnotationsInfo">
+
+ <p tal:condition="not:annotations">
+ <em i18n:translate="">
+ There were no annotations or they were not inspectable.
+ </em>
+ </p>
+
+ <ul class="attr-list"
+ tal:condition="annotations">
+ <li tal:repeat="ann annotations">
+
+ <b>
+ <code tal:content="ann/key_string">'key'</code>
+ </b>
+ <br />
+ <a href=""
+ tal:attributes="
+ href string:++annotations++${ann/key}/@@introspector.html">
+ <code tal:content="ann/value">'value'</code>
+ </a>
+ <tal:omit-tag condition="not: ann/value_type_link">
+ (type: <code tal:content="ann/value_type" />)
+ </tal:omit-tag>
+ <tal:omit-tag condition="ann/value_type_link">
+ (<span i18n:translate="">type:</span>
+ <a href=""
+ tal:attributes="href
+ string:${view/getBaseURL}/Code/${ann/value_type_link}/index.html">
+ <code tal:content="ann/value_type" /></a>)
+ </tal:omit-tag>
+
+ </li>
+ </ul>
+ </div>
+ </div>
+
</div>
</body>
</html>
Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.py 2005-10-15 17:45:11 UTC (rev 39467)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.py 2005-10-15 23:27:58 UTC (rev 39468)
@@ -17,37 +17,221 @@
"""
__docformat__ = 'restructuredtext'
+import inspect
+import types
+
import zope.interface
-
-from zope.security.proxy import removeSecurityProxy
-from zope.app.container.contained import getProxiedObject
+import zope.security.proxy
+from zope.interface import directlyProvidedBy
+from zope.app import zapi, apidoc, annotation
+from zope.app.location import location
from zope.app.publisher.browser import BrowserView
+from zope.app.traversing.interfaces import IPhysicallyLocatable
+from zope.app.traversing.interfaces import IContainmentRoot
+def getTypeLink(type):
+ if type is types.NoneType:
+ return None
+ path = apidoc.utilities.getPythonPath(type)
+ return path.replace('.', '/')
+
+
+class annotationsNamespace(object):
+ """Used to traverse to the annotations of an object."""
+
+ def __init__(self, ob, request=None):
+ self.context = ob
+
+ def traverse(self, name, ignore):
+ # This is pretty unsafe, so this should really just be available in
+ # devmode.
+ naked = zope.security.proxy.removeSecurityProxy(self.context)
+ annotations = annotation.interfaces.IAnnotations(naked)
+ obj = name and annotations[name] or annotations
+ if not IPhysicallyLocatable(obj, False):
+ obj = location.LocationProxy(
+ obj, self.context, '++annotations++'+name)
+ return obj
+
+
+class sequenceItemsNamespace(object):
+ """Used to traverse to the values of a sequence."""
+
+ def __init__(self, ob, request=None):
+ self.context = ob
+
+ def traverse(self, name, ignore):
+ obj = self.context[int(name)]
+ if not IPhysicallyLocatable(obj, False):
+ obj = location.LocationProxy(obj, self.context, '++items++'+name)
+ return obj
+
+
+class mappingItemsNamespace(object):
+ """Used to traverse to the values of a mapping.
+
+ Important: This might seem like overkill, but we do not know that (1)
+ every mapping has a traverser and (2) whether the location is available. A
+ location might not be available, if we have a mapping in the annotations,
+ for example.
+ """
+
+ def __init__(self, ob, request=None):
+ self.context = ob
+
+ def traverse(self, name, ignore):
+ obj = self.context[name]
+ if not IPhysicallyLocatable(obj, False):
+ obj = location.LocationProxy(obj, self.context, '++items++'+name)
+ return obj
+
+
+# Small hack to simulate a traversla root.
+class TraversalRoot(object):
+ zope.interface.implements(IContainmentRoot)
+
+
class Introspector(BrowserView):
- def get_object(self):
- return getProxiedObject(removeSecurityProxy(self.context))
+ def __init__(self, context, request):
+ super(Introspector, self).__init__(context, request)
+ path = apidoc.utilities.getPythonPath(
+ context.__class__).replace('.', '/')
- def class_name(self):
- klass = type(self.get_object())
- return "%s.%s" % (klass.__module__, klass.__name__)
+ self.klassView = zapi.traverse(
+ TraversalRoot(),
+ '/++apidoc++/Code/%s/@@index.html' %path, request=request)
- def class_url(self):
- klass = type(self.get_object())
- url = self.request.getApplicationURL() + '/++apidoc++/Code/'
- url += klass.__module__.replace('.', '/') + '/'
- return url + klass.__name__ + '/index.html'
+ def parent(self):
+ return zapi.getParent(self.context)
- def direct_interfaces(self):
- ifaces = zope.interface.directlyProvidedBy(self.get_object())
- result = []
- urlbase = self.request.getApplicationURL() + '/++apidoc++/Interface/'
- for iface in ifaces:
- url = "%s%s.%s/apiindex.html" % (
- urlbase, iface.__module__, iface.__name__)
- result.append(("%s.%s" % (iface.__module__, iface.__name__),
- {"name": iface.__name__,
- "module": iface.__module__,
- "url": url}))
- return [dict for name, dict in result]
+ def getBaseURL(self):
+ return self.klassView.getBaseURL()
+
+ def getDirectlyProvidedInterfaces(self):
+ # Getting the directly provided interfaces works only on naked objects
+ obj = zope.security.proxy.removeSecurityProxy(self.context)
+ return [apidoc.utilities.getPythonPath(iface)
+ for iface in zope.interface.directlyProvidedBy(obj)]
+
+ def getProvidedInterfaces(self):
+ return self.klassView.getInterfaces()
+
+ def getBases(self):
+ return self.klassView.getBases()
+
+ def getAttributes(self):
+ # remove the security proxy, so that `attr` is not proxied. We could
+ # unproxy `attr` for each turn, but that would be less efficient.
+ #
+ # `getPermissionIds()` also expects the class's security checker not
+ # to be proxied.
+ klass = zope.security.proxy.removeSecurityProxy(self.klassView.context)
+ obj = zope.security.proxy.removeSecurityProxy(self.context)
+
+ for name in apidoc.utilities.getPublicAttributes(obj):
+ value = getattr(obj, name)
+ if inspect.ismethod(value) or inspect.ismethoddescriptor(value):
+ continue
+ entry = {
+ 'name': name,
+ 'value': `value`,
+ 'value_linkable': IPhysicallyLocatable(value, False) and True,
+ 'type': type(value).__name__,
+ 'type_link': getTypeLink(type(value)),
+ 'interface': apidoc.utilities.getInterfaceForAttribute(
+ name, klass._Class__all_ifaces)
+ }
+ entry.update(apidoc.utilities.getPermissionIds(
+ name, klass.getSecurityChecker()))
+ yield entry
+
+ def getMethods(self):
+ # remove the security proxy, so that `attr` is not proxied. We could
+ # unproxy `attr` for each turn, but that would be less efficient.
+ #
+ # `getPermissionIds()` also expects the class's security checker not
+ # to be proxied.
+ klass = zope.security.proxy.removeSecurityProxy(self.klassView.context)
+ obj = zope.security.proxy.removeSecurityProxy(self.context)
+
+ for name in apidoc.utilities.getPublicAttributes(obj):
+ val = getattr(obj, name)
+ if not (inspect.ismethod(val) or inspect.ismethoddescriptor(val)):
+ continue
+ if inspect.ismethod(val):
+ signature = apidoc.utilities.getFunctionSignature(val)
+ else:
+ signature = '(...)'
+
+ entry = {
+ 'name': name,
+ 'signature': signature,
+ 'doc': apidoc.utilities.renderText(
+ val.__doc__ or '',
+ zapi.getParent(self.klassView.context).getPath()),
+ 'interface': apidoc.utilities.getInterfaceForAttribute(
+ name, klass._Class__all_ifaces)}
+
+ entry.update(apidoc.utilities.getPermissionIds(
+ name, klass.getSecurityChecker()))
+
+ yield entry
+
+ def isSequence(self):
+ return zope.interface.common.sequence.IExtendedReadSequence.providedBy(
+ self.context)
+
+ def getSequenceItems(self):
+ ann = []
+ # Make the object naked, so that we can inspect the value types.
+ naked = zope.security.proxy.removeSecurityProxy(self.context)
+ for index in xrange(0, len(self.context)):
+ value = naked[index]
+ ann.append({
+ 'index': index,
+ 'value': `value`,
+ 'value_type': type(value).__name__,
+ 'value_type_link': getTypeLink(type(value))
+ })
+ return ann
+
+ def isMapping(self):
+ return zope.interface.common.mapping.IEnumerableMapping.providedBy(
+ self.context)
+
+ def getMappingItems(self):
+ ann = []
+ # Make the object naked, so that we can inspect the value types.
+ naked = zope.security.proxy.removeSecurityProxy(self.context)
+ for key, value in naked.items():
+ ann.append({
+ 'key': key,
+ 'key_string': `key`,
+ 'value': `value`,
+ 'value_type': type(value).__name__,
+ 'value_type_link': getTypeLink(type(value))
+ })
+ return ann
+
+ def isAnnotatable(self):
+ return annotation.interfaces.IAnnotatable.providedBy(self.context)
+
+ def getAnnotationsInfo(self):
+ # We purposefully strip the security here; this is the introspector,
+ # so we want to see things that we usually cannot see
+ naked = zope.security.proxy.removeSecurityProxy(self.context)
+ annotations = annotation.interfaces.IAnnotations(naked)
+ if not hasattr(annotations, 'items'):
+ return
+ ann = []
+ for key, value in annotations.items():
+ ann.append({
+ 'key': key,
+ 'key_string': `key`,
+ 'value': `value`,
+ 'value_type': type(value).__name__,
+ 'value_type_link': getTypeLink(type(value))
+ })
+ return ann
Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.txt 2005-10-15 17:45:11 UTC (rev 39467)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.txt 2005-10-15 23:27:58 UTC (rev 39468)
@@ -2,86 +2,221 @@
Object Introspector View
========================
-The "Introspector" view provides access to information about the class
-that implements an object and the interfaces that the object provides
-directly.
+The "Introspector" view provides access to information about the current
+obejct, the context of the introspector view. When in `devmode`, the
+introspector is simply available as follows:
-::
+ >>> from zope.testbrowser import Browser
+ >>> browser = Browser()
+ >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+ >>> browser.handleErrors = False
- >>> response = str(http("""
- ... GET /@@introspector.html HTTP/1.1
- ... Authorization: Basic mgr:mgrpw
- ... """))
+ >>> browser.open('http://localhost/manage')
+ >>> browser.getLink('Introspector').click()
- >>> print response
- HTTP/1.1 200 Ok
- ...
+The page starts with telling you the class/type
-The class information is provided as a link from the class name to the
-introspection page for the class::
+ >>> browser.getLink('zope.app.folder.folder.Folder').url
+ 'http://localhost/++apidoc++/Code/zope/app/folder/folder/Folder/index.html'
- >>> ("http://localhost/++apidoc++/Code/"
- ... "zope/app/folder/folder/Folder/index.html") in response
- True
- >>> ">zope.app.folder.folder.Folder</" in response
- True
+and the name of the object:
-Information about the directly provided interfaces is provided by
-links from the provided interface names to the introspection pages for
-the interfaces::
+ >>> '<no name>' in browser.contents
+ True
- >>> ("http://localhost/++apidoc++/Interface/"
- ... "zope.app.component.interfaces.ISite/apiindex.html") in response
- True
- >>> ">zope.app.component.interfaces.ISite</" in response
- True
+Of course, the root folder does not have a name. As you can see the type links
+directly to the API documentation of the class.
- >>> ("http://localhost/++apidoc++/Interface/"
- ... "zope.app.folder.interfaces.IRootFolder/apiindex.html") in response
- True
- >>> ">zope.app.folder.interfaces.IRootFolder</" in response
- True
+The next section lists all directly provided interfaces. The root folder
+directly provides the ``ISite`` and ``IRootFolder`` interface, so we should
+see those:
-All the proxies will be removed from the object before getting
-any information. For example let's add a file::
+ >>> browser.getLink('zope.app.component.interfaces.ISite').url
+ '.../++apidoc++/Interface/zope.app.component.interfaces.ISite/apiindex.html'
- >>> print http(r"""
- ... POST /+/zope.app.file.File%3D HTTP/1.1
- ... Authorization: Basic mgr:mgrpw
- ... Content-Type: multipart/form-data; boundary=---------------------------24464570528145
- ... -----------------------------24464570528145
- ... Content-Disposition: form-data; name="field.contentType"
- ...
- ... -----------------------------24464570528145
- ... Content-Disposition: form-data; name="field.data"; filename=""
- ... Content-Type: application/octet-stream
- ...
- ... -----------------------------24464570528145
- ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
- ...
- ... Add
- ... -----------------------------24464570528145
- ... Content-Disposition: form-data; name="add_input_name"
- ...
- ... file
- ... -----------------------------24464570528145--
- ... """)
- HTTP/1.1 303 See Other
- ...
+ >>> browser.getLink('zope.app.folder.interfaces.IRootFolder').url
+ '...apidoc++/Interface/zope.app.folder.interfaces.IRootFolder/apiindex.html'
-And now we can check the class that the file implements::
+The next two section, the implemented interfaces and the base classes, are not
+instance specific pieces of information, but they are still nice to see at
+this point. For example, a ``Folder`` instance provides the following
+interfaces:
- >>> response = str(http(r"""
- ... GET /file/@@introspector.html HTTP/1.1
- ... Authorization: Basic mgr:mgrpw
- ... """))
+ >>> browser.getLink('zope.app.folder.interfaces.IFolder').url
+ '.../++apidoc++/Interface/zope.app.folder.interfaces.IFolder/apiindex.html'
- >>> print response
- HTTP/1.1 200 Ok
- ...
+ >>> browser.getLink('persistent.interfaces.IPersistent').url
+ '.../++apidoc++/Interface/persistent.interfaces.IPersistent/apiindex.html'
- >>> ("http://localhost/++apidoc++/Code/"
- ... "zope/app/file/file/File/index.html") in response
- True
- >>> ">zope.app.file.file.File</" in response
- True
+ >>> browser.getLink('zope.app.component.interfaces.IPossibleSite').url
+ '.../Interface/zope.app.component.interfaces.IPossibleSite/apiindex.html'
+
+ >>> browser.getLink('zope.app.container.interfaces.IContained').url
+ '...doc++/Interface/zope.app.container.interfaces.IContained/apiindex.html'
+
+The base classes of the ``Folder`` are as follows:
+
+ >>> browser.getLink('persistent.Persistent').url
+ 'http://localhost/++apidoc++/Code/persistent/Persistent/index.html'
+
+ >>> browser.getLink('zope.app.component.site.SiteManagerContainer').url
+ '...apidoc++/Code/zope/app/component/site/SiteManagerContainer/index.html'
+
+ >>> browser.getLink('zope.app.container.contained.Contained').url
+ '.../++apidoc++/Code/zope/app/container/contained/Contained/index.html'
+
+Now that we described the component and class level of the object, the view
+dives into some details. First it lists the attributes/properties of the
+object, including the value of the attribute. This is information can be very
+useful when debugging an application. The only attribute of the folder is the
+data attribute:
+
+ >>> print browser.contents
+ <!DOCTYPE...
+ ...
+ <h2>Attributes/Properties</h2>
+ <div class="indent">
+ <ul class="attr-list">
+ <li>
+ <b><code>data</code></b>
+ ...
+ <br />
+ <i>Value:</i>
+ <code><BTrees._OOBTree.OOBTree object at ...></code>
+ <br />
+ <span class="small">
+ <i>Permissions:</i>
+ n/a
+ <span>(read)</span>,
+ n/a
+ <span>(write)</span>
+ </span>
+ </li>
+ </ul>
+ </div>
+ ...
+
+There are, however, several methods since the full mapping interface is
+implemented. Like for the class method documentation, the method's signature,
+doc string, permissions and the interface the method is declared in. Here an
+example:
+
+ >>> print browser.contents
+ <!DOCTYPE...
+ ...
+ <h2>Methods</h2>
+ <div class="indent">
+ <ul class="attr-list">
+ <li>
+ <b><code>get(name, default=None)</code>
+ </b><br>
+ <div class="inline documentation"><dl class="docutils">
+ <dt>Return ...
+ </dl>
+ </div>
+ <span class="small">
+ <i>Interface:</i>
+ <a href="...">zope.interface.common.mapping.IReadMapping</a><br />
+ </span>
+ <span class="small">
+ <i>Permissions:</i>
+ zope.View
+ <span>(read)</span>,
+ n/a
+ <span>(write)</span>
+ </span>
+ </li>
+ ...
+ </ul>
+ </div>
+ ...
+
+Towards the bottom of the page, there are some optional sections. Some
+objects, for example our root folder, are inheritely mappings or
+sequences. Their data then is often hard to see in the attributes section, so
+they are provided in a aseparate section. To see anything useful, we have to
+add an object to the folder first:
+
+ >>> browser.getLink('File').click()
+ >>> import cStringIO
+ >>> browser.getControl('Data').value = cStringIO.StringIO('content')
+ >>> browser.getControl(name='add_input_name').value = 'file.txt'
+ >>> browser.getControl('Add').click()
+ >>> browser.getLink('Introspector').click()
+
+Now the introspector will show the file and allow you to click on it:
+
+ >>> print browser.contents
+ <!DOCTYPE...
+ ...
+ <h2>Mapping Items</h2>
+ <div class="indent">
+ <ul class="attr-list">
+ <li>
+ <b>
+ <code>u'file.txt'</code>
+ </b>
+ <br />
+ <a href="++items++file.txt/@@introspector.html">
+ <code><zope.app.file.file.File object at ...></code>
+ </a>
+ (<span>type:</span>
+ <a href="...zope/app/container/contained/ContainedProxy/index.html">
+ <code>ContainedProxy</code></a>)
+ </li>
+ </ul>
+ </div>
+ ...
+
+The final section of the introspector displays the annotations that are
+declared for the object. The standard annotation that almost every object
+provides is the Dublin Core:
+
+ >>> print browser.contents
+ <!DOCTYPE...
+ ...
+ <h2>Annotations</h2>
+ <div class="indent">
+ <ul class="attr-list">
+ <li>
+ <b>
+ <code>'zope.app.dublincore.ZopeDublinCore'</code>
+ </b>
+ <br />
+ <a href="++annotations++zope.app.dublincore.ZopeDublinCore/@@int...">
+ <code><...annotatableadapter.ZDCAnnotationData ...></code>
+ </a>
+ (<span>type:</span>
+ <a href="...lincore/annotatableadapter/ZDCAnnotationData/index.html">
+ <code>ZDCAnnotationData</code></a>)
+ </li>
+ ...
+ </ul>
+ </div>
+ ...
+
+As you can see you can click on the annotation to discover it further:
+
+ >>> browser.getLink('annotatableadapter.ZDCAnnotationData').click()
+ >>> print browser.contents
+ <!DOCTYPE...
+ ...
+ <h2>Attributes/Properties</h2>
+ <div class="indent">
+ <ul class="attr-list">
+ <li>
+ <b><code>data</code></b>
+ (<span>type:</span>
+ <a href=".../++apidoc++/Code/__builtin__/dict/index.html">
+ <code>dict</code></a>)
+ <br />
+ <i>Value:</i>
+ <code>{u'Date.Modified': (u'...',), u'Creator': (u'zope.mgr',)}</code>
+ <br />
+ </li>
+ </ul>
+ </div>
+ ...
+
+That's it! The introspector view has a lot more potential, but that's for
+someone else to do.
\ No newline at end of file
Added: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.zcml 2005-10-15 17:45:11 UTC (rev 39467)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.zcml 2005-10-15 23:27:58 UTC (rev 39468)
@@ -0,0 +1,55 @@
+<configure
+ xmlns="http://namespaces.zope.org/browser"
+ xmlns:zope="http://namespaces.zope.org/zope"
+ i18n_domain="zope">
+
+ <!-- ++annotations++ Namespace -->
+ <zope:view
+ name="annotations" type="*"
+ provides="zope.app.traversing.interfaces.ITraversable" for="*"
+ factory=".introspector.annotationsNamespace"
+ />
+ <zope:adapter
+ name="annotations"
+ provides="zope.app.traversing.interfaces.ITraversable" for="*"
+ factory=".introspector.annotationsNamespace"
+ />
+
+ <!-- ISequence ++items++ Namespace -->
+ <zope:view
+ name="items" type="*"
+ provides="zope.app.traversing.interfaces.ITraversable"
+ for="zope.interface.common.sequence.IMinimalSequence"
+ factory=".introspector.sequenceItemsNamespace"
+ />
+ <zope:adapter
+ name="items"
+ provides="zope.app.traversing.interfaces.ITraversable"
+ for="zope.interface.common.sequence.IMinimalSequence"
+ factory=".introspector.sequenceItemsNamespace"
+ />
+
+ <!-- IMapping ++items++ Namespace -->
+ <zope:view
+ name="items" type="*"
+ provides="zope.app.traversing.interfaces.ITraversable"
+ for="zope.interface.common.mapping.IItemMapping"
+ factory=".introspector.mappingItemsNamespace"
+ />
+ <zope:adapter
+ name="items"
+ provides="zope.app.traversing.interfaces.ITraversable"
+ for="zope.interface.common.mapping.IItemMapping"
+ factory=".introspector.mappingItemsNamespace"
+ />
+
+ <page
+ name="introspector.html"
+ for="*"
+ class=".introspector.Introspector"
+ permission="zope.ManageContent"
+ menu="zmi_views" title="Introspector"
+ template="introspector.pt"
+ />
+
+</configure>
Property changes on: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/tests.py 2005-10-15 17:45:11 UTC (rev 39467)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/tests.py 2005-10-15 23:27:58 UTC (rev 39468)
@@ -58,8 +58,7 @@
'''
def setUp(test):
- placelesssetup.setUp()
- setup.setUpTraversal()
+ test.globs['rootFolder'] = setup.placefulSetUp(True)
class RootModule(str):
implements(IAPIDocRootModule)
@@ -84,6 +83,18 @@
ztapi.provideUtility(IFactory, ReStructuredTextSourceFactory,
'zope.source.stx')
+ # Register ++apidoc++ namespace
+ from zope.app.apidoc.apidoc import apidocNamespace
+ from zope.app.traversing.interfaces import ITraversable
+ ztapi.provideAdapter(None, ITraversable, apidocNamespace, name="apidoc")
+ ztapi.provideView(None, None, ITraversable, "apidoc", apidocNamespace)
+
+ # Register ++apidoc++ namespace
+ from zope.app.traversing.namespace import view
+ from zope.app.traversing.interfaces import ITraversable
+ ztapi.provideAdapter(None, ITraversable, view, name="view")
+ ztapi.provideView(None, None, ITraversable, "view", view)
+
context = xmlconfig.string(meta)
# Fix up path for tests.
@@ -97,9 +108,17 @@
zope.app.appsetup.appsetup.__config_source = os.path.join(
os.path.dirname(zope.app.__file__), 'meta.zcml')
+ # Register the index.html view for codemodule.class_.Class
+ from zope.app.apidoc.codemodule.class_ import Class
+ from zope.app.apidoc.codemodule.browser.class_ import ClassDetails
+ from zope.app.publisher.browser import BrowserView
+ class Details(ClassDetails, BrowserView):
+ pass
+ ztapi.browserView(Class, 'index.html', Details)
+
def tearDown(test):
- placelesssetup.tearDown()
+ setup.placefulTearDown()
global old_context, old_source_file
zope.app.appsetup.appsetup.__config_context = old_context
zope.app.appsetup.appsetup.__config_source = old_source_file
@@ -107,14 +126,16 @@
def test_suite():
return unittest.TestSuite((
- doctest.DocFileSuite('README.txt',
- setUp=setUp, tearDown=tearDown,
- globs={'pprint': doctestunit.pprint},
- optionflags=doctest.NORMALIZE_WHITESPACE),
- doctest.DocTestSuite('zope.app.apidoc.codemodule.browser.menu',
- setUp=setUp, tearDown=tearDown,
- globs={'pprint': doctestunit.pprint},
- optionflags=doctest.NORMALIZE_WHITESPACE),
+ doctest.DocFileSuite(
+ 'README.txt',
+ setUp=setUp, tearDown=tearDown,
+ globs={'pprint': doctestunit.pprint},
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS),
+ doctest.DocTestSuite(
+ 'zope.app.apidoc.codemodule.browser.menu',
+ setUp=setUp, tearDown=tearDown,
+ globs={'pprint': doctestunit.pprint},
+ optionflags=doctest.NORMALIZE_WHITESPACE),
))
if __name__ == '__main__':
Modified: Zope3/trunk/src/zope/app/file/browser/file_add.pt
===================================================================
--- Zope3/trunk/src/zope/app/file/browser/file_add.pt 2005-10-15 17:45:11 UTC (rev 39467)
+++ Zope3/trunk/src/zope/app/file/browser/file_add.pt 2005-10-15 23:27:58 UTC (rev 39468)
@@ -1,4 +1,4 @@
-<html metal:use-macro="context/@@standard_macros/view"
+<html metal:use-macro="context/@@standard_macros/page"
i18n:domain="zope">
<body>
<div metal:fill-slot="body">
More information about the Zope3-Checkins
mailing list