[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