[Checkins] SVN: grok/branches/regebro-guido-templates/ Simplification of the template story.

Lennart Regebro regebro at gmail.com
Fri Nov 2 12:19:39 EDT 2007


Log message for revision 81410:
  Simplification of the template story.
  

Changed:
  U   grok/branches/regebro-guido-templates/doc/minitutorials/template-languages.txt
  U   grok/branches/regebro-guido-templates/src/grok/components.py
  U   grok/branches/regebro-guido-templates/src/grok/interfaces.py
  U   grok/branches/regebro-guido-templates/src/grok/meta.py
  A   grok/branches/regebro-guido-templates/src/grok/tests/template/
  A   grok/branches/regebro-guido-templates/src/grok/tests/template/__init__.py
  A   grok/branches/regebro-guido-templates/src/grok/tests/template/lascaux.html
  A   grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability.py
  A   grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability_templates/
  A   grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability_templates/kakadu.mtl
  A   grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability_templates/sierra.mtl
  U   grok/branches/regebro-guido-templates/src/grok/tests/test_grok.py

-=-
Modified: grok/branches/regebro-guido-templates/doc/minitutorials/template-languages.txt
===================================================================
--- grok/branches/regebro-guido-templates/doc/minitutorials/template-languages.txt	2007-11-02 11:27:23 UTC (rev 81409)
+++ grok/branches/regebro-guido-templates/doc/minitutorials/template-languages.txt	2007-11-02 16:19:38 UTC (rev 81410)
@@ -11,147 +11,71 @@
 want in Grok, but to get the automatic association between template
 objects/files and views you need to do a little bit of extra work.
 
+
 Inline templates
 ----------------
 "Inline" templates are templates where the template code is written inside the
 python class. To get the automatic association to a view class, you need to
-write a class that subclasses from grok.components.GrokPageTemplate, so it
-will be picked up by the template grokkers. It also needs to implement
-grok.interfaces.ITemplateFile. Here is an example:
+write a class that subclasses from grok.components.GrokPageTemplate. There are 
+however some methods you need to override, namely the fromFile and fromString
+factory methods, and the render method.
 
+Here is an example of a minimal page template integration:
+
 .. code-block:: python
 
-    class MyPageTemplate(grok.components.GrokPageTemplate):
+class MyPageTemplate(grok.components.GrokPageTemplate):
 
-        def __init__(self, html):
-            self._template = MyTemplate(html)
-            self.__grok_module__ = martian.util.caller_module()
+    def fromTemplate(self, template):
+        return MyTemplate(template)
 
-        def _initFactory(self, factory):
-            pass
-    
-        def namespace(self, view):
-            namespace = {}
-            namespace['request'] = view.request
-            namespace['view'] = view
-            namespace['context'] = view.context
-            # XXX need to check whether we really want to put None here if missing
-            namespace['static'] = view.static
-            
-            return namespace
-    
-        def render(self, view):
-            namespace = self.namespace(view)
-            namespace.update(view.namespace())        
-            return self._template.render(**namespace)
+    def fromFile(self, filename, _prefix=None):
+        file = open(os.path.join(_prefix, filename))
+        return MyTemplate(file.read())
 
-In the __init__ method of a typical template you pass in what should be
-rendered. This is usually HTML, but can equally well be XML or text, or
-whatever your template renders. Note the line setting
-self.__grok_module__.This is necessary to support inline templates.
+    def render(self, view):
+        return self.getTemplate().render(**self.getNamespace(view))
 
