summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2008-07-08 04:37:07 +0000
committerChris McDonough <chrism@agendaless.com>2008-07-08 04:37:07 +0000
commit4bf7723f67b49cb14f0d82511a924adc1147505c (patch)
treecd60d3acdb8deca1cbc40b984dbadedb71ccafc3
parent703422d397607eefb5fe695c657e5b59991ed9c1 (diff)
downloadpyramid-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.zcml8
-rw-r--r--repoze/bfg/meta.zcml15
-rw-r--r--repoze/bfg/metaconfigure.py113
-rw-r--r--repoze/bfg/sampleapp/app.py25
-rw-r--r--repoze/bfg/sampleapp/configure.zcml35
-rw-r--r--repoze/bfg/sampleapp/www/blog_view.pt7
-rw-r--r--repoze/bfg/template.py40
-rw-r--r--repoze/bfg/tests/fixtures/minimal.pt3
-rw-r--r--repoze/bfg/tests/test_metaconfigure.py92
-rw-r--r--repoze/bfg/tests/test_template.py103
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)
+
+
+