summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-09-16 04:56:49 +0000
committerChris McDonough <chrism@agendaless.com>2009-09-16 04:56:49 +0000
commita9fed7675d8da572dee840676714b2653e3f7f79 (patch)
tree93327afae95fb9cf6b1d0cb72da265af42a705bd
parenta37220b84dee4cc8b1b12f34643ce97dad89ffe1 (diff)
downloadpyramid-a9fed7675d8da572dee840676714b2653e3f7f79.tar.gz
pyramid-a9fed7675d8da572dee840676714b2653e3f7f79.tar.bz2
pyramid-a9fed7675d8da572dee840676714b2653e3f7f79.zip
Checkpoint. Not 100% test coverage.
-rw-r--r--CHANGES.txt54
-rw-r--r--docs/narr/templates.rst4
-rw-r--r--docs/narr/views.rst759
-rw-r--r--repoze/bfg/chameleon_text.py61
-rw-r--r--repoze/bfg/chameleon_zpt.py46
-rw-r--r--repoze/bfg/includes/configure.zcml16
-rw-r--r--repoze/bfg/includes/meta.zcml6
-rw-r--r--repoze/bfg/interfaces.py23
-rw-r--r--repoze/bfg/renderers.py (renamed from repoze/bfg/templating.py)73
-rw-r--r--repoze/bfg/testing.py2
-rw-r--r--repoze/bfg/tests/test_chameleon_text.py4
-rw-r--r--repoze/bfg/tests/test_chameleon_zpt.py2
-rw-r--r--repoze/bfg/tests/test_renderers.py (renamed from repoze/bfg/tests/test_templating.py)38
-rw-r--r--repoze/bfg/tests/test_testing.py8
-rw-r--r--repoze/bfg/tests/test_view.py142
-rw-r--r--repoze/bfg/tests/test_zcml.py40
-rw-r--r--repoze/bfg/view.py93
-rw-r--r--repoze/bfg/zcml.py45
-rw-r--r--setup.py4
19 files changed, 882 insertions, 538 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index c94364efd..539d3d921 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,8 +1,60 @@
Next release
============
+- When used under Python < 2.6, BFG now has an installation time
+ dependency on the ``simplejson`` package.
+
+- The previous release (1.1a2) added a view configuration attribute
+ named ``template``. In this release, the attribute has been renamed
+ to ``renderer``. This signifies that the attribute is more generic:
+ it can now be not just a template name but any renderer name (ala
+ ``json``). A "renderer" is an object which accepts the return value
+ of a view and converts it to a string. This includes, but is not
+ limited to, templating systems.
+
+- In the previous release (1.1a2), the Chameleon text template
+ renderer was used if the system didn't associate the ``template``
+ view configuration value with a filename with a "known" extension.
+ In this release, you must use a ``renderer`` attribute which is a
+ path that ends with a ``.txt`` extension
+ (e.g. ``templates/foo.txt``) to use the Chameleon text renderer.
+
+- The ``ITemplateRenderer`` interface has been changed. Previously
+ its ``__call__`` method accepted ``**kw``. It now accepts a single
+ positional parameter named ``kw``.
+
+- The ``ITemplateRendererFactory`` interface has been changed.
+ Previously its ``__call__`` method accepted an ``auto_reload``
+ keyword parameter. Now it accepts no keyword parameters. Renderers
+ are now themselves responsible for determining details of
+ auto-reload.
+
+- The ``templating`` module has been removed. The bulk of its
+ functionality has been moved to a different module named
+ ``renderers``.
+
+- A new interface named ``IRenderer`` was added. The existing
+ interface, ``ITemplateRenderer`` now derives from this new
+ interface. This interface is internal.
+
+- A new interface named ``IRendererFactory`` was added. An existing
+ interface named ``ITemplateRenderer`` now derives from this
+ interface. This interface is internal.
+
+- The "Views" narrative chapter in the documentation has been updated
+ extensively to discuss "renderers".
+
- The ``view`` attribute of the ``view`` ZCML directive is no longer
- required if the ZCML directive has a ``template`` attribute.
+ required if the ZCML directive also has a ``renderer`` attribute.
+ This is useful when the renderer is a template renderer and no names
+ need be passed to the template at render time.
+
+- A new zcml directive ``renderer_factory`` has been added. It is
+ documented in the "Views" narrative chapter of the documentation.
+
+- The ``template_renderer`` ZCML directive introduced in 1.1a2 has
+ been removed. It has been replaced by the ``renderer_factory``
+ directive.
- A ZCML ``view`` directive (and the associated ``bfg_view``
decorator) can now accept a "wrapper" value. If a "wrapper" value
diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst
index a19778f04..d554a8adf 100644
--- a/docs/narr/templates.rst
+++ b/docs/narr/templates.rst
@@ -7,6 +7,8 @@ information that is static. :mod:`repoze.bfg` offers a number of ways
to perform templating tasks "out of the box", and provides alternative
templating language support via add-on "bindings" packages.
+.. _chameleon_zpt_templates:
+
Templating With :term:`Chameleon` ZPT Page Templates
----------------------------------------------------
@@ -174,6 +176,8 @@ And ``templates/mytemplate.pt`` might look like so:
</span>
</html>
+.. _chameleon_text_templates:
+
Templating with :term:`Chameleon` Text Templates
------------------------------------------------
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index 3916e6aac..888337b17 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -6,8 +6,20 @@ Views
A :term:`view` is a callable which is invoked when a request enters
your application. The primary job of any :mod:`repoze.bfg`
application is is to find and call a :term:`view` when a
-:term:`request` reaches it. The value returned by a :term:`view` must
-implement the :term:`WebOb` ``Response`` object interface.
+:term:`request` reaches it.
+
+A view callable may either return a :term:`WebOb` ``Response`` object
+directly, or it may return another arbitrary value. If a view
+callable returns a non-Response result, the result will be renderered
+to a response by the :term:`renderer` associated with the view
+configuration for the view. If no renderer is associated with a view,
+and that view returns a non-Response object, an error is eventually
+raised.
+
+A view is "configured" via :term:`ZCML` or via a decorator. Either
+mechanism is equivalent. The result of making a view declaration in
+ZCML or by attaching a view decorator to a Python object that you'd
+like to act as a view is known as :term:`view configuration`.
.. _function_as_view:
@@ -15,8 +27,9 @@ Defining a View as a Function
-----------------------------
The easiest way to define a view is to create a function that accepts
-two arguments: :term:`context`, and :term:`request`. For example,
-this is a hello world view implemented as a function:
+two arguments: :term:`context`, and :term:`request` and which returns
+a response object. For example, this is a "hello world" view
+implemented as a function:
.. code-block:: python
:linenos:
@@ -26,19 +39,6 @@ this is a hello world view implemented as a function:
def hello_world(context, request):
return Response('Hello world!')
-The :term:`context` and :term:`request` arguments passed to a view
-function can be defined as follows:
-
-context
-
- An instance of a :term:`context` found via graph :term:`traversal`
- or :term:`URL dispatch`. If the context is found via traversal, it
- will be a :term:`model` object.
-
-request
-
- A WebOb request object representing the current WSGI request.
-
.. _class_as_view:
Defining a View as a Class
@@ -46,15 +46,13 @@ Defining a View as a Class
.. note:: This feature is new as of :mod:`repoze.bfg` 0.8.1.
-When a view callable is a class, the calling semantics are slightly
-different than when it is a function or another non-class callable.
-When a view is a class, the class' ``__init__`` is called with the
-context and the request parameters. As a result, an instance of the
-class is created. Subsequently, that instance's ``__call__`` method
-is invoked with no parameters. The class' ``__call__`` method must
-return a response. This provides behavior similar to a Zope 'browser
-view' (Zope 'browser views' are typically classes instead of simple
-callables). So the simplest class that can be a view must have:
+A view callable may also be a class instead of a function. When a
+view callable is a class, the calling semantics are slightly different
+than when it is a function or another non-class callable. When a view
+is a class, the class' ``__init__`` is called with the context and the
+request parameters. As a result, an instance of the class is created.
+Subsequently, that instance's ``__call__`` method is invoked with no
+parameters. The class' ``__call__`` method must return a response.
- an ``__init__`` method that accepts a ``context`` and a ``request``
as positional arguments.
@@ -80,11 +78,29 @@ For example:
The context and request objects passed to ``__init__`` are the same
types of objects as described in :ref:`function_as_view`.
-Alternate "Request-Only" View Argument Convention
--------------------------------------------------
+If you'd like to use a different attribute than ``__call__`` to
+represent the method expected to return a response, you can use an
+``attr`` value as part of view configuration. See
+:ref:`the_view_zcml_directive`.
+
+Arguments Passed to a View
+--------------------------
+
+The :term:`context` and :term:`request` arguments passed to a view
+function can be defined as follows:
+
+context
+
+ An instance of a :term:`context` found via graph :term:`traversal`
+ or :term:`URL dispatch`. If the context is found via traversal, it
+ will be a :term:`model` object.
+
+request
+
+ A WebOb request object representing the current WSGI request.
Views may alternately be defined as callables that accept only a
-request object, instead of both a context and a request. The
+*request* object, instead of both a context and a request. The
following types work as views in this style:
#. Functions that accept a single argument ``request``, e.g.::
@@ -119,14 +135,15 @@ code itself. The view always has access to the context via
``request.context`` in any case, so it's still available even if you
use the request-only calling convention.
+.. _the_response:
+
The Response
------------
-A view callable must return an object that implements the
-:term:`WebOb` ``Response`` interface. The easiest way to return
-something that implements this interface is to return a
-``webob.Response`` object. But any object that has the following
-attributes will work:
+A view callable may return an object that implements the :term:`WebOb`
+``Response`` interface. The easiest way to return something that
+implements this interface is to return a ``webob.Response`` object.
+But any object that has the following attributes will work:
status
@@ -147,8 +164,46 @@ app_iter
other sort of iterable.
If a view happens to return something to the :mod:`repoze.bfg`
-:term:`router` that does not implement this interface, the router will
-raise an error.
+:term:`router` which does not implement this interface, BFG will
+attempt to use an associated :term:`renderer` to construct a response.
+The specific renderer used can be varied by changing the ``renderer``
+attribute in the view configuration. See
+:ref:`views_which_use_a_renderer`.
+
+.. _views_which_use_a_renderer:
+
+Writing Views Which Use a Renderer
+----------------------------------
+
+.. note:: This feature is new as of :mod:`repoze.bfg` 1.1
+
+Views needn't always return a WebOb Response object. Instead, they
+may return an arbitrary Python object, with the expectation that a
+:term:`renderer` will convert that object into a response on behalf of
+the developer.
+
+View configuration can vary the renderer associated with a view via
+the ``renderer`` attribute. The default renderer is the null renderer
+(meaning no rendering is done). There is a ``json`` renderer, which
+renders view return values to :term:`JSON`. Other built-in renderers
+include renderers which use the :term:`Chameleon` templating language
+to render a dictionary to a response. See :ref:`build_in_renders` for
+the available built-in renders. Renderers can be added to the system
+as necessary via ZCML directives (see
+:ref:`adding_and_overriding_renderers`).
+
+If the ``view`` callable associated with a ``view`` directive returns
+a Response object (an object with the attributes ``status``,
+``headerlist`` and ``app_iter``), any renderer associated with the
+``view`` declaration is ignored, and the response is passed back to
+BFG unmolested. For example, if your page callable returns an
+``HTTPFound`` response, no renderer will be employed.
+
+.. code-block:: python
+ :linenos:
+
+ from webob.exc import HTTPFound
+ return HTTPFound(location='http://example.com') # renderer avoided
.. _mapping_views_to_urls_using_zcml_section:
@@ -220,6 +275,8 @@ A ZCML ``view`` declaration's ``view`` attribute can also name a
class. In this case, the rules described in :ref:`class_as_view`
apply for the class which is named.
+.. _the_view_zcml_directive:
+
The ``view`` ZCML Directive
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -263,21 +320,40 @@ attr
``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
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
+
+renderer
+
+ This is either a single term (e.g. ``json``) or a string implying a
+ path (e.g. ``templates/views.pt``). If the renderer is a single
+ term, the specified term will be used to look up a renderer
+ implementation, and that renderer inplementation will be used to
+ construct a response from the view return value. If the renderer
+ term contains a dot (``.``), the specified term will be treated as a
+ path, and the filename extension of the last element in the path
+ will be used to look up the renderer implementation, which will be
+ passed the full path. The renderer implementation will be used to
+ construct a response from the view return value.
+
+ Note that if the view itself returns a response (see
+ :ref:`the_response), the specified renderer implementation is never
+ called.
+
+ When the renderer is a path, although a path is usually just a
+ simple relative pathname (e.g. ``templates/foo.pt``, implying that a
+ template named "foo.pt" 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.
+ address template resources which live in a separate package.
+
+ The ``renderer`` attribute is optional. If it is not defined, the
+ "null" renderer is assumed (no rendering is performed and the value
+ is passed back to the upstream BFG machinery unmolested).
+
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
wrapper
@@ -297,6 +373,8 @@ wrapper
view is the same context and request of the inner view. If this
attribute is unspecified, no view wrapping is done.
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
+
request_method
This value can either be one of the strings 'GET', 'POST', 'PUT',
@@ -305,6 +383,8 @@ request_method
called when the request's ``method`` (aka ``REQUEST_METHOD``) string
matches the supplied value.
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
+
request_param
This value can be any string. A view declaration with this
@@ -317,6 +397,8 @@ request_param
the right hand side of the expression (``123``) for the view to
"match" the current request.
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
+
containment
This value should be a Python dotted-path string representing the
@@ -326,6 +408,8 @@ containment
models must be "location-aware" to use this feature. See
:ref:`location_aware` for more information about location-awareness.
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
+
route_name
*This attribute services an advanced feature that isn't often used
@@ -354,56 +438,6 @@ request_type
``request_method`` attribute instead for maximum forward
compatibility.
-.. _view_lookup_ordering:
-
-View Lookup Ordering
---------------------
-
-Attributes of the ZCML ``view`` directive can be thought of like
-"narrowers" or "predicates". In general, the greater number of
-attributes possessed by a view directive, the more specific the
-circumstances need to be before the registered view will be called.
-
-For any given request, a view with five predicates will always be
-found and evaluated before a view with two, for example. All
-predicatese must match for the associated view to be called.
-
-This does not mean however, that :mod:`repoze.bfg` "stops looking"
-when it finds a view registration with predicates that don't match.
-If one set of view predicates does not match, the "next most specific"
-view (if any) view is consulted for predicates, and so on, until a
-view is found, or no view can be matched up with the request. The
-first view with a set of predicates all of which match the request
-environment will be invoked.
-
-If no view can be found which has predicates which allow it to be
-matched up with the request, :mod:`repoze.bfg` will return an error to
-the user's browser, representing a "not found" (404) page. See
-:ref:`changing_the_notfound_view` for more information about changing
-the default notfound view.
-
-There are a several exceptions to the the rule which says that ZCML
-directive attributes represent "narrowings". Several attributes of
-the ``view`` directive are *not* narrowing predicates. These are
-``permission`` and ``name``.
-
-The value of the ``permission`` attribute represents the permission
-that must be possessed by the user to invoke any found view. When a
-view is found that matches all predicates, but the invoking user does
-not possess the permission implied by any associated ``permission`` in
-the current context, processing stops, and an ``Unauthorized`` error
-is raised, usually resulting in a "forbidden" view being shown to the
-invoking user. No further view narrowing or view lookup is done.
-
-.. note::
-
- See :ref:`changing_the_forbidden_view` for more information about
- changing the default forbidden view.
-
-The value of the ``name`` attribute represents a direct match of the
-view name returned via traversal. It is part of intial view lookup
-rather than a predicate/narrower.
-
.. _mapping_views_to_urls_using_a_decorator_section:
Mapping Views to URLs Using a Decorator
@@ -415,9 +449,11 @@ use the ``repoze.bfg.view.bfg_view`` decorator to associate your view
functions with URLs instead of using :term:`ZCML` for the same
purpose. ``repoze.bfg.view.bfg_view`` can be used to associate
``for``, ``name``, ``permission`` and ``request_method``,
-``containment``, ``request_param`` and ``request_type`` information --
-as done via the equivalent ZCML -- with a function that acts as a
-:mod:`repoze.bfg` view.
+``containment``, ``request_param`` and ``request_type``, ``attr``,
+``renderer``, and ``wrapper`` information -- as done via the
+equivalent ZCML -- with a function that acts as a :mod:`repoze.bfg`
+view. All ZCML attributes are available in decorator form and mean
+precisely the same thing.
To make :mod:`repoze.bfg` process your ``bfg_view`` declarations, you
*must* insert the following boilerplate into your application's
@@ -481,8 +517,8 @@ 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 ``renderer`` is not supplied, ``None`` is used (meaning that the
+null renderer 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
@@ -574,155 +610,188 @@ 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.
-View callables should set these values into the dictionary they return
-to influence response attributes.
-
-``content_type_``
+.. _view_lookup_ordering:
- Defines the content-type of the resulting response,
- e.g. ``text/xml``.
+View Lookup Ordering
+--------------------
-``headerlist_``
+Most attributes of view configuration can be thought of like
+"narrowers" or "predicates". In general, the greater number of
+attributes possessed by a view's configuration, the more specific the
+circumstances need to be before the registered view callable will be
+invoked.
- A sequence of tuples describing cookie values that should be set in
- the response, e.g. ``[('Set-Cookie', 'abc=123'), ('X-My-Header',
- 'foo')]``.
+For any given request, a view with five predicates will always be
+found and evaluated before a view with two, for example. All
+predicates must match for the associated view to be called.
+
+This does not mean however, that :mod:`repoze.bfg` "stops looking"
+when it finds a view registration with predicates that don't match.
+If one set of view predicates does not match, the "next most specific"
+view (if any) view is consulted for predicates, and so on, until a
+view is found, or no view can be matched up with the request. The
+first view with a set of predicates all of which match the request
+environment will be invoked.
-``status_``
+If no view can be found which has predicates which allow it to be
+matched up with the request, :mod:`repoze.bfg` will return an error to
+the user's browser, representing a "not found" (404) page. See
+:ref:`changing_the_notfound_view` for more information about changing
+the default notfound view.
- A WSGI-style status code (e.g. ``200 OK``) describing the status of
- the response.
+There are a several exceptions to the the rule which says that view
+configuration attributes represent "narrowings". Several attributes
+of the ``view`` directive are *not* narrowing predicates. These are
+``permission``, ``name``, ``renderer``, and ``attr``.
-``charset_``
+The value of the ``permission`` attribute represents the permission
+that must be possessed by the user to invoke any found view. When a
+view is found that matches all predicates, but the invoking user does
+not possess the permission implied by any associated ``permission`` in
+the current context, processing stops, and an ``Unauthorized`` error
+is raised, usually resulting in a "forbidden" view being shown to the
+invoking user. No further view narrowing or view lookup is done.
- The character set (e.g. ``UTF-8``) of the response.
+.. note::
-``cache_for_``
+ See :ref:`changing_the_forbidden_view` for more information about
+ changing the default forbidden view.
- 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.
+The value of the ``name`` attribute represents a direct match of the
+view name returned via traversal. It is part of intial view lookup
+rather than a predicate/narrower.
-View Template Filename Extension Mappings
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The value of the ``renderer`` attribute represents the renderer used
+to convert non-response return values from a view.
-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.
+The value of the ``attr`` attribute represents the attribute name
+looked up on the view object to return a response.
-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.
+.. _built_in_renders:
-Adding and Overriding Template Filename Extension Mappings
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Built-In Renderers
+------------------
-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.
+Several built-in renderers exist in BFG. These renderers can be used
+in the ``renderer`` attribute of view configurations.
-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::
+``json``: JSON Renderer
+~~~~~~~~~~~~~~~~~~~~~~~
- <template_renderer
- extension=".jinja"
- renderer="my.package.MyJinja2Renderer"/>
+The ``json`` renderer is a renderer which renders view callable
+results to :term:`JSON`. If a view callable returns a non-Response
+object it is called. It passes the return value through the
+``simplejson.dumps`` function, and wraps the result in a response
+object. For example:
-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::
+Here's an example of a view that returns a dictionary. If the
+``json`` renderer is specified in the configuration for this view, the
+view will render the returned dictionary to a JSON serialization:
- <template_renderer
- extension=".pt"
- renderer="my.package.pt_renderer"/>
+.. code-block:: python
+ :linenos:
-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``::
+ from webob import Response
+ from repoze.bfg.view import bfg_view
- <template_renderer
- renderer="my.package.default_renderer"/>
+ @bfg_view(renderer='json')
+ def hello_world(context, request):
+ return {'content':'Hello!'}
-A renderer must be a class that has the following interface:
+The body of the response returned by such a view will be a string
+representing the JSON serialization of the return value:
-.. code-block:: python
+.. code-block: python
:linenos:
- class TemplateRendererFactory:
- def __init__(self, path, auto_reload=False):
- """ Constructor """
+ '{"content": "Hello!"}'
- 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 """
+The return value needn't be a dictionary, but the return value must
+contain values renderable by ``simplejson.dumps``.
- 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) """
+You can configure a view to use the JSON renderer in ZCML by naming
+``json`` as the ``renderer`` attribute of a view configuration, e.g.:
+
+.. code-block:: xml
+ :linenos:
+
+ <view
+ for=".models.Hello"
+ view=".views.hello_world"
+ name="hello"
+ renderer="json"
+ />
+
+Views which use the JSON renderer can vary non-body response
+attributes by attaching properties to the request. See
+:ref:`response_request_attrs`.
+
+``*.pt`` or ``*.txt``: Chameleon Template Renderers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Two built-in renderers exist for :term:`Chameleon` templates.
+
+If the ``renderer`` attribute of a view configuration is a path which
+has a final path element with a filename extension of ``.pt``, the
+Chameleon ZPT renderer is used. See :ref:`chameleon_zpt_templates`
+for more information about ZPT templates.
+
+If the ``renderer`` attribute of a view configuration is a path which
+has a final path element with a filename extension of ``.txt``, the
+Chameleon text renderer is used. See :ref:`chameleon_zpt_templates`
+for more information about Chameleon text templates.
+
+The behavior of these renderers is the same, except for the engine
+used to render the template.
+
+When a ``renderer`` attribute that names a Chameleon template path
+(e.g. ``templates/foo.pt`` or ``templates/foo.txt``) is used, the view
+must return a Response object or a Python *dictionary*. 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
+``renderer`` 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).
+
+Here's an example view configuration which uses a Chameleon ZPT
+renderer:
+
+.. code-block:: xml
+ :linenos:
+
+ <view
+ for=".models.Hello"
+ view=".views.hello_world"
+ name="hello"
+ renderer="templates/foo.pt"
+ />
+
+Here's an example view configuration which uses a Chameleon text
+renderer:
+
+.. code-block:: xml
+ :linenos:
+
+ <view
+ for=".models.Hello"
+ view=".views.hello_world"
+ name="hello"
+ renderer="templates/foo.txt"
+ />
+
+Views with use a Chameleon renderer can vary response attributes by
+attaching properties to the request. See
+:ref:`response_request_attrs`.
.. _using_model_interfaces:
@@ -811,73 +880,6 @@ view registered for the context's class will "win".
See :term:`Interface` in the glossary to find more information about
interfaces.
-.. _view_request_types_section:
-
-Standard View Request Types
----------------------------
-
-You can optionally add a *request_type* attribute to your ``view``
-declaration or ``bfg_view`` decorator, which indicates what "kind" of
-request the view should be used for. If the request type for a
-request doesn't match the request type that a view defines as its
-``request_type`` argument, that view won't be called.
-
-The request type can be one of the strings 'GET', 'POST', 'PUT',
-'DELETE', or 'HEAD'. When the request type is one of these strings,
-the view will only be called when the HTTP method of a request matches
-this type.
-
-For example, the following bit of ZCML will match an HTTP POST
-request:
-
-.. code-block:: xml
- :linenos:
-
- <view
- for=".models.Hello"
- view=".views.handle_post"
- name="handle_post"
- request_type="POST"
- />
-
-A ``bfg_view`` decorator that does the same as the above ZCML ``view``
-declaration which matches only on HTTP POST might look something like:
-
-.. code-block:: python
- :linenos:
-
- from myproject.models import Hello
- from webob import Response
-
- @bfg_view(for=Hello, request_type='POST')
- def handle_post(context, request):
- return Response('hello'
-
-The above examples register views for the POST request type, so it
-will only be called if the request's HTTP method is ``POST``. Even if
-all the other specifiers match (e.g. the model type is the class
-``.models.Hello``, and the view_name is ``handle_post``), if the
-request verb is not POST, it will not be invoked. This provides a way
-to ensure that views you write are only called via specific HTTP
-verbs.
-
-The least specific request type is ``None``. All requests are
-guaranteed to implement this request type. It is also the default
-request type for views that omit a ``request_type`` argument.
-
-Custom View Request Types
--------------------------
-
-You can make use of *custom* view request types by attaching an
-:term:`interface` to the request and specifying this interface in the
-``request_type`` parameter as a dotted Python name. For example, you
-might want to make use of simple "content negotiation", only invoking
-a particular view if the request has a content-type of
-'application/json'.
-
-For information about using interface to specify a request type, see
-:ref:`using_an_event_to_vary_the_request_type`.
-
.. _view_security_section:
View Security
@@ -1208,3 +1210,170 @@ rendered in a request that has a ``;charset=utf-8`` stanza on its
to Unicode objects implicitly in :mod:`repoze.bfg`'s default
configuration. The keys are still strings.
+.. _view_request_types_section:
+
+Custom View Request Types
+-------------------------
+
+You can make use of *custom* view request types by attaching an
+:term:`interface` to the request and specifying this interface in the
+``request_type`` parameter as a dotted Python name. For example, you
+might want to make use of simple "content negotiation", only invoking
+a particular view if the request has a content-type of
+'application/json'.
+
+For information about using interface to specify a request type, see
+:ref:`using_an_event_to_vary_the_request_type`.
+
+.. _response_request_attrs:
+
+Varying Attributes of Rendered Responses
+----------------------------------------
+
+Before a response that is constructed as the result of the use of a
+:term:`renderer` is returned to BFG, several attributes of the request
+are examined which have the potential to influence response behavior.
+
+View callables that don't directly return a response should set these
+values on the ``request`` object via ``setattr`` within the view
+callable to influence automatically constructed response attributes.
+
+``response_content_type``
+
+ Defines the content-type of the resulting response,
+ e.g. ``text/xml``.
+
+``response_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')]``.
+
+``response_status``
+
+ A WSGI-style status code (e.g. ``200 OK``) describing the status of
+ the response.
+
+``response_charset``
+
+ The character set (e.g. ``UTF-8``) of the response.
+
+``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 ``response_headerlist``,
+ this is purely a convenience.
+
+.. _adding_and_overriding_renderers:
+
+Adding and Overriding Renderers
+-------------------------------
+
+Additonal ZCML declarations can be made which override an existing
+:term:`renderer` or which add a new renderer. Adding or overriding a
+renderer is accomplished via one or more separate ZCML directives.
+
+For example, to add a renderer which renders views which have a
+``renderer`` attribute that is a path that ends in ``.jinja``::
+
+ <renderer
+ name=".jinja"
+ factory="my.package.MyJinja2Renderer"/>
+
+The ``factory`` attribute is a dotted Python name that must point to
+an implementation of a renderer. A renderer implementation is usually
+a class which has the following interface:
+
+.. code-block:: python
+ :linenos:
+
+ class RendererFactory:
+ def __init__(self, name):
+ """ Constructor: ``name`` may be a path """
+
+ def __call__(self, value):
+ """ Call a the renderer implementation with the value
+ passed in as arguments and return the result (a string or
+ unicode object) """
+
+A renderer's ``name`` is the second element. There are essentially
+two different kinds of ``renderer`` registrations with respect to the
+name:
+
+- a ``renderer`` registration which has a ``name`` attribute which
+ has a value that starts with a dot (``.``).
+
+- a ``renderer`` registration which has a ``name`` attribute which
+ has a value that *does not* start with a dot.
+
+Renderer registrations that have a ``name`` attribute which starts
+with a dot are meant to be *wildcard* registrations. When a ``view``
+configuration is encountered which has a ``renderer`` attribute that
+contains a dot, at startup time, the path is split on its final dot,
+and the second element of the split (the filename extension,
+typically) is used to look up a renderer for the configured view. The
+renderer's factory is still passed the entire ``renderer`` attribute
+value (not just the extension).
+
+Renderer registrations that have ``name`` attribute which *does not*
+start with a dot are meant to be absolute registrations. When a
+``view`` configuration is encountered which has a ``renderer``
+attribute that does not contain a dot, the full value of the
+``renderer`` attribute is used to look up the renderer for the
+configured view.
+
+Here's an example of a renderer registration in ZCML:
+
+ <renderer
+ name="amf"
+ factory="my.package.MyAMFRenderer"/>
+
+Adding the above ZCML to your application will allow you to use the
+``my.package.MyAMFRenderer`` renderer implementation in ``view``
+configurations by referring to it as ``amf`` in the ``renderer``
+attribute:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.view import bfg_view
+
+ @bfg_view(renderer='amf')
+ def myview(context, request):
+ return {'Hello':'world'}
+
+By default, when a template extension is unrecognized, an error is
+thrown at rendering time. You can associate more than one filename
+extension with the same renderer implementation as necessary if you
+need to use a different file extension for the same kinds of
+templates. For example, to associate the ``.zpt`` extension with the
+Chameleon page template renderer factory, use::
+
+ <renderer
+ name=".zpt"
+ factory="repoze.bfg.chameleon_zpt.renderer_factory"/>
+
+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 in your application's ZCML::
+
+ <renderer
+ name=".pt"
+ factory="my.package.pt_renderer"/>
+
+To override the default mapping in which files with a ``.txt``
+extension are rendered via a Chameleon text template renderer, use a
+variation on the following in your application's ZCML::
+
+ <renderer
+ name=".txt"
+ factory="my.package.text_renderer"/>
+
+To associate a *default* renderer with *all* view configurations (even
+ones which do not possess a ``renderer`` attribute), use a variation
+on the following (ie. omit the ``name`` attribute to the renderer
+tag):
+
+ <renderer
+ factory="repoze.bfg.renderers.json_renderer_factory"/>
diff --git a/repoze/bfg/chameleon_text.py b/repoze/bfg/chameleon_text.py
index f482d795f..1c9518b88 100644
--- a/repoze/bfg/chameleon_text.py
+++ b/repoze/bfg/chameleon_text.py
@@ -1,47 +1,68 @@
from webob import Response
from zope.component import queryUtility
+from zope.interface import implements
from repoze.bfg.interfaces import IResponseFactory
+from repoze.bfg.interfaces import ITemplateRenderer
-from repoze.bfg.templating import renderer_from_cache
-from repoze.bfg.templating import TextTemplateRenderer
-from repoze.bfg.templating import _auto_reload
+from chameleon.core.template import TemplateFile
+from chameleon.zpt.language import Parser
+
+from repoze.bfg.renderers import template_renderer_factory
+from repoze.bfg.settings import get_settings
+
+class TextTemplateFile(TemplateFile):
+ default_parser = Parser()
+
+ def __init__(self, filename, parser=None, format='text', doctype=None,
+ **kwargs):
+ if parser is None:
+ parser = self.default_parser
+ super(TextTemplateFile, self).__init__(filename, parser, format,
+ doctype, **kwargs)
+
+def renderer_factory(path):
+ return template_renderer_factory(path, TextTemplateRenderer, level=4)
+
+class TextTemplateRenderer(object):
+ implements(ITemplateRenderer)
+ def __init__(self, path):
+ settings = get_settings()
+ auto_reload = settings and settings['reload_templates']
+ self.template = TextTemplateFile(path, auto_reload=auto_reload)
+
+ def implementation(self):
+ return self.template
+
+ def __call__(self, kw):
+ return self.template(**kw)
def get_renderer(path):
""" Return a callable ``ITemplateRenderer`` object representing a
- ``chameleon`` text template at the package-relative path (may also
+ ``Chameleon`` text template at the package-relative path (may also
be absolute)."""
- auto_reload = _auto_reload()
- renderer = renderer_from_cache(path, TextTemplateRenderer,
- auto_reload=auto_reload)
- return renderer
+ return renderer_factory(path)
def get_template(path):
- """ Return a ``chameleon`` text template at the package-relative
+ """ Return a ``Chameleon`` text template at the package-relative
path (may also be absolute)."""
- auto_reload = _auto_reload()
- renderer = renderer_from_cache(path, TextTemplateRenderer,
- auto_reload=auto_reload)
+ renderer = renderer_factory(path)
return renderer.implementation()
def render_template(path, **kw):
""" Render a ``chameleon`` text template at the package-relative
path (may also be absolute) using the kwargs in ``*kw`` as
top-level names and return a string."""
- auto_reload = _auto_reload()
- renderer = renderer_from_cache(path, TextTemplateRenderer,
- auto_reload=auto_reload)
- return renderer(**kw)
+ renderer = renderer_factory(path)
+ return renderer(kw)
def render_template_to_response(path, **kw):
""" Render a ``chameleon`` text template at the package-relative
path (may also be absolute) using the kwargs in ``*kw`` as
top-level names and return a Response object with the body as the
template result."""
- auto_reload = _auto_reload()
- renderer = renderer_from_cache(path, TextTemplateRenderer,
- auto_reload=auto_reload)
- result = renderer(**kw)
+ renderer = renderer_factory(path)
+ result = renderer(kw)
response_factory = queryUtility(IResponseFactory, default=Response)
return response_factory(result)
diff --git a/repoze/bfg/chameleon_zpt.py b/repoze/bfg/chameleon_zpt.py
index c66a69e66..b4be1a4fc 100644
--- a/repoze/bfg/chameleon_zpt.py
+++ b/repoze/bfg/chameleon_zpt.py
@@ -1,65 +1,63 @@
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.templating import renderer_from_cache
-from repoze.bfg.templating import _auto_reload
from chameleon.zpt.template import PageTemplateFile
+from repoze.bfg.renderers import template_renderer_factory
+from repoze.bfg.settings import get_settings
+
+def renderer_factory(path, level=4):
+ return template_renderer_factory(path, ZPTTemplateRenderer, level=4)
+
class ZPTTemplateRenderer(object):
- classProvides(ITemplateRendererFactory)
implements(ITemplateRenderer)
-
- def __init__(self, path, auto_reload=False):
+ def __init__(self, path):
+ settings = get_settings()
+ auto_reload = settings and settings['reload_templates']
self.template = PageTemplateFile(path, auto_reload=auto_reload)
def implementation(self):
return self.template
- def __call__(self, **kw):
+ def __call__(self, kw):
return self.template(**kw)
def get_renderer(path):
""" Return a callable ``ITemplateRenderer`` object representing a
``chameleon.zpt`` template at the package-relative path (may also
be absolute). """
- auto_reload = _auto_reload()
- renderer = renderer_from_cache(path, ZPTTemplateRenderer,
- auto_reload=auto_reload)
- return renderer
+ return renderer_factory(path)
def get_template(path):
""" Return a ``chameleon.zpt`` template at the package-relative
path (may also be absolute). """
- auto_reload = _auto_reload()
- renderer = renderer_from_cache(path, ZPTTemplateRenderer,
- auto_reload=auto_reload)
+ renderer = renderer_factory(path)
return renderer.implementation()
def render_template(path, **kw):
""" Render a ``chameleon.zpt`` template at the package-relative
path (may also be absolute) using the kwargs in ``*kw`` as
top-level names and return a string."""
- auto_reload = _auto_reload()
- renderer = renderer_from_cache(path, ZPTTemplateRenderer,
- auto_reload=auto_reload)
- return renderer(**kw)
+ renderer = renderer_factory(path)
+ return renderer(kw)
def render_template_to_response(path, **kw):
""" Render a ``chameleon.zpt`` template at the package-relative
path (may also be absolute) using the kwargs in ``*kw`` as
top-level names and return a Response object with the body as the
template result. """
- auto_reload = _auto_reload()
- renderer = renderer_from_cache(path, ZPTTemplateRenderer,
- auto_reload=auto_reload)
- result = renderer(**kw)
+ renderer = renderer_factory(path)
+ result = renderer(kw)
response_factory = queryUtility(IResponseFactory, default=Response)
return response_factory(result)
+
+def _auto_reload():
+ settings = get_settings()
+ if settings:
+ return settings['reload_templates']
+ return False
diff --git a/repoze/bfg/includes/configure.zcml b/repoze/bfg/includes/configure.zcml
index be8a69543..37cd4d75a 100644
--- a/repoze/bfg/includes/configure.zcml
+++ b/repoze/bfg/includes/configure.zcml
@@ -18,9 +18,19 @@
for="* repoze.bfg.interfaces.IRequest"
/>
- <template_renderer
- renderer="repoze.bfg.chameleon_zpt.ZPTTemplateRenderer"
- extension=".pt"
+ <renderer
+ factory="repoze.bfg.chameleon_zpt.renderer_factory"
+ name=".pt"
+ />
+
+ <renderer
+ factory="repoze.bfg.chameleon_text.renderer_factory"
+ name=".txt"
+ />
+
+ <renderer
+ factory="repoze.bfg.renderers.json_renderer_factory"
+ name="json"
/>
</configure>
diff --git a/repoze/bfg/includes/meta.zcml b/repoze/bfg/includes/meta.zcml
index 8195101be..2eeb87314 100644
--- a/repoze/bfg/includes/meta.zcml
+++ b/repoze/bfg/includes/meta.zcml
@@ -71,9 +71,9 @@
/>
<meta:directive
- name="template_renderer"
- schema="repoze.bfg.zcml.ITemplateRendererDirective"
- handler="repoze.bfg.zcml.template_renderer"
+ name="renderer"
+ schema="repoze.bfg.zcml.IRendererDirective"
+ handler="repoze.bfg.zcml.renderer"
/>
</meta:directives>
diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py
index 827654b1d..928ee6c54 100644
--- a/repoze/bfg/interfaces.py
+++ b/repoze/bfg/interfaces.py
@@ -74,21 +74,26 @@ class ITraverserFactory(Interface):
def __call__(context):
""" Return an object that implements ITraverser """
-class ITemplateRenderer(Interface):
+class IRenderer(Interface):
+ def __call__(value):
+ """ Call a the renderer implementation with the result of the
+ view (``value``) passed in and return a result (a string or
+ unicode object useful as a response body)"""
+
+class IRendererFactory(Interface):
+ def __call__(name):
+ """ Return an object that implements ``IRenderer`` """
+
+class ITemplateRenderer(IRenderer):
def implementation():
""" 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__(**kw):
- """ Call a the template implementation with the keywords
- passed in as arguments and return the result (a string or
- unicode object) """
-
-class ITemplateRendererFactory(Interface):
- def __call__(path, auto_reload=False):
- """ Return an object that implements ``ITemplateRenderer`` """
+class ITemplateRendererFactory(IRendererFactory):
+ def __call__(path):
+ """ Return an object that implements ``ITemplateRenderer`` """
class IViewPermission(Interface):
def __call__(context, request):
diff --git a/repoze/bfg/templating.py b/repoze/bfg/renderers.py
index 2ec049ca5..495a35541 100644
--- a/repoze/bfg/templating.py
+++ b/repoze/bfg/renderers.py
@@ -4,43 +4,27 @@ 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)
+from repoze.bfg.interfaces import IRendererFactory
+from repoze.bfg.interfaces import ITemplateRenderer
+
+try:
+ import json
+except ImportError:
+ import simplejson as json
-class TextTemplateRenderer(object):
- classProvides(ITemplateRendererFactory)
- implements(ITemplateRenderer)
+# concrete renderer factory implementations
- def __init__(self, path, auto_reload=False):
- self.template = TextTemplateFile(path, format='text',
- auto_reload=auto_reload)
+def json_renderer_factory(name):
+ def _render(value):
+ return json.dumps(value)
+ return _render
- def implementation(self):
- return self.template
-
- def __call__(self, **kw):
- return self.template(**kw)
+# utility functions
-def renderer_from_cache(path, factory, level=3, **kw):
+def template_renderer_factory(path, impl, level=3):
if os.path.isabs(path):
# 'path' is an absolute filename (not common and largely only
# for backwards compatibility)
@@ -48,7 +32,7 @@ def renderer_from_cache(path, factory, level=3, **kw):
raise ValueError('Missing template file: %s' % path)
renderer = queryUtility(ITemplateRenderer, name=path)
if renderer is None:
- renderer = factory(path, **kw)
+ renderer = impl(path)
sm = getSiteManager()
sm.registerUtility(renderer, ITemplateRenderer, name=path)
@@ -72,9 +56,8 @@ def renderer_from_cache(path, factory, level=3, **kw):
if not pkg_resources.resource_exists(*spec):
raise ValueError('Missing template resource: %s' % utility_name)
abspath = pkg_resources.resource_filename(*spec)
- renderer = factory(abspath, **kw)
- settings = get_settings()
- if (not settings) or (not settings.get('reload_resources')):
+ renderer = impl(abspath)
+ if not _reload_resources():
# cache the template
sm = getSiteManager()
sm.registerUtility(renderer, ITemplateRenderer,
@@ -82,14 +65,18 @@ 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 renderer_from_name(path, level=4):
+ name = os.path.splitext(path)[1]
+ if not name:
+ name = path
+ factory = queryUtility(IRendererFactory, name=name)
+ if factory is None:
+ raise ValueError('No renderer for renderer name %r' % name)
+ return factory(path)
-def _auto_reload():
- settings = queryUtility(ISettings)
- auto_reload = settings and settings['reload_templates']
- return auto_reload
+def _reload_resources():
+ settings = get_settings()
+ if settings:
+ return settings['reload_resources']
+ return False
diff --git a/repoze/bfg/testing.py b/repoze/bfg/testing.py
index 1c9dad9e0..8fbeb6012 100644
--- a/repoze/bfg/testing.py
+++ b/repoze/bfg/testing.py
@@ -264,7 +264,7 @@ class DummyTemplateRenderer:
def implementation(self):
return self
- def __call__(self, **kw):
+ def __call__(self, kw):
self._received.update(kw)
return self.string_response
diff --git a/repoze/bfg/tests/test_chameleon_text.py b/repoze/bfg/tests/test_chameleon_text.py
index 139a77072..ddb6f717a 100644
--- a/repoze/bfg/tests/test_chameleon_text.py
+++ b/repoze/bfg/tests/test_chameleon_text.py
@@ -51,7 +51,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
self._zcmlConfigure()
minimal = self._getTemplatePath('minimal.txt')
instance = self._makeOne(minimal)
- result = instance()
+ result = instance({})
self.failUnless(isinstance(result, str))
self.assertEqual(result, 'Hello.\n')
@@ -59,7 +59,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
self._zcmlConfigure()
nonminimal = self._getTemplatePath('nonminimal.txt')
instance = self._makeOne(nonminimal)
- result = instance(name='Chris')
+ result = instance({'name':'Chris'})
self.failUnless(isinstance(result, str))
self.assertEqual(result, 'Hello, Chris!\n')
diff --git a/repoze/bfg/tests/test_chameleon_zpt.py b/repoze/bfg/tests/test_chameleon_zpt.py
index 4a1cac166..f6d7c0da5 100644
--- a/repoze/bfg/tests/test_chameleon_zpt.py
+++ b/repoze/bfg/tests/test_chameleon_zpt.py
@@ -44,7 +44,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
self._zcmlConfigure()
minimal = self._getTemplatePath('minimal.pt')
instance = self._makeOne(minimal)
- result = instance()
+ result = instance({})
self.failUnless(isinstance(result, unicode))
self.assertEqual(result,
'<div xmlns="http://www.w3.org/1999/xhtml">\n</div>')
diff --git a/repoze/bfg/tests/test_templating.py b/repoze/bfg/tests/test_renderers.py
index c3894cce4..aa159cc18 100644
--- a/repoze/bfg/tests/test_templating.py
+++ b/repoze/bfg/tests/test_renderers.py
@@ -3,16 +3,16 @@ import unittest
from repoze.bfg.testing import cleanUp
from repoze.bfg import testing
-class TestRendererFromCache(unittest.TestCase):
+class TestTemplateRendererFactory(unittest.TestCase):
def setUp(self):
cleanUp()
def tearDown(self):
cleanUp()
- def _callFUT(self, path, factory, level=3, **kw):
- from repoze.bfg.templating import renderer_from_cache
- return renderer_from_cache(path, factory, level, **kw)
+ def _callFUT(self, path, factory, level=3):
+ from repoze.bfg.renderers import template_renderer_factory
+ return template_renderer_factory(path, factory, level)
def test_abspath_notfound(self):
from repoze.bfg.interfaces import ITemplateRenderer
@@ -87,10 +87,10 @@ class TestRendererFromCache(unittest.TestCase):
import os
from repoze.bfg.tests import test_templating
module_name = test_templating.__name__
- relpath = 'test_templating.py'
+ relpath = 'test_renderers.py'
renderer = {}
factory = DummyFactory(renderer)
- result = self._callFUT('test_templating.py', factory)
+ result = self._callFUT('test_renderers.py', factory)
self.failUnless(result is renderer)
path = os.path.abspath(__file__)
if path.endswith('pyc'): # pragma: no cover
@@ -102,7 +102,7 @@ class TestRendererFromCache(unittest.TestCase):
import os
from repoze.bfg import tests
module_name = tests.__name__
- relpath = 'test_templating.py'
+ relpath = 'test_renderers.py'
renderer = {}
factory = DummyFactory(renderer)
spec = '%s:%s' % (module_name, relpath)
@@ -122,9 +122,9 @@ class TestRendererFromCache(unittest.TestCase):
testing.registerUtility(settings, ISettings)
renderer = {}
factory = DummyFactory(renderer)
- result = self._callFUT('test_templating.py', factory)
+ result = self._callFUT('test_renderers.py', factory)
self.failUnless(result is renderer)
- spec = '%s:%s' % ('repoze.bfg.tests', 'test_templating.py')
+ spec = '%s:%s' % ('repoze.bfg.tests', 'test_renderers.py')
self.assertEqual(queryUtility(ITemplateRenderer, name=spec),
None)
@@ -136,9 +136,9 @@ class TestRendererFromCache(unittest.TestCase):
testing.registerUtility(settings, ISettings)
renderer = {}
factory = DummyFactory(renderer)
- result = self._callFUT('test_templating.py', factory)
+ result = self._callFUT('test_renderers.py', factory)
self.failUnless(result is renderer)
- spec = '%s:%s' % ('repoze.bfg.tests', 'test_templating.py')
+ spec = '%s:%s' % ('repoze.bfg.tests', 'test_renderers.py')
self.assertNotEqual(queryUtility(ITemplateRenderer, name=spec),
None)
@@ -149,19 +149,11 @@ class TestRendererFromPath(unittest.TestCase):
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 _callFUT(self, path, level=4):
+ from repoze.bfg.renderers import renderer_from_name
+ return renderer_from_name(path, level)
- def test_with_nondefault(self):
+ def test_it(self):
from repoze.bfg.interfaces import ITemplateRendererFactory
import os
here = os.path.dirname(os.path.abspath(__file__))
diff --git a/repoze/bfg/tests/test_testing.py b/repoze/bfg/tests/test_testing.py
index e81a90219..9ebd8468a 100644
--- a/repoze/bfg/tests/test_testing.py
+++ b/repoze/bfg/tests/test_testing.py
@@ -65,7 +65,7 @@ class TestTestingFunctions(unittest.TestCase):
def test_registerDummyRenderer_explicitrenderer(self):
from repoze.bfg import testing
- def renderer(**kw):
+ def renderer(kw):
raise ValueError
renderer = testing.registerDummyRenderer('templates/foo', renderer)
from repoze.bfg.chameleon_zpt import render_template_to_response
@@ -460,20 +460,20 @@ class TestDummyTemplateRenderer(unittest.TestCase):
def test_getattr(self):
renderer = self._makeOne()
- renderer(a=1)
+ renderer({'a':1})
self.assertEqual(renderer.a, 1)
self.assertRaises(AttributeError, renderer.__getattr__, 'b')
def test_assert_(self):
renderer = self._makeOne()
- renderer(a=1, b=2)
+ renderer({'a':1, 'b':2})
self.assertRaises(AssertionError, renderer.assert_, c=1)
self.assertRaises(AssertionError, renderer.assert_, b=3)
self.failUnless(renderer.assert_(a=1, b=2))
def test_nondefault_string_response(self):
renderer = self._makeOne('abc')
- result = renderer(a=1, b=2)
+ result = renderer({'a':1, 'b':2})
self.assertEqual(result, 'abc')
class CleanUpTests(object):
diff --git a/repoze/bfg/tests/test_view.py b/repoze/bfg/tests/test_view.py
index 5639e9799..3106359a3 100644
--- a/repoze/bfg/tests/test_view.py
+++ b/repoze/bfg/tests/test_view.py
@@ -631,6 +631,25 @@ class Test_map_view(unittest.TestCase):
from repoze.bfg.view import map_view
return map_view(view, *arg, **kw)
+ def _registerRenderer(self):
+ from repoze.bfg.interfaces import IRendererFactory
+ from repoze.bfg.interfaces import ITemplateRenderer
+ from zope.interface import implements
+ from zope.component import getSiteManager
+ class Renderer:
+ implements(ITemplateRenderer)
+ def __call__(self, *arg):
+ return 'Hello!'
+
+ class RendererFactory:
+ def __call__(self, path):
+ self.path = path
+ return Renderer()
+
+ factory = RendererFactory()
+ sm = getSiteManager()
+ sm.registerUtility(factory, IRendererFactory, name='.txt')
+
def test_view_as_function_context_and_request(self):
def view(context, request):
return 'OK'
@@ -646,10 +665,11 @@ class Test_map_view(unittest.TestCase):
self.assertRaises(TypeError, result, None, None)
def test_view_as_function_with_attr_and_template(self):
+ self._registerRenderer()
def view(context, request):
""" """
result = self._callFUT(view, attr='__name__',
- template='fixtures/minimal.txt')
+ renderer='fixtures/minimal.txt')
self.failIf(result is view)
self.assertRaises(TypeError, result, None, None)
@@ -701,18 +721,20 @@ class Test_map_view(unittest.TestCase):
def test_view_as_newstyle_class_context_and_request_with_attr_and_template(
self):
+ self._registerRenderer()
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')
+ renderer='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')
+ request = DummyRequest()
+ self.assertEqual(result(None, request).body, 'Hello!')
def test_view_as_newstyle_class_requestonly(self):
class view(object):
@@ -741,18 +763,20 @@ class Test_map_view(unittest.TestCase):
self.assertEqual(result(None, None), 'OK')
def test_view_as_newstyle_class_requestonly_with_attr_and_template(self):
+ self._registerRenderer()
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')
+ renderer='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')
+ request = DummyRequest()
+ self.assertEqual(result(None, request).body, 'Hello!')
def test_view_as_oldstyle_class_context_and_request(self):
class view:
@@ -782,18 +806,20 @@ class Test_map_view(unittest.TestCase):
def test_view_as_oldstyle_class_context_and_request_with_attr_and_template(
self):
+ self._registerRenderer()
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')
+ renderer='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')
+ request = DummyRequest()
+ self.assertEqual(result(None, request).body, 'Hello!')
def test_view_as_oldstyle_class_requestonly(self):
class view:
@@ -822,18 +848,20 @@ class Test_map_view(unittest.TestCase):
self.assertEqual(result(None, None), 'OK')
def test_view_as_oldstyle_class_requestonly_with_attr_and_template(self):
+ self._registerRenderer()
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')
+ renderer='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')
+ request = DummyRequest()
+ self.assertEqual(result(None, request).body, 'Hello!')
def test_view_as_instance_context_and_request(self):
class View:
@@ -854,14 +882,16 @@ class Test_map_view(unittest.TestCase):
self.assertEqual(result(None, None), 'OK')
def test_view_as_instance_context_and_request_attr_and_template(self):
+ self._registerRenderer()
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')
+ renderer='repoze.bfg.tests:fixtures/minimal.txt')
self.failIf(result is view)
- self.assertEqual(result(None, None).body, 'Hello.\n')
+ request = DummyRequest()
+ self.assertEqual(result(None, request).body, 'Hello!')
def test_view_as_instance_requestonly(self):
class View:
@@ -888,27 +918,31 @@ class Test_map_view(unittest.TestCase):
self.assertEqual(result(None, None), 'OK')
def test_view_as_instance_requestonly_with_attr_and_template(self):
+ self._registerRenderer()
class View:
def index(self, request):
return {'a':'1'}
view = View()
result = self._callFUT(view, attr='index',
- template='repoze.bfg.tests:fixtures/minimal.txt')
+ renderer='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')
+ request = DummyRequest()
+ self.assertEqual(result(None, request).body, 'Hello!')
def test_view_templateonly(self):
+ self._registerRenderer()
def view(context, request):
return {'a':'1'}
result = self._callFUT(view,
- template='repoze.bfg.tests:fixtures/minimal.txt')
+ renderer='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')
+ request = DummyRequest()
+ self.assertEqual(result(None, request).body, 'Hello!')
class TestRequestOnly(unittest.TestCase):
def _callFUT(self, arg):
@@ -1112,7 +1146,7 @@ class TestDecorateView(unittest.TestCase):
self.failUnless(view1.__predicated__.im_func is
view2.__predicated__.im_func)
-class Test_templated_response(unittest.TestCase):
+class Test_rendered_response(unittest.TestCase):
def setUp(self):
cleanUp()
@@ -1120,61 +1154,105 @@ class Test_templated_response(unittest.TestCase):
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)
+ context=None, request=None):
+ from repoze.bfg.view import rendered_response
+ if request is None:
+ request = DummyRequest()
+ return rendered_response(template_name, response, view, context,
+ request)
+
+ def _registerRenderer(self):
+ from repoze.bfg.interfaces import IRendererFactory
+ from repoze.bfg.interfaces import ITemplateRenderer
+ from zope.interface import implements
+ from zope.component import getSiteManager
+ class Renderer:
+ implements(ITemplateRenderer)
+ def __call__(self, *arg):
+ return 'Hello!'
+
+ class RendererFactory:
+ def __call__(self, path):
+ self.path = path
+ return Renderer()
+
+ factory = RendererFactory()
+ sm = getSiteManager()
+ sm.registerUtility(factory, IRendererFactory, name='.txt')
def test_is_response(self):
+ self._registerRenderer()
response = DummyResponse()
result = self._callFUT(
'repoze.bfg.tests:fixtures/minimal.txt', response)
self.assertEqual(result, response)
def test_is_not_valid_dict(self):
+ self._registerRenderer()
response = None
result = self._callFUT(
'repoze.bfg.tests:fixtures/minimal.txt', response)
self.assertEqual(result, response)
def test_valid_dict(self):
+ self._registerRenderer()
response = {'a':'1'}
result = self._callFUT(
'repoze.bfg.tests:fixtures/minimal.txt', response)
- self.assertEqual(result.body, 'Hello.\n')
+ self.assertEqual(result.body, 'Hello!')
def test_with_content_type(self):
- response = {'a':'1', 'content_type_':'text/nonsense'}
+ self._registerRenderer()
+ response = {'a':'1'}
+ request = DummyRequest()
+ attrs = {'response_content_type':'text/nonsense'}
+ request.environ['webob.adhoc_attrs'] = attrs
result = self._callFUT(
- 'repoze.bfg.tests:fixtures/minimal.txt', response)
+ 'repoze.bfg.tests:fixtures/minimal.txt', response, request=request)
self.assertEqual(result.content_type, 'text/nonsense')
def test_with_headerlist(self):
- response = {'a':'1', 'headerlist_':[('a', '1'), ('b', '2')]}
+ self._registerRenderer()
+ response = {'a':'1'}
+ request = DummyRequest()
+ attrs = {'response_headerlist':[('a', '1'), ('b', '2')]}
+ request.environ['webob.adhoc_attrs'] = attrs
result = self._callFUT(
- 'repoze.bfg.tests:fixtures/minimal.txt', response)
+ 'repoze.bfg.tests:fixtures/minimal.txt', response, request=request)
self.assertEqual(result.headerlist,
[('Content-Type', 'text/html; charset=UTF-8'),
- ('Content-Length', '7'),
+ ('Content-Length', '6'),
('a', '1'),
('b', '2')])
def test_with_status(self):
- response = {'a':'1', 'status_':'406 You Lose'}
+ self._registerRenderer()
+ response = {'a':'1'}
+ request = DummyRequest()
+ attrs = {'response_status':'406 You Lose'}
+ request.environ['webob.adhoc_attrs'] = attrs
result = self._callFUT(
- 'repoze.bfg.tests:fixtures/minimal.txt', response)
+ 'repoze.bfg.tests:fixtures/minimal.txt', response, request=request)
self.assertEqual(result.status, '406 You Lose')
def test_with_charset(self):
- response = {'a':'1', 'charset_':'UTF-16'}
+ self._registerRenderer()
+ response = {'a':'1'}
+ request = DummyRequest()
+ attrs = {'response_charset':'UTF-16'}
+ request.environ['webob.adhoc_attrs'] = attrs
result = self._callFUT(
- 'repoze.bfg.tests:fixtures/minimal.txt', response)
+ 'repoze.bfg.tests:fixtures/minimal.txt', response, request=request)
self.assertEqual(result.charset, 'UTF-16')
def test_with_cache_for(self):
- response = {'a':'1', 'cache_for_':100}
+ self._registerRenderer()
+ response = {'a':'1'}
+ request = DummyRequest()
+ attrs = {'response_cache_for':100}
+ request.environ['webob.adhoc_attrs'] = attrs
result = self._callFUT(
- 'repoze.bfg.tests:fixtures/minimal.txt', response)
+ 'repoze.bfg.tests:fixtures/minimal.txt', response, request=request)
self.assertEqual(result.cache_control.max_age, 100)
class DummyContext:
diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py
index 208e8e754..21d55504d 100644
--- a/repoze/bfg/tests/test_zcml.py
+++ b/repoze/bfg/tests/test_zcml.py
@@ -320,8 +320,7 @@ class TestViewDirective(unittest.TestCase):
from zope.component import getSiteManager
from repoze.bfg.interfaces import IRequest
from repoze.bfg.interfaces import IView
- from repoze.bfg.interfaces import IViewPermission
-
+ from repoze.bfg.interfaces import IRendererFactory
import repoze.bfg.tests
context = DummyContext(repoze.bfg.tests)
@@ -335,9 +334,17 @@ class TestViewDirective(unittest.TestCase):
def __call__(self):
return {'a':'1'}
- import os
+ class Renderer:
+ def __call__(self, path):
+ self.path = path
+ return lambda *arg: 'Hello!'
+
+ renderer = Renderer()
+ sm = getSiteManager()
+ sm.registerUtility(renderer, IRendererFactory, name='.txt')
+
fixture = 'fixtures/minimal.txt'
- self._callFUT(context, 'repoze.view', IFoo, view=view, template=fixture)
+ self._callFUT(context, 'repoze.view', IFoo, view=view, renderer=fixture)
actions = context.actions
self.assertEqual(len(actions), 1)
@@ -347,29 +354,33 @@ class TestViewDirective(unittest.TestCase):
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')
+ request = DummyRequest()
+ result = wrapper(None, request)
+ self.assertEqual(result.body, 'Hello!')
+ self.assertEqual(renderer.path, 'repoze.bfg.tests:fixtures/minimal.txt')
def test_with_template_no_view_callable(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
+ from repoze.bfg.interfaces import IRendererFactory
import repoze.bfg.tests
context = DummyContext(repoze.bfg.tests)
class IFoo(Interface):
pass
- import os
- fixture = 'fixtures/minimal.txt'
- self._callFUT(context, 'repoze.view', IFoo, template=fixture)
+ sm = getSiteManager()
+ def renderer_factory(path):
+ return lambda *arg: 'Hello!'
+ sm.registerUtility(renderer_factory, IRendererFactory, name='.txt')
+
+ self._callFUT(context, 'repoze.view', IFoo, renderer='foo.txt')
actions = context.actions
self.assertEqual(len(actions), 1)
@@ -379,10 +390,11 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(action['discriminator'], discrim)
register = action['callable']
register()
- sm = getSiteManager()
wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='')
- result = wrapper(None, None)
- self.assertEqual(result.body, 'Hello.\n')
+ request = DummyRequest()
+ request.environ = {}
+ result = wrapper(None, request)
+ self.assertEqual(result.body, 'Hello!')
def test_request_type_asinterface(self):
from zope.component import getSiteManager
diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py
index ba96812e7..0f899df53 100644
--- a/repoze/bfg/view.py
+++ b/repoze/bfg/view.py
@@ -26,15 +26,16 @@ from zope.interface import implements
from zope.deprecation import deprecated
from repoze.bfg.interfaces import IResponseFactory
+from repoze.bfg.interfaces import IRendererFactory
from repoze.bfg.interfaces import IView
from repoze.bfg.interfaces import IMultiView
+from repoze.bfg.interfaces import ITemplateRenderer
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
+from repoze.bfg.renderers import renderer_from_name
deprecated('view_execution_permitted',
"('from repoze.bfg.view import view_execution_permitted' was "
@@ -236,8 +237,8 @@ class bfg_view(object):
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 ``renderer`` is not supplied, ``None`` is used (meaning that no
+ renderer is associated with this view).
If ``wrapper`` is not supplied, ``None`` is used (meaning that no
view wrapper is associated with this view).
@@ -335,7 +336,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, attr=None, template=None, wrapper=None):
+ containment=None, attr=None, renderer='', wrapper=None):
self.name = name
self.request_type = request_type
self.for_ = for_
@@ -345,11 +346,11 @@ class bfg_view(object):
self.request_param = request_param
self.containment = containment
self.attr = attr
- self.template = template
+ self.renderer = renderer
self.wrapper_viewname = wrapper
def __call__(self, wrapped):
- _bfg_view = map_view(wrapped, self.attr, self.template)
+ _bfg_view = map_view(wrapped, self.attr, self.renderer)
_bfg_view.__is_bfg_view__ = True
_bfg_view.__permission__ = self.permission
_bfg_view.__for__ = self.for_
@@ -430,41 +431,43 @@ class MultiView(object):
continue
raise NotFound(self.name)
-def templated_response(template_name, response, view, context, request,
- auto_reload=False):
+def rendered_response(renderer_name, response, view, context, request):
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)
+ renderer = renderer_from_name(renderer_name)
+ if ITemplateRenderer.providedBy(renderer):
+ kw = {'view':view, 'template_name':renderer_name, 'context':context,
+ 'request':request}
+ try:
+ kw.update(response)
+ except TypeError:
+ return response
+ result = renderer(kw)
+ else:
+ result = renderer(response)
response_factory = queryUtility(IResponseFactory, default=Response)
response = response_factory(result)
- content_type = kw.get('content_type_', None)
+ attrs = request.environ.get('webob.adhoc_attrs', {})
+ content_type = attrs.get('response_content_type', None)
if content_type is not None:
response.content_type = content_type
- headerlist = kw.get('headerlist_', None)
+ headerlist = attrs.get('response_headerlist', None)
if headerlist is not None:
for k, v in headerlist:
response.headers.add(k, v)
- status = kw.get('status_', None)
+ status = attrs.get('response_status', None)
if status is not None:
response.status = status
- charset = kw.get('charset_', None)
+ charset = attrs.get('response_charset', None)
if charset is not None:
response.charset = charset
- cache_for = kw.get('cache_for_', None)
+ cache_for = attrs.get('response_cache_for', None)
if cache_for is not None:
response.cache_expires = cache_for
return response
-def map_view(view, attr=None, template=None):
+def map_view(view, attr=None, renderer=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
@@ -482,9 +485,9 @@ def map_view(view, attr=None, template=None):
response = inst()
else:
response = getattr(inst, attr)()
- if template:
- response = templated_response(template, response, inst,
- context, request, auto_reload)
+ if renderer:
+ response = rendered_response(renderer, response, inst,
+ context, request)
return response
wrapped_view = _bfg_class_requestonly_view
else:
@@ -495,9 +498,9 @@ def map_view(view, attr=None, template=None):
response = inst()
else:
response = getattr(inst, attr)()
- if template:
- response = templated_response(template, response, inst,
- context, request, auto_reload)
+ if renderer:
+ response = rendered_response(renderer, response, inst,
+ context, request)
return response
wrapped_view = _bfg_class_view
@@ -510,28 +513,36 @@ def map_view(view, attr=None, template=None):
else:
response = getattr(view, attr)(request)
- if template:
- response = templated_response(template, response, view,
- context, request, auto_reload)
+ if renderer:
+ response = rendered_response(renderer, response, view,
+ context, request)
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)
+ if renderer:
+ response = rendered_response(renderer, response, view,
+ context, request)
return response
wrapped_view = _bfg_attr_view
- elif template:
- def _templated_view(context, request):
+ elif renderer:
+ def _rendered_view(context, request):
+ response = view(context, request)
+ response = rendered_response(renderer, response, view,
+ context, request)
+ return response
+ wrapped_view = _rendered_view
+
+ elif queryUtility(IRendererFactory):
+ def _default_rendered_view(context, request):
response = view(context, request)
- response = templated_response(template, response, view,
- context, request, auto_reload)
+ response = rendered_response(renderer, response, view,
+ context, request)
return response
- wrapped_view = _templated_view
+ wrapped_view = _default_rendered_view
decorate_view(wrapped_view, view)
return wrapped_view
diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py
index ff2549447..94bb7371d 100644
--- a/repoze/bfg/zcml.py
+++ b/repoze/bfg/zcml.py
@@ -39,7 +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.interfaces import IRendererFactory
from repoze.bfg.path import package_name
@@ -83,18 +83,18 @@ def view(
request_param=None,
containment=None,
attr=None,
- template=None,
+ renderer=None,
wrapper=None,
cacheable=True, # not used, here for b/w compat < 0.8
):
if not view:
- if template:
+ if renderer:
def view(context, request):
return {}
else:
raise ConfigurationError('"view" attribute was not specified and '
- 'no template specified')
+ 'no renderer specified')
sm = getSiteManager()
@@ -173,14 +173,15 @@ 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)
+ if renderer and '.' in renderer:
+ if (not ':' in renderer) and (not os.path.isabs(renderer) ):
+ # 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.
+ renderer = '%s:%s' % (package_name(_context.resolve('.')), renderer)
def register():
- derived_view = derive_view(view, permission, predicates, attr, template,
+ derived_view = derive_view(view, permission, predicates, attr, renderer,
wrapper)
r_for_ = for_
r_request_type = request_type
@@ -251,8 +252,8 @@ def forbidden(_context, view):
view_utility(_context, view, IForbiddenView)
def derive_view(original_view, permission=None, predicates=(), attr=None,
- template=None, wrapper_viewname=None):
- mapped_view = map_view(original_view, attr, template)
+ renderer=None, wrapper_viewname=None):
+ mapped_view = map_view(original_view, attr, renderer)
owrapped_view = owrap_view(mapped_view, wrapper_viewname)
secured_view = secure_view(owrapped_view, permission)
debug_view = authdebug_view(secured_view, permission)
@@ -560,21 +561,21 @@ def connect_route(path, name, factory):
mapper = getUtility(IRoutesMapper)
mapper.connect(path, name, factory)
-class ITemplateRendererDirective(Interface):
- renderer = GlobalObject(
- title=u'ITemplateRendererFactory implementation',
+class IRendererDirective(Interface):
+ factory = GlobalObject(
+ title=u'IRendererFactory implementation',
required=True)
- extension = TextLine(
- title=u'Filename extension (e.g. ".pt")',
+ name = TextLine(
+ title=u'Token (e.g. ``json``) or filename extension (e.g. ".pt")',
required=False)
-def template_renderer(_context, renderer, extension=''):
+def renderer(_context, factory, name=''):
# 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))
+ sm.registerUtility(factory, IRendererFactory, name=name)
+ _context.action(discriminator=(IRendererFactory, name))
class IStaticDirective(Interface):
name = TextLine(
@@ -642,8 +643,8 @@ class IViewDirective(Interface):
description=u'',
required=False)
- template = TextLine(
- title=u'The template asssociated with the view',
+ renderer = TextLine(
+ title=u'The renderer asssociated with the view',
description=u'',
required=False)
diff --git a/setup.py b/setup.py
index 9dc45b174..87aae3343 100644
--- a/setup.py
+++ b/setup.py
@@ -15,6 +15,7 @@
__version__ = '1.1a2'
import os
+import sys
from ez_setup import use_setuptools
use_setuptools()
@@ -44,6 +45,9 @@ install_requires=[
'martian',
]
+if sys.version_info[:2] < (2, 6):
+ install_requires.append('simplejson')
+
setup(name='repoze.bfg',
version=__version__,
description='A web framework for WSGI',