[Checkins] SVN: grok/trunk/src/grok/ Implement traversal via
@grok.traverse or grok.Traverser.
Philipp von Weitershausen
philikon at philikon.de
Thu Oct 19 04:06:42 EDT 2006
Log message for revision 70797:
Implement traversal via @grok.traverse or grok.Traverser.
Also moved all utility functions from _grok to util.
Use removeSecurityProxy instead of removeAllProxies (I still hope we can get rid of it eventually...)
Whitespace consistency cleanup
Changed:
U grok/trunk/src/grok/__init__.py
U grok/trunk/src/grok/_grok.py
U grok/trunk/src/grok/components.py
U grok/trunk/src/grok/ftests/test_grok_functional.py
A grok/trunk/src/grok/ftests/traversal/
A grok/trunk/src/grok/ftests/traversal/__init__.py
A grok/trunk/src/grok/ftests/traversal/modeltraverse.py
A grok/trunk/src/grok/ftests/traversal/traverser.py
U grok/trunk/src/grok/util.py
-=-
Modified: grok/trunk/src/grok/__init__.py
===================================================================
--- grok/trunk/src/grok/__init__.py 2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/__init__.py 2006-10-19 08:06:38 UTC (rev 70797)
@@ -29,10 +29,11 @@
IContainerModifiedEvent, ContainerModifiedEvent)
from grok.components import Model, Adapter, MultiAdapter, View, XMLRPC
-from grok.components import PageTemplate, Utility, Container
+from grok.components import PageTemplate, Utility, Container, Traverser
from grok.directive import context, name, template, templatedir
from grok._grok import do_grok as grok # Avoid name clash within _grok
from grok._grok import SubscribeDecorator as subscribe
+from grok._grok import traverseDecorator as traverse
from grok.error import GrokError, GrokImportError
# Our __init__ provides the grok API directly so using 'import grok' is enough.
Modified: grok/trunk/src/grok/_grok.py
===================================================================
--- grok/trunk/src/grok/_grok.py 2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/_grok.py 2006-10-19 08:06:38 UTC (rev 70797)
@@ -24,7 +24,8 @@
from zope.security.checker import (defineChecker, getCheckerForInstancesOf,
NoProxy)
from zope.publisher.interfaces.browser import (IDefaultBrowserLayer,
- IBrowserRequest)
+ IBrowserRequest,
+ IBrowserPublisher)
from zope.app.publisher.xmlrpc import MethodPublisher
from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
@@ -36,19 +37,31 @@
from grok.directive import (ClassDirectiveContext, ModuleDirectiveContext,
ClassOrModuleDirectiveContext,
TextDirective, InterfaceOrClassDirective,
- frame_is_module)
+ frame_is_module, frame_is_class)
-AMBIGUOUS_CONTEXT = object()
-
-
-def do_grok(dotted_name):
+_bootstrapped = False
+def bootstrap():
+ component.provideAdapter(components.ModelTraverser)
# register the name 'index' as the default view name
- # TODO this needs to be moved to grok startup time (similar to ZCML-time)
component.provideAdapter('index',
adapts=(grok.Model, IBrowserRequest),
provides=IDefaultViewName)
+# add a cleanup hook so that grok will bootstrap itself again whenever
+# the Component Architecture is torn down.
+def resetBootstrap():
+ global _bootstrapped
+ _bootstrapped = False
+from zope.testing.cleanup import addCleanUp
+addCleanUp(resetBootstrap)
+
+
+def do_grok(dotted_name):
+ global _bootstrapped
+ if not _bootstrapped:
+ bootstrap()
+
module_info = scan.module_info_from_dotted_name(dotted_name)
grok_tree(module_info)
@@ -78,12 +91,12 @@
def grok_module(module_info):
- (models, adapters, multiadapters, utilities,
- views, xmlrpc_views, templates, subscribers) = scan_module(module_info)
+ (models, adapters, multiadapters, utilities, views, xmlrpc_views,
+ traversers, templates, subscribers) = scan_module(module_info)
find_filesystem_templates(module_info, templates)
- context = determine_module_context(module_info, models)
+ context = util.determine_module_context(module_info, models)
register_models(models)
register_adapters(context, adapters)
@@ -91,6 +104,7 @@
register_utilities(utilities)
register_views(context, views, templates)
register_xmlrpc(context, xmlrpc_views)
+ register_traversers(context, traversers)
register_unassociated_templates(context, templates)
register_subscribers(subscribers)
@@ -102,7 +116,8 @@
grok.MultiAdapter: [],
grok.Utility: [],
grok.View: [],
- grok.XMLRPC: []
+ grok.XMLRPC: [],
+ grok.Traverser: []
}
templates = TemplateRegistry()
subscribers = module_info.getAnnotation('grok.subscribers', [])
@@ -111,7 +126,7 @@
for name in dir(module):
obj = getattr(module, name)
- if not defined_locally(obj, module_info.dotted_name):
+ if not util.defined_locally(obj, module_info.dotted_name):
continue
if isinstance(obj, grok.PageTemplate):
@@ -126,10 +141,12 @@
return (components[grok.Model], components[grok.Adapter],
components[grok.MultiAdapter], components[grok.Utility],
- components[grok.View], components[grok.XMLRPC], templates, subscribers)
+ components[grok.View], components[grok.XMLRPC],
+ components[grok.Traverser], templates, subscribers)
def find_filesystem_templates(module_info, templates):
- template_dir_name = module_info.getAnnotation('grok.templatedir', module_info.name)
+ template_dir_name = module_info.getAnnotation('grok.templatedir',
+ module_info.name)
template_dir = module_info.getResourcePath(template_dir_name)
if os.path.isdir(template_dir):
template_files = os.listdir(template_dir)
@@ -179,27 +196,27 @@
def register_adapters(context, adapters):
for factory in adapters:
- adapter_context = determine_class_context(factory, context)
- check_implements_one(factory)
- name = class_annotation(factory, 'grok.name', '')
+ adapter_context = util.determine_class_context(factory, context)
+ util.check_implements_one(factory)
+ name = util.class_annotation(factory, 'grok.name', '')
component.provideAdapter(factory, adapts=(adapter_context,), name=name)
def register_multiadapters(multiadapters):
for factory in multiadapters:
- check_implements_one(factory)
- check_adapts(factory)
- name = class_annotation(factory, 'grok.name', '')
+ util.check_implements_one(factory)
+ util.check_adapts(factory)
+ name = util.class_annotation(factory, 'grok.name', '')
component.provideAdapter(factory, name=name)
def register_utilities(utilities):
for factory in utilities:
- check_implements_one(factory)
- name = class_annotation(factory, 'grok.name', '')
+ util.check_implements_one(factory)
+ name = util.class_annotation(factory, 'grok.name', '')
component.provideUtility(factory(), name=name)
def register_xmlrpc(context, views):
for view in views:
- view_context = determine_class_context(view, context)
+ view_context = util.determine_class_context(view, context)
candidates = [getattr(view, name) for name in dir(view)]
methods = [c for c in candidates if inspect.ismethod(c)]
@@ -216,12 +233,12 @@
def register_views(context, views, templates):
for factory in views:
- view_context = determine_class_context(factory, context)
+ view_context = util.determine_class_context(factory, context)
factory_name = factory.__name__.lower()
# find inline templates
- template_name = class_annotation(factory, 'grok.template',
- factory_name)
+ template_name = util.class_annotation(factory, 'grok.template',
+ factory_name)
template = templates.get(template_name)
if factory_name != template_name:
@@ -248,7 +265,7 @@
"'render' method." % factory,
factory)
- view_name = class_annotation(factory, 'grok.name', factory_name)
+ view_name = util.class_annotation(factory, 'grok.name', factory_name)
component.provideAdapter(factory,
adapts=(view_context, IDefaultBrowserLayer),
provides=interface.Interface,
@@ -257,9 +274,16 @@
# TODO minimal security here (read: everything is public)
defineChecker(factory, NoProxy)
+def register_traversers(context, traversers):
+ for factory in traversers:
+ factory_context = util.determine_class_context(factory, context)
+ component.provideAdapter(factory,
+ adapts=(factory_context, IBrowserRequest),
+ provides=IBrowserPublisher)
+
def register_unassociated_templates(context, templates):
for name, unassociated in templates.listUnassociatedTemplates():
- check_context(unassociated, context)
+ util.check_context(unassociated, context)
class TemplateView(grok.View):
template = unassociated
@@ -302,55 +326,7 @@
if not entry['associated']:
yield name, entry['template']
-def defined_locally(obj, dotted_name):
- obj_module = getattr(obj, '__grok_module__', None)
- if obj_module is None:
- obj_module = getattr(obj, '__module__', None)
- return obj_module == dotted_name
-def check_context(component, context):
- if context is None:
- raise GrokError("No module-level context for %r, please use "
- "grok.context." % component, component)
- elif context is AMBIGUOUS_CONTEXT:
- raise GrokError("Multiple possible contexts for %r, please use "
- "grok.context." % component, component)
-
-def check_implements_one(class_):
- if len(list(interface.implementedBy(class_))) != 1:
- raise GrokError("%r must implement exactly one interface "
- "(use grok.implements to specify)."
- % class_, class_)
-
-def check_adapts(class_):
- if component.adaptedBy(class_) is None:
- raise GrokError("%r must specify which contexts it adapts "
- "(use grok.adapts to specify)."
- % class_, class_)
-
-def determine_module_context(module_info, models):
- if len(models) == 0:
- context = None
- elif len(models) == 1:
- context = models[0]
- else:
- context = AMBIGUOUS_CONTEXT
-
- module_context = module_info.getAnnotation('grok.context', None)
- if module_context:
- context = module_context
-
- return context
-
-def determine_class_context(class_, module_context):
- context = class_annotation(class_, 'grok.context', module_context)
- check_context(class_, context)
- return context
-
-def class_annotation(obj, name, default):
- return getattr(obj, '__%s__' % name.replace('.', '_'), default)
-
-
# decorators
class SubscribeDecorator:
def __init__(self, *args):
@@ -370,3 +346,11 @@
if subscribers is None:
frame.f_locals['__grok_subscribers__'] = subscribers = []
subscribers.append((function, self.subscribed))
+
+def traverseDecorator(function):
+ frame = sys._getframe(1)
+ if not frame_is_class(frame):
+ raise GrokImportError("@grok.traverse can only be used on class "
+ "level.")
+
+ frame.f_locals['__grok_traverse__'] = function
Modified: grok/trunk/src/grok/components.py
===================================================================
--- grok/trunk/src/grok/components.py 2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/components.py 2006-10-19 08:06:38 UTC (rev 70797)
@@ -17,11 +17,15 @@
import persistent
from zope import component
from zope import interface
-from zope.proxy import removeAllProxies
+from zope.security.proxy import removeSecurityProxy
from zope.publisher.browser import BrowserPage
+from zope.publisher.interfaces import NotFound
+from zope.publisher.interfaces.browser import (IBrowserPublisher,
+ IBrowserRequest)
from zope.pagetemplate import pagetemplate
from zope.app.pagetemplate.engine import TrustedAppPT
+from zope.app.publisher.browser import getDefaultViewName
from zope.app.publisher.browser import directoryresource
from zope.app.publisher.browser.pagetemplateresource import \
PageTemplateResourceFactory
@@ -46,14 +50,17 @@
class Utility(object):
pass
+
class MultiAdapter(object):
pass
+
class View(BrowserPage):
def __init__(self, context, request):
- self.context = removeAllProxies(context)
- self.request = removeAllProxies(request)
+ # Jim would say: WAAAAAAAAAAAAH!
+ self.context = removeSecurityProxy(context)
+ self.request = removeSecurityProxy(request)
def __call__(self):
self.before()
@@ -64,9 +71,8 @@
namespace = template.pt_getContext()
namespace['request'] = self.request
- # Jim would say: WAAAAAAAAAAAAH!
namespace['view'] = self
- namespace['context'] = removeAllProxies(self.context)
+ namespace['context'] = self.context
module_info = template.__grok_module_info__
directory_resource = component.queryAdapter(self.request,
@@ -87,7 +93,6 @@
pass
-
class PageTemplate(TrustedAppPT, pagetemplate.PageTemplate):
expand = 0
@@ -142,3 +147,40 @@
resource.__name__ = self.__name
return resource
+
+class Traverser(object):
+ interface.implements(IBrowserPublisher)
+
+ def __init__(self, context, request):
+ # Jim would say: WAAAAAAAAAAAAH!
+ self.context = removeSecurityProxy(context)
+ self.request = removeSecurityProxy(request)
+
+ def browserDefault(self, request):
+ view_name = getDefaultViewName(self.context, request)
+ view_uri = "@@%s" % view_name
+ return self.context, (view_uri,)
+
+ def publishTraverse(self, request, name):
+ subob = self.traverse(name)
+ if subob:
+ return subob
+
+ view = component.queryMultiAdapter((self.context, request), name=name)
+ if view:
+ return view
+
+ raise NotFound(self.context, name, request)
+
+ def traverse(self, name):
+ # this will be overridden by subclasses
+ pass
+
+
+class ModelTraverser(Traverser):
+ component.adapts(Model, IBrowserRequest)
+
+ def traverse(self, name):
+ traverser = util.class_annotation(self.context, 'grok.traverse', None)
+ if traverser:
+ return traverser(name)
Modified: grok/trunk/src/grok/ftests/test_grok_functional.py
===================================================================
--- grok/trunk/src/grok/ftests/test_grok_functional.py 2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/ftests/test_grok_functional.py 2006-10-19 08:06:38 UTC (rev 70797)
@@ -56,7 +56,7 @@
def test_suite():
suite = unittest.TestSuite()
- for name in ['view', 'static', 'xmlrpc']:
+ for name in ['view', 'static', 'xmlrpc', 'traversal']:
suite.addTest(suiteFromPackage(name))
return suite
Copied: grok/trunk/src/grok/ftests/traversal/__init__.py (from rev 70796, grok/trunk/src/grok/ftests/__init__.py)
Added: grok/trunk/src/grok/ftests/traversal/modeltraverse.py
===================================================================
--- grok/trunk/src/grok/ftests/traversal/modeltraverse.py 2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/ftests/traversal/modeltraverse.py 2006-10-19 08:06:38 UTC (rev 70797)
@@ -0,0 +1,50 @@
+"""
+Models can determine how they want to be traversed by using the
+``@grok.traverse`` decorator:
+
+ >>> import grok
+ >>> from grok.ftests.traversal.modeltraverse import Herd
+ >>> grok.grok('grok.ftests.traversal.modeltraverse')
+ >>> getRootFolder()["herd"] = Herd()
+
+ >>> from zope.testbrowser.testing import Browser
+ >>> browser = Browser()
+ >>> browser.handleErrors = False
+ >>> browser.open("http://localhost/herd/manfred")
+ >>> print browser.contents
+ <html>
+ <body>
+ <h1>Hello, Manfred!</h1>
+ </body>
+ </html>
+
+ >>> browser.open("http://localhost/herd/ellie")
+ >>> print browser.contents
+ <html>
+ <body>
+ <h1>Hello, Ellie!</h1>
+ </body>
+ </html>
+
+"""
+import grok
+
+class Herd(grok.Model):
+
+ @grok.traverse
+ def getMammoth(self, name):
+ return Mammoth(name)
+
+class Mammoth(grok.Model):
+
+ def __init__(self, name):
+ self.name = name
+
+grok.context(Mammoth)
+index = grok.PageTemplate("""\
+<html>
+<body>
+<h1>Hello, <span tal:replace="context/name/title" />!</h1>
+</body>
+</html>
+""")
Property changes on: grok/trunk/src/grok/ftests/traversal/modeltraverse.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: grok/trunk/src/grok/ftests/traversal/traverser.py
===================================================================
--- grok/trunk/src/grok/ftests/traversal/traverser.py 2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/ftests/traversal/traverser.py 2006-10-19 08:06:38 UTC (rev 70797)
@@ -0,0 +1,53 @@
+"""
+Apart from using the ``@grok.traverse`` decorator on a model, you can
+also create a separate traverser component:
+
+ >>> import grok
+ >>> from grok.ftests.traversal.modeltraverse import Herd
+ >>> grok.grok('grok.ftests.traversal.modeltraverse')
+ >>> getRootFolder()["herd"] = Herd()
+
+ >>> from zope.testbrowser.testing import Browser
+ >>> browser = Browser()
+ >>> browser.handleErrors = False
+ >>> browser.open("http://localhost/herd/manfred")
+ >>> print browser.contents
+ <html>
+ <body>
+ <h1>Hello, Manfred!</h1>
+ </body>
+ </html>
+
+ >>> browser.open("http://localhost/herd/ellie")
+ >>> print browser.contents
+ <html>
+ <body>
+ <h1>Hello, Ellie!</h1>
+ </body>
+ </html>
+
+"""
+import grok
+
+class Herd(grok.Model):
+ pass
+
+class HerdTraverser(grok.Traverser):
+ grok.context(Herd)
+
+ def traverse(self, name):
+ return Mammoth(name)
+
+class Mammoth(grok.Model):
+
+ def __init__(self, name):
+ self.name = name
+
+grok.context(Mammoth)
+index = grok.PageTemplate("""\
+<html>
+<body>
+<h1>Hello, <span tal:replace="context/name/title" />!</h1>
+</body>
+</html>
+""")
Property changes on: grok/trunk/src/grok/ftests/traversal/traverser.py
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: grok/trunk/src/grok/util.py
===================================================================
--- grok/trunk/src/grok/util.py 2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/util.py 2006-10-19 08:06:38 UTC (rev 70797)
@@ -18,6 +18,11 @@
import types
import sys
+from zope import component
+from zope import interface
+
+from grok.error import GrokError, GrokImportError
+
def not_unicode_or_ascii(value):
if isinstance(value, unicode):
return False
@@ -27,15 +32,74 @@
is_not_ascii = re.compile(eval(r'u"[\u0080-\uffff]"')).search
+
def isclass(obj):
"""We cannot use ``inspect.isclass`` because it will return True
for interfaces"""
return type(obj) in (types.ClassType, type)
+
def check_subclass(obj, class_):
if not isclass(obj):
return False
return issubclass(obj, class_)
+
def caller_module():
return sys._getframe(2).f_globals['__name__']
+
+
+def class_annotation(obj, name, default):
+ return getattr(obj, '__%s__' % name.replace('.', '_'), default)
+
+
+def defined_locally(obj, dotted_name):
+ obj_module = getattr(obj, '__grok_module__', None)
+ if obj_module is None:
+ obj_module = getattr(obj, '__module__', None)
+ return obj_module == dotted_name
+
+
+AMBIGUOUS_CONTEXT = object()
+def check_context(component, context):
+ if context is None:
+ raise GrokError("No module-level context for %r, please use "
+ "grok.context." % component, component)
+ elif context is AMBIGUOUS_CONTEXT:
+ raise GrokError("Multiple possible contexts for %r, please use "
+ "grok.context." % component, component)
+
+
+def check_implements_one(class_):
+ if len(list(interface.implementedBy(class_))) != 1:
+ raise GrokError("%r must implement exactly one interface "
+ "(use grok.implements to specify)."
+ % class_, class_)
+
+
+def check_adapts(class_):
+ if component.adaptedBy(class_) is None:
+ raise GrokError("%r must specify which contexts it adapts "
+ "(use grok.adapts to specify)."
+ % class_, class_)
+
+
+def determine_module_context(module_info, models):
+ if len(models) == 0:
+ context = None
+ elif len(models) == 1:
+ context = models[0]
+ else:
+ context = AMBIGUOUS_CONTEXT
+
+ module_context = module_info.getAnnotation('grok.context', None)
+ if module_context:
+ context = module_context
+
+ return context
+
+
+def determine_class_context(class_, module_context):
+ context = class_annotation(class_, 'grok.context', module_context)
+ check_context(class_, context)
+ return context
More information about the Checkins
mailing list