Chris McDonough
2011-07-12 f55b54a16def0bb0c463ee302dd12eefaa3638ad
commit | author | age
fdaa89 1 import mimetypes
e6fa66 2 import venusian
CM 3
4174e4 4 from zope.interface import providedBy
5f4780 5 from zope.deprecation import deprecated
7e2c6c 6
b60bdb 7 from pyramid.interfaces import IRoutesMapper
CM 8 from pyramid.interfaces import IView
9 from pyramid.interfaces import IViewClassifier
d66bfb 10
99edc5 11 from pyramid.httpexceptions import HTTPFound
CM 12 from pyramid.httpexceptions import default_exceptionresponse_view
95c9f6 13 from pyramid.renderers import RendererHelper
b33dca 14 from pyramid.static import static_view
b60bdb 15 from pyramid.threadlocal import get_current_registry
b29429 16
70d504 17 def init_mimetypes(mimetypes):
CM 18     # this is a function so it can be unittested
19     if hasattr(mimetypes, 'init'):
20         mimetypes.init()
21         return True
22     return False
23
24 # See http://bugs.python.org/issue5853 which is a recursion bug
25 # that seems to effect Python 2.6, Python 2.6.1, and 2.6.2 (a fix
26 # has been applied on the Python 2 trunk).  This workaround should
27 # really be in Paste if anywhere, but it's easiest to just do it
28 # here and get it over with to avoid needing to deal with any
29 # fallout.
30 init_mimetypes(mimetypes)
31
87a85f 32 # Nasty BW compat hack: dont yet deprecate this (ever?)
b33dca 33 class static(static_view): # only subclass for purposes of autodoc
CM 34     __doc__ = static_view.__doc__
d75fe7 35
f66290 36 _marker = object()
85078f 37
7e2c6c 38 def render_view_to_response(context, request, name='', secure=True):
b93d19 39     """ Call the :term:`view callable` configured with a :term:`view
c5f24b 40     configuration` that matches the :term:`view name` ``name``
b93d19 41     registered against the specified ``context`` and ``request`` and
CM 42     return a :term:`response` object.  This function will return
43     ``None`` if a corresponding :term:`view callable` cannot be found
44     (when no :term:`view configuration` matches the combination of
45     ``name`` / ``context`` / and ``request``).
8b1f6e 46
b93d19 47     If `secure`` is ``True``, and the :term:`view callable` found is
99edc5 48     protected by a permission, the permission will be checked before calling
CM 49     the view function.  If the permission check disallows view execution
50     (based on the current :term:`authorization policy`), a
51     :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised.
52     The exception's ``args`` attribute explains why the view access was
53     disallowed.
b93d19 54
7e2c6c 55     If ``secure`` is ``False``, no permission checking is done."""
ff1213 56     provides = [IViewClassifier] + map(providedBy, (request, context))
d0b398 57     try:
CM 58         reg = request.registry
59     except AttributeError:
60         reg = get_current_registry()
1c0210 61     view = reg.adapters.lookup(provides, IView, name=name)
d66bfb 62     if view is None:
CM 63         return None
17ce57 64
d66bfb 65     if not secure:
7850a4 66         # the view will have a __call_permissive__ attribute if it's
d66bfb 67         # secured; otherwise it won't.
CM 68         view = getattr(view, '__call_permissive__', view)
69
d75fe7 70     # if this view is secured, it will raise a Forbidden
d66bfb 71     # appropriately if the executing user does not have the proper
CM 72     # permission
73     return view(context, request)
7e2c6c 74
CM 75 def render_view_to_iterable(context, request, name='', secure=True):
b93d19 76     """ Call the :term:`view callable` configured with a :term:`view
c5f24b 77     configuration` that matches the :term:`view name` ``name``
b93d19 78     registered against the specified ``context`` and ``request`` and
CM 79     return an iterable object which represents the body of a response.
80     This function will return ``None`` if a corresponding :term:`view
81     callable` cannot be found (when no :term:`view configuration`
82     matches the combination of ``name`` / ``context`` / and
83     ``request``).  Additionally, this function will raise a
84     :exc:`ValueError` if a view function is found and called but the
85     view function's result does not have an ``app_iter`` attribute.
8b1f6e 86
b93d19 87     You can usually get the string representation of the return value
CM 88     of this function by calling ``''.join(iterable)``, or just use
c81aad 89     :func:`pyramid.view.render_view` instead.
8b1f6e 90
99edc5 91     If ``secure`` is ``True``, and the view is protected by a permission, the
CM 92     permission will be checked before the view function is invoked.  If the
93     permission check disallows view execution (based on the current
94     :term:`authentication policy`), a
95     :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised; its
96     ``args`` attribute explains why the view access was disallowed.
b93d19 97
CM 98     If ``secure`` is ``False``, no permission checking is
99     done."""
7e2c6c 100     response = render_view_to_response(context, request, name, secure)
CM 101     if response is None:
102         return None
103     return response.app_iter
104
885bfb 105 def render_view(context, request, name='', secure=True):
b93d19 106     """ Call the :term:`view callable` configured with a :term:`view
c5f24b 107     configuration` that matches the :term:`view name` ``name``
27d735 108     registered against the specified ``context`` and ``request``
c5f24b 109     and unwind the view response's ``app_iter`` (see
b93d19 110     :ref:`the_response`) into a single string.  This function will
CM 111     return ``None`` if a corresponding :term:`view callable` cannot be
112     found (when no :term:`view configuration` matches the combination
113     of ``name`` / ``context`` / and ``request``).  Additionally, this
114     function will raise a :exc:`ValueError` if a view function is
115     found and called but the view function's result does not have an
116     ``app_iter`` attribute. This function will return ``None`` if a
117     corresponding view cannot be found.
8b1f6e 118
99edc5 119     If ``secure`` is ``True``, and the view is protected by a permission, the
CM 120     permission will be checked before the view is invoked.  If the permission
121     check disallows view execution (based on the current :term:`authorization
122     policy`), a :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be
123     raised; its ``args`` attribute explains why the view access was
b93d19 124     disallowed.
CM 125
885bfb 126     If ``secure`` is ``False``, no permission checking is done."""
CM 127     iterable = render_view_to_iterable(context, request, name, secure)
128     if iterable is None:
129         return None
130     return ''.join(iterable)
131
197f0c 132 class view_config(object):
8b1f6e 133     """ A function, class or method :term:`decorator` which allows a
CM 134     developer to create view registrations nearer to a :term:`view
c1eb0c 135     callable` definition than use :term:`imperative
8b1f6e 136     configuration` to do the same.
5a7f9a 137
8b1f6e 138     For example, this code in a module ``views.py``::
5a7f9a 139
3e2f12 140       from resources import MyResource
5a7f9a 141
3e2f12 142       @view_config(name='my_view', context=MyResource, permission='read',
197f0c 143                    route_name='site1')
5a7f9a 144       def my_view(context, request):
8b1f6e 145           return 'OK'
5a7f9a 146
b93d19 147     Might replace the following call to the
aff443 148     :meth:`pyramid.config.Configurator.add_view` method::
8b1f6e 149
CM 150        import views
3e2f12 151        from resources import MyResource
CM 152        config.add_view(views.my_view, context=MyResource, name='my_view',
8b1f6e 153                        permission='read', 'route_name='site1')
5a7f9a 154
197f0c 155     .. note: :class:`pyramid.view.view_config` is also importable, for
CM 156              backwards compatibility purposes, as the name
157              :class:`pyramid.view.bfg_view`.
158
8b1f6e 159     The following arguments are supported as arguments to
cf7d8b 160     :class:`pyramid.view.view_config`: ``context``, ``permission``, ``name``,
CM 161     ``request_type``, ``route_name``, ``request_method``, ``request_param``,
162     ``containment``, ``xhr``, ``accept``, ``header``, ``path_info``,
0fa199 163     ``custom_predicates``, ``decorator``, ``mapper``, and ``http_cache``.
5a7f9a 164
cf7d8b 165     The meanings of these arguments are the same as the arguments passed to
CM 166     :meth:`pyramid.config.Configurator.add_view`.
0b0e74 167
cb14cd 168     See :ref:`mapping_views_using_a_decorator_section` for details about
CM 169     using :class:`view_config`.
5a7f9a 170
CM 171     """
e6fa66 172     venusian = venusian # for testing injection
dfc2b6 173     def __init__(self, name='', request_type=None, for_=None, permission=None,
d66bfb 174                  route_name=None, request_method=None, request_param=None,
083422 175                  containment=None, attr=None, renderer=None, wrapper=None,
6225a2 176                  xhr=False, accept=None, header=None, path_info=None,
1d9ade 177                  custom_predicates=(), context=None, decorator=None,
0fa199 178                  mapper=None, http_cache=None):
5a7f9a 179         self.name = name
CM 180         self.request_type = request_type
eecdbc 181         self.context = context or for_
5a7f9a 182         self.permission = permission
dfc2b6 183         self.route_name = route_name
d66bfb 184         self.request_method = request_method
CM 185         self.request_param = request_param
186         self.containment = containment
04e182 187         self.attr = attr
a9fed7 188         self.renderer = renderer
1dc390 189         self.wrapper = wrapper
083422 190         self.xhr = xhr
CM 191         self.accept = accept
192         self.header = header
7a13fb 193         self.path_info = path_info
6225a2 194         self.custom_predicates = custom_predicates
1d9ade 195         self.decorator = decorator
5e3d64 196         self.mapper = mapper
0fa199 197         self.http_cache = http_cache
5a7f9a 198
CM 199     def __call__(self, wrapped):
e6fa66 200         settings = self.__dict__.copy()
CM 201
202         def callback(context, name, ob):
95c9f6 203             renderer = settings.get('renderer')
CM 204             if isinstance(renderer, basestring):
205                 renderer = RendererHelper(name=renderer,
206                                           package=info.module,
207                                           registry=context.config.registry)
208             settings['renderer'] = renderer
e6fa66 209             context.config.add_view(view=ob, **settings)
CM 210
cba2e1 211         info = self.venusian.attach(wrapped, callback, category='pyramid')
e6fa66 212
CM 213         if info.scope == 'class':
32418e 214             # if the decorator was attached to a method in a class, or
CM 215             # otherwise executed at class scope, we need to set an
216             # 'attr' into the settings if one isn't already in there
e6fa66 217             if settings['attr'] is None:
CM 218                 settings['attr'] = wrapped.__name__
89968d 219
14b78b 220         settings['_info'] = info.codeinfo
c89bcb 221         return wrapped
5a7f9a 222
87a85f 223 bfg_view = view_config
197f0c 224
5f4780 225 deprecated(
CM 226     'bfg_view',
227     'pyramid.view.bfg_view is deprecated as of Pyramid 1.0.  Use '
228     'pyramid.view.view_config instead (API-compat, simple '
229     'rename).')
230
d96ff9 231 class AppendSlashNotFoundViewFactory(object):
CM 232     """ There can only be one :term:`Not Found view` in any
fd5ae9 233     :app:`Pyramid` application.  Even if you use
c81aad 234     :func:`pyramid.view.append_slash_notfound_view` as the Not
fd5ae9 235     Found view, :app:`Pyramid` still must generate a ``404 Not
d96ff9 236     Found`` response when it cannot redirect to a slash-appended URL;
CM 237     this not found response will be visible to site users.
238
239     If you don't care what this 404 response looks like, and you only
240     need redirections to slash-appended route URLs, you may use the
c81aad 241     :func:`pyramid.view.append_slash_notfound_view` object as the
d96ff9 242     Not Found view.  However, if you wish to use a *custom* notfound
CM 243     view callable when a URL cannot be redirected to a slash-appended
244     URL, you may wish to use an instance of this class as the Not
245     Found view, supplying a :term:`view callable` to be used as the
246     custom notfound view as the first argument to its constructor.
247     For instance:
248
249     .. code-block:: python
250
99edc5 251        from pyramid.httpexceptions import HTTPNotFound
c81aad 252        from pyramid.view import AppendSlashNotFoundViewFactory
d96ff9 253
1b4360 254        def notfound_view(context, request): return HTTPNotFound('nope')
d96ff9 255
CM 256        custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view)
a7e625 257        config.add_view(custom_append_slash, context=HTTPNotFound)
d96ff9 258
CM 259     The ``notfound_view`` supplied must adhere to the two-argument
260     view callable calling convention of ``(context, request)``
261     (``context`` will be the exception object).
262
263     """
264     def __init__(self, notfound_view=None):
265         if notfound_view is None:
266             notfound_view = default_exceptionresponse_view
267         self.notfound_view = notfound_view
268
269     def __call__(self, context, request):
270         if not isinstance(context, Exception):
271             # backwards compat for an append_notslash_view registered via
272             # config.set_notfound_view instead of as a proper exception view
8c2a9e 273             context = getattr(request, 'exception', None) or context
b596e1 274         path = request.path
30e64f 275         registry = request.registry
d96ff9 276         mapper = registry.queryUtility(IRoutesMapper)
CM 277         if mapper is not None and not path.endswith('/'):
278             slashpath = path + '/'
279             for route in mapper.get_routes():
280                 if route.match(slashpath) is not None:
b596e1 281                     qs = request.query_string
CM 282                     if qs:
283                         slashpath += '?' + qs
d96ff9 284                     return HTTPFound(location=slashpath)
CM 285         return self.notfound_view(context, request)
a9454c 286
d96ff9 287 append_slash_notfound_view = AppendSlashNotFoundViewFactory()
CM 288 append_slash_notfound_view.__doc__ = """\
289 For behavior like Django's ``APPEND_SLASH=True``, use this view as the
290 :term:`Not Found view` in your application.
a9454c 291
87a85f 292 When this view is the Not Found view (indicating that no view was found), and
CM 293 any routes have been defined in the configuration of your application, if the
294 value of the ``PATH_INFO`` WSGI environment variable does not already end in
295 a slash, and if the value of ``PATH_INFO`` *plus* a slash matches any route's
296 path, do an HTTP redirect to the slash-appended PATH_INFO.  Note that this
297 will *lose* ``POST`` data information (turning it into a GET), so you
298 shouldn't rely on this to redirect POST requests.  Note also that static
299 routes are not considered when attempting to find a matching route.
d66bfb 300
c1eb0c 301 Use the :meth:`pyramid.config.Configurator.add_view` method to configure this
CM 302 view as the Not Found view::
8b1f6e 303
99edc5 304   from pyramid.httpexceptions import HTTPNotFound
c81aad 305   from pyramid.view import append_slash_notfound_view
a7e625 306   config.add_view(append_slash_notfound_view, context=HTTPNotFound)
8b1f6e 307
d96ff9 308 See also :ref:`changing_the_notfound_view`.
d66bfb 309
d96ff9 310 """
d66bfb 311
ce9b9b 312 def is_response(ob):
CM 313     """ Return ``True`` if ``ob`` implements the interface implied by
314     :ref:`the_response`. ``False`` if not.
315
920990 316     .. warning:: This function is deprecated as of :app:`Pyramid` 1.1.  New
CM 317        code should not use it.  Instead, new code should use the
318        :func:`pyramid.request.Request.is_response` method."""
ce9b9b 319     if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and
CM 320          hasattr(ob, 'status') ):
321         return True
322     return False
ab27bd 323
920990 324 deprecated(
CM 325     'is_response',
326     'pyramid.view.is_response is deprecated as of Pyramid 1.1.  Use '
327     'pyramid.request.Request.is_response instead.')