[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