from paste.urlparser import StaticURLParser
from zope.component import queryMultiAdapter
from zope.component import queryUtility
from repoze.bfg.interfaces import ISecurityPolicy
from repoze.bfg.interfaces import IViewPermission
from repoze.bfg.interfaces import IView
from repoze.bfg.path import caller_path
from repoze.bfg.security import Unauthorized
from repoze.bfg.security import Allowed
from zope.interface import Interface
from repoze.bfg.interfaces import IRequest
_marker = object()
def view_execution_permitted(context, request, name=''):
""" If the view specified by ``context`` and ``name`` is protected
by a permission, return the result of checking the permission
associated with the view using the effective security policy and
the ``request``. If no security policy is in effect, or if the
view is not protected by a permission, return a True value. """
security_policy = queryUtility(ISecurityPolicy)
if security_policy is not None:
permission = queryMultiAdapter((context, request), IViewPermission,
name=name)
if permission is None:
return Allowed(
'Allowed: view name %r in context %r (no permission '
'registered for name %r).', name, context, name)
return permission(security_policy)
return Allowed('Allowed: view name %r in context %r (no security policy '
'in use).', name, context)
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. Additionally, this function will raise a
``ValueError`` if a view function is found and called but the view
returns an object which does not implement
``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."""
if secure:
permitted = view_execution_permitted(context, request, name)
if not permitted:
raise Unauthorized(permitted)
response = queryMultiAdapter((context, request), IView, name=name,
default=_marker)
if response is _marker:
return None
if not is_response(response):
raise ValueError('response did not implement IResponse: %r' % response)
return response
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; this is written awkwardly for
# performance reasons
try:
ob.app_iter, ob.headerlist, ob.status
except AttributeError:
return False
try:
ob.app_iter.__iter__, ob.headerlist.__iter__
except AttributeError:
return False
if not isinstance(ob.status, basestring):
return False
return True
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):
""" 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.
To make use of bfg_view declarations, insert the following
boilerplate into your application registry's ZCML::
"""
def __init__(self, name='', request_type=IRequest, for_=Interface,
permission=None):
self.name = name
self.request_type = request_type
self.for_ = for_
self.permission = permission
def __call__(self, wrapped):
# We intentionally return a do-little un-functools-wrapped
# decorator here so as to make the decorated function
# unpickleable; applications which use bfg_view decorators
# should never be able to load actions from an actions cache;
# instead they should rerun the file_configure function each
# time the application starts in case any of the decorators
# has been changed. Disallowing these functions from being
# pickled enforces that.
def _bfg_view(context, request):
return wrapped(context, request)
_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
# we assign to __grok_module__ here rather than __module__ to
# make it unpickleable but allow for the grokker to be able to
# find it
_bfg_view.__grok_module__ = wrapped.__module__
_bfg_view.__name__ = wrapped.__name__
_bfg_view.__doc__ = wrapped.__doc__
_bfg_view.__dict__.update(wrapped.__dict__)
return _bfg_view