import inspect
from paste.urlparser import StaticURLParser
from zope.component import queryMultiAdapter
from zope.deprecation import deprecated
from repoze.bfg.interfaces import IView
from repoze.bfg.path import caller_path
from repoze.bfg.security import view_execution_permitted
from repoze.bfg.security import Unauthorized
deprecated('view_execution_permitted',
"('from repoze.bfg.view import view_execution_permitted' is now "
"deprecated; instead use 'from repoze.bfg.security import "
"view_execution_permitted')",
)
_marker = object()
def render_view_to_response(context, request, name='', secure=True):
""" Render the view named ``name`` against the specified
``context`` and ``request`` to an object implementing
``repoze.bfg.interfaces.IResponse`` or ``None`` if no such view
exists. This function will return ``None`` if a corresponding
view cannot be found. If ``secure`` is ``True``, and the view is
protected by a permission, the permission will be checked before
calling the view function. If the permission check disallows view
execution (based on the current security policy), a
``repoze.bfg.security.Unauthorized`` exception will be raised; its
``args`` attribute explains why the view access was disallowed.
If ``secure`` is ``False``, no permission checking is done."""
if secure:
permitted = view_execution_permitted(context, request, name)
if not permitted:
raise Unauthorized(permitted)
# It's no use trying to distinguish below whether response is None
# because a) we were returned a default or b) because the view
# function returned None: the zope.component/zope.interface
# machinery doesn't distinguish a None returned from the view from
# a sentinel None returned during queryMultiAdapter (even if we
# pass a non-None default).
return queryMultiAdapter((context, request), IView, name=name)
def render_view_to_iterable(context, request, name='', secure=True):
""" Render the view named ``name`` against the specified
``context`` and ``request``, and return an iterable representing
the view response's ``app_iter`` (see the interface named
``repoze.bfg.interfaces.IResponse``). This function will return
``None`` if a corresponding view cannot be found. Additionally,
this function will raise a ``ValueError`` if a view function is
found and called but the view does not return an object which
implements ``repoze.bfg.interfaces.IResponse``. You can usually
get the string representation of the return value of this function
by calling ``''.join(iterable)``, or just use ``render_view``
instead. If ``secure`` is ``True``, and the view is protected by
a permission, the permission will be checked before calling the
view function. If the permission check disallows view execution
(based on the current security policy), a
``repoze.bfg.security.Unauthorized`` exception will be raised; its
``args`` attribute explains why the view access was disallowed.
If ``secure`` is ``False``, no permission checking is done."""
response = render_view_to_response(context, request, name, secure)
if response is None:
return None
return response.app_iter
def render_view(context, request, name='', secure=True):
""" Render the view named ``name`` against the specified
``context`` and ``request``, and unwind the the view response's
``app_iter`` (see the interface named
``repoze.bfg.interfaces.IResponse``) into a single string. This
function will return ``None`` if a corresponding view cannot be
found. Additionally, this function will raise a ``ValueError`` if
a view function is found and called but the view does not return
an object which implements ``repoze.bfg.interfaces.IResponse``.
If ``secure`` is ``True``, and the view is protected by a
permission, the permission will be checked before calling the view
function. If the permission check disallows view execution (based
on the current security policy), a
``repoze.bfg.security.Unauthorized`` exception will be raised; its
``args`` attribute explains why the view access was disallowed.
If ``secure`` is ``False``, no permission checking is done."""
iterable = render_view_to_iterable(context, request, name, secure)
if iterable is None:
return None
return ''.join(iterable)
def is_response(ob):
""" Return True if ``ob`` implements the
``repoze.bfg.interfaces.IResponse`` interface, False if not. Note
that this isn't actually a true Zope interface check, it's a
duck-typing check, as response objects are not obligated to
actually implement a Zope interface."""
# response objects aren't obligated to implement a Zope interface,
# so we do it the hard way
if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and
hasattr(ob, 'status') ):
if ( hasattr(ob.app_iter, '__iter__') and
hasattr(ob.headerlist, '__iter__') and
isinstance(ob.status, basestring) ) :
return True
return False
class static(object):
""" An instance of this class is a callable which can act as a BFG
view; this view will serve static files from a directory on disk
based on the ``root_dir`` you provide to its constructor. The
directory may contain subdirectories (recursively); the static
view implementation will descend into these directories as
necessary based on the components of the URL in order to resolve a
path into a response.
You may pass an absolute or relative filesystem path to the
directory containing static files directory to the constructor as
the ``root_dir`` argument. If the path is relative, it will be
considered relative to the directory in which the Python file
which calls ``static`` resides. ``cache_max_age`` influences the
Expires and Max-Age response headers returned by the view (default
is 3600 seconds or five minutes). ``level`` influences how
relative directories are resolved (the number of hops in the call
stack), not used very often.
"""
def __init__(self, root_dir, cache_max_age=3600, level=2):
root_dir = caller_path(root_dir, level=level)
self.app = StaticURLParser(root_dir, cache_max_age=cache_max_age)
def __call__(self, context, request):
subpath = '/'.join(request.subpath)
request_copy = request.copy()
# Fix up PATH_INFO to get rid of everything but the "subpath"
# (the actual path to the file relative to the root dir).
request_copy.environ['PATH_INFO'] = '/' + subpath
# Zero out SCRIPT_NAME for good measure.
request_copy.environ['SCRIPT_NAME'] = ''
return request_copy.get_response(self.app)
class bfg_view(object):
""" Function or class decorator which allows Python code to make
view registrations instead of using ZCML for the same purpose.
E.g. in the module ``views.py``::
from models import IMyModel
from repoze.bfg.interfaces import IRequest
@bfg_view(name='my_view', request_type=IRequest, for_=IMyModel,
permission='read'))
def my_view(context, request):
return render_template_to_response('templates/my.pt')
Equates to the ZCML::
If ``name`` is not supplied, the empty string is used (implying
the default view).
If ``request_type`` is not supplied, the interface
``repoze.bfg.interfaces.IRequest`` is used.
If ``for_`` is not supplied, the interface
``zope.interface.Interface`` (implying *all* interfaces) is used.
If ``permission`` is not supplied, no permission is registered for
this view (it's accessible by any caller).
Any individual or all parameters can be omitted. The simplest
bfg_view declaration then becomes::
@bfg_view()
def my_view(...):
...
Such a registration implies that the view name will be
``my_view``, registered for models with the
``zope.interface.Interface`` interface, using no permission,
registered against requests which implement the default IRequest
interface.
The ``bfg_view`` decorator can also be used as a class decorator
in Python 2.6 and better (Python 2.5 and below do not support
class decorators)::
from webob import Response
from repoze.bfg.view import bfg_view
@bfg_view()
class MyView(object):
def __init__(self, context, request):
self.context = context
self.request = request
def __call__(self):
return Response('hello from %s!' % self.context)
.. warning:: This feature is new in 0.8.1.
.. note:: When a view is a class, the calling semantics are
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, creating an instance. 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).
To make use of any bfg_view declaration, you *must* insert the
following boilerplate into your application registry's ZCML::
"""
def __init__(self, name='', request_type=None, for_=None, permission=None):
self.name = name
self.request_type = request_type
self.for_ = for_
self.permission = permission
def __call__(self, wrapped):
_bfg_view = wrapped
if inspect.isclass(_bfg_view):
# If the object we're decorating is a class, turn it into
# a function that operates like a Zope view (when it's
# invoked, construct an instance using 'context' and
# 'request' as position arguments, then immediately invoke
# the __call__ method of the instance with no arguments;
# __call__ should return an IResponse).
def _bfg_class_view(context, request):
inst = wrapped(context, request)
return inst()
_bfg_class_view.__module__ = wrapped.__module__
_bfg_class_view.__name__ = wrapped.__name__
_bfg_class_view.__doc__ = wrapped.__doc__
_bfg_view = _bfg_class_view
_bfg_view.__is_bfg_view__ = True
_bfg_view.__permission__ = self.permission
_bfg_view.__for__ = self.for_
_bfg_view.__view_name__ = self.name
_bfg_view.__request_type__ = self.request_type
return _bfg_view