[Checkins] SVN: five.grok/trunk/src/five/grok/ Template dir support.

Lennart Regebro regebro at gmail.com
Thu Jul 17 08:49:52 EDT 2008


Log message for revision 88434:
  Template dir support.
  

Changed:
  U   five.grok/trunk/src/five/grok/__init__.py
  U   five.grok/trunk/src/five/grok/components.py
  U   five.grok/trunk/src/five/grok/directive.py
  U   five.grok/trunk/src/five/grok/meta.py
  A   five.grok/trunk/src/five/grok/templatereg.py
  U   five.grok/trunk/src/five/grok/testing.py
  U   five.grok/trunk/src/five/grok/tests/test_all.py
  A   five.grok/trunk/src/five/grok/tests/view/ambiguouscontext.py
  A   five.grok/trunk/src/five/grok/tests/view/dirtemplate.py
  A   five.grok/trunk/src/five/grok/tests/view/dirtemplate_templates/
  A   five.grok/trunk/src/five/grok/tests/view/dirtemplate_templates/cavepainting.pt
  A   five.grok/trunk/src/five/grok/tests/view/dirtemplate_templates/food.pt

-=-
Modified: five.grok/trunk/src/five/grok/__init__.py
===================================================================
--- five.grok/trunk/src/five/grok/__init__.py	2008-07-17 11:49:10 UTC (rev 88433)
+++ five.grok/trunk/src/five/grok/__init__.py	2008-07-17 12:49:51 UTC (rev 88434)
@@ -8,7 +8,7 @@
 from grokcore.component.decorators import subscribe
 
 from five.grok.components import View, Model
-from five.grok.directive import require, layer
+from five.grok.directive import require, layer, template, templatedir
 
 # I don't know why this is necessary:
 from five.grok import testing
\ No newline at end of file

Modified: five.grok/trunk/src/five/grok/components.py
===================================================================
--- five.grok/trunk/src/five/grok/components.py	2008-07-17 11:49:10 UTC (rev 88433)
+++ five.grok/trunk/src/five/grok/components.py	2008-07-17 12:49:51 UTC (rev 88434)
@@ -1,14 +1,19 @@
+import martian
 from zope import interface
+from zope import component
 from zope.annotation.interfaces import IAttributeAnnotatable
+from zope.app.pagetemplate.engine import TrustedAppPT
+from zope.pagetemplate import pagetemplate, pagetemplatefile
+from zope.publisher.publish import mapply
 
 from OFS.SimpleItem import SimpleItem
-from Products.Five import BrowserView
-
+from zope.publisher.browser import BrowserPage
 from five.grok import interfaces
 
 from zope.app.container.contained import Contained
 import persistent
 
+# XXX Should probably be a SimpleItem.
 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
@@ -16,8 +21,201 @@
     interface.implements(IAttributeAnnotatable, interfaces.IContext)
     
     
-class View(BrowserView):
-    
+class View(BrowserPage):
+    interface.implements(interfaces.IGrokView)
+
+    def __init__(self, context, request):
+        super(View, self).__init__(context, request)
+        self.__name__ = self.__view_name__
+        self.static = component.queryAdapter(
+            self.request,
+            interface.Interface,
+            name=self.module_info.package_dotted_name
+            )
+
+    @property
+    def response(self):
+        return self.request.response
+
     def __call__(self):
-        return self.render()
+        mapply(self.update, (), self.request)
+        if self.request.response.getStatus() in (302, 303):
+            # A redirect was triggered somewhere in update().  Don't
+            # continue rendering the template or doing anything else.
+            return
 
