[Repoze-checkins] r1308 - in repoze.bfg/trunk: . repoze/bfg repoze/bfg/sampleapp repoze/bfg/tests repoze/bfg/tests/fixtureapp

Chris McDonough chrism at agendaless.com
Mon Jul 14 03:31:31 EDT 2008


Author: Chris McDonough <chrism at agendaless.com>
Date: Mon Jul 14 03:31:31 2008
New Revision: 1308

Log:
Less magical templated view model: make custom template views responsible for 
knowing their template's name.


Added:
   repoze.bfg/trunk/repoze/bfg/tests/test_view.py   (contents, props changed)
   repoze.bfg/trunk/repoze/bfg/view.py   (contents, props changed)
Modified:
   repoze.bfg/trunk/README.txt
   repoze.bfg/trunk/repoze/bfg/__init__.py
   repoze.bfg/trunk/repoze/bfg/interfaces.py
   repoze.bfg/trunk/repoze/bfg/meta.zcml
   repoze.bfg/trunk/repoze/bfg/sampleapp/configure.zcml
   repoze.bfg/trunk/repoze/bfg/sampleapp/views.py
   repoze.bfg/trunk/repoze/bfg/template.py
   repoze.bfg/trunk/repoze/bfg/tests/fixtureapp/configure.zcml
   repoze.bfg/trunk/repoze/bfg/tests/test_template.py
   repoze.bfg/trunk/repoze/bfg/tests/test_zcml.py
   repoze.bfg/trunk/repoze/bfg/zcml.py

Modified: repoze.bfg/trunk/README.txt
==============================================================================
--- repoze.bfg/trunk/README.txt	(original)
+++ repoze.bfg/trunk/README.txt	Mon Jul 14 03:31:31 2008
@@ -266,7 +266,7 @@
 A views.py module might look like so::
 
   from webob import Response
-  from repoze.bfg.template import TemplateView
+  from repoze.bfg.view import TemplateView
 
   class MyHelloView(object):
       def __init__(self, context, request):
@@ -274,11 +274,15 @@
           self.request = request
 
       def __call__(self):
