[Checkins] SVN: grok/trunk/src/grok/ Big refactoring of grok's basic grokking mechanism. Grok now groks

Martijn Faassen faassen at infrae.com
Mon Dec 11 18:58:06 EST 2006


Log message for revision 71537:
  Big refactoring of grok's basic grokking mechanism. Grok now groks
  bits of itself!
  
  Grok is now prepared for easy pluggability of the basic grok mechanism, 
  though we have a few loose ends to tie before we're
  fully there. Lots is less hardcoded now. Short summary, if you want to
  create a new grokkable base class, add it to meta.py, and you should
  be done.
  
  Proposed is a convention where any meta.py in any grokked
  packages will be grokked first, after which everything else is grokked.
  meta.py can then define new grok base classes that can be grokker. This
  is done using the grok mechanism, like this:
  
  class MyClassGrokker(grok.ClassGrokker):
      component_class = MyClass
  
      def register(self, context, name, factory, module_info, templates):
         ... register factory in some way here ...
  
  Grok's meta.py is grokked by grok to initialize the core grok base classes.
  
  Split out a lot of code into new modules:
  
  * grokker.py - the basic extensibility mechanism
  
  * templatereg.py - a template registry. (cannot be called 'template' 
                     as that name already exists).
  
  * meta.py - core grokkable base classes.
  
  Some future todos:
  
  * Some grok ordering assumptions are hardcoded into grokker.py; get
    rid of them replaced by a priority mechanism.
  
  * grokker.py also makes assumptions concerning which objects can be
    a context. This should be made pluggable.
  
  * make templating system support other template engines besides
    PageTemplates.
  
  

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/formlib.py
  A   grok/trunk/src/grok/grokker.py
  U   grok/trunk/src/grok/interfaces.py
  A   grok/trunk/src/grok/meta.py
  U   grok/trunk/src/grok/scan.py
  A   grok/trunk/src/grok/templatereg.py

-=-
Modified: grok/trunk/src/grok/__init__.py
===================================================================
--- grok/trunk/src/grok/__init__.py	2006-12-11 23:44:01 UTC (rev 71536)
+++ grok/trunk/src/grok/__init__.py	2006-12-11 23:58:05 UTC (rev 71537)
@@ -29,6 +29,7 @@
     IObjectRemovedEvent, ObjectRemovedEvent,
     IContainerModifiedEvent, ContainerModifiedEvent)
 
+from grok.components import ClassGrokker, InstanceGrokker
 from grok.components import Model, Adapter, MultiAdapter, View, XMLRPC
 from grok.components import PageTemplate, Utility, Container, Traverser, Site
 from grok.components import EditForm, DisplayForm, AddForm