+        template = getattr(self, 'template', None)
+        if template is not None:
+            return self._render_template()
+        return mapply(self.render, (), self.request)
+
+    def _render_template(self):
+        return self.template.render(self)
+
+    def default_namespace(self):
+        namespace = {}
+        namespace['context'] = self.context
+        namespace['request'] = self.request
+        namespace['static'] = self.static
+        namespace['view'] = self
+        return namespace
+
+    def namespace(self):
+        return {}
+
+    def __getitem__(self, key):
+        # This is BBB code for Zope page templates only:
+        if not isinstance(self.template, PageTemplate):
+            raise AttributeError("View has no item %s" % key)
+
+        value = self.template._template.macros[key]
+        # When this deprecation is done with, this whole __getitem__ can
+        # be removed.
+        warnings.warn("Calling macros directly on the view is deprecated. "
+                      "Please use context/@@viewname/macros/macroname\n"
+                      "View %r, macro %s" % (self, key),
+                      DeprecationWarning, 1)
+        return value
+
+
+    def url(self, obj=None, name=None, data=None):
+        """Return string for the URL based on the obj and name. The data
+        argument is used to form a CGI query string.
+        """
+        if isinstance(obj, basestring):
+            if name is not None:
+                raise TypeError(
+                    'url() takes either obj argument, obj, string arguments, '
+                    'or string argument')
+            name = obj
+            obj = None
+
+        if name is None and obj is None:
+            # create URL to view itself
+            obj = self
+        elif name is not None and obj is None:
+            # create URL to view on context
+            obj = self.context
+
+        if data is None:
+            data = {}
+        else:
+            if not isinstance(data, dict):
+                raise TypeError('url() data argument must be a dict.')
+
+        return util.url(self.request, obj, name, data=data)
+
+    def application_url(self, name=None):
+        obj = self.context
+        while obj is not None:
+            if isinstance(obj, Application):
+                return self.url(obj, name)
+            obj = obj.__parent__
+        raise ValueError("No application found.")
+
+    def redirect(self, url):
+        return self.request.response.redirect(url)
+
+    def update(self):
+        pass
+
+    def flash(self, message, type='message'):
+        source = component.getUtility(
+            z3c.flashmessage.interfaces.IMessageSource, name='session')
+        source.send(message, type)
+    
+    
+class BaseTemplate(object):
+    """Any sort of page template"""
+
+    interface.implements(interfaces.ITemplate)
+
+    __grok_name__ = ''
+    __grok_location__ = ''
+
+    def __repr__(self):
+        return '<%s template in %s>' % (self.__grok_name__,
+                                        self.__grok_location__)
+
+    def _annotateGrokInfo(self, name, location):
+        self.__grok_name__ = name
+        self.__grok_location__ = location
+
+    def _initFactory(self, factory):
+        pass
+
+class GrokTemplate(BaseTemplate):
+    """A slightly more advanced page template
+
+    This provides most of what a page template needs and is a good base for
+    writing your own page template"""
+
+    def __init__(self, string=None, filename=None, _prefix=None):
+
+        # __grok_module__ is needed to make defined_locally() return True for
+        # inline templates
+        # XXX unfortunately using caller_module means that care must be taken
+        # when GrokTemplate is subclassed. You can not do a super().__init__
+        # for example.
+        self.__grok_module__ = martian.util.caller_module()
+
+        if not (string is None) ^ (filename is None):
+            raise AssertionError(
+                "You must pass in template or filename, but not both.")
+
+        if string:
+            self.setFromString(string)
+        else:
+            if _prefix is None:
+                module = sys.modules[self.__grok_module__]
+                _prefix = os.path.dirname(module.__file__)
+            self.setFromFilename(filename, _prefix)
+
+    def __repr__(self):
+        return '<%s template in %s>' % (self.__grok_name__,
+                                        self.__grok_location__)
+
+    def _annotateGrokInfo(self, name, location):
+        self.__grok_name__ = name
+        self.__grok_location__ = location
+
+    def _initFactory(self, factory):
+        pass
+
+    def namespace(self, view):
+        # By default use the namespaces that are defined as the
+        # default by the view implementation.
+        return view.default_namespace()
+
+    def getNamespace(self, view):
+        namespace = self.namespace(view)
+        namespace.update(view.namespace())
+        return namespace
+
+class TrustedPageTemplate(TrustedAppPT, pagetemplate.PageTemplate):
+    pass
+
+class TrustedFilePageTemplate(TrustedAppPT, pagetemplatefile.PageTemplateFile):
+    pass
+
+class PageTemplate(GrokTemplate):
+
+    def setFromString(self, string):
+        zpt = TrustedPageTemplate()
+        if martian.util.not_unicode_or_ascii(string):
+            raise ValueError("Invalid page template. Page templates must be "
+                             "unicode or ASCII.")
+        zpt.write(string)
+        self._template = zpt
+
+    def setFromFilename(self, filename, _prefix=None):
+        self._template = TrustedFilePageTemplate(filename, _prefix)
+
+    def _initFactory(self, factory):
+        factory.macros = self._template.macros
+
+    def render(self, view):
+        namespace = self.getNamespace(view)
+        template = self._template
+        namespace.update(template.pt_getContext())
+        return template.pt_render(namespace)

