diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-09-16 04:56:49 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-09-16 04:56:49 +0000 |
| commit | a9fed7675d8da572dee840676714b2653e3f7f79 (patch) | |
| tree | 93327afae95fb9cf6b1d0cb72da265af42a705bd | |
| parent | a37220b84dee4cc8b1b12f34643ce97dad89ffe1 (diff) | |
| download | pyramid-a9fed7675d8da572dee840676714b2653e3f7f79.tar.gz pyramid-a9fed7675d8da572dee840676714b2653e3f7f79.tar.bz2 pyramid-a9fed7675d8da572dee840676714b2653e3f7f79.zip | |
Checkpoint. Not 100% test coverage.
| -rw-r--r-- | CHANGES.txt | 54 | ||||
| -rw-r--r-- | docs/narr/templates.rst | 4 | ||||
| -rw-r--r-- | docs/narr/views.rst | 759 | ||||
| -rw-r--r-- | repoze/bfg/chameleon_text.py | 61 | ||||
| -rw-r--r-- | repoze/bfg/chameleon_zpt.py | 46 | ||||
| -rw-r--r-- | repoze/bfg/includes/configure.zcml | 16 | ||||
| -rw-r--r-- | repoze/bfg/includes/meta.zcml | 6 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 23 | ||||
| -rw-r--r-- | repoze/bfg/renderers.py (renamed from repoze/bfg/templating.py) | 73 | ||||
| -rw-r--r-- | repoze/bfg/testing.py | 2 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_chameleon_text.py | 4 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_chameleon_zpt.py | 2 | ||||
| -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.py | 8 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_view.py | 142 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_zcml.py | 40 | ||||
| -rw-r--r-- | repoze/bfg/view.py | 93 | ||||
| -rw-r--r-- | repoze/bfg/zcml.py | 45 | ||||
| -rw-r--r-- | setup.py | 4 |
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) @@ -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', |
