summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt17
-rw-r--r--docs/narr/views.rst185
-rw-r--r--repoze/bfg/chameleon_text.py43
-rw-r--r--repoze/bfg/chameleon_zpt.py7
-rw-r--r--repoze/bfg/includes/configure.zcml7
-rw-r--r--repoze/bfg/includes/meta.zcml6
-rw-r--r--repoze/bfg/templating.py43
-rw-r--r--repoze/bfg/tests/test_chameleon_text.py7
-rw-r--r--repoze/bfg/tests/test_templating.py31
-rw-r--r--repoze/bfg/tests/test_view.py264
-rw-r--r--repoze/bfg/tests/test_zcml.py149
-rw-r--r--repoze/bfg/view.py106
-rw-r--r--repoze/bfg/zcml.py44
13 files changed, 805 insertions, 104 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 858770da7..65ed57635 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,20 @@
+Next release
+============
+
+- A ZCML ``view`` directive (and the associated ``bfg_view``
+ decorator) can now accept an "attr" value. If an "attr" value is
+ supplied, it is considered a method named of the view object to be
+ called when the response is required. This is typically only good
+ for views that are classes or instances (not so useful for
+ functions, as functions typically have no methods other than
+ ``__call__``).
+
+- A ZCML ``view`` directive (and the associated ``bfg_view``
+ decorator) can now accept a "template" value. If a "template" value
+ is supplied, and the view callable returns a dictionary, the
+ associated template is rendered with the dictionary as keyword
+ arguments.
+
1.1a1 (2009-09-06)
==================
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index 28fe643d8..794c36d20 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -247,6 +247,34 @@ permission
call the view. See :ref:`view_security_section` for more
information about view security and permissions.
+attr
+
+ The view machinery defaults to using the ``__call__`` method of the
+ view callable (or the function itself, if the view callable is a
+ funcion) to obtain a response dictionary. The ``attr`` value allows
+ you to vary the method attribute used to obtain the response. For
+ example, if your view was a class, and the class has a method named
+ ``index`` and you wanted to use this method instead of the class'
+ ``__call__`` method to return the response, you'd say
+ ``attr="index"`` in the page ZCML definition. This is most useful
+ when the page definition is a class.
+
+template
+
+ This is a string implying a path to a filesystem template. Although
+ a path is usually just a simple relative pathname
+ (e.g. ``templates/foo.pt``, implying that the template is in the
+ "templates" directory relative to the directory in which the ZCML
+ file is defined), a path can be absolute, starting with a slash on
+ UNIX or a drive letter prefix on Windows. The path can alternately
+ be a :term:`resource` "specification" in the form
+ ``some.dotted.package_name:relative/path``, making it possible to
+ address template resources which live in a separate package. The
+ ``template`` attribute is optional. If it is not defined, no
+ template is assoicated with the view. See
+ :ref:`views_with_templates` for more information about view
+ templates.
+
request_method
This value can either be one of the strings 'GET', 'POST', 'PUT',
@@ -427,6 +455,13 @@ All arguments to ``bfg_view`` are optional.
If ``name`` is not supplied, the empty string is used (implying
the default view).
+If ``attr`` is not supplied, ``None`` is used (implying the function
+itself if the view is a function, or the ``__call__`` callable
+attribute if the view is a class).
+
+If ``template`` is not supplied, ``None`` is used (meaning that no
+template is associated with this view).
+
If ``request_type`` is not supplied, the value ``None`` is used,
implying any request type. Otherwise, this should be a class or
interface.
@@ -515,6 +550,156 @@ decorator syntactic sugar), if you wish:
my_view = bfg_view()(MyView)
+.. _views_with_templates:
+
+Views That Have a ``template``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Using a ``view`` with an associated ``template`` attribute differs
+from using a ``view`` without an associated ``template`` in a number
+of important ways:
+
+- When the ``template`` attribute is used, the BFG view machinery
+ finds and renders the template internally, unlike a view without an
+ associated ``template``, which, if it needs to render a template,
+ must find and render the template by itself.
+
+- When a ``template`` attribute is used, the may return a Response
+ object *or* a Python dictionary. This is unlike a BFG ``view``
+ without an associated template, which must always return a Response
+ object. If a BFG view without an associated template returns a
+ dictionary, an error will result at rendering time.
+
+- If the view callable with an associated template returns a Python
+ dictionary, the named template will be passed the dictionary as its
+ keyword arguments, and the view implementation will return the
+ resulting rendered template in a response to the user. The callable
+ object (whatever object was used to define the ``view``) will be
+ automatically inserted into the set of keyword arguments passed to
+ the template as the ``view`` keyword. If the view callable was a
+ class, the ``view`` keyword will be an instance of that class. Also
+ inserted into the keywords passed to the template are
+ ``template_name`` (the name of the template, which may be a full
+ path or a package-relative name, typically the full string used in
+ the ``template`` atttribute of the directive), ``context`` (the
+ context of the view used to render the template), and ``request``
+ (the request passed to the view used to render the template). None
+ of these default names are available to a template when the view
+ directive has no associated ``template`` attribute; the developer is
+ responsible for inserting them herself.
+
+- If the ``view`` callable associated with a ``view`` directive
+ returns a Response object (an object with the attributes ``status``,
+ ``headerlist`` and ``app_iter``), any template associated with the
+ ``page`` declaration is ignored, and the response is passed back to
+ BFG. For example, if your page callable returns an ``HTTPFound``
+ response, no template rendering will be performed:
+
+ .. code-block:: python
+ :linenos:
+
+ from webob.exc import HTTPFound
+ return HTTPFound(location='http://example.com') # templating avoided
+
+Several keyword names in a dictionary return value of a view callable
+are treated specially by :mod:`repoze.bfg`. These values are passed
+through to the template during rendering, but they also influence the
+response returned to the user separate from any template rendering.
+Page callables should set these values into the dictionary they return
+to influence response attributes.
+
+content_type_
+
+ Defines the content-type of the resulting response,
+ e.g. ``text/xml``.
+
+headerlist_
+
+ A sequence of tuples describing cookie values that should be set in
+ the response, e.g. ``[('Set-Cookie', 'abc=123'), ('X-My-Header',
+ 'foo')]``.
+
+status_
+
+ A WSGI-style status code (e.g. ``200 OK``) describing the status of
+ the response.
+
+charset_
+
+ The character set (e.g. ``UTF-8``) of the response.
+
+cache_for_
+
+ A value in seconds which will influence ``Cache-Control`` and
+ ``Expires`` headers in the returned response. The same can also be
+ achieved by returning various values in the headerlist, this is
+ purely a convenience.
+
+View Template Filename Extension Mappings
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When the ``template`` attribute of a view directive is used, a
+filename extension based mapping is consulted to determine which
+templating renderer implementation to use. By default, a single
+filename-extension-to-renderer mapping is used: any template name with
+a filename extension of ".pt" is assumed to be rendered via a
+Chameleon ZPT template.
+
+If a template renderer cannot be recognized by the extension of a
+template, it will be assumed that a Chameleon text renderer should be
+used to render the template.
+
+Adding and Overriding Template Filename Extension Mappings
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Additonal declarations can be made which override a default
+file-extension-to-renderer mapping or add a new
+file-extension-to-renderer mapping. This is accomplished via one or
+more separate ZCML directives.
+
+For example, to add Jinja2 rendering (after installing the
+repoze.bfg.jinja2" package), whereby filenames that end in ``.jinja``
+are rendered by a Jinja2 renderer::
+
+ <template_renderer
+ extension=".jinja"
+ renderer="my.package.MyJinja2Renderer"/>
+
+To override the default mapping in which files with a ``.pt``
+extension are rendered via a Chameleon ZPT page template renderer, use
+a variation on the following::
+
+ <template_renderer
+ extension=".pt"
+ renderer="my.package.pt_renderer"/>
+
+By default, when a template extension is unrecognized, the Chameleon
+text templating engine is assumed. You can override the default
+renderer by creating a directive which has no ``extension``::
+
+ <template_renderer
+ renderer="my.package.default_renderer"/>
+
+A renderer must be a class that has the following interface:
+
+.. code-block:: python
+ :linenos:
+
+ class TemplateRendererFactory:
+ def __init__(self, path, auto_reload=False):
+ """ Constructor """
+
+ def implementation(self):
+ """ Return the object that the underlying templating system
+ uses to render the template; it is typically a callable that
+ accepts arbitrary keyword arguments and returns a string or
+ unicode object """
+
+ def __call__(self, **kw):
+ """ Call a the template implementation with the keywords
+ passed in as arguments and return the result (a string or
+ unicode object) """
+
.. _using_model_interfaces:
Using Model Interfaces
diff --git a/repoze/bfg/chameleon_text.py b/repoze/bfg/chameleon_text.py
index 453328d9c..f482d795f 100644
--- a/repoze/bfg/chameleon_text.py
+++ b/repoze/bfg/chameleon_text.py
@@ -2,50 +2,11 @@ from webob import Response
from zope.component import queryUtility
-from zope.interface import classProvides
-from zope.interface import implements
-
from repoze.bfg.interfaces import IResponseFactory
-from repoze.bfg.interfaces import ITemplateRenderer
-from repoze.bfg.interfaces import ITemplateRendererFactory
-from repoze.bfg.interfaces import ISettings
from repoze.bfg.templating import renderer_from_cache
-
-from chameleon.core.template import TemplateFile
-from chameleon.zpt.language import Parser
-
-class TextTemplateFile(TemplateFile):
- default_parser = Parser()
-
- def __init__(self, filename, parser=None, format=None,
- doctype=None, **kwargs):
- if parser is None:
- parser = self.default_parser
- super(TextTemplateFile, self).__init__(filename, parser, format,
- doctype, **kwargs)
-
-class TextTemplateRenderer(object):
- classProvides(ITemplateRendererFactory)
- implements(ITemplateRenderer)
-
- def __init__(self, path, auto_reload=False):
- self.template = TextTemplateFile(
- path,
- format='text',
- auto_reload=auto_reload
- )
-
- def implementation(self):
- return self.template
-
- def __call__(self, **kw):
- return self.template(**kw)
-
-def _auto_reload():
- settings = queryUtility(ISettings)
- auto_reload = settings and settings.reload_templates
- return auto_reload
+from repoze.bfg.templating import TextTemplateRenderer
+from repoze.bfg.templating import _auto_reload
def get_renderer(path):
""" Return a callable ``ITemplateRenderer`` object representing a
diff --git a/repoze/bfg/chameleon_zpt.py b/repoze/bfg/chameleon_zpt.py
index f4f899879..c66a69e66 100644
--- a/repoze/bfg/chameleon_zpt.py
+++ b/repoze/bfg/chameleon_zpt.py
@@ -8,9 +8,9 @@ from zope.interface import implements
from repoze.bfg.interfaces import IResponseFactory
from repoze.bfg.interfaces import ITemplateRenderer
from repoze.bfg.interfaces import ITemplateRendererFactory
-from repoze.bfg.interfaces import ISettings
from repoze.bfg.templating import renderer_from_cache
+from repoze.bfg.templating import _auto_reload
from chameleon.zpt.template import PageTemplateFile
class ZPTTemplateRenderer(object):
@@ -26,11 +26,6 @@ class ZPTTemplateRenderer(object):
def __call__(self, **kw):
return self.template(**kw)
-def _auto_reload():
- settings = queryUtility(ISettings)
- auto_reload = settings and settings.reload_templates
- return auto_reload
-
def get_renderer(path):
""" Return a callable ``ITemplateRenderer`` object representing a
``chameleon.zpt`` template at the package-relative path (may also
diff --git a/repoze/bfg/includes/configure.zcml b/repoze/bfg/includes/configure.zcml
index 7e2036476..be8a69543 100644
--- a/repoze/bfg/includes/configure.zcml
+++ b/repoze/bfg/includes/configure.zcml
@@ -4,6 +4,8 @@
<!-- traversal adapters -->
+ <include file="meta.zcml" />
+
<adapter
factory="repoze.bfg.traversal.ModelGraphTraverser"
provides="repoze.bfg.interfaces.ITraverserFactory"
@@ -16,6 +18,9 @@
for="* repoze.bfg.interfaces.IRequest"
/>
- <include file="meta.zcml" />
+ <template_renderer
+ renderer="repoze.bfg.chameleon_zpt.ZPTTemplateRenderer"
+ extension=".pt"
+ />
</configure>
diff --git a/repoze/bfg/includes/meta.zcml b/repoze/bfg/includes/meta.zcml
index 69e2eabc0..8195101be 100644
--- a/repoze/bfg/includes/meta.zcml
+++ b/repoze/bfg/includes/meta.zcml
@@ -70,6 +70,12 @@
handler="repoze.bfg.zcml.aclauthorizationpolicy"
/>
+ <meta:directive
+ name="template_renderer"
+ schema="repoze.bfg.zcml.ITemplateRendererDirective"
+ handler="repoze.bfg.zcml.template_renderer"
+ />
+
</meta:directives>
</configure>
diff --git a/repoze/bfg/templating.py b/repoze/bfg/templating.py
index 1f242b306..2ec049ca5 100644
--- a/repoze/bfg/templating.py
+++ b/repoze/bfg/templating.py
@@ -4,10 +4,42 @@ import pkg_resources
from zope.component import queryUtility
from zope.component import getSiteManager
+from zope.interface import classProvides
+from zope.interface import implements
+
+from chameleon.core.template import TemplateFile
+from chameleon.zpt.language import Parser
+
+from repoze.bfg.interfaces import ISettings
from repoze.bfg.interfaces import ITemplateRenderer
+from repoze.bfg.interfaces import ITemplateRendererFactory
from repoze.bfg.path import caller_package
from repoze.bfg.settings import get_settings
+class TextTemplateFile(TemplateFile):
+ default_parser = Parser()
+
+ def __init__(self, filename, parser=None, format=None, doctype=None,
+ **kwargs):
+ if parser is None:
+ parser = self.default_parser
+ super(TextTemplateFile, self).__init__(filename, parser, format,
+ doctype, **kwargs)
+
+class TextTemplateRenderer(object):
+ classProvides(ITemplateRendererFactory)
+ implements(ITemplateRenderer)
+
+ def __init__(self, path, auto_reload=False):
+ self.template = TextTemplateFile(path, format='text',
+ auto_reload=auto_reload)
+
+ def implementation(self):
+ return self.template
+
+ def __call__(self, **kw):
+ return self.template(**kw)
+
def renderer_from_cache(path, factory, level=3, **kw):
if os.path.isabs(path):
# 'path' is an absolute filename (not common and largely only
@@ -50,3 +82,14 @@ def renderer_from_cache(path, factory, level=3, **kw):
return renderer
+def renderer_from_path(path, level=4, **kw):
+ extension = os.path.splitext(path)[1]
+ factory = queryUtility(ITemplateRendererFactory, name=extension,
+ default=TextTemplateRenderer)
+ return renderer_from_cache(path, factory, level, **kw)
+
+def _auto_reload():
+ settings = queryUtility(ISettings)
+ auto_reload = settings and settings['reload_templates']
+ return auto_reload
+
diff --git a/repoze/bfg/tests/test_chameleon_text.py b/repoze/bfg/tests/test_chameleon_text.py
index 74d348d14..139a77072 100644
--- a/repoze/bfg/tests/test_chameleon_text.py
+++ b/repoze/bfg/tests/test_chameleon_text.py
@@ -5,6 +5,13 @@ from repoze.bfg.testing import cleanUp
class Base:
def setUp(self):
cleanUp()
+ import os
+ try:
+ # avoid spew from chameleon logger?
+ os.unlink(self._getTemplatePath('minimal.txt.py'))
+ except:
+ pass
+
def tearDown(self):
cleanUp()
diff --git a/repoze/bfg/tests/test_templating.py b/repoze/bfg/tests/test_templating.py
index 6b317fb7d..c3894cce4 100644
--- a/repoze/bfg/tests/test_templating.py
+++ b/repoze/bfg/tests/test_templating.py
@@ -142,6 +142,37 @@ class TestRendererFromCache(unittest.TestCase):
self.assertNotEqual(queryUtility(ITemplateRenderer, name=spec),
None)
+class TestRendererFromPath(unittest.TestCase):
+ def setUp(self):
+ cleanUp()
+
+ def tearDown(self):
+ cleanUp()
+
+ def _callFUT(self, path, level=4, **kw):
+ from repoze.bfg.templating import renderer_from_path
+ return renderer_from_path(path, level, **kw)
+
+ def test_with_default(self):
+ from repoze.bfg.templating import TextTemplateRenderer
+ import os
+ here = os.path.dirname(os.path.abspath(__file__))
+ fixture = os.path.join(here, 'fixtures/minimal.txt')
+ result = self._callFUT(fixture)
+ self.assertEqual(result.__class__, TextTemplateRenderer)
+
+ def test_with_nondefault(self):
+ from repoze.bfg.interfaces import ITemplateRendererFactory
+ import os
+ here = os.path.dirname(os.path.abspath(__file__))
+ fixture = os.path.join(here, 'fixtures/minimal.pt')
+ renderer = {}
+ def factory(path, **kw):
+ return renderer
+ testing.registerUtility(factory, ITemplateRendererFactory, name='.pt')
+ result = self._callFUT(fixture)
+ self.assertEqual(result, renderer)
+
class DummyFactory:
def __init__(self, renderer):
self.renderer = renderer
diff --git a/repoze/bfg/tests/test_view.py b/repoze/bfg/tests/test_view.py
index f496ebb36..5639e9799 100644
--- a/repoze/bfg/tests/test_view.py
+++ b/repoze/bfg/tests/test_view.py
@@ -620,7 +620,7 @@ class TestMultiView(unittest.TestCase):
response = mv.__call_permissive__(context, request)
self.assertEqual(response, expected_response)
-class TestMapView(unittest.TestCase):
+class Test_map_view(unittest.TestCase):
def setUp(self):
cleanUp()
@@ -636,7 +636,22 @@ class TestMapView(unittest.TestCase):
return 'OK'
result = self._callFUT(view)
self.failUnless(result is view)
- self.assertEqual(view(None, None), 'OK')
+ self.assertEqual(result(None, None), 'OK')
+
+ def test_view_as_function_with_attr(self):
+ def view(context, request):
+ """ """
+ result = self._callFUT(view, attr='__name__')
+ self.failIf(result is view)
+ self.assertRaises(TypeError, result, None, None)
+
+ def test_view_as_function_with_attr_and_template(self):
+ def view(context, request):
+ """ """
+ result = self._callFUT(view, attr='__name__',
+ template='fixtures/minimal.txt')
+ self.failIf(result is view)
+ self.assertRaises(TypeError, result, None, None)
def test_view_as_function_requestonly(self):
def view(request):
@@ -648,6 +663,16 @@ class TestMapView(unittest.TestCase):
self.assertEqual(view.__name__, result.__name__)
self.assertEqual(result(None, None), 'OK')
+ def test_view_as_function_requestonly_with_attr(self):
+ def view(request):
+ """ """
+ result = self._callFUT(view, attr='__name__')
+ self.failIf(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertRaises(TypeError, result, None, None)
+
def test_view_as_newstyle_class_context_and_request(self):
class view(object):
def __init__(self, context, request):
@@ -660,10 +685,38 @@ class TestMapView(unittest.TestCase):
self.assertEqual(view.__doc__, result.__doc__)
self.assertEqual(view.__name__, result.__name__)
self.assertEqual(result(None, None), 'OK')
+
+ def test_view_as_newstyle_class_context_and_request_with_attr(self):
+ class view(object):
+ def __init__(self, context, request):
+ pass
+ def index(self):
+ return 'OK'
+ result = self._callFUT(view, attr='index')
+ self.failIf(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertEqual(result(None, None), 'OK')
+
+ def test_view_as_newstyle_class_context_and_request_with_attr_and_template(
+ self):
+ class view(object):
+ def __init__(self, context, request):
+ pass
+ def index(self):
+ return {'a':'1'}
+ result = self._callFUT(view, attr='index',
+ template='repoze.bfg.tests:fixtures/minimal.txt')
+ self.failIf(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertEqual(result(None, None).body, 'Hello.\n')
def test_view_as_newstyle_class_requestonly(self):
class view(object):
- def __init__(self, context, request):
+ def __init__(self, request):
pass
def __call__(self):
return 'OK'
@@ -674,6 +727,33 @@ class TestMapView(unittest.TestCase):
self.assertEqual(view.__name__, result.__name__)
self.assertEqual(result(None, None), 'OK')
+ def test_view_as_newstyle_class_requestonly_with_attr(self):
+ class view(object):
+ def __init__(self, request):
+ pass
+ def index(self):
+ return 'OK'
+ result = self._callFUT(view, attr='index')
+ self.failIf(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertEqual(result(None, None), 'OK')
+
+ def test_view_as_newstyle_class_requestonly_with_attr_and_template(self):
+ class view(object):
+ def __init__(self, request):
+ pass
+ def index(self):
+ return {'a':'1'}
+ result = self._callFUT(view, attr='index',
+ template='repoze.bfg.tests:fixtures/minimal.txt')
+ self.failIf(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertEqual(result(None, None).body, 'Hello.\n')
+
def test_view_as_oldstyle_class_context_and_request(self):
class view:
def __init__(self, context, request):
@@ -686,11 +766,39 @@ class TestMapView(unittest.TestCase):
self.assertEqual(view.__doc__, result.__doc__)
self.assertEqual(view.__name__, result.__name__)
self.assertEqual(result(None, None), 'OK')
-
- def test_view_as_oldstyle_class_requestonly(self):
+
+ def test_view_as_oldstyle_class_context_and_request_with_attr(self):
class view:
def __init__(self, context, request):
pass
+ def index(self):
+ return 'OK'
+ result = self._callFUT(view, attr='index')
+ self.failIf(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertEqual(result(None, None), 'OK')
+
+ def test_view_as_oldstyle_class_context_and_request_with_attr_and_template(
+ self):
+ class view:
+ def __init__(self, context, request):
+ pass
+ def index(self):
+ return {'a':'1'}
+ result = self._callFUT(view, attr='index',
+ template='repoze.bfg.tests:fixtures/minimal.txt')
+ self.failIf(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertEqual(result(None, None).body, 'Hello.\n')
+
+ def test_view_as_oldstyle_class_requestonly(self):
+ class view:
+ def __init__(self, request):
+ pass
def __call__(self):
return 'OK'
result = self._callFUT(view)
@@ -700,6 +808,33 @@ class TestMapView(unittest.TestCase):
self.assertEqual(view.__name__, result.__name__)
self.assertEqual(result(None, None), 'OK')
+ def test_view_as_oldstyle_class_requestonly_with_attr(self):
+ class view:
+ def __init__(self, request):
+ pass
+ def index(self):
+ return 'OK'
+ result = self._callFUT(view, attr='index')
+ self.failIf(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertEqual(result(None, None), 'OK')
+
+ def test_view_as_oldstyle_class_requestonly_with_attr_and_template(self):
+ class view:
+ def __init__(self, request):
+ pass
+ def index(self):
+ return {'a':'1'}
+ result = self._callFUT(view, attr='index',
+ template='repoze.bfg.tests:fixtures/minimal.txt')
+ self.failIf(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertEqual(result(None, None).body, 'Hello.\n')
+
def test_view_as_instance_context_and_request(self):
class View:
def __call__(self, context, request):
@@ -709,6 +844,25 @@ class TestMapView(unittest.TestCase):
self.failUnless(result is view)
self.assertEqual(result(None, None), 'OK')
+ def test_view_as_instance_context_and_request_and_attr(self):
+ class View:
+ def index(self, context, request):
+ return 'OK'
+ view = View()
+ result = self._callFUT(view, attr='index')
+ self.failIf(result is view)
+ self.assertEqual(result(None, None), 'OK')
+
+ def test_view_as_instance_context_and_request_attr_and_template(self):
+ class View:
+ def index(self, context, request):
+ return {'a':'1'}
+ view = View()
+ result = self._callFUT(view, attr='index',
+ template='repoze.bfg.tests:fixtures/minimal.txt')
+ self.failIf(result is view)
+ self.assertEqual(result(None, None).body, 'Hello.\n')
+
def test_view_as_instance_requestonly(self):
class View:
def __call__(self, request):
@@ -721,6 +875,41 @@ class TestMapView(unittest.TestCase):
self.failUnless('instance' in result.__name__)
self.assertEqual(result(None, None), 'OK')
+ def test_view_as_instance_requestonly_with_attr(self):
+ class View:
+ def index(self, request):
+ return 'OK'
+ view = View()
+ result = self._callFUT(view, attr='index')
+ self.failIf(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.failUnless('instance' in result.__name__)
+ self.assertEqual(result(None, None), 'OK')
+
+ def test_view_as_instance_requestonly_with_attr_and_template(self):
+ class View:
+ def index(self, request):
+ return {'a':'1'}
+ view = View()
+ result = self._callFUT(view, attr='index',
+ template='repoze.bfg.tests:fixtures/minimal.txt')
+ self.failIf(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.failUnless('instance' in result.__name__)
+ self.assertEqual(result(None, None).body, 'Hello.\n')
+
+ def test_view_templateonly(self):
+ def view(context, request):
+ return {'a':'1'}
+ result = self._callFUT(view,
+ template='repoze.bfg.tests:fixtures/minimal.txt')
+ self.failIf(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(result(None, None).body, 'Hello.\n')
+
class TestRequestOnly(unittest.TestCase):
def _callFUT(self, arg):
from repoze.bfg.view import requestonly
@@ -923,6 +1112,71 @@ class TestDecorateView(unittest.TestCase):
self.failUnless(view1.__predicated__.im_func is
view2.__predicated__.im_func)
+class Test_templated_response(unittest.TestCase):
+ def setUp(self):
+ cleanUp()
+
+ def tearDown(self):
+ cleanUp()
+
+ def _callFUT(self, template_name, response, view=None,
+ context=None, request=None, auto_reload=False):
+ from repoze.bfg.view import templated_response
+ return templated_response(template_name, response, view, context,
+ request, auto_reload)
+
+ def test_is_response(self):
+ response = DummyResponse()
+ result = self._callFUT(
+ 'repoze.bfg.tests:fixtures/minimal.txt', response)
+ self.assertEqual(result, response)
+
+ def test_is_not_valid_dict(self):
+ response = None
+ result = self._callFUT(
+ 'repoze.bfg.tests:fixtures/minimal.txt', response)
+ self.assertEqual(result, response)
+
+ def test_valid_dict(self):
+ response = {'a':'1'}
+ result = self._callFUT(
+ 'repoze.bfg.tests:fixtures/minimal.txt', response)
+ self.assertEqual(result.body, 'Hello.\n')
+
+ def test_with_content_type(self):
+ response = {'a':'1', 'content_type_':'text/nonsense'}
+ result = self._callFUT(
+ 'repoze.bfg.tests:fixtures/minimal.txt', response)
+ self.assertEqual(result.content_type, 'text/nonsense')
+
+ def test_with_headerlist(self):
+ response = {'a':'1', 'headerlist_':[('a', '1'), ('b', '2')]}
+ result = self._callFUT(
+ 'repoze.bfg.tests:fixtures/minimal.txt', response)
+ self.assertEqual(result.headerlist,
+ [('Content-Type', 'text/html; charset=UTF-8'),
+ ('Content-Length', '7'),
+ ('a', '1'),
+ ('b', '2')])
+
+ def test_with_status(self):
+ response = {'a':'1', 'status_':'406 You Lose'}
+ result = self._callFUT(
+ 'repoze.bfg.tests:fixtures/minimal.txt', response)
+ self.assertEqual(result.status, '406 You Lose')
+
+ def test_with_charset(self):
+ response = {'a':'1', 'charset_':'UTF-16'}
+ result = self._callFUT(
+ 'repoze.bfg.tests:fixtures/minimal.txt', response)
+ self.assertEqual(result.charset, 'UTF-16')
+
+ def test_with_cache_for(self):
+ response = {'a':'1', 'cache_for_':100}
+ result = self._callFUT(
+ 'repoze.bfg.tests:fixtures/minimal.txt', response)
+ self.assertEqual(result.cache_control.max_age, 100)
+
class DummyContext:
pass
diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py
index 9e8c70cbd..e54bd108f 100644
--- a/repoze/bfg/tests/test_zcml.py
+++ b/repoze/bfg/tests/test_zcml.py
@@ -1,3 +1,7 @@
+import logging
+
+logging.basicConfig()
+
import unittest
from repoze.bfg.testing import cleanUp
@@ -35,7 +39,8 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -66,7 +71,8 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -102,7 +108,8 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -133,7 +140,8 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -172,7 +180,8 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -210,7 +219,8 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -249,7 +259,8 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -287,7 +298,8 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -303,6 +315,45 @@ class TestViewDirective(unittest.TestCase):
perm = sm.adapters.lookup((IFoo, IRequest), IViewPermission, name='')
self.assertEqual(perm, None)
+ def test_with_reltemplate(self):
+ from zope.interface import Interface
+ from zope.component import getSiteManager
+ from repoze.bfg.interfaces import IRequest
+ from repoze.bfg.interfaces import IView
+ from repoze.bfg.interfaces import IViewPermission
+
+ import repoze.bfg.tests
+
+ context = DummyContext(repoze.bfg.tests)
+ class IFoo(Interface):
+ pass
+ class view(object):
+ def __init__(self, context, request):
+ self.request = request
+ self.context = context
+
+ def __call__(self):
+ return {'a':'1'}
+
+ import os
+ fixture = 'fixtures/minimal.txt'
+ self._callFUT(context, 'repoze.view', IFoo, view=view, template=fixture)
+ actions = context.actions
+ self.assertEqual(len(actions), 1)
+
+ action = actions[0]
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None,
+ None)
+ self.assertEqual(action['discriminator'], discrim)
+ register = action['callable']
+ register()
+ sm = getSiteManager()
+ wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='')
+ self.assertEqual(wrapper.__module__, view.__module__)
+ self.assertEqual(wrapper.__name__, view.__name__)
+ self.assertEqual(wrapper.__doc__, view.__doc__)
+ result = wrapper(None, None)
+ self.assertEqual(result.body, 'Hello.\n')
def test_request_type_asinterface(self):
from zope.component import getSiteManager
@@ -322,7 +373,8 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IDummy, IView, None, None, None, None)
+ discrim = ('view', IFoo, '', IDummy, IView, None, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -352,7 +404,7 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', Dummy, IView, None, None, None, None)
+ discrim = ('view', IFoo, '', Dummy, IView, None, None, None, None, None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -378,7 +430,8 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, None, 'GET', None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, 'GET', None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -407,7 +460,8 @@ class TestViewDirective(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 1)
- discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None,
+ None)
self.assertEqual(actions[0]['discriminator'], discrim)
register = actions[0]['callable']
register()
@@ -441,7 +495,7 @@ class TestViewDirective(unittest.TestCase):
factory = sm.getUtility(IRouteRequest, 'foo')
request_type = implementedBy(factory)
discrim = ('view', IFoo, '', request_type, IView, None, None, None,
- 'foo')
+ 'foo', None)
self.assertEqual(action['discriminator'], discrim)
the_view = sm.adapters.lookup((IFoo, request_type), IView, name='')
request = factory({})
@@ -463,7 +517,8 @@ class TestViewDirective(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, None, 'POST', None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, 'POST', None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -489,7 +544,8 @@ class TestViewDirective(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, None, 'POST', None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, 'POST', None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -514,7 +570,8 @@ class TestViewDirective(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, 'abc', None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, 'abc', None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -540,7 +597,8 @@ class TestViewDirective(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, 'abc', None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, 'abc', None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -565,7 +623,8 @@ class TestViewDirective(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, 'abc', None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, 'abc', None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -591,7 +650,8 @@ class TestViewDirective(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, 'abc', None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, 'abc', None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -618,7 +678,8 @@ class TestViewDirective(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, IFoo, None, None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, IFoo, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -645,7 +706,8 @@ class TestViewDirective(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, IFoo, None, None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, IFoo, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -673,7 +735,8 @@ class TestViewDirective(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -715,7 +778,8 @@ class TestViewDirective(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None)
+ discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -741,7 +805,8 @@ class TestViewDirective(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 1)
action = actions[0]
- discrim = ('view', Foo, '', IRequest, IView, None, None, None, None)
+ discrim = ('view', Foo, '', IRequest, IView, None, None, None, None,
+ None)
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
@@ -1110,7 +1175,7 @@ class TestDeriveView(unittest.TestCase):
def test_view_with_debug_authorization_no_authpol(self):
def view(context, request):
return 'OK'
- self._registerSettings(debug_authorization=True)
+ self._registerSettings(debug_authorization=True, reload_templates=True)
logger = self._registerLogger()
result = self._callFUT(view, permission='view')
self.assertEqual(view.__module__, result.__module__)
@@ -1130,7 +1195,7 @@ class TestDeriveView(unittest.TestCase):
def test_view_with_debug_authorization_no_permission(self):
def view(context, request):
return 'OK'
- self._registerSettings(debug_authorization=True)
+ self._registerSettings(debug_authorization=True, reload_templates=True)
self._registerSecurityPolicy(True)
logger = self._registerLogger()
result = self._callFUT(view)
@@ -1151,7 +1216,7 @@ class TestDeriveView(unittest.TestCase):
def test_view_with_debug_authorization_permission_authpol_permitted(self):
def view(context, request):
return 'OK'
- self._registerSettings(debug_authorization=True)
+ self._registerSettings(debug_authorization=True, reload_templates=True)
logger = self._registerLogger()
self._registerSecurityPolicy(True)
result = self._callFUT(view, permission='view')
@@ -1172,7 +1237,7 @@ class TestDeriveView(unittest.TestCase):
from repoze.bfg.security import Unauthorized
def view(context, request):
""" """
- self._registerSettings(debug_authorization=True)
+ self._registerSettings(debug_authorization=True, reload_templates=True)
logger = self._registerLogger()
self._registerSecurityPolicy(False)
result = self._callFUT(view, permission='view')
@@ -1192,7 +1257,7 @@ class TestDeriveView(unittest.TestCase):
def test_view_with_debug_authorization_permission_authpol_denied2(self):
def view(context, request):
""" """
- self._registerSettings(debug_authorization=True)
+ self._registerSettings(debug_authorization=True, reload_templates=True)
logger = self._registerLogger()
self._registerSecurityPolicy(False)
result = self._callFUT(view, permission='view')
@@ -1330,7 +1395,7 @@ class TestRouteDirective(unittest.TestCase):
request_factory = sm.getUtility(IRouteRequest, 'name')
request_type = implementedBy(request_factory)
view_discriminator = view_action['discriminator']
- self.assertEqual(len(view_discriminator), 9)
+ self.assertEqual(len(view_discriminator), 10)
self.assertEqual(view_discriminator[0], 'view')
self.assertEqual(view_discriminator[1], None)
self.assertEqual(view_discriminator[2],'')
@@ -1340,6 +1405,7 @@ class TestRouteDirective(unittest.TestCase):
self.assertEqual(view_discriminator[6], None)
self.assertEqual(view_discriminator[7], None)
self.assertEqual(view_discriminator[8], 'name')
+ self.assertEqual(view_discriminator[9], None)
register = view_action['callable']
register()
sm = getSiteManager()
@@ -1378,7 +1444,7 @@ class TestRouteDirective(unittest.TestCase):
request_factory = sm.getUtility(IRouteRequest, 'name')
request_type = implementedBy(request_factory)
view_discriminator = view_action['discriminator']
- self.assertEqual(len(view_discriminator), 9)
+ self.assertEqual(len(view_discriminator), 10)
self.assertEqual(view_discriminator[0], 'view')
self.assertEqual(view_discriminator[1], IDummy)
self.assertEqual(view_discriminator[2],'')
@@ -1388,6 +1454,7 @@ class TestRouteDirective(unittest.TestCase):
self.assertEqual(view_discriminator[6], None)
self.assertEqual(view_discriminator[7], None)
self.assertEqual(view_discriminator[8], 'name')
+ self.assertEqual(view_discriminator[9], None)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
request = DummyRequest()
self.assertEqual(wrapped(None, request), '123')
@@ -1441,7 +1508,7 @@ class TestRouteDirective(unittest.TestCase):
request_factory = sm.getUtility(IRouteRequest, 'name')
request_type = implementedBy(request_factory)
view_discriminator = view_action['discriminator']
- self.assertEqual(len(view_discriminator), 9)
+ self.assertEqual(len(view_discriminator), 10)
self.assertEqual(view_discriminator[0], 'view')
self.assertEqual(view_discriminator[1], None)
self.assertEqual(view_discriminator[2],'')
@@ -1451,6 +1518,7 @@ class TestRouteDirective(unittest.TestCase):
self.assertEqual(view_discriminator[6], None)
self.assertEqual(view_discriminator[7], 'GET')
self.assertEqual(view_discriminator[8], 'name')
+ self.assertEqual(view_discriminator[9], None)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -1485,7 +1553,7 @@ class TestRouteDirective(unittest.TestCase):
request_factory = sm.getUtility(IRouteRequest, 'name')
request_type = implementedBy(request_factory)
view_discriminator = view_action['discriminator']
- self.assertEqual(len(view_discriminator), 9)
+ self.assertEqual(len(view_discriminator), 10)
self.assertEqual(view_discriminator[0], 'view')
self.assertEqual(view_discriminator[1], None)
self.assertEqual(view_discriminator[2],'')
@@ -1495,6 +1563,7 @@ class TestRouteDirective(unittest.TestCase):
self.assertEqual(view_discriminator[6], None)
self.assertEqual(view_discriminator[7], 'GET')
self.assertEqual(view_discriminator[8], 'name')
+ self.assertEqual(view_discriminator[9], None)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -1530,7 +1599,7 @@ class TestRouteDirective(unittest.TestCase):
request_factory = sm.getUtility(IRouteRequest, 'name')
request_type = implementedBy(request_factory)
view_discriminator = view_action['discriminator']
- self.assertEqual(len(view_discriminator), 9)
+ self.assertEqual(len(view_discriminator), 10)
self.assertEqual(view_discriminator[0], 'view')
self.assertEqual(view_discriminator[1], None)
self.assertEqual(view_discriminator[2],'')
@@ -1540,6 +1609,7 @@ class TestRouteDirective(unittest.TestCase):
self.assertEqual(view_discriminator[6], None)
self.assertEqual(view_discriminator[7], 'GET')
self.assertEqual(view_discriminator[8], 'name')
+ self.assertEqual(view_discriminator[9], None)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -1574,7 +1644,7 @@ class TestRouteDirective(unittest.TestCase):
request_factory = sm.getUtility(IRouteRequest, 'name')
request_type = implementedBy(request_factory)
view_discriminator = view_action['discriminator']
- self.assertEqual(len(view_discriminator), 9)
+ self.assertEqual(len(view_discriminator), 10)
self.assertEqual(view_discriminator[0], 'view')
self.assertEqual(view_discriminator[1], None)
self.assertEqual(view_discriminator[2],'')
@@ -1584,6 +1654,7 @@ class TestRouteDirective(unittest.TestCase):
self.assertEqual(view_discriminator[6], None)
self.assertEqual(view_discriminator[7], 'GET')
self.assertEqual(view_discriminator[8], 'name')
+ self.assertEqual(view_discriminator[9], None)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -1618,7 +1689,7 @@ class TestRouteDirective(unittest.TestCase):
request_factory = sm.getUtility(IRouteRequest, 'name')
request_type = implementedBy(request_factory)
view_discriminator = view_action['discriminator']
- self.assertEqual(len(view_discriminator), 9)
+ self.assertEqual(len(view_discriminator), 10)
self.assertEqual(view_discriminator[0], 'view')
self.assertEqual(view_discriminator[1], None)
self.assertEqual(view_discriminator[2],'')
@@ -1628,6 +1699,7 @@ class TestRouteDirective(unittest.TestCase):
self.assertEqual(view_discriminator[6], None)
self.assertEqual(view_discriminator[7], None)
self.assertEqual(view_discriminator[8], 'name')
+ self.assertEqual(view_discriminator[9], None)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -1662,7 +1734,7 @@ class TestRouteDirective(unittest.TestCase):
request_factory = sm.getUtility(IRouteRequest, 'name')
request_type = implementedBy(request_factory)
view_discriminator = view_action['discriminator']
- self.assertEqual(len(view_discriminator), 9)
+ self.assertEqual(len(view_discriminator), 10)
self.assertEqual(view_discriminator[0], 'view')
self.assertEqual(view_discriminator[1], None)
self.assertEqual(view_discriminator[2],'')
@@ -1672,6 +1744,7 @@ class TestRouteDirective(unittest.TestCase):
self.assertEqual(view_discriminator[6], None)
self.assertEqual(view_discriminator[7], None)
self.assertEqual(view_discriminator[8], 'name')
+ self.assertEqual(view_discriminator[9], None)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -2010,6 +2083,8 @@ class TestBFGViewFunctionGrokker(unittest.TestCase):
obj.__request_method__ = None
obj.__request_param__ = None
obj.__containment__ = None
+ obj.__attr__ = None
+ obj.__template__ = None
context = DummyContext()
result = grokker.grok('name', obj, context=context)
self.assertEqual(result, True)
diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py
index 8e6d1c7fe..794c7fa65 100644
--- a/repoze/bfg/view.py
+++ b/repoze/bfg/view.py
@@ -33,6 +33,9 @@ from repoze.bfg.path import caller_package
from repoze.bfg.static import PackageURLParser
+from repoze.bfg.templating import renderer_from_path
+from repoze.bfg.templating import _auto_reload
+
deprecated('view_execution_permitted',
"('from repoze.bfg.view import view_execution_permitted' was "
"deprecated as of repoze.bfg 1.0; instead use 'from "
@@ -229,6 +232,13 @@ class bfg_view(object):
If ``name`` is not supplied, the empty string is used (implying
the default view name).
+ If ``attr`` is not supplied, ``None`` is used (implying the
+ function itself if the view is a function, or the ``__call__``
+ callable attribute if the view is a class).
+
+ If ``template`` is not supplied, ``None`` is used (meaning that no
+ template is associated with this view).
+
If ``request_type`` is not supplied, the interface
``repoze.bfg.interfaces.IRequest`` is used, implying the standard
request interface type.
@@ -322,7 +332,7 @@ class bfg_view(object):
"""
def __init__(self, name='', request_type=None, for_=None, permission=None,
route_name=None, request_method=None, request_param=None,
- containment=None):
+ containment=None, attr=None, template=None):
self.name = name
self.request_type = request_type
self.for_ = for_
@@ -331,9 +341,11 @@ class bfg_view(object):
self.request_method = request_method
self.request_param = request_param
self.containment = containment
+ self.attr = attr
+ self.template = template
def __call__(self, wrapped):
- _bfg_view = map_view(wrapped)
+ _bfg_view = map_view(wrapped, self.attr, self.template)
_bfg_view.__is_bfg_view__ = True
_bfg_view.__permission__ = self.permission
_bfg_view.__for__ = self.for_
@@ -413,8 +425,41 @@ class MultiView(object):
continue
raise NotFound(self.name)
-def map_view(view):
+def templated_response(template_name, response, view, context, request,
+ auto_reload=False):
+ if is_response(response):
+ return response
+ renderer = renderer_from_path(template_name, auto_reload=auto_reload)
+ kw = {'view':view, 'template_name':template_name, 'context':context,
+ 'request':request}
+ try:
+ kw.update(response)
+ except TypeError:
+ return response
+ result = renderer(**kw)
+ response_factory = queryUtility(IResponseFactory, default=Response)
+ response = response_factory(result)
+ content_type = kw.get('content_type_', None)
+ if content_type is not None:
+ response.content_type = content_type
+ headerlist = kw.get('headerlist_', None)
+ if headerlist is not None:
+ for k, v in headerlist:
+ response.headers.add(k, v)
+ status = kw.get('status_', None)
+ if status is not None:
+ response.status = status
+ charset = kw.get('charset_', None)
+ if charset is not None:
+ response.charset = charset
+ cache_for = kw.get('cache_for_', None)
+ if cache_for is not None:
+ response.cache_expires = cache_for
+ return response
+
+def map_view(view, attr=None, template=None):
wrapped_view = view
+ auto_reload = _auto_reload()
if inspect.isclass(view):
# If the object we've located is a class, turn it into a
@@ -423,33 +468,74 @@ def map_view(view):
# position arguments, then immediately invoke the __call__
# method of the instance with no arguments; __call__ should
# return an IResponse).
- if requestonly(view):
+ if requestonly(view, attr):
# its __init__ accepts only a single request argument,
# instead of both context and request
def _bfg_class_requestonly_view(context, request):
inst = view(request)
- return inst()
+ if attr is None:
+ response = inst()
+ else:
+ response = getattr(inst, attr)()
+ if template:
+ response = templated_response(template, response, inst,
+ context, request, auto_reload)
+ return response
wrapped_view = _bfg_class_requestonly_view
else:
# its __init__ accepts both context and request
def _bfg_class_view(context, request):
inst = view(context, request)
- return inst()
+ if attr is None:
+ response = inst()
+ else:
+ response = getattr(inst, attr)()
+ if template:
+ response = templated_response(template, response, inst,
+ context, request, auto_reload)
+ return response
wrapped_view = _bfg_class_view
- elif requestonly(view):
+ elif requestonly(view, attr):
# its __call__ accepts only a single request argument,
# instead of both context and request
def _bfg_requestonly_view(context, request):
- return view(request)
+ if attr is None:
+ response = view(request)
+ else:
+ response = getattr(view, attr)(request)
+
+ if template:
+ response = templated_response(template, response, view,
+ context, request, auto_reload)
+ return response
wrapped_view = _bfg_requestonly_view
+ elif attr:
+ def _bfg_attr_view(context, request):
+ response = getattr(view, attr)(context, request)
+ if template:
+ response = templated_response(template, response, view,
+ context, request, auto_reload)
+ return response
+ wrapped_view = _bfg_attr_view
+
+ elif template:
+ def _templated_view(context, request):
+ response = view(context, request)
+ response = templated_response(template, response, view,
+ context, request, auto_reload)
+ return response
+ wrapped_view = _templated_view
+
decorate_view(wrapped_view, view)
return wrapped_view
-def requestonly(class_or_callable):
+def requestonly(class_or_callable, attr=None):
""" Return true of the class or callable accepts only a request argument,
as opposed to something that accepts context, request """
+ if attr is None:
+ attr = '__call__'
if inspect.isfunction(class_or_callable):
fn = class_or_callable
elif inspect.isclass(class_or_callable):
@@ -459,7 +545,7 @@ def requestonly(class_or_callable):
return False
else:
try:
- fn = class_or_callable.__call__
+ fn = getattr(class_or_callable, attr)
except AttributeError:
return False
diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py
index f584d17fb..3a2a24819 100644
--- a/repoze/bfg/zcml.py
+++ b/repoze/bfg/zcml.py
@@ -39,6 +39,7 @@ from repoze.bfg.interfaces import ILogger
from repoze.bfg.interfaces import IPackageOverrides
from repoze.bfg.interfaces import IRequest
from repoze.bfg.interfaces import IRouteRequest
+from repoze.bfg.interfaces import ITemplateRendererFactory
from repoze.bfg.path import package_name
@@ -81,6 +82,8 @@ def view(
request_method=None,
request_param=None,
containment=None,
+ attr=None,
+ template=None,
cacheable=True, # not used, here for b/w compat < 0.8
):
@@ -164,8 +167,14 @@ def view(
else:
score = sys.maxint
+ if template and (not ':' in template) and (not os.path.isabs(template)):
+ # if it's not a package:relative/name and it's not an
+ # /absolute/path it's a relative/path; this means its relative
+ # to the package in which the ZCML file is defined.
+ template = '%s:%s' % (package_name(_context.resolve('.')), template)
+
def register():
- derived_view = derive_view(view, permission, predicates)
+ derived_view = derive_view(view, permission, predicates, attr, template)
r_for_ = for_
r_request_type = request_type
if r_for_ is None:
@@ -210,7 +219,7 @@ def view(
name, _context.info)
_context.action(
discriminator = ('view', for_, name, request_type, IView, containment,
- request_param, request_method, route_name),
+ request_param, request_method, route_name, attr),
callable = register,
args = (),
)
@@ -234,8 +243,9 @@ def notfound(_context, view):
def forbidden(_context, view):
view_utility(_context, view, IForbiddenView)
-def derive_view(original_view, permission=None, predicates=()):
- mapped_view = map_view(original_view)
+def derive_view(original_view, permission=None, predicates=(), attr=None,
+ template=None):
+ mapped_view = map_view(original_view, attr, template)
secured_view = secure_view(mapped_view, permission)
debug_view = authdebug_view(secured_view, permission)
derived_view = predicate_wrap(debug_view, predicates)
@@ -530,6 +540,22 @@ def connect_route(path, name, factory):
mapper = getUtility(IRoutesMapper)
mapper.connect(path, name, factory)
+class ITemplateRendererDirective(Interface):
+ renderer = GlobalObject(
+ title=u'ITemplateRendererFactory implementation',
+ required=True)
+
+ extension = TextLine(
+ title=u'Filename extension (e.g. ".pt")',
+ required=False)
+
+def template_renderer(_context, renderer, extension=''):
+ # renderer factories must be registered eagerly so they can be
+ # found by the view machinery
+ sm = getSiteManager()
+ sm.registerUtility(renderer, ITemplateRendererFactory, name=extension)
+ _context.action(discriminator=(ITemplateRendererFactory, extension))
+
class IStaticDirective(Interface):
name = TextLine(
title=u"The URL prefix of the static view",
@@ -591,6 +617,16 @@ class IViewDirective(Interface):
required=False,
)
+ attr = TextLine(
+ title=u'The callable attribute of the view object(default is __call__)',
+ description=u'',
+ required=False)
+
+ template = TextLine(
+ title=u'The template asssociated with the view',
+ description=u'',
+ required=False)
+
request_type = TextLine(
title=u"The request type string or dotted name interface for the view",
description=(u"The view will be called if the interface represented by "