Modified: five.grok/trunk/src/five/grok/directive.py
===================================================================
--- five.grok/trunk/src/five/grok/directive.py	2008-07-17 11:49:10 UTC (rev 88433)
+++ five.grok/trunk/src/five/grok/directive.py	2008-07-17 12:49:51 UTC (rev 88434)
@@ -32,9 +32,19 @@
         self.set(func, [permission])
         return func
 
-
+    
 class layer(martian.Directive):
     scope = martian.CLASS_OR_MODULE
     store = martian.ONCE
     validate = martian.validateInterfaceOrClass
 
+class template(martian.Directive):
+    scope = martian.CLASS
+    store = martian.ONCE
+    validate = martian.validateText
+
+class templatedir(martian.Directive):
+    scope = martian.MODULE
+    store = martian.ONCE
+    validate = martian.validateText
+    
\ No newline at end of file

Modified: five.grok/trunk/src/five/grok/meta.py
===================================================================
--- five.grok/trunk/src/five/grok/meta.py	2008-07-17 11:49:10 UTC (rev 88433)
+++ five.grok/trunk/src/five/grok/meta.py	2008-07-17 12:49:51 UTC (rev 88434)
@@ -1,4 +1,5 @@
-import martian.util
+import martian
+from martian import util
 from martian.error import GrokError
 from zope import interface, component
 from zope.publisher.interfaces.browser import IDefaultBrowserLayer
@@ -8,6 +9,7 @@
 from Products.Five.security import protectClass
 from Globals import InitializeClass as initializeClass
 
+import templatereg
 
 def default_view_name(factory, module=None, **data):
     return factory.__name__.lower()
@@ -39,7 +41,7 @@
 
         # safety belt: make sure that the programmer didn't use
         # @grok.require on any of the view's methods.