-The _initFactory method is a call made when setting up views (when the server
-starts). Basically, you can here initialize the view in ways needed to your
-template language. For example, Zope Page Templates set the macro attribute on
-the view class, so that you can access ZPT macros with the standard syntax of
-"context/@@viewname/macros/themacro". Most likely your class doesn't need to
-do anything here.
-
-The namespace method should return a dictionary with variables that should be
-included in the namespace that is specific for your template language. Common
-variables is 'context', 'view', 'request' and 'static', which is what the
-default is if you don't override this method. Your language might want to use
-more or fewer, depending on how the language works. For example, a template
-language like Kid or Templess that doesn't have object attribute access would
-have no use of any of these variables, and instead you need to in each view
-override the namespace() method to pass in exactly the values needed. If your
-template language has attribute access you probably don't need to override
-this method at all.
-
-Lastly, render() is the method normally used to render a template attached to
-a view. Here you do whatever necessary to render your template. This is
-usually to call view.namespace() and pass that into the method that renders
-the template. The above example is for Genshi, and something similar would
-probably be used for your template language too.
-
 With this class finished you can create an inline template, like this:
 
 .. code-block:: python
 
-    class AView(grok.View):
-        pass
-        
-    aview = MyPageTemplate('<html><body>Some text</body></html>')
+>>> class AView(grok.View):
+>>>     pass
+>>>     
+>>> aview = MyPageTemplate('<html><body>Some text</body></html>')
 
+And also you can create a filebase template, inline:
 
-File templates
---------------
-Mostly you want your templates to reside on disk. To do this you need a
-file template class. It looks and works as the template class above, except
-that it loads the template from a file instead:
-
 .. code-block:: python
 
-    class MyPageTemplateFile(grok.components.GrokPageTemplate):
+>>> class AView(grok.View):
+>>>     pass
+>>>     
+>>> aview = MyTemplateFile('lasceuax.html')
 
-        def __init__(self, filename, _prefix=None):
-            file = open(os.path.join(_prefix, filename))
-            self._template = MyTemplate(file.read())
-            self.__grok_module__ = martian.util.caller_module()
-    
-        def render(self, view):
-            return self._template.render(**self.namespace(view))
 
-Here _initFactory() and namespace() is left out, as GrokPageTemplate alredy
-has them as defaults. The __init__ now takes two parameters, filename and
-_prefix, which is the directory in which the file resides. Although there is
-no requirement that these are the parameters used it is a good idea, since
-that makes the next step easier.
-
-Now you can use this filebase template:
-
-.. code-block:: python
-
-    class AView(grok.View):
-        pass
-        
-    aview = MyPageTemplateFile('lasceuax.html', '.')
-
-
 Templates in the _templates directory
 -------------------------------------
 
 The most common usecase is however to place the templates in the views
 template directory. To do that, a global utility that generates
 MyPageTemplates from the filenames found is needed. That utility needs to
-implement the ITemplateFileFactory interface. The easiest way of doing this is
-to let your template file class implement it directly, by having the above
-filename and _prefix parameters in the __init__ call and tell the component
-architecture that the class provides the interface with a classProvides call.
-You also need to tell Grok that the class should be a direct global utility by
-subclassing from grok.GlobalUtility, and annotating it with a grok.direct()
-annotation. Lastly you need to choose an extension for your template files,
-and set the grok.name to that extension:
+implement the ITemplateFileFactory interface. 
 
 .. code-block:: python
 
-    class MyPageTemplateFile(grok.components.GrokPageTemplate):
-    
-        zope.interface.implements(grok.interfaces.ITemplateFile)
-        zope.interface.classProvides(grok.interfaces.ITemplateFileFactory)
-        grok.name('mtl')
-        grok.direct()
+class MyPageTemplateFactory(grok.GlobalUtility):
 
