diff options
| author | Chris McDonough <chrism@agendaless.com> | 2008-07-08 04:37:07 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2008-07-08 04:37:07 +0000 |
| commit | 4bf7723f67b49cb14f0d82511a924adc1147505c (patch) | |
| tree | cd60d3acdb8deca1cbc40b984dbadedb71ccafc3 | |
| parent | 703422d397607eefb5fe695c657e5b59991ed9c1 (diff) | |
| download | pyramid-4bf7723f67b49cb14f0d82511a924adc1147505c.tar.gz pyramid-4bf7723f67b49cb14f0d82511a924adc1147505c.tar.bz2 pyramid-4bf7723f67b49cb14f0d82511a924adc1147505c.zip | |
Add a browser:page directive type and make our sample app use it.
| -rw-r--r-- | repoze/bfg/configure.zcml | 8 | ||||
| -rw-r--r-- | repoze/bfg/meta.zcml | 15 | ||||
| -rw-r--r-- | repoze/bfg/metaconfigure.py | 113 | ||||
| -rw-r--r-- | repoze/bfg/sampleapp/app.py | 25 | ||||
| -rw-r--r-- | repoze/bfg/sampleapp/configure.zcml | 35 | ||||
| -rw-r--r-- | repoze/bfg/sampleapp/www/blog_view.pt | 7 | ||||
| -rw-r--r-- | repoze/bfg/template.py | 40 | ||||
| -rw-r--r-- | repoze/bfg/tests/fixtures/minimal.pt | 3 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_metaconfigure.py | 92 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_template.py | 103 |
10 files changed, 407 insertions, 34 deletions
diff --git a/repoze/bfg/configure.zcml b/repoze/bfg/configure.zcml index 66790df9b..15a49b2c0 100644 --- a/repoze/bfg/configure.zcml +++ b/repoze/bfg/configure.zcml @@ -2,6 +2,12 @@ i18n_domain="repoze.bfg"> <include package="z3c.pt" /> + <include package="zope.security" file="meta.zcml"/> + + <permission + id="repoze.view" + title="View" + /> <adapter factory=".traversal.NaivePublishTraverser" @@ -15,4 +21,6 @@ for="* *" /> + <include file="meta.zcml" /> + </configure> diff --git a/repoze/bfg/meta.zcml b/repoze/bfg/meta.zcml new file mode 100644 index 000000000..eba63db8d --- /dev/null +++ b/repoze/bfg/meta.zcml @@ -0,0 +1,15 @@ +<configure + xmlns="http://namespaces.zope.org/zope" + xmlns:meta="http://namespaces.zope.org/meta"> + + <meta:directives namespace="http://namespaces.repoze.org/browser"> + + <meta:directive + name="page" + schema=".metaconfigure.IPageDirective" + handler=".metaconfigure.page" + /> + + </meta:directives> + +</configure> diff --git a/repoze/bfg/metaconfigure.py b/repoze/bfg/metaconfigure.py new file mode 100644 index 000000000..b486fbea7 --- /dev/null +++ b/repoze/bfg/metaconfigure.py @@ -0,0 +1,113 @@ +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.security.zcml import Permission + +from repoze.bfg.interfaces import IRequest +from repoze.bfg.interfaces import IViewFactory + +from repoze.bfg.template import ViewPageTemplateFile + +class ViewBase: + def __init__(self, context, request): + self.context = context + self.request = request + + def __call__(self, *arg, **kw): + return self.index(*arg, **kw) + +def page(_context, + permission, + for_, + 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: + template = os.path.abspath(str(_context.path(template))) + if not os.path.isfile(template): + raise ConfigurationError("No such file", 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) + return ViewClass(context, request) + + else: + return class_(context, request) + + if for_ is not None: + _context.action( + discriminator = None, + callable = provideInterface, + args = ('', for_) + ) + + _context.action( + discriminator = ('view', for_, name, IRequest, IViewFactory), + callable = handler, + args = ('registerAdapter', + view_factory, (for_, IRequest), IViewFactory, name, + _context.info), + ) + +class IPageDirective(Interface): + """ + The page directive is used to create views that provide a single + url or page. + + The page directive creates a new view class from a given template + and/or class and registers it. + """ + + for_ = GlobalObject( + title=u"The interface or class this view is for.", + required=False + ) + + permission = Permission( + title=u"Permission", + description=u"The permission needed to use the view.", + required=True + ) + + class_ = GlobalObject( + title=u"Class", + description=u"A class that provides a __call__ used by the view.", + required=False, + ) + + name = TextLine( + title=u"The name of the page (view)", + description=u""" + The name shows up in URLs/paths. For example 'foo' or + 'foo.html'.""", + required=False, + ) + + 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').""", + required=False + ) + diff --git a/repoze/bfg/sampleapp/app.py b/repoze/bfg/sampleapp/app.py index db66cbcf8..10b43d384 100644 --- a/repoze/bfg/sampleapp/app.py +++ b/repoze/bfg/sampleapp/app.py @@ -1,32 +1,21 @@ -from zope.interface import classProvides -from zope.interface import implements - -from repoze.bfg.interfaces import IViewFactory -from repoze.bfg.interfaces import IView +from repoze.bfg.template import View from webob import Response -class View(object): - classProvides(IViewFactory) - implements(IView) - - def __init__(self, context, request): - self.context = context - self.request = request - class BlogDefaultView(View): - def __call__(self): - return Response('Hello world from the blog %s!' % self.context.id) + def getInfo(self): + return {'greeting':'Hello, I\'m the default view', + 'id':self.context.id} class BlogWooHooView(View): - def __call__(self): - return Response('Woo hoo from the blog named %s!' % self.context.id) + def getInfo(self): + return {'greeting':'Woo hoo, I\'m another view' , + 'id':self.context.id} class DefaultView(View): def __call__(self): return Response('Default page, context is %s' % self.context) - if __name__ == '__main__': from repoze.bfg import sampleapp from repoze.bfg.sampleapp.models import BlogModel diff --git a/repoze/bfg/sampleapp/configure.zcml b/repoze/bfg/sampleapp/configure.zcml index 58399f241..cddefc172 100644 --- a/repoze/bfg/sampleapp/configure.zcml +++ b/repoze/bfg/sampleapp/configure.zcml @@ -1,25 +1,28 @@ <configure xmlns="http://namespaces.zope.org/zope" + xmlns:browser="http://namespaces.repoze.org/browser" i18n_domain="repoze.bfg"> <include package="repoze.bfg" /> - <adapter - factory=".app.BlogDefaultView" - provides="repoze.bfg.interfaces.IViewFactory" - for=".models.IBlogModel repoze.bfg.interfaces.IRequest" - /> + <browser:page + for=".models.IBlogModel" + class=".app.BlogWooHooView" + permission="repoze.view" + name="woohoo.html" + template="www/blog_view.pt" + /> - <adapter - factory=".app.BlogWooHooView" - provides="repoze.bfg.interfaces.IViewFactory" - for=".models.IBlogModel repoze.bfg.interfaces.IRequest" - name="woohoo.html" - /> + <browser:page + for=".models.IBlogModel" + class=".app.BlogDefaultView" + permission="repoze.view" + template="www/blog_view.pt" + /> - <adapter - factory=".app.DefaultView" - provides="repoze.bfg.interfaces.IViewFactory" - for="* repoze.bfg.interfaces.IRequest" - /> + <browser:page + for="*" + class=".app.DefaultView" + permission="repoze.view" + /> </configure> diff --git a/repoze/bfg/sampleapp/www/blog_view.pt b/repoze/bfg/sampleapp/www/blog_view.pt new file mode 100644 index 000000000..e936e0a3d --- /dev/null +++ b/repoze/bfg/sampleapp/www/blog_view.pt @@ -0,0 +1,7 @@ +<div xmlns="http://www.w3.org/1999/xhtml" + xmlns:tal="http://xml.zope.org/namespaces/tal"> + <div tal:define="info view.getInfo()"> + <span tal:content="info.greeting"/> from + <span tal:content="info.id"/> + </div> +</div> diff --git a/repoze/bfg/template.py b/repoze/bfg/template.py new file mode 100644 index 000000000..eb1dda816 --- /dev/null +++ b/repoze/bfg/template.py @@ -0,0 +1,40 @@ +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 + + +class PageTemplateFile(PageTemplateFileBase): + def render(self, *arg, **kw): + result = PageTemplateFileBase.render(self, *arg, **kw) + return Response(result) + +class ViewPageTemplateFile(property): + def __init__(self, filename, **kwargs): + self.template = PageTemplateFile(filename, **kwargs) + 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 BrowserView(object): + classProvides(IViewFactory) + implements(IView) + + def __init__(self, context, request): + self.context = context + self.request = request + + def __call__(self, *arg, **kw): + """ See metaconfigure.py to see where 'index' comes from """ + return self.index(*arg, **kw) + diff --git a/repoze/bfg/tests/fixtures/minimal.pt b/repoze/bfg/tests/fixtures/minimal.pt new file mode 100644 index 000000000..693d155ef --- /dev/null +++ b/repoze/bfg/tests/fixtures/minimal.pt @@ -0,0 +1,3 @@ +<div xmlns="http://www.w3.org/1999/xhtml" + xmlns:tal="http://xml.zope.org/namespaces/tal"> +</div> diff --git a/repoze/bfg/tests/test_metaconfigure.py b/repoze/bfg/tests/test_metaconfigure.py new file mode 100644 index 000000000..e349abe5d --- /dev/null +++ b/repoze/bfg/tests/test_metaconfigure.py @@ -0,0 +1,92 @@ +import unittest + +from zope.component.testing import PlacelessSetup + +class TestPageDirective(unittest.TestCase, PlacelessSetup): + def setUp(self): + PlacelessSetup.setUp(self) + + def tearDown(self): + PlacelessSetup.tearDown(self) + + def _getFUT(self): + from repoze.bfg.metaconfigure import page + return page + + def test_no_class_or_template(self): + f = self._getFUT() + from zope.configuration.exceptions import ConfigurationError + context = DummyContext() + self.assertRaises(ConfigurationError, f, context, 'repoze.view', None) + + def test_no_such_file(self): + f = self._getFUT() + from zope.configuration.exceptions import ConfigurationError + context = DummyContext() + self.assertRaises(ConfigurationError, f, context, 'repoze.view', None, + template='notthere.pt') + + def test_only_template(self): + f = self._getFUT() + context = DummyContext() + f(context, 'repoze.view', None, template='minimal.pt') + 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) + + def test_template_and_class(self): + f = self._getFUT() + context = DummyContext() + f(context, 'repoze.view', None, template='minimal.pt', + class_=DummyViewClass) + 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) + +class DummyViewClass: + pass + +class DummyContext: + def __init__(self): + self.actions = [] + self.info = None + + def path(self, name): + import os + here = os.path.dirname(__file__) + fixtures = os.path.join(here, 'fixtures') + return os.path.join(fixtures, name) + + def action(self, discriminator, callable, args): + self.actions.append( + {'discriminator':discriminator, + 'callable':callable, + 'args':args} + ) + + + diff --git a/repoze/bfg/tests/test_template.py b/repoze/bfg/tests/test_template.py new file mode 100644 index 000000000..67f4e3c47 --- /dev/null +++ b/repoze/bfg/tests/test_template.py @@ -0,0 +1,103 @@ +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 PageTemplateFileTests(unittest.TestCase, Base): + def setUp(self): + Base.setUp(self) + + def tearDown(self): + Base.tearDown(self) + + def _getTargetClass(self): + from repoze.bfg.template import PageTemplateFile + return PageTemplateFile + + def _makeOne(self, *arg, **kw): + klass = self._getTargetClass() + return klass(*arg, **kw) + + def test_render(self): + self._zcmlConfigure() + minimal = self._getTemplatePath('minimal.pt') + instance = self._makeOne(minimal) + result = instance.render() + 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() + minimal = self._getTemplatePath('minimal.pt') + instance = self._makeOne(minimal) + class View: + context = 'context' + request = 'request' + view = View() + template = instance.render(view) + result = template() + 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 BrowserViewTests(unittest.TestCase, Base): + def setUp(self): + Base.setUp(self) + + def tearDown(self): + Base.tearDown(self) + + def _getTargetClass(self): + from repoze.bfg.template import BrowserView + return BrowserView + + def _makeOne(self, *arg, **kw): + klass = self._getTargetClass() + return klass(*arg, **kw) + + def test_call(self): + view = self._makeOne(None, None) + _marker = () + view.index = lambda *arg, **kw: _marker + result = view('foo') + self.assertEqual(result, _marker) + + + |