-          response = Response('Hello from %s @ %s' % (self.context.__name__, 
-                                                      self.request['PATH_INFO'])
+          response = Response('Hello from %s @ %s' % (
+                                  self.context.__name__, 
+                                  self.request.environ['PATH_INFO']))
           return response
 
   class MyTemplateView(TemplateView):
+
+       template = 'templates/my.pt'
+
        def getInfo(self):
            return {'name':self.context.__name__}
 
@@ -318,24 +322,23 @@
 A view registry might look like so::
 
   <configure xmlns="http://namespaces.zope.org/zope"
-      xmlns:browser="http://namespaces.repoze.org/browser"
-  	           i18n_domain="repoze.bfg">
+      xmlns:bfg="http://namespaces.repoze.org/bfg"
+      i18n_domain="repoze.bfg">
 
     <!-- this must be included for the view declarations to work -->
     <include package="repoze.bfg" />
 
     <!-- the default view for a MyModel -->
-    <browser:page
+    <bfg:view
         for=".models.IMyModel"
-        class=".views.MyHelloView"
+        factory=".views.MyHelloView"
         permission="repoze.view"
         />
 
     <!-- the templated view for a MyModel -->
-    <browser:page
+    <bfg:view
         for=".models.IMyModel"
-        class=".views.MyTemplateView"
-        template="templates/my.pt"
+        factory=".views.MyTemplateView"
         name="templated.html"
         permission="repoze.view"
         />

Modified: repoze.bfg/trunk/repoze/bfg/__init__.py
==============================================================================
--- repoze.bfg/trunk/repoze/bfg/__init__.py	(original)
+++ repoze.bfg/trunk/repoze/bfg/__init__.py	Mon Jul 14 03:31:31 2008
@@ -1,2 +1,2 @@
 from repoze.bfg.router import make_app # for import elsewhere
-
+from repoze.bfg.view import View

Modified: repoze.bfg/trunk/repoze/bfg/interfaces.py
==============================================================================
--- repoze.bfg/trunk/repoze/bfg/interfaces.py	(original)
+++ repoze.bfg/trunk/repoze/bfg/interfaces.py	Mon Jul 14 03:31:31 2008
@@ -39,3 +39,7 @@
     def __call__(context, request, view):
         """ Return an object that implements IWSGIApplication """
 
+class ITemplateFactory(Interface):
+    def __call__(template_path):
+        """ Return an IView given a template path """
+        

Modified: repoze.bfg/trunk/repoze/bfg/meta.zcml
==============================================================================
--- repoze.bfg/trunk/repoze/bfg/meta.zcml	(original)
+++ repoze.bfg/trunk/repoze/bfg/meta.zcml	Mon Jul 14 03:31:31 2008
@@ -2,12 +2,12 @@
     xmlns="http://namespaces.zope.org/zope"
     xmlns:meta="http://namespaces.zope.org/meta">
 
-  <meta:directives namespace="http://namespaces.repoze.org/browser">
+  <meta:directives namespace="http://namespaces.repoze.org/bfg">
 
     <meta:directive
-        name="page"
-        schema=".zcml.IPageDirective"
-        handler=".zcml.page"
+        name="view"
+        schema=".zcml.IViewDirective"
+        handler=".zcml.view"
         />
 
   </meta:directives>

Modified: repoze.bfg/trunk/repoze/bfg/sampleapp/configure.zcml
==============================================================================
--- repoze.bfg/trunk/repoze/bfg/sampleapp/configure.zcml	(original)
+++ repoze.bfg/trunk/repoze/bfg/sampleapp/configure.zcml	Mon Jul 14 03:31:31 2008
@@ -1,27 +1,25 @@
 <configure xmlns="http://namespaces.zope.org/zope"
-    xmlns:browser="http://namespaces.repoze.org/browser"
+    xmlns:bfg="http://namespaces.repoze.org/bfg"
 	           i18n_domain="repoze.bfg">
 
   <include package="repoze.bfg" />
 
   <!-- the default view for a Blog -->
-  <browser:page
+  <bfg:view
       for=".models.IBlog"
-      class=".views.BlogDefaultView"
-      template="templates/blog.pt"
+      factory=".views.BlogDefaultView"
       permission="repoze.view"
       />
 
   <!-- the default view for a BlogEntry -->
-  <browser:page
+  <bfg:view
       for=".models.IBlogEntry"
-      class=".views.BlogEntryDefaultView"
-      template="templates/blog_entry.pt"
+      factory=".views.BlogEntryDefaultView"
       permission="repoze.view"
       />
 
   <!-- the add template for a BlogEntry -->
-  <browser:page
+  <bfg:view
       for=".models.IBlog"
       template="templates/blog_entry_add.pt"
       name="add_entry.html"
@@ -29,15 +27,15 @@
       />
 
   <!-- the add handler for a BlogEntry -->
-  <browser:page
+  <bfg:view
       for=".models.IBlog"
-      class=".views.BlogEntryAddView"
+      factory=".views.BlogEntryAddView"
       permission="repoze.view"
       name="add_entry_handler"
       />
 
   <!-- the contents view for any mapping (shows dict members) -->
-  <browser:page
+  <bfg:view
       for=".models.IMapping"
       template="templates/contents.pt"
       name="contents.html"

Modified: repoze.bfg/trunk/repoze/bfg/sampleapp/views.py
==============================================================================
--- repoze.bfg/trunk/repoze/bfg/sampleapp/views.py	(original)
+++ repoze.bfg/trunk/repoze/bfg/sampleapp/views.py	Mon Jul 14 03:31:31 2008
@@ -2,13 +2,18 @@
 
 from webob.exc import HTTPFound
 
-from repoze.bfg.template import TemplateView
+from repoze.bfg.view import TemplateView
+from repoze.bfg.view import View
+
 from repoze.bfg.sampleapp.models import BlogEntry
 
 def datestring(dt):
-    return dt.strftime('%Y-%m-%dT%H:%M:%S')
+    return dt.strftime('%Y-%m-%d %H:%M:%S')
 
 class BlogDefaultView(TemplateView):
+
+    template = 'templates/blog.pt'
+
     def getInfo(self):
         entrydata = []
         for name, entry in self.context.items():
@@ -23,6 +28,9 @@
         return {'name':self.context.__name__, 'entries':entrydata}
 
 class BlogEntryDefaultView(TemplateView):
+
+    template = 'templates/blog_entry.pt'
+    
     def getInfo(self):
         return {
             'name':self.context.__name__,
@@ -32,10 +40,7 @@
             'created':datestring(self.context.created),
             }
 
-class BlogEntryAddView(object):
-    def __init__(self, context, request):
-        self.context = context
-        self.request = request
+class BlogEntryAddView(View):
 
     def __call__(self):
         author = self.request.params['author']

Modified: repoze.bfg/trunk/repoze/bfg/template.py
==============================================================================
--- repoze.bfg/trunk/repoze/bfg/template.py	(original)
+++ repoze.bfg/trunk/repoze/bfg/template.py	Mon Jul 14 03:31:31 2008
@@ -1,40 +1,56 @@
+import os
+import sys
+
+from zope.component import queryUtility
+from zope.component.interfaces import ComponentLookupError
+from zope.component import getSiteManager
+
 from zope.interface import classProvides
 from zope.interface import implements
 
-from z3c.pt import PageTemplateFile as PageTemplateFileBase
 from webob import Response
 
-from repoze.bfg.interfaces import IViewFactory
 from repoze.bfg.interfaces import IView
+from repoze.bfg.interfaces import ITemplateFactory
 
-
-class PageTemplateFile(PageTemplateFileBase):
-    def render(self, *arg, **kw):
-        result = PageTemplateFileBase.render(self, *arg, **kw)
-        return Response(result)
-
-class ViewPageTemplateFile(property):
-    def __init__(self, template):
-        self.template = template
-        property.__init__(self, self.render)
-
-    def render(self, view):
-        def template(**kwargs):
-            return self.template.render(view=view,
-                                        context=view.context,
-                                        request=view.request,
-                                        options=kwargs)
-        return template        
-    
-class TemplateView(object):
-    classProvides(IViewFactory)
+class Z3CPTTemplateFactory(object):
+    classProvides(ITemplateFactory)
     implements(IView)
 
-    def __init__(self, context, request):
-        self.context = context
-        self.request = request
+    def __init__(self, path):
+        from z3c.pt import PageTemplateFile
+        self.template = PageTemplateFile(path)
 
     def __call__(self, *arg, **kw):
-        """ See metaconfigure.py to see where 'index' comes from """
-        return self.index(*arg, **kw)
+        result = self.template.render(**kw)
+        response = Response(result)
+        return response
+
+def package_path(package):
+    return os.path.abspath(os.path.dirname(package.__file__))
+
+def render_template(view, template_path, **kw):
+    # XXX use pkg_resources
+
+    if not os.path.isabs(template_path):
+        package_globals = sys._getframe(1).f_globals
+        package_name = package_globals['__name__']
+        package = sys.modules[package_name]
+        prefix = package_path(package)
+        template_path = os.path.join(prefix, template_path)
+
+    template = queryUtility(IView, template_path)
+
+    if template is None:
+        if not os.path.exists(template_path):
+            raise ValueError('Missing template file: %s' % template_path)
+        template = Z3CPTTemplateFactory(template_path)
+        try:
+            sm = getSiteManager()
+        except ComponentLookupError:
+            pass
+        else:
+            sm.registerUtility(template, IView, name=template_path)
 
+    return template(view=view, context=view.context, request=view.request,
+                    options=kw)

Modified: repoze.bfg/trunk/repoze/bfg/tests/fixtureapp/configure.zcml
==============================================================================
--- repoze.bfg/trunk/repoze/bfg/tests/fixtureapp/configure.zcml	(original)
+++ repoze.bfg/trunk/repoze/bfg/tests/fixtureapp/configure.zcml	Mon Jul 14 03:31:31 2008
@@ -1,14 +1,20 @@
 <configure xmlns="http://namespaces.zope.org/zope"
-    xmlns:browser="http://namespaces.repoze.org/browser"
+    xmlns:bfg="http://namespaces.repoze.org/bfg"
 	           i18n_domain="repoze.bfg">
 
   <include package="repoze.bfg" />
 
-  <browser:page
+  <bfg:view
+      for=".models.IFixture"
+      factory=".views.FixtureView"
+      permission="repoze.view"
+      />
+
+  <bfg:view
       for=".models.IFixture"
-      class=".views.FixtureView"
       template="templates/fixture.pt"
       permission="repoze.view"
+      name="fixture.html"
       />
 
 </configure>

Modified: repoze.bfg/trunk/repoze/bfg/tests/test_template.py
==============================================================================
--- repoze.bfg/trunk/repoze/bfg/tests/test_template.py	(original)
+++ repoze.bfg/trunk/repoze/bfg/tests/test_template.py	Mon Jul 14 03:31:31 2008
@@ -19,8 +19,7 @@
         here = os.path.abspath(os.path.dirname(__file__))
         return os.path.join(here, 'fixtures', name)
         
-
-class PageTemplateFileTests(unittest.TestCase, Base):
+class Z3CPTTemplateFactoryTests(unittest.TestCase, Base):
     def setUp(self):
         Base.setUp(self)
 
@@ -28,76 +27,90 @@
         Base.tearDown(self)
 
     def _getTargetClass(self):
-        from repoze.bfg.template import PageTemplateFile
-        return PageTemplateFile
+        from repoze.bfg.template import Z3CPTTemplateFactory
+        return Z3CPTTemplateFactory
 
     def _makeOne(self, *arg, **kw):
         klass = self._getTargetClass()
         return klass(*arg, **kw)
 
-    def test_render(self):
+    def test_instance_conforms_to_IView(self):
+        from zope.interface.verify import verifyObject
+        from repoze.bfg.interfaces import IView
+        path = self._getTemplatePath('minimal.pt')
+        verifyObject(IView, self._makeOne(path))
+
+    def test_class_conforms_to_IView(self):
+        from zope.interface.verify import verifyClass
+        from repoze.bfg.interfaces import IView
+        verifyClass(IView, self._getTargetClass())
+
+    def test_class_conforms_to_ITemplateFactory(self):
+        from zope.interface.verify import verifyObject
+        from repoze.bfg.interfaces import ITemplateFactory
+        verifyObject(ITemplateFactory, self._getTargetClass())
+
+    def test_call(self):
         self._zcmlConfigure()
         minimal = self._getTemplatePath('minimal.pt')
         instance = self._makeOne(minimal)
-        result = instance.render()
+        result = instance()
         from webob import Response
         self.failUnless(isinstance(result, Response))
         self.assertEqual(result.app_iter, ['<div>\n</div>'])
         self.assertEqual(result.status, '200 OK')
         self.assertEqual(len(result.headerlist), 2)
-        
-class ViewPageTemplateFileTests(unittest.TestCase, Base):
-    def setUp(self):
-        Base.setUp(self)
 
-    def tearDown(self):
-        Base.tearDown(self)
-
-    def _getTargetClass(self):
-        from repoze.bfg.template import ViewPageTemplateFile
-        return ViewPageTemplateFile
-
-    def _makeOne(self, *arg, **kw):
-        klass = self._getTargetClass()
-        return klass(*arg, **kw)
-
-    def test_render(self):
-        self._zcmlConfigure()
-        f = DummyPageTemplateFile()
-        instance = self._makeOne(f)
-        class View:
-            context = 'context'
-            request = 'request'
-        view = View()
-        template = instance.render(view)
-        args, kw = template(foo='bar')
-        self.assertEqual(kw['request'], 'request')
-        self.assertEqual(kw['options'], {'foo':'bar'})
-        self.assertEqual(kw['context'], 'context')
-        self.assertEqual(kw['view'], view)
-
-class TemplateViewTests(unittest.TestCase, Base):
+class RenderTemplateTests(unittest.TestCase, Base):
     def setUp(self):
         Base.setUp(self)
 
     def tearDown(self):
         Base.tearDown(self)
 
-    def _getTargetClass(self):
-        from repoze.bfg.template import TemplateView
-        return TemplateView
+    def _getFUT(self):
+        from repoze.bfg.template import render_template
+        return render_template
 
-    def _makeOne(self, *arg, **kw):
-        klass = self._getTargetClass()
-        return klass(*arg, **kw)
+    def test_nonabs_unregistered(self):
+        self._zcmlConfigure()
+        from zope.component import queryUtility
+        from repoze.bfg.interfaces import IView
+        minimal = self._getTemplatePath('minimal.pt')
+        self.assertEqual(queryUtility(IView, minimal), None)
+        view = DummyView()
+        render = self._getFUT()
+        result = render(view, minimal)
+        from webob import Response
+        self.failUnless(isinstance(result, Response))
+        self.assertEqual(result.app_iter, ['<div>\n</div>'])
+        self.assertEqual(result.status, '200 OK')
+        self.assertEqual(len(result.headerlist), 2)
+        from repoze.bfg.template import Z3CPTTemplateFactory
+        self.failUnless(isinstance(queryUtility(IView, minimal),
+                                   Z3CPTTemplateFactory))
 
-    def test_call(self):
-        view = self._makeOne(None, None)
-        _marker = ()
-        view.index = lambda *arg, **kw: _marker
-        result = view('foo')
-        self.assertEqual(result, _marker)
+    def test_nonabs_registered(self):
+        self._zcmlConfigure()
+        from zope.component import getGlobalSiteManager
+        from zope.component import queryUtility
+        from repoze.bfg.template import Z3CPTTemplateFactory
+        from repoze.bfg.interfaces import IView
+        minimal = self._getTemplatePath('minimal.pt')
+        utility = Z3CPTTemplateFactory(minimal)
+        gsm = getGlobalSiteManager()
+        gsm.registerUtility(utility, IView, name=minimal)
+        view = DummyView()
+        render = self._getFUT()
+        result = render(view, minimal)
+        from webob import Response
+        self.failUnless(isinstance(result, Response))
+        self.assertEqual(result.app_iter, ['<div>\n</div>'])
+        self.assertEqual(result.status, '200 OK')
+        self.assertEqual(len(result.headerlist), 2)
+        self.assertEqual(queryUtility(IView, minimal), utility)
+        
+class DummyView:
+    context = 'context'
+    request = 'request'
         
-class DummyPageTemplateFile:
-    def render(self, *arg, **kw):
-        return arg, kw

Added: repoze.bfg/trunk/repoze/bfg/tests/test_view.py
==============================================================================
--- (empty file)
+++ repoze.bfg/trunk/repoze/bfg/tests/test_view.py	Mon Jul 14 03:31:31 2008
@@ -0,0 +1,64 @@
+import unittest
+
+from zope.component.testing import PlacelessSetup
+
+class Base(PlacelessSetup):
+    def setUp(self):
+        PlacelessSetup.setUp(self)
+
+    def tearDown(self):
+        PlacelessSetup.tearDown(self)
+
+    def _zcmlConfigure(self):
+        import repoze.bfg
+        import zope.configuration.xmlconfig
+        zope.configuration.xmlconfig.file('configure.zcml', package=repoze.bfg)
+
+    def _getTemplatePath(self, name):
+        import os
+        here = os.path.abspath(os.path.dirname(__file__))
+        return os.path.join(here, 'fixtures', name)
+
+class ViewTests(unittest.TestCase):
+    def _getTargetClass(self):
+        from repoze.bfg.view import View
+        return View
+
+    def _makeOne(self, *arg, **kw):
+        klass = self._getTargetClass()
+        return klass(*arg, **kw)
+
+    def test_call(self):
+        view = self._makeOne(None, None)
+        self.assertRaises(NotImplementedError, view)
+
+class TemplateViewTests(unittest.TestCase, Base):
+    def setUp(self):
+        Base.setUp(self)
+
+    def tearDown(self):
+        Base.tearDown(self)
+
+    def _getTargetClass(self):
+        from repoze.bfg.view import TemplateView
+        return TemplateView
+
+    def _makeOne(self, *arg, **kw):
+        klass = self._getTargetClass()
+        return klass(*arg, **kw)
+
+    def test_call(self):
+        self._zcmlConfigure()
+        view = self._makeOne(None, None)
+        view.template = self._getTemplatePath('minimal.pt')
+        result = view(foo='foo')
+        from webob import Response
+        self.failUnless(isinstance(result, Response))
+        self.assertEqual(result.app_iter, ['<div>\n</div>'])
+        self.assertEqual(result.status, '200 OK')
+        self.assertEqual(len(result.headerlist), 2)
+        
+    def test_call_no_template(self):
+        self._zcmlConfigure()
+        view = self._makeOne(None, None)
+        self.assertRaises(ValueError, view)

Modified: repoze.bfg/trunk/repoze/bfg/tests/test_zcml.py
==============================================================================
--- repoze.bfg/trunk/repoze/bfg/tests/test_zcml.py	(original)
+++ repoze.bfg/trunk/repoze/bfg/tests/test_zcml.py	Mon Jul 14 03:31:31 2008
@@ -2,7 +2,7 @@
 
 from zope.component.testing import PlacelessSetup
 
-class TestPageDirective(unittest.TestCase, PlacelessSetup):
+class TestViewDirective(unittest.TestCase, PlacelessSetup):
     def setUp(self):
         PlacelessSetup.setUp(self)
 
@@ -10,8 +10,8 @@
         PlacelessSetup.tearDown(self)
 
     def _getFUT(self):
-        from repoze.bfg.zcml import page
-        return page
+        from repoze.bfg.zcml import view
+        return view
 
     def test_no_class_or_template(self):
         f = self._getFUT()
@@ -29,45 +29,107 @@
     def test_only_template(self):
         f = self._getFUT()
         context = DummyContext()
-        f(context, 'repoze.view', None, template='minimal.pt')
+        class IFoo:
+            pass
+        f(context, 'repoze.view', IFoo, template='minimal.pt')
         actions = context.actions
+        from repoze.bfg.interfaces import IView
         from repoze.bfg.interfaces import IRequest
         from repoze.bfg.interfaces import IViewFactory
         from zope.component.zcml import handler
-        expected0 = ('view', None, '', IRequest, IViewFactory)
-        expected1 = handler
-        self.assertEqual(actions[0]['discriminator'], expected0)
-        self.assertEqual(actions[0]['callable'], expected1)
-        self.assertEqual(actions[0]['args'][0], 'registerAdapter')
-        import types
-        self.failUnless(isinstance(actions[0]['args'][1], types.FunctionType))
-        self.assertEqual(actions[0]['args'][2], (None, IRequest))
-        self.assertEqual(actions[0]['args'][3], IViewFactory)
-        self.assertEqual(actions[0]['args'][4], '')
-        self.assertEqual(actions[0]['args'][5], None)
+        from zope.component.interface import provideInterface
 
-    def test_template_and_class(self):
+        self.assertEqual(len(actions), 3)
+
+        regutil_discriminator = ('utility', IView, context.path('minimal.pt'))
+        regutil = actions[0]
+        self.assertEqual(regutil['discriminator'], regutil_discriminator)
+        self.assertEqual(regutil['callable'], handler)
+        self.assertEqual(regutil['args'][0], 'registerUtility')
+        self.assertEqual(regutil['args'][1].template.filename,
+                         context.path('minimal.pt'))
+        self.assertEqual(regutil['args'][2], IView)
+        self.assertEqual(regutil['args'][3], context.path('minimal.pt'))
+
+        provide = actions[1]
+        self.assertEqual(provide['discriminator'], None)
+        self.assertEqual(provide['callable'], provideInterface)
+        self.assertEqual(provide['args'][0], '')
+        self.assertEqual(provide['args'][1], IFoo)
+        
+        regadapt = actions[2]
+        regadapt_discriminator = ('view', IFoo, '', IRequest, IViewFactory)
+        self.assertEqual(regadapt['discriminator'], regadapt_discriminator)
+        self.assertEqual(regadapt['callable'], handler)
+        self.assertEqual(regadapt['args'][0], 'registerAdapter')
+        self.assertEqual(regadapt['args'][1].template,
+                         context.path('minimal.pt'))
+        self.assertEqual(regadapt['args'][2], (IFoo, IRequest))
+        self.assertEqual(regadapt['args'][3], IViewFactory)
+        self.assertEqual(regadapt['args'][4], '')
+        self.assertEqual(regadapt['args'][5], None)
+
+    def test_only_factory(self):
         f = self._getFUT()
         context = DummyContext()
-        f(context, 'repoze.view', None, template='minimal.pt',
-          class_=DummyViewClass)
+        class IFoo:
+            pass
+        f(context, 'repoze.view', IFoo, factory=Dummy)
         actions = context.actions
         from repoze.bfg.interfaces import IRequest
         from repoze.bfg.interfaces import IViewFactory
         from zope.component.zcml import handler
-        expected0 = ('view', None, '', IRequest, IViewFactory)
-        expected1 = handler
-        self.assertEqual(actions[0]['discriminator'], expected0)
-        self.assertEqual(actions[0]['callable'], expected1)
-        self.assertEqual(actions[0]['args'][0], 'registerAdapter')
-        import types
-        self.failUnless(isinstance(actions[0]['args'][1], types.FunctionType))
-        self.assertEqual(actions[0]['args'][2], (None, IRequest))
-        self.assertEqual(actions[0]['args'][3], IViewFactory)
-        self.assertEqual(actions[0]['args'][4], '')
-        self.assertEqual(actions[0]['args'][5], None)
+        from zope.component.interface import provideInterface
+
+        self.assertEqual(len(actions), 2)
+
+        provide = actions[0]
+        self.assertEqual(provide['discriminator'], None)
+        self.assertEqual(provide['callable'], provideInterface)
+        self.assertEqual(provide['args'][0], '')
+        self.assertEqual(provide['args'][1], IFoo)
+        
+        regadapt = actions[1]
+        regadapt_discriminator = ('view', IFoo, '', IRequest, IViewFactory)
+        self.assertEqual(regadapt['discriminator'], regadapt_discriminator)
+        self.assertEqual(regadapt['callable'], handler)
+        self.assertEqual(regadapt['args'][0], 'registerAdapter')
+        self.assertEqual(regadapt['args'][1], Dummy)
+        self.assertEqual(regadapt['args'][2], (IFoo, IRequest))
+        self.assertEqual(regadapt['args'][3], IViewFactory)
+        self.assertEqual(regadapt['args'][4], '')
+        self.assertEqual(regadapt['args'][5], None)
+
+    def test_template_and_factory_raises(self):
+        f = self._getFUT()
+        context = DummyContext()
+        from zope.configuration.exceptions import ConfigurationError
+        self.assertRaises(ConfigurationError, f, context, 'repoze.view', None,
+                          Dummy, 'minimal.html', 'minimal.pt')
+
+class TestTemplateViewFactory(unittest.TestCase):
+    def _getTargetClass(self):
+        from repoze.bfg.zcml import TemplateViewFactory
+        return TemplateViewFactory
+
+    def _makeOne(self, template):
+        return self._getTargetClass()(template)
+
+    def test_instance_conforms_to_IViewFactory(self):
+        from zope.interface.verify import verifyObject
+        from repoze.bfg.interfaces import IViewFactory
+        verifyObject(IViewFactory, self._makeOne('a'))
+
+    def test_call(self):
+        context = DummyContext()
+        template = context.path('minimal.pt')
+        factory = self._makeOne(template)
+        view = factory(None, None)
+        from repoze.bfg.view import TemplateView
+        self.failUnless(isinstance(view, TemplateView))
+        
 
-class DummyViewClass:
+class Dummy:
     pass
 
 class DummyContext:

Added: repoze.bfg/trunk/repoze/bfg/view.py
==============================================================================
--- (empty file)
+++ repoze.bfg/trunk/repoze/bfg/view.py	Mon Jul 14 03:31:31 2008
@@ -0,0 +1,27 @@
+from repoze.bfg.template import render_template
+
+class View(object):
+    """ Convenience base class for user-defined views """
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def __call__(self, **kw):
+        raise NotImplementedError
+
+class TemplateView(View):
+    template = None
+    def __call__(self, **kw):
+        if self.template is None:
+            raise ValueError('a "template" attribute must be attached to '
+                             'a TemplateView')
+        return render_template(self, self.template, **kw)
+
+    def __repr__(self):
+        klass = self.__class__
+        return '<%s.%s object at %s for %s>' % (klass.__module__,
+                                                klass.__mame__,
+                                                id(self),
+                                                self.template)
+
+    

Modified: repoze.bfg/trunk/repoze/bfg/zcml.py
==============================================================================
--- repoze.bfg/trunk/repoze/bfg/zcml.py	(original)
+++ repoze.bfg/trunk/repoze/bfg/zcml.py	Mon Jul 14 03:31:31 2008
@@ -1,62 +1,68 @@
 import os
 
-from zope.schema import TextLine
-from zope.configuration.fields import Path
-from zope.interface import Interface
 from zope.component.zcml import handler
 from zope.component.interface import provideInterface
 from zope.configuration.exceptions import ConfigurationError
 from zope.configuration.fields import GlobalObject
+from zope.configuration.fields import Path
+
+from zope.schema import TextLine
+from zope.interface import Interface
+from zope.interface import implements
+
 from zope.security.zcml import Permission
 
 from repoze.bfg.interfaces import IRequest
 from repoze.bfg.interfaces import IViewFactory
+from repoze.bfg.interfaces import IView
+
+from repoze.bfg.template import Z3CPTTemplateFactory
+from repoze.bfg.view import TemplateView
 
-from repoze.bfg.template import ViewPageTemplateFile
-from repoze.bfg.template import PageTemplateFile
+class TemplateViewFactory(object):
+    """ Pickleable template view factory """
 
-class ViewBase:
-    def __init__(self, context, request):
-        self.context = context
-        self.request = request
+    implements(IViewFactory)
 
-    def __call__(self, *arg, **kw):
-        return self.index(*arg, **kw)
+    def __init__(self, template):
+        self.template = template
 
-def page(_context,
+    def __call__(self, context, request):
+        factory = TemplateView(context, request)
+        factory.template = self.template
+        return factory
+        
+def view(_context,
          permission,
-         for_,
+         for_=None,
+         factory=None,
          name="",
          template=None,
-         class_=None,
          ):
 
     # XXX we do nothing yet with permission
 
-    if not (class_ or template):
-        raise ConfigurationError("Must specify a class or a template")
+    if template and factory:
+        raise ConfigurationError('A template must not be specified if a '
+                                 'factory is also specified')
+
+    if not (template or factory):
+        raise ConfigurationError(
+            'One of template or factory must be specified')
+        
 
     if template:
-        template = os.path.abspath(str(_context.path(template)))
-        if not os.path.isfile(template):
-            raise ConfigurationError("No such file", template)
-
-        template_inst = PageTemplateFile(template)
-
-    def view_factory(context, request):
-        if template:
-            if class_ is None:
-                base = ViewBase
-            else:
-                base = class_
-            class ViewClass(base):
-                __name__ = name
-                index = ViewPageTemplateFile(template_inst)
-            return ViewClass(context, request)
-                    
-        else:
-            return class_(context, request)
-        
+        template_abs = os.path.abspath(str(_context.path(template)))
+        if not os.path.exists(template_abs):
+            raise ConfigurationError('No template file named %s' % template_abs)
+        utility = Z3CPTTemplateFactory(template_abs)
+        _context.action(
+            discriminator = ('utility', IView, template_abs),
+            callable = handler,
+            args = ('registerUtility', utility, IView, template_abs),
+            )
+        factory = TemplateViewFactory(template_abs)
+
     if for_ is not None:
         _context.action(
             discriminator = None,
@@ -68,11 +74,11 @@
         discriminator = ('view', for_, name, IRequest, IViewFactory),
         callable = handler,
         args = ('registerAdapter',
-                view_factory, (for_, IRequest), IViewFactory, name,
+                factory, (for_, IRequest), IViewFactory, name,
                 _context.info),
         )
 
-class IPageDirective(Interface):
+class IViewDirective(Interface):
     """
     The page directive is used to create views that provide a single
     url or page.
@@ -92,7 +98,7 @@
         required=True
         )
 
-    class_ = GlobalObject(
+    factory = GlobalObject(
         title=u"Class",
         description=u"A class that provides a __call__ used by the view.",
         required=False,
@@ -108,9 +114,8 @@
 
     template = Path(
         title=u"The name of a template that implements the page.",
-        description=u"""
-        Refers to a file containing a page template (should end in
-        extension '.pt' or '.html').""",
+        description=u"""Refers to a file containing a z3c.pt page template""",
         required=False
         )
 
+


More information about the Repoze-checkins mailing list