Modified: grok/trunk/src/grok/_grok.py
===================================================================
--- grok/trunk/src/grok/_grok.py	2006-12-11 23:44:01 UTC (rev 71536)
+++ grok/trunk/src/grok/_grok.py	2006-12-11 23:58:05 UTC (rev 71537)
@@ -15,29 +15,20 @@
 """
 import os
 import sys
-import inspect
 
 from zope import component
 from zope import interface
 import zope.component.interface
 from zope.component.interfaces import IDefaultViewName
-from zope.security.checker import (defineChecker, getCheckerForInstancesOf,
-                                   NoProxy)
 from zope.publisher.interfaces.browser import (IDefaultBrowserLayer,
-                                               IBrowserRequest,
-                                               IBrowserPublisher)
-from zope.app.publisher.xmlrpc import MethodPublisher
-from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
+                                               IBrowserRequest)
 from zope.app.component.site import LocalSiteManager
 
 import grok
 
-from grok import util, scan, components, security, formlib
+from grok import util, scan, components, grokker, meta
 from grok.error import GrokError, GrokImportError
-from grok.directive import (ClassDirectiveContext, ModuleDirectiveContext,
-                            ClassOrModuleDirectiveContext,
-                            TextDirective, InterfaceOrClassDirective,
-                            frame_is_module, frame_is_class)
+from grok.directive import frame_is_module
 
 
 _bootstrapped = False
@@ -57,6 +48,9 @@
     component.provideHandler(
         addSiteHandler, adapts=(grok.Site, grok.IObjectAddedEvent))
 
+    # now grok the grokkers
+    grokker.grokkerRegistry.grok(scan.module_info_from_module(meta))
+    
 def addSiteHandler(site, event):
     sitemanager = LocalSiteManager(site)
     site.setSiteManager(sitemanager)
@@ -103,285 +97,25 @@
     for sub_module_info in module_info.getSubModuleInfos():
         grok_tree(sub_module_info)
 
-
 def grok_module(module_info):
-    components, templates, subscribers = scan_module(module_info)
+    grokker.grokkerRegistry.grok(module_info)
 
-    find_filesystem_templates(module_info, templates)
-
-    context = util.determine_module_context(module_info,
-                                            components[grok.Model])
-    register_models(components[grok.Model])
-    register_adapters(context, components[grok.Adapter])
-    register_multiadapters(components[grok.MultiAdapter])
-    register_utilities(components[grok.Utility])
-    register_views(context, components[grok.View], templates)
-    register_xmlrpc(context, components[grok.XMLRPC])
-    register_traversers(context, components[grok.Traverser])
-    register_unassociated_templates(context, templates, module_info)
-    register_subscribers(subscribers)
-
-    # Do various other initializations
-    formlib.initialize_schema(components[grok.Model])
-
-def scan_module(module_info):
-    models = []
-    components = {
-            grok.Model: models,
-            grok.Container: models,
-            grok.Adapter: [],
-            grok.MultiAdapter: [],
-            grok.Utility: [],
-            grok.View: [],
-            grok.XMLRPC: [],
-            grok.Traverser: []
-            }
-    templates = TemplateRegistry()
-    subscribers = module_info.getAnnotation('grok.subscribers', [])
-
-    module = module_info.getModule()
-    for name in dir(module):
-        obj = getattr(module, name)
-        # we don't care about picking up module-level annotations from grok
-        if name.startswith('__grok_'):
-            continue
-        if not util.defined_locally(obj, module_info.dotted_name):
-            continue
-
-        if isinstance(obj, grok.PageTemplate):
-            templates.register(name, obj)
-            obj._annotateGrokInfo(name, module_info.dotted_name)
-            continue
-        # XXX refactor
-        elif util.check_subclass(obj, grok.View):
-            obj.module_info = module_info
-            components[grok.View].append(obj)
-            continue
-
-        for candidate_class, found_list in components.items():
-            if util.check_subclass(obj, candidate_class):
-                found_list.append(obj)
-                break
-
-    return components, templates, subscribers
-
-def find_filesystem_templates(module_info, templates):
-    template_dir_name = module_info.getAnnotation(
-        'grok.templatedir',
-        module_info.name + '_templates')
-    template_dir = module_info.getResourcePath(template_dir_name)
-    if os.path.isdir(template_dir):
-        template_files = os.listdir(template_dir)
-        for template_file in template_files:
-            if template_file.startswith('.') or template_file.endswith('~'):
-                continue
-
-            if not template_file.endswith('.pt'):
-                raise GrokError("Unrecognized file '%s' in template directory "
-                                "'%s'." % (template_file, template_dir),
-                                module_info.getModule())
-
-            template_name = template_file[:-3] # cut off .pt
-            template_path = os.path.join(template_dir, template_file)
-
-            f = open(template_path, 'rb')
-            contents = f.read()
-            f.close()
-
-            template = grok.PageTemplate(contents)
-            template._annotateGrokInfo(template_name, template_path)
-
-            inline_template = templates.get(template_name)
-            if inline_template:
-                raise GrokError("Conflicting templates found for name '%s' "
-                                "in module %r, both inline and in template "
-                                "directory '%s'."
-                                % (template_name, module_info.getModule(),
-                                   template_dir), inline_template)
-            templates.register(template_name, template)
-
-
+    # XXX we should ideally also make it pluggable to register decorators like
+    # the ones for subscribers.
+    register_subscribers(module_info.getAnnotation('grok.subscribers', []))    
+    
 def register_static_resources(dotted_name, resource_directory):
     resource_factory = components.DirectoryResourceFactory(resource_directory,
                                                     dotted_name)
     component.provideAdapter(resource_factory, (IDefaultBrowserLayer,),
                              interface.Interface, name=dotted_name)
 
-def register_models(models):
-    for model in models:
-        # TODO minimal security here (read: everything is public)
-        if not getCheckerForInstancesOf(model):
-            defineChecker(model, NoProxy)
-
-def register_adapters(context, adapters):
-    for factory in adapters:
-        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:
-        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:
-        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 = 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)]
-
-        for method in methods:
-            # Make sure that the class inherits MethodPublisher, so that the views
-            # have a location
-            method_view = type(view.__name__, (view, MethodPublisher),
-                               {'__call__': method,
-                                '__Security_checker__': security.GrokChecker()}
-                               )
-            component.provideAdapter(
-                method_view, (view_context, IXMLRPCRequest), interface.Interface,
-                name=method.__name__)
-
-def register_views(context, views, templates):
-    for factory in views:
-        view_context = util.determine_class_context(factory, context)
-
-        # some extra work to take care of if this view is a form
-        if util.check_subclass(factory, components.EditForm):
-            formlib.setup_editform(factory, view_context)
-        elif util.check_subclass(factory, components.DisplayForm):
-            formlib.setup_displayform(factory, view_context)
-        elif util.check_subclass(factory, components.AddForm):
-            formlib.setup_addform(factory, view_context)
-
-        factory_name = factory.__name__.lower()
-
-        # find templates
-        template_name = util.class_annotation(factory, 'grok.template',
-                                              factory_name)
-        template = templates.get(template_name)
-
-        if factory_name != template_name:
-            # grok.template is being used
-            if templates.get(factory_name):
-                raise GrokError("Multiple possible templates for view %r. It "
-                                "uses grok.template('%s'), but there is also "
-                                "a template called '%s'."
-                                % (factory, template_name, factory_name),
-                                factory)
-
-        # we never accept a 'render' method for forms
-        if util.check_subclass(factory, components.Form):
-            if getattr(factory, 'render', None):
-                raise GrokError(
-                    "It is not allowed to specify a custom 'render' "
-                    "method for form %r. Forms either use the default "
-                    "template or a custom-supplied one." % factory,
-                    factory)
-
-        if template:
-            if getattr(factory, 'render', None):
-                # we do not accept render and template both for a view
-                raise GrokError(
-                    "Multiple possible ways to render view %r. "
-                    "It has both a 'render' method as well as "
-                    "an associated template." % factory,
-                    factory)
-
-            templates.markAssociated(template_name)
-            factory.template = template
-        else:
-            if not getattr(factory, 'render', None):
-                if util.check_subclass(factory, components.EditForm):
-                    # we have a edit form without template
-                    factory.template = formlib.defaultEditTemplate
-                elif util.check_subclass(factory, components.DisplayForm):
-                    # we have a display form without template
-                    factory.template = formlib.defaultDisplayTemplate
-                elif util.check_subclass(factory, components.AddForm):
-                    # we have an add form without template
-                    factory.template = formlib.defaultEditTemplate
-                else:
-                    # we do not accept a view without any way to render it
-                    raise GrokError("View %r has no associated template or "
-                                    "'render' method." % factory,
-                                    factory)
-
-        view_name = util.class_annotation(factory, 'grok.name', factory_name)
-        # __view_name__ is needed to support IAbsoluteURL on views
-        factory.__view_name__ = view_name
-        component.provideAdapter(factory,
-                                 adapts=(view_context, IDefaultBrowserLayer),
-                                 provides=interface.Interface,
-                                 name=view_name)
-
-        # 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, module_info):
-    for name, unassociated in templates.listUnassociatedTemplates():
-        util.check_context(unassociated, context)
-
-        module_info_ = module_info
-        class TemplateView(grok.View):
-            template = unassociated
-            module_info = module_info_
-
-        templates.markAssociated(name)
-
-        TemplateView.__view_name__ = name
-        component.provideAdapter(TemplateView,
-                                 adapts=(context, IDefaultBrowserLayer),
-                                 provides=interface.Interface,
-                                 name=name)
-
-        # TODO minimal security here (read: everything is public)
-        defineChecker(TemplateView, NoProxy)
-
 def register_subscribers(subscribers):
     for factory, subscribed in subscribers:
         component.provideHandler(factory, adapts=subscribed)
         for iface in subscribed:
             zope.component.interface.provideInterface('', iface)
 
-class TemplateRegistry(object):
-
-    def __init__(self):
-        self._reg = {}
-
-    def register(self, name, template):
-        self._reg[name] = dict(template=template, associated=False)
-
-    def markAssociated(self, name):
-        self._reg[name]['associated'] = True
-
-    def get(self, name):
-        entry = self._reg.get(name)
-        if entry is None:
-            return None
-        return entry['template']
-
-    def listUnassociatedTemplates(self):
-        for name, entry in self._reg.iteritems():
-            if not entry['associated']:
-                yield name, entry['template']
-
-
 # decorators
 class SubscribeDecorator:
     def __init__(self, *args):

Modified: grok/trunk/src/grok/components.py
===================================================================
--- grok/trunk/src/grok/components.py	2006-12-11 23:44:01 UTC (rev 71536)
+++ grok/trunk/src/grok/components.py	2006-12-11 23:58:05 UTC (rev 71537)
@@ -46,7 +46,26 @@
 
 from grok import util, security, interfaces
 
+class ClassGrokker(object):
+    # subclasses should have a component_class class variable
+    
+    def match(self, obj):
+        return util.check_subclass(obj, self.component_class)
+    
+    def register(self, context, name, factory, module_info, templates):
+        raise NotImplementedError
 
+
+class InstanceGrokker(object):
+    # subclasses should have a component_class class variable
+    
+    def match(self, obj):
+        return isinstance(obj, self.component_class)
+   
+    def register(self, context, name, instance, module_info, templates):
+        raise NotImplementedError
+
+
 class Model(Contained, persistent.Persistent):
     # XXX Inheritance order is important here. If we reverse this,
     # then containers can't be models anymore because no unambigous MRO

Modified: grok/trunk/src/grok/formlib.py
===================================================================
--- grok/trunk/src/grok/formlib.py	2006-12-11 23:44:01 UTC (rev 71536)
+++ grok/trunk/src/grok/formlib.py	2006-12-11 23:58:05 UTC (rev 71537)
@@ -69,13 +69,6 @@
     # this information during *runtime* not groktime.
     factory.__real_form__ = RealAddForm
 
-def initialize_schema(models):
-    """Set the default values as class attributes to make formlib work
-    """
-    for model in models:
-        for field in get_context_schema_fields(model):
-            setattr(model, field.__name__, field.default)
-
 def get_context_schema_fields(context):
     """Get the schema fields for a context object.
     """

Added: grok/trunk/src/grok/grokker.py
===================================================================
--- grok/trunk/src/grok/grokker.py	2006-12-11 23:44:01 UTC (rev 71536)
+++ grok/trunk/src/grok/grokker.py	2006-12-11 23:58:05 UTC (rev 71537)
@@ -0,0 +1,91 @@
+import grok
+from grok import util, templatereg
+
+class GrokkerRegistry(object):
+    def __init__(self):
+        self._grokkers = {}
+        
+    def registerGrokker(self, grokker):
+        self._grokkers[grokker.component_class] = grokker
+
+    def scan(self, module_info):
+        components = {}
+        for grokker in self._grokkers.values():
+            components[grokker.component_class] = []
+    
+        module = module_info.getModule()
+        for name in dir(module):
+            obj = getattr(module, name)
+            if name.startswith('__grok_'):
+                continue
+            if not util.defined_locally(obj, module_info.dotted_name):
+                continue
+            # XXX find way to get rid of this inner loop by doing hash table
+            # lookup?
+            for grokker in self._grokkers.values():
+                if grokker.match(obj):
+                    components[grokker.component_class].append((name, obj))
+                    break
+
+        return components
+
+    def grok(self, module_info):
+        scanned_results = self.scan(module_info)
+
+        # XXX hardcoded in here which base classes are possible contexts
+        # this should be made extensible
+        possible_contexts = [obj for (name, obj) in
+                             (scanned_results.get(grok.Model, []) +
+                              scanned_results.get(grok.Container, []))]
+        context = util.determine_module_context(module_info, possible_contexts)
+        
+        templates = templatereg.TemplateRegistry()
+
+        # XXX because templates are instances and we need access to
+        # registered module-level templates during class grokking,
+        # we need to make sure we do PageTemplate grokking before any
+        # other grokking. We need to revise this as we work out
+        # extensible template grokking. Possibly we need to introduce
+        # a priority for grokkers so we can sort them.
+        page_templates = scanned_results.pop(grok.PageTemplate, None)
+        if page_templates is not None:
+            grokker = self._grokkers[grok.PageTemplate]
+            for name, component in page_templates:
+                grokker.register(context,
+                                 name, component,
+                                 module_info, templates)
+
+        # XXX filesystem level templates need to be scanned for after
+        # inline templates to produce the right errors
+        templates.findFilesystem(module_info)
+
+        # now grok the rest
+        for component_class, components in scanned_results.items():
+            grokker = self._grokkers[component_class]
+            for name, component in components:
+                grokker.register(context,
+                                 name, component,
+                                 module_info, templates)
+    
+        templates.registerUnassociated(context, module_info)
+
+
+# deep meta mode here - we define grokkers for grok.ClassGrokker and
+# grok.InstanceGrokker.
+
+class MetaGrokker(grok.ClassGrokker):
+    def register(self, context, name, factory, module_info, templates):
+        grokkerRegistry.registerGrokker(factory())
+    
+class ClassGrokkerGrokker(MetaGrokker):
+    component_class = grok.ClassGrokker
+
+class InstanceGrokkerGrokker(MetaGrokker):
+    component_class = grok.InstanceGrokker
+
+# the global grokker registry
+grokkerRegistry = GrokkerRegistry()
+
+# register the meta grokkers manually as we can't grok those
+grokkerRegistry.registerGrokker(ClassGrokkerGrokker())
+grokkerRegistry.registerGrokker(InstanceGrokkerGrokker())

Modified: grok/trunk/src/grok/interfaces.py
===================================================================
--- grok/trunk/src/grok/interfaces.py	2006-12-11 23:44:01 UTC (rev 71536)
+++ grok/trunk/src/grok/interfaces.py	2006-12-11 23:58:05 UTC (rev 71537)
@@ -16,7 +16,10 @@
 from zope import interface
 
 class IGrokBaseClasses(interface.Interface):
-
+    ClassGrokker = interface.Attribute("Base class to define a class "
+                                       "grokker.")
+    InstanceGrokker = interface.Attribute("Base class to define an "
+                                          "instance grokker.")
     Model = interface.Attribute("Base class for persistent content objects "
                                 "(models).")
     Container = interface.Attribute("Base class for containers.")

Added: grok/trunk/src/grok/meta.py
===================================================================
--- grok/trunk/src/grok/meta.py	2006-12-11 23:44:01 UTC (rev 71536)
+++ grok/trunk/src/grok/meta.py	2006-12-11 23:58:05 UTC (rev 71537)
@@ -0,0 +1,175 @@
+import inspect
+
+from zope import interface, component
+from zope.security.checker import (defineChecker, getCheckerForInstancesOf,
+                                   NoProxy)
+from zope.publisher.interfaces.browser import (IDefaultBrowserLayer,
+                                               IBrowserRequest,
+                                               IBrowserPublisher)
+from zope.app.publisher.xmlrpc import MethodPublisher
+from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
+
+import grok
+from grok import util, components, security, formlib
+from grok.error import GrokError
+
+class ModelGrokker(grok.ClassGrokker):
+    component_class = grok.Model
+
+    def register(self, context, name, factory, module_info, templates):
+        if not getCheckerForInstancesOf(factory):
+            defineChecker(factory, NoProxy)
+
+        for field in formlib.get_context_schema_fields(factory):
+            setattr(factory, field.__name__, field.default)       
+
+class ContainerGrokker(ModelGrokker):
+    component_class = grok.Container
+    
+class AdapterGrokker(grok.ClassGrokker):
+    component_class = grok.Adapter
+
+    def register(self, context, name, factory, module_info, templates):
+        adapter_context = util.determine_class_context(factory, context)
+        util.check_implements_one(factory)
+        name = util.class_annotation(factory, 'grok.name', '')
+        try:
+            component.provideAdapter(factory, adapts=(adapter_context,),
+                                     name=name)
+        except TypeError:
+            import pdb; pdb.set_trace()
+            
+class MultiAdapterGrokker(grok.ClassGrokker):
+    component_class = grok.MultiAdapter
+    
+    def register(self, context, name, factory, module_info, templates):
+        util.check_implements_one(factory)
+        util.check_adapts(factory)
+        name = util.class_annotation(factory, 'grok.name', '')
+        component.provideAdapter(factory, name=name)
+
+class UtilityGrokker(grok.ClassGrokker):
+    component_class = grok.Utility
+
+    def register(self, context, name, factory, module_info, templates):
+        util.check_implements_one(factory)
+        name = util.class_annotation(factory, 'grok.name', '')
+        component.provideUtility(factory(), name=name)
+
+class XMLRPCGrokker(grok.ClassGrokker):
+    component_class = grok.XMLRPC
+    
+    def register(self, context, name, factory, module_info, templates):
+        view_context = util.determine_class_context(factory, context)
+        candidates = [getattr(factory, name) for name in dir(factory)]
+        methods = [c for c in candidates if inspect.ismethod(c)]
+
+        for method in methods:
+            # Make sure that the class inherits MethodPublisher, so that the
+            # views have a location
+            method_view = type(
+                factory.__name__, (factory, MethodPublisher),
+                {'__call__': method,
+                 '__Security_checker__': security.GrokChecker()}
+                )
+            component.provideAdapter(
+                method_view, (view_context, IXMLRPCRequest),
+                interface.Interface,
+                name=method.__name__)
+
+class ViewGrokker(grok.ClassGrokker):
+    component_class = grok.View
+
+    def register(self, context, name, factory, module_info, templates):
+        view_context = util.determine_class_context(factory, context)
+
+        factory.module_info = module_info
+
+        # some extra work to take care of if this view is a form
+        if util.check_subclass(factory, components.EditForm):
+            formlib.setup_editform(factory, view_context)
+        elif util.check_subclass(factory, components.DisplayForm):
+            formlib.setup_displayform(factory, view_context)
+        elif util.check_subclass(factory, components.AddForm):
+            formlib.setup_addform(factory, view_context)
+
+        factory_name = factory.__name__.lower()
+
+        # find templates
+        template_name = util.class_annotation(factory, 'grok.template',
+                                              factory_name)
+        template = templates.get(template_name)
+
+        if factory_name != template_name:
+            # grok.template is being used
+            if templates.get(factory_name):
+                raise GrokError("Multiple possible templates for view %r. It "
+                                "uses grok.template('%s'), but there is also "
+                                "a template called '%s'."
+                                % (factory, template_name, factory_name),
+                                factory)
+
+        # we never accept a 'render' method for forms
+        if util.check_subclass(factory, components.Form):
+            if getattr(factory, 'render', None):
+                raise GrokError(
+                    "It is not allowed to specify a custom 'render' "
+                    "method for form %r. Forms either use the default "
+                    "template or a custom-supplied one." % factory,
+                    factory)
+
+        if template:
+            if getattr(factory, 'render', None):
+                # we do not accept render and template both for a view
+                raise GrokError(
+                    "Multiple possible ways to render view %r. "
+                    "It has both a 'render' method as well as "
+                    "an associated template." % factory,
+                    factory)
+
+            templates.markAssociated(template_name)
+            factory.template = template
+        else:
+            if not getattr(factory, 'render', None):
+                if util.check_subclass(factory, components.EditForm):
+                    # we have a edit form without template
+                    factory.template = formlib.defaultEditTemplate
+                elif util.check_subclass(factory, components.DisplayForm):
+                    # we have a display form without template
+                    factory.template = formlib.defaultDisplayTemplate
+                elif util.check_subclass(factory, components.AddForm):
+                    # we have an add form without template
+                    factory.template = formlib.defaultEditTemplate
+                else:
+                    # we do not accept a view without any way to render it
+                    raise GrokError("View %r has no associated template or "
+                                    "'render' method." % factory,
+                                    factory)
+
+        view_name = util.class_annotation(factory, 'grok.name',
+                                          factory_name)
+        # __view_name__ is needed to support IAbsoluteURL on views
+        factory.__view_name__ = view_name
+        component.provideAdapter(factory,
+                                 adapts=(view_context, IDefaultBrowserLayer),
+                                 provides=interface.Interface,
+                                 name=view_name)
+
+        # TODO minimal security here (read: everything is public)
+        defineChecker(factory, NoProxy)
+
+class TraverserGrokker(grok.ClassGrokker):
+    component_class = grok.Traverser
+
+    def register(self, context, name, factory, module_info, templates):
+        factory_context = util.determine_class_context(factory, context)
+        component.provideAdapter(factory,
+                                 adapts=(factory_context, IBrowserRequest),
+                                 provides=IBrowserPublisher)
+
+class PageTemplateGrokker(grok.InstanceGrokker):
+    component_class = grok.PageTemplate
+
+    def register(self, context, name, instance, module_info, templates):
+        templates.register(name, instance)
+        instance._annotateGrokInfo(name, module_info.dotted_name)

Modified: grok/trunk/src/grok/scan.py
===================================================================
--- grok/trunk/src/grok/scan.py	2006-12-11 23:44:01 UTC (rev 71536)
+++ grok/trunk/src/grok/scan.py	2006-12-11 23:58:05 UTC (rev 71537)
@@ -120,3 +120,7 @@
 def module_info_from_dotted_name(dotted_name):
     module = resolve(dotted_name)
     return ModuleInfo(module.__file__, dotted_name)
+
+def module_info_from_module(module):
+    return ModuleInfo(module.__file__, module.__name__)
+

Added: grok/trunk/src/grok/templatereg.py
===================================================================
--- grok/trunk/src/grok/templatereg.py	2006-12-11 23:44:01 UTC (rev 71536)
+++ grok/trunk/src/grok/templatereg.py	2006-12-11 23:58:05 UTC (rev 71537)
@@ -0,0 +1,89 @@
+import os
+
+from zope import interface, component
+from zope.security.checker import (defineChecker, getCheckerForInstancesOf,
+                                   NoProxy)
+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+
+import grok
+from grok import util
+from grok.error import GrokError
+
+class TemplateRegistry(object):
+    def __init__(self):
+        self._reg = {}
+
+    def register(self, name, template):
+        self._reg[name] = dict(template=template, associated=False)
+
+    def markAssociated(self, name):
+        self._reg[name]['associated'] = True
+
+    def get(self, name):
+        entry = self._reg.get(name)
+        if entry is None:
+            return None
+        return entry['template']
+
+    def findFilesystem(self, module_info):
+        template_dir_name = module_info.getAnnotation(
+            'grok.templatedir',
+            module_info.name + '_templates')
+
+        template_dir = module_info.getResourcePath(template_dir_name)
+
+        if not os.path.isdir(template_dir):
+            return
+        
+        for template_file in os.listdir(template_dir):
+            if template_file.startswith('.') or template_file.endswith('~'):
+                continue
+
+            if not template_file.endswith('.pt'):
+                raise GrokError("Unrecognized file '%s' in template directory "
+                                "'%s'." % (template_file, template_dir),
+                                module_info.getModule())
+
+            template_name = template_file[:-3] # cut off .pt
+            template_path = os.path.join(template_dir, template_file)
+
+            f = open(template_path, 'rb')
+            contents = f.read()
+            f.close()
+
+            template = grok.PageTemplate(contents)
+            template._annotateGrokInfo(template_name, template_path)
+
+            inline_template = self.get(template_name)
+            if inline_template:
+                raise GrokError("Conflicting templates found for name '%s' "
+                                "in module %r, both inline and in template "
+                                "directory '%s'."
+                                % (template_name, module_info.getModule(),
+                                   template_dir), inline_template)
+            self.register(template_name, template)
+
+    def listUnassociated(self):
+        for name, entry in self._reg.iteritems():
+            if not entry['associated']:
+                yield name, entry['template']
+
+    def registerUnassociated(self, context, module_info):
+        for name, unassociated in self.listUnassociated():
+            util.check_context(unassociated, context)
+
+            module_info_ = module_info
+            class TemplateView(grok.View):
+                template = unassociated
+                module_info = module_info_
+
+            self.markAssociated(name)
+
+            TemplateView.__view_name__ = name
+            component.provideAdapter(TemplateView,
+                                     adapts=(context, IDefaultBrowserLayer),
+                                     provides=interface.Interface,
+                                     name=name)
+
+            # TODO minimal security here (read: everything is public)
+            defineChecker(TemplateView, NoProxy)



More information about the Checkins mailing list