-        def __init__(self, filename, _prefix=None):
-            file = open(os.path.join(_prefix, filename)
-            self._template = MyTemplate(file.read())
-            self.__grok_module__ = martian.util.caller_module()
-    
-        def render(self, view):
-            return self._template.render(**self.namespace(view))
+    grok.implements(grok.interfaces.ITemplateFileFactory)
+    grok.name('mtl')
 
-When your module gets grokked, Grok will now pick up on the MyPageTemplateFile
-class, register it as a global utility for templates with the '.mtl' extension
-and you can start creating .mtl files in the template directory for your class.
+    def __call__(self, filename, _prefix=None):
+        return MyPageTemplate(filename=filename, _prefix=_prefix)
 
-Have fun!
+When your module gets grokked, Grok will now pick up on the
+MyPageTemplateFactory class, register it as a global utility for templates
+with the '.mtl' extension and you can start creating .mtl files in the
+template directory for your class.
+
+That's all you need! Have fun!

Modified: grok/branches/regebro-guido-templates/src/grok/components.py
===================================================================
--- grok/branches/regebro-guido-templates/src/grok/components.py	2007-11-02 11:27:23 UTC (rev 81409)
+++ grok/branches/regebro-guido-templates/src/grok/components.py	2007-11-02 16:19:38 UTC (rev 81410)
@@ -13,6 +13,7 @@
 ##############################################################################
 """Grok components"""
 
+import sys
 import os
 import persistent
 import urllib
@@ -190,8 +191,11 @@
         return simplejson.dumps(method_result)
 
 
-class GrokPageTemplate(object):
+class BasePageTemplate(object):
+    """Any sort of page template"""
     
+    interface.implements(interfaces.ITemplate)
+
     __grok_name__ = ''
     __grok_location__ = ''
 
@@ -216,8 +220,62 @@
         
         return namespace
 
+class GrokPageTemplate(BasePageTemplate):
+    """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, template=None, filename=None, _prefix=None):
 
-class PageTemplate(GrokPageTemplate, TrustedAppPT, pagetemplate.PageTemplate):
+        # __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 GrokPageTemplate is subclassed.
+        self.__grok_module__ = martian.util.caller_module()
+        
+        if not (template is None) ^ (filename is None):
+            raise AssertionError("You must pass in template or filename, but not both.")
+        
+        if template:
+            self._template = self.fromTemplate(template)
+        else:
+            if _prefix is None:
+                module = sys.modules[self.__grok_module__]
+                _prefix = os.path.dirname(module.__file__)
+            self._template = self.fromFile(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):
+        namespace = {}
+        namespace['request'] = view.request
+        namespace['view'] = view
+        namespace['context'] = view.context
+        # XXX need to check whether we really want to put None here if missing
+        namespace['static'] = view.static
+        
+        return namespace
+    
+    def getNamespace(self, view):
+        namespace = self.namespace(view)
+        namespace.update(view.namespace())
+        return namespace
+    
+    def getTemplate(self):
+        return self._template
+
+
+class PageTemplate(BasePageTemplate, TrustedAppPT, pagetemplate.PageTemplate):
     expand = 0
 
     def __init__(self, template):
@@ -237,7 +295,7 @@
         factory.macros = self.macros
 
     def namespace(self, view):
-        namespace = GrokPageTemplate.namespace(self, view)
+        namespace = BasePageTemplate.namespace(self, view)
         namespace.update(self.pt_getContext())
         return namespace
 
@@ -247,11 +305,9 @@
         return self.pt_render(namespace)
 
 
-class PageTemplateFile(GrokPageTemplate, TrustedAppPT,
+class PageTemplateFile(BasePageTemplate, TrustedAppPT,
                        pagetemplatefile.PageTemplateFile):
     
-    interface.implements(interfaces.ITemplateFile)
-
     def __init__(self, filename, _prefix=None):
         _prefix = self.get_path_from_prefix(_prefix)
         super(PageTemplateFile, self).__init__(filename, _prefix)
@@ -266,7 +322,7 @@
         factory.macros = self.macros
 
     def namespace(self, view):
-        namespace = GrokPageTemplate.namespace(self, view)
+        namespace = BasePageTemplate.namespace(self, view)
         namespace.update(self.pt_getContext())
         return namespace
 

Modified: grok/branches/regebro-guido-templates/src/grok/interfaces.py
===================================================================
--- grok/branches/regebro-guido-templates/src/grok/interfaces.py	2007-11-02 11:27:23 UTC (rev 81409)
+++ grok/branches/regebro-guido-templates/src/grok/interfaces.py	2007-11-02 16:19:38 UTC (rev 81410)
@@ -435,21 +435,18 @@
     """
     
     def __call__(filename, _prefix=None):
-        """Creates an ITemplateFile
+        """Creates an ITemplate from a file
         
         _prefix is the directory the file is located in
         """
 
-class ITemplateFile(interface.Interface):
-    """Template objects created from files
+class ITemplate(interface.Interface):
+    """Template objects
     """
     
     def _initFactory(factory):
         """Template language specific initializations on the view factory."""
-    
-    def namespace():
-        """Returns a dictionary of template language specific variables."""
-    
+        
     def render(view):
         """Renders the template"""
         
\ No newline at end of file

Modified: grok/branches/regebro-guido-templates/src/grok/meta.py
===================================================================
--- grok/branches/regebro-guido-templates/src/grok/meta.py	2007-11-02 11:27:23 UTC (rev 81409)
+++ grok/branches/regebro-guido-templates/src/grok/meta.py	2007-11-02 16:19:38 UTC (rev 81410)
@@ -80,6 +80,10 @@
 class GlobalUtilityGrokker(martian.ClassGrokker):
     component_class = grok.GlobalUtility
 
+    # This needs to happen before the FilesystemPageTemplateGrokker grokker 
+    # happens, since it relies on the ITemplateFileFactoris being grokked.
+    priority = 1100
+    
     def grok(self, name, factory, context, module_info, templates):
         provides = util.class_annotation(factory, 'grok.provides', None)
         if provides is None:
@@ -257,7 +261,7 @@
     # use the templates
     priority = 1000
 
-    component_class = grok.components.GrokPageTemplate
+    component_class = grok.components.BasePageTemplate
 
     def grok(self, name, instance, context, module_info, templates):
         templates.register(name, instance)

Added: grok/branches/regebro-guido-templates/src/grok/tests/template/__init__.py
===================================================================
--- grok/branches/regebro-guido-templates/src/grok/tests/template/__init__.py	                        (rev 0)
+++ grok/branches/regebro-guido-templates/src/grok/tests/template/__init__.py	2007-11-02 16:19:38 UTC (rev 81410)
@@ -0,0 +1 @@
+#
\ No newline at end of file


Property changes on: grok/branches/regebro-guido-templates/src/grok/tests/template/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: grok/branches/regebro-guido-templates/src/grok/tests/template/lascaux.html
===================================================================
--- grok/branches/regebro-guido-templates/src/grok/tests/template/lascaux.html	                        (rev 0)
+++ grok/branches/regebro-guido-templates/src/grok/tests/template/lascaux.html	2007-11-02 16:19:38 UTC (rev 81410)
@@ -0,0 +1 @@
+<html><body>Lascaux is in France</body></html>
\ No newline at end of file

Added: grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability.py
===================================================================
--- grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability.py	                        (rev 0)
+++ grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability.py	2007-11-02 16:19:38 UTC (rev 81410)
@@ -0,0 +1,91 @@
+"""
+Testing the plugging in of a template language
+
+  >>> grok.grok(__name__)
+  
+  >>> cave = Cave()
+  >>> from zope.publisher.browser import TestRequest
+  >>> request = TestRequest()
+  >>> from zope import component
+  
+  # The inline template should work:
+  >>> view = component.getMultiAdapter((cave, request), name='sebaayeni')
+  >>> print view()
+  <html><body>Sebaayeni is in South Africa</body></html>
+
+  # And the inline file template:
+  >>> view = component.getMultiAdapter((cave, request), name='lascaux')
+  >>> print view()
+  <html><body>Lascaux is in France</body></html>
+
+  # And the template directory template:
+  >>> view = component.getMultiAdapter((cave, request), name='kakadu')
+  >>> print view()
+  <html><body>Kakadu is in Australia</body></html>
+
+  # We should be able to extend the namespac in the view and 
+  >>> view = component.getMultiAdapter((cave, request), name='sierra')
+  >>> print view()
+  <html><body>Sierra de San Fransisco is in Mexico</body></html>
+
+"""
+import grok, os
+
+# Dummy template language:
+class MyTemplate(object):
+    
+    def __init__(self, text):
+        self._text = text
+            
+    def render(self, **kw):
+        # Silliest template language ever:
+        return self._text % kw
+
+class MyPageTemplate(grok.components.GrokPageTemplate):
+
+    def fromTemplate(self, template):
+        return MyTemplate(template)
+
+    def fromFile(self, filename, _prefix=None):
+        file = open(os.path.join(_prefix, filename))
+        return MyTemplate(file.read())
+
+    def namespace(self, view):
+        # I'll override the default namespace here for testing:
+        return {'middle_text': 'is in'}
+
+    def render(self, view):
+        return self.getTemplate().render(**self.getNamespace(view))
+
+class MyPageTemplateFactory(grok.GlobalUtility):
+
+    grok.implements(grok.interfaces.ITemplateFileFactory)
+    grok.name('mtl')
+
+    def __call__(self, filename, _prefix=None):
+        return MyPageTemplate(filename=filename, _prefix=_prefix)
+
+# Small hack to make sure this gets grokked in the right order:
+grok.grok_component('MyPageTemplateFactory', MyPageTemplateFactory)
+
+class Cave(grok.Model):
+    pass
+
+class Sebaayeni(grok.View):
+    pass
+    
+sebaayeni = MyPageTemplate('<html><body>Sebaayeni is in South Africa</body></html>')
+
+class Lascaux(grok.View):
+    pass
+    
+lascaux = MyPageTemplate(filename='lascaux.html')
+
+class Kakadu(grok.View):
+    pass
+
+class Sierra(grok.View):
+    
+    def namespace(self):
+        return {'cave': 'Sierra de San Fransisco',
+                'country': 'Mexico'}


Property changes on: grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability_templates/kakadu.mtl
===================================================================
--- grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability_templates/kakadu.mtl	                        (rev 0)
+++ grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability_templates/kakadu.mtl	2007-11-02 16:19:38 UTC (rev 81410)
@@ -0,0 +1 @@
+<html><body>Kakadu is in Australia</body></html>
\ No newline at end of file

Added: grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability_templates/sierra.mtl
===================================================================
--- grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability_templates/sierra.mtl	                        (rev 0)
+++ grok/branches/regebro-guido-templates/src/grok/tests/template/pluggability_templates/sierra.mtl	2007-11-02 16:19:38 UTC (rev 81410)
@@ -0,0 +1 @@
+<html><body>%(cave)s %(middle_text)s %(country)s</body></html>
\ No newline at end of file

Modified: grok/branches/regebro-guido-templates/src/grok/tests/test_grok.py
===================================================================
--- grok/branches/regebro-guido-templates/src/grok/tests/test_grok.py	2007-11-02 11:27:23 UTC (rev 81409)
+++ grok/branches/regebro-guido-templates/src/grok/tests/test_grok.py	2007-11-02 16:19:38 UTC (rev 81410)
@@ -35,7 +35,8 @@
                                     tearDown=cleanUpZope,
                                     checker=checker,
                                     optionflags=doctest.ELLIPSIS+
-                                    doctest.NORMALIZE_WHITESPACE)
+                                    doctest.NORMALIZE_WHITESPACE+
+                                    doctest.REPORT_ONLY_FIRST_FAILURE)
 
         suite.addTest(test)
     return suite
@@ -45,7 +46,7 @@
     for name in ['adapter', 'error', 'view', 'event', 'security', 'catalog',
                  'zcml', 'static', 'utility', 'xmlrpc', 'json', 'container',
                  'traversal', 'form', 'site', 'grokker', 'directive', 'util',
-                 'baseclass', 'annotation', 'application']:
+                 'baseclass', 'annotation', 'application', 'template']:
         suite.addTest(suiteFromPackage(name))
     return suite
 



More information about the Checkins mailing list