-        methods = martian.util.methods_from_class(factory)
+        methods = util.methods_from_class(factory)
         for method in methods:
             if grok.require.bind().get(method) is not None:
                 raise GrokError('The @grok.require decorator is used for '
@@ -74,9 +76,60 @@
 
     def checkTemplates(self, templates, module_info, factory):
         def has_render(factory):
-            return (getattr(factory, 'render', None) and
-                    not util.check_subclass(factory, grok.components.GrokForm))
+            # XXX We haven't implemented GrokForm yet
+            return (getattr(factory, 'render', None))
+                    #and
+                    #not util.check_subclass(factory, grok.components.GrokForm))
         def has_no_render(factory):
             return not getattr(factory, 'render', None)
         templates.checkTemplates(module_info, factory, 'view',
                                  has_render, has_no_render)
+        
+        
+class FilesystemPageTemplateGrokker(martian.GlobalGrokker):
+    # do this early on, but after ModulePageTemplateGrokker, as
+    # findFilesystem depends on module-level templates to be
+    # already grokked for error reporting
+    martian.priority(999)
+
+    def grok(self, name, module, module_info, config, **kw):
+        templates = module_info.getAnnotation('grok.templates', None)
+        if templates is None:
+            return False
+        config.action(
+            discriminator=None,
+            callable=templates.findFilesystem,
+            args=(module_info,)
+            )
+        return True
+
+class TemplateGrokker(martian.GlobalGrokker):
+    # this needs to happen before any other grokkers execute that use
+    # the template registry
+    martian.priority(1001)
+
+    def grok(self, name, module, module_info, config, **kw):
+        module.__grok_templates__ = templatereg.TemplateRegistry()
+        return True
+
+class ModulePageTemplateGrokker(martian.InstanceGrokker):
+    martian.component(grok.components.BaseTemplate)
+    # this needs to happen before any other grokkers execute that actually
+    # use the templates
+    martian.priority(1000)
+    
+    def grok(self, name, instance, module_info, config, **kw):
+        templates = module_info.getAnnotation('grok.templates', None)
+        if templates is None:
+            return False
+        config.action(
+            discriminator=None,
+            callable=templates.register,
+            args=(name, instance)
+            )
+        config.action(
+            discriminator=None,
+            callable=instance._annotateGrokInfo,
+            args=(name, module_info.dotted_name)
+            )
+        return True    
\ No newline at end of file

Added: five.grok/trunk/src/five/grok/templatereg.py
===================================================================
--- five.grok/trunk/src/five/grok/templatereg.py	                        (rev 0)
+++ five.grok/trunk/src/five/grok/templatereg.py	2008-07-17 12:49:51 UTC (rev 88434)
@@ -0,0 +1,131 @@
+from martian.error import GrokError
+from martian import util
+
+import os
+import zope.component
+from five import grok
+import warnings
+
+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 = grok.templatedir.bind().get(
+            module=module_info.getModule())
+        if template_dir_name is None:
+            template_dir_name = module_info.name + '_templates'
+
+        template_dir = module_info.getResourcePath(template_dir_name)
+
+        if not os.path.isdir(template_dir):
+            return
+
+        if module_info.isPackage():
+            return
+
+        for template_file in os.listdir(template_dir):
+            if template_file.startswith('.') or template_file.endswith('~'):
+                continue
+
+            template_name, extension = os.path.splitext(template_file)
+            extension = extension[1:] # Get rid of the leading dot.
+            template_factory = zope.component.queryUtility(
+                grok.interfaces.ITemplateFileFactory,
+                name=extension)
+
+            if template_factory is None:
+                # Warning when importing files. This should be
+                # allowed because people may be using editors that generate
+                # '.bak' files and such.
+                warnings.warn("File '%s' has an unrecognized extension in "
+                              "directory '%s'" %
+                              (template_file, template_dir), UserWarning, 2)
+                continue
+
+            inline_template = self.get(template_name)
+            if inline_template:
+                raise GrokError("Conflicting templates found for name '%s' "
+                                "in module %r, either inline and in template "
+                                "directory '%s', or two templates with the "
+                                "same name and different extensions."
+                                % (template_name, module_info.getModule(),
+                                   template_dir), inline_template)
+
+            template = template_factory(template_file, template_dir)
+            template_path = os.path.join(template_dir, template_file)
+            template._annotateGrokInfo(template_name, template_path)
+
+            self.register(template_name, template)
+
+    def listUnassociated(self):
+        for name, entry in self._reg.iteritems():
+            if not entry['associated']:
+                yield name
+
+    def checkUnassociated(self, module_info):
+        unassociated = list(self.listUnassociated())
+        if unassociated:
+            msg = (
+                "Found the following unassociated template(s) when "
+                "grokking %r: %s.  Define view classes inheriting "
+                "from grok.View to enable the template(s)." % (
+                module_info.dotted_name, ', '.join(unassociated)))
+            warnings.warn(msg, UserWarning, 1)
+
+    def checkTemplates(self, module_info, factory, component_name,
+                       has_render, has_no_render):
+        factory_name = factory.__name__.lower()
+        template_name = grok.template.bind().get(factory)
+        if template_name is None:
+            template_name = factory_name
+
+        if factory_name != template_name:
+            # grok.template is being used
+
+            if self.get(factory_name):
+                raise GrokError("Multiple possible templates for %s %r. It "
+                                "uses grok.template('%s'), but there is also "
+                                "a template called '%s'."
+                                % (component_name, factory, template_name,
+                                   factory_name), factory)
+        template = self.get(template_name)
+        if template is not None:
+            if has_render(factory):
+                # we do not accept render and template both for a view
+                # (unless it's a form, they happen to have render.
+                raise GrokError(
+                    "Multiple possible ways to render %s %r. "
+                    "It has both a 'render' method as well as "
+                    "an associated template." %
+                    (component_name, factory), factory)
+            self.markAssociated(template_name)
+            factory.template = template
+            template._initFactory(factory)
+        else:
+            if has_no_render(factory):
+                # we do not accept a view without any way to render it
+                raise GrokError("%s %r has no associated template or "
+                                "'render' method." %
+                                (component_name.title(), factory), factory)
+
+class PageTemplateFileFactory(grok.GlobalUtility):
+
+    grok.implements(grok.interfaces.ITemplateFileFactory)
+    grok.name('pt')
+
+    def __call__(self, filename, _prefix=None):
+        return grok.components.PageTemplate(filename=filename, _prefix=_prefix)


Property changes on: five.grok/trunk/src/five/grok/templatereg.py
___________________________________________________________________
Name: svn:keywords
   + Id

Modified: five.grok/trunk/src/five/grok/testing.py
===================================================================
--- five.grok/trunk/src/five/grok/testing.py	2008-07-17 11:49:10 UTC (rev 88433)
+++ five.grok/trunk/src/five/grok/testing.py	2008-07-17 12:49:51 UTC (rev 88434)
@@ -22,6 +22,7 @@
     config = ConfigurationMachine()
     zcml.do_grok('grokcore.component.meta', config)
     zcml.do_grok('five.grok.meta', config)
+    zcml.do_grok('five.grok.templatereg', config)
     zcml.do_grok(module_name, config)
     config.execute_actions()
 

Modified: five.grok/trunk/src/five/grok/tests/test_all.py
===================================================================
--- five.grok/trunk/src/five/grok/tests/test_all.py	2008-07-17 11:49:10 UTC (rev 88433)
+++ five.grok/trunk/src/five/grok/tests/test_all.py	2008-07-17 12:49:51 UTC (rev 88434)
@@ -1,4 +1,5 @@
 import unittest
+from pkg_resources import resource_listdir
 
 from zope.testing import doctestunit, doctest
 from zope.component import testing
@@ -18,6 +19,28 @@
     zcml.load_config('configure.zcml', package=five.grok)
     zcml.load_config('configure.zcml', package=five.grok.tests)
 
+    
+def suiteFromPackage(name):
+    files = resource_listdir(__name__, name)
+    suite = unittest.TestSuite()
+    for filename in files:
+        if not filename.endswith('.py'):
+            continue
+        if filename.endswith('_fixture.py'):
+            continue
+        if filename == '__init__.py':
+            continue
+
+        dottedname = 'five.grok.tests.%s.%s' % (name, filename[:-3])
+        test = doctest.DocTestSuite(dottedname,
+                                    setUp=setUp,
+                                    tearDown=testing.tearDown, 
+                                    optionflags=doctest.ELLIPSIS+
+                                                doctest.NORMALIZE_WHITESPACE)
+
+        suite.addTest(test)
+    return suite
+    
 def test_suite():
     return unittest.TestSuite([
 
@@ -41,12 +64,19 @@
         doctestunit.DocTestSuite(
             module='five.grok.tests.subscribers',
             setUp=setUp, tearDown=testing.tearDown),
+        
+        suiteFromPackage('view'),
 
-        doctestunit.DocTestSuite(
-            module='five.grok.tests.view.view',
-            setUp=setUp, tearDown=testing.tearDown, 
-            optionflags=doctest.ELLIPSIS+doctest.NORMALIZE_WHITESPACE)
+        #doctestunit.DocTestSuite(
+            #module='five.grok.tests.view.view',
+            #setUp=setUp, tearDown=testing.tearDown, 
+            #optionflags=doctest.ELLIPSIS+doctest.NORMALIZE_WHITESPACE),
 
+        #doctestunit.DocTestSuite(
+            #module='five.grok.tests.view.ambiguouscontext',
+            #setUp=setUp, tearDown=testing.tearDown, 
+            #optionflags=doctest.ELLIPSIS+doctest.NORMALIZE_WHITESPACE),
+        
         # Integration tests that use ZopeTestCase
         #ztc.ZopeDocFileSuite(
         #    'README.txt', package='something.foo',
@@ -54,8 +84,8 @@
 
         #ztc.FunctionalDocFileSuite(
         #    'browser.txt', package='something.foo'),
-
         ])
-
+        
+        
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')

Added: five.grok/trunk/src/five/grok/tests/view/ambiguouscontext.py
===================================================================
--- five.grok/trunk/src/five/grok/tests/view/ambiguouscontext.py	                        (rev 0)
+++ five.grok/trunk/src/five/grok/tests/view/ambiguouscontext.py	2008-07-17 12:49:51 UTC (rev 88434)
@@ -0,0 +1,22 @@
+"""
+Templates with ambiguous context cannot be grokked:
+
+  >>> grok.testing.grok(__name__)
+  Traceback (most recent call last):
+    ...
+  GrokError: Multiple possible contexts for
+  <class 'five.grok.tests.view.ambiguouscontext.Club'>, please use the
+  'context' directive.
+
+"""
+
+from five import grok
+
+class Cave(grok.Model):
+    pass
+
+class Mammoth(grok.Model):
+    pass
+
+class Club(grok.View):
+    pass


Property changes on: five.grok/trunk/src/five/grok/tests/view/ambiguouscontext.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: five.grok/trunk/src/five/grok/tests/view/dirtemplate.py
===================================================================
--- five.grok/trunk/src/five/grok/tests/view/dirtemplate.py	                        (rev 0)
+++ five.grok/trunk/src/five/grok/tests/view/dirtemplate.py	2008-07-17 12:49:51 UTC (rev 88434)
@@ -0,0 +1,36 @@
+"""
+Templates can also be found in a directory with the same name as the module:
+
+  >>> grok.testing.grok(__name__)
+  
+  >>> manfred = Mammoth()
+  >>> from zope.publisher.browser import TestRequest
+  >>> request = TestRequest()
+  >>> from zope import component
+  >>> view = component.getMultiAdapter((manfred, request), name='cavepainting')
+  >>> print view()
+  <html>
+  <body>
+  A cave painting.
+  </body>
+  </html>
+
+  >>> view = component.getMultiAdapter((manfred, request), name='food')
+  >>> print view()
+  <html>
+  <body>
+  ME GROK EAT MAMMOTH!
+  </body>
+  </html>
+
+"""
+from five import grok
+
+class Mammoth(grok.Model):
+    pass
+
+class CavePainting(grok.View):
+    pass
+
+class Food(grok.View):
+    pass


Property changes on: five.grok/trunk/src/five/grok/tests/view/dirtemplate.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: five.grok/trunk/src/five/grok/tests/view/dirtemplate_templates/cavepainting.pt
===================================================================
--- five.grok/trunk/src/five/grok/tests/view/dirtemplate_templates/cavepainting.pt	                        (rev 0)
+++ five.grok/trunk/src/five/grok/tests/view/dirtemplate_templates/cavepainting.pt	2008-07-17 12:49:51 UTC (rev 88434)
@@ -0,0 +1,5 @@
+<html>
+<body>
+A cave painting.
+</body>
+</html>


Property changes on: five.grok/trunk/src/five/grok/tests/view/dirtemplate_templates/cavepainting.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: five.grok/trunk/src/five/grok/tests/view/dirtemplate_templates/food.pt
===================================================================
--- five.grok/trunk/src/five/grok/tests/view/dirtemplate_templates/food.pt	                        (rev 0)
+++ five.grok/trunk/src/five/grok/tests/view/dirtemplate_templates/food.pt	2008-07-17 12:49:51 UTC (rev 88434)
@@ -0,0 +1,5 @@
+<html>
+<body>
+ME GROK EAT MAMMOTH!
+</body>
+</html>


Property changes on: five.grok/trunk/src/five/grok/tests/view/dirtemplate_templates/food.pt
___________________________________________________________________
Name: svn:eol-style
   + native



More information about the Checkins mailing list