commit | author | age
|
fb2824
|
1 |
import itertools |
252fa5
|
2 |
import sys |
CM |
3 |
|
e6fa66
|
4 |
import venusian |
CM |
5 |
|
4174e4
|
6 |
from zope.interface import providedBy |
7e2c6c
|
7 |
|
0c1c39
|
8 |
from pyramid.interfaces import ( |
CM |
9 |
IRoutesMapper, |
85d801
|
10 |
IMultiView, |
MM |
11 |
ISecuredView, |
0c1c39
|
12 |
IView, |
CM |
13 |
IViewClassifier, |
17c7f4
|
14 |
IRequest, |
531428
|
15 |
IExceptionViewClassifier, |
0c1c39
|
16 |
) |
d66bfb
|
17 |
|
849196
|
18 |
from pyramid.compat import decode_path_info |
6f2f04
|
19 |
from pyramid.compat import reraise as reraise_ |
0db4a1
|
20 |
|
e8c66a
|
21 |
from pyramid.exceptions import ( |
MM |
22 |
ConfigurationError, |
|
23 |
PredicateMismatch, |
|
24 |
) |
e6c2d2
|
25 |
|
0c1c39
|
26 |
from pyramid.httpexceptions import ( |
CM |
27 |
HTTPFound, |
e045cf
|
28 |
HTTPNotFound, |
0c1c39
|
29 |
default_exceptionresponse_view, |
CM |
30 |
) |
0db4a1
|
31 |
|
dda9fa
|
32 |
from pyramid.threadlocal import ( |
BJR |
33 |
get_current_registry, |
|
34 |
manager, |
|
35 |
) |
|
36 |
|
19016b
|
37 |
from pyramid.util import hide_attrs |
70d504
|
38 |
|
f66290
|
39 |
_marker = object() |
56d0fe
|
40 |
|
7e2c6c
|
41 |
def render_view_to_response(context, request, name='', secure=True): |
b93d19
|
42 |
""" Call the :term:`view callable` configured with a :term:`view |
c5f24b
|
43 |
configuration` that matches the :term:`view name` ``name`` |
b93d19
|
44 |
registered against the specified ``context`` and ``request`` and |
CM |
45 |
return a :term:`response` object. This function will return |
|
46 |
``None`` if a corresponding :term:`view callable` cannot be found |
|
47 |
(when no :term:`view configuration` matches the combination of |
|
48 |
``name`` / ``context`` / and ``request``). |
8b1f6e
|
49 |
|
b93d19
|
50 |
If `secure`` is ``True``, and the :term:`view callable` found is |
99edc5
|
51 |
protected by a permission, the permission will be checked before calling |
CM |
52 |
the view function. If the permission check disallows view execution |
|
53 |
(based on the current :term:`authorization policy`), a |
|
54 |
:exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised. |
|
55 |
The exception's ``args`` attribute explains why the view access was |
|
56 |
disallowed. |
b93d19
|
57 |
|
7e2c6c
|
58 |
If ``secure`` is ``False``, no permission checking is done.""" |
17ce57
|
59 |
|
849196
|
60 |
registry = getattr(request, 'registry', None) |
CM |
61 |
if registry is None: |
|
62 |
registry = get_current_registry() |
d66bfb
|
63 |
|
849196
|
64 |
context_iface = providedBy(context) |
713bc5
|
65 |
# We explicitly pass in the interfaces provided by the request as |
CM |
66 |
# request_iface to _call_view; we don't want _call_view to use |
|
67 |
# request.request_iface, because render_view_to_response and friends are |
|
68 |
# pretty much limited to finding views that are not views associated with |
|
69 |
# routes, and the only thing request.request_iface is used for is to find |
|
70 |
# route-based views. The render_view_to_response API is (and always has |
|
71 |
# been) a stepchild API reserved for use of those who actually use |
|
72 |
# traversal. Doing this fixes an infinite recursion bug introduced in |
|
73 |
# Pyramid 1.6a1, and causes the render_view* APIs to behave as they did in |
|
74 |
# 1.5 and previous. We should probably provide some sort of different API |
|
75 |
# that would allow people to find views for routes. See |
|
76 |
# https://github.com/Pylons/pyramid/issues/1643 for more info. |
|
77 |
request_iface = providedBy(request) |
849196
|
78 |
|
CM |
79 |
response = _call_view( |
|
80 |
registry, |
|
81 |
request, |
|
82 |
context, |
|
83 |
context_iface, |
|
84 |
name, |
03c11e
|
85 |
secure=secure, |
713bc5
|
86 |
request_iface=request_iface, |
849196
|
87 |
) |
CM |
88 |
|
|
89 |
return response # NB: might be None |
|
90 |
|
7e2c6c
|
91 |
|
CM |
92 |
def render_view_to_iterable(context, request, name='', secure=True): |
b93d19
|
93 |
""" Call the :term:`view callable` configured with a :term:`view |
c5f24b
|
94 |
configuration` that matches the :term:`view name` ``name`` |
b93d19
|
95 |
registered against the specified ``context`` and ``request`` and |
CM |
96 |
return an iterable object which represents the body of a response. |
|
97 |
This function will return ``None`` if a corresponding :term:`view |
|
98 |
callable` cannot be found (when no :term:`view configuration` |
|
99 |
matches the combination of ``name`` / ``context`` / and |
|
100 |
``request``). Additionally, this function will raise a |
|
101 |
:exc:`ValueError` if a view function is found and called but the |
|
102 |
view function's result does not have an ``app_iter`` attribute. |
8b1f6e
|
103 |
|
3d2dd3
|
104 |
You can usually get the bytestring representation of the return value of |
CM |
105 |
this function by calling ``b''.join(iterable)``, or just use |
c81aad
|
106 |
:func:`pyramid.view.render_view` instead. |
8b1f6e
|
107 |
|
99edc5
|
108 |
If ``secure`` is ``True``, and the view is protected by a permission, the |
CM |
109 |
permission will be checked before the view function is invoked. If the |
|
110 |
permission check disallows view execution (based on the current |
|
111 |
:term:`authentication policy`), a |
|
112 |
:exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised; its |
|
113 |
``args`` attribute explains why the view access was disallowed. |
b93d19
|
114 |
|
CM |
115 |
If ``secure`` is ``False``, no permission checking is |
|
116 |
done.""" |
7e2c6c
|
117 |
response = render_view_to_response(context, request, name, secure) |
CM |
118 |
if response is None: |
|
119 |
return None |
|
120 |
return response.app_iter |
|
121 |
|
885bfb
|
122 |
def render_view(context, request, name='', secure=True): |
b93d19
|
123 |
""" Call the :term:`view callable` configured with a :term:`view |
c5f24b
|
124 |
configuration` that matches the :term:`view name` ``name`` |
27d735
|
125 |
registered against the specified ``context`` and ``request`` |
c5f24b
|
126 |
and unwind the view response's ``app_iter`` (see |
23de5b
|
127 |
:ref:`the_response`) into a single bytestring. This function will |
b93d19
|
128 |
return ``None`` if a corresponding :term:`view callable` cannot be |
CM |
129 |
found (when no :term:`view configuration` matches the combination |
|
130 |
of ``name`` / ``context`` / and ``request``). Additionally, this |
|
131 |
function will raise a :exc:`ValueError` if a view function is |
|
132 |
found and called but the view function's result does not have an |
|
133 |
``app_iter`` attribute. This function will return ``None`` if a |
|
134 |
corresponding view cannot be found. |
8b1f6e
|
135 |
|
99edc5
|
136 |
If ``secure`` is ``True``, and the view is protected by a permission, the |
CM |
137 |
permission will be checked before the view is invoked. If the permission |
|
138 |
check disallows view execution (based on the current :term:`authorization |
|
139 |
policy`), a :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be |
|
140 |
raised; its ``args`` attribute explains why the view access was |
b93d19
|
141 |
disallowed. |
CM |
142 |
|
885bfb
|
143 |
If ``secure`` is ``False``, no permission checking is done.""" |
CM |
144 |
iterable = render_view_to_iterable(context, request, name, secure) |
|
145 |
if iterable is None: |
|
146 |
return None |
0a8ea9
|
147 |
return b''.join(iterable) |
885bfb
|
148 |
|
197f0c
|
149 |
class view_config(object): |
8b1f6e
|
150 |
""" A function, class or method :term:`decorator` which allows a |
CM |
151 |
developer to create view registrations nearer to a :term:`view |
c1eb0c
|
152 |
callable` definition than use :term:`imperative |
8b1f6e
|
153 |
configuration` to do the same. |
5a7f9a
|
154 |
|
8b1f6e
|
155 |
For example, this code in a module ``views.py``:: |
5a7f9a
|
156 |
|
3e2f12
|
157 |
from resources import MyResource |
5a7f9a
|
158 |
|
3e2f12
|
159 |
@view_config(name='my_view', context=MyResource, permission='read', |
197f0c
|
160 |
route_name='site1') |
5a7f9a
|
161 |
def my_view(context, request): |
8b1f6e
|
162 |
return 'OK' |
5a7f9a
|
163 |
|
b93d19
|
164 |
Might replace the following call to the |
aff443
|
165 |
:meth:`pyramid.config.Configurator.add_view` method:: |
8b1f6e
|
166 |
|
CM |
167 |
import views |
3e2f12
|
168 |
from resources import MyResource |
CM |
169 |
config.add_view(views.my_view, context=MyResource, name='my_view', |
34606c
|
170 |
permission='read', route_name='site1') |
5a7f9a
|
171 |
|
197f0c
|
172 |
.. note: :class:`pyramid.view.view_config` is also importable, for |
CM |
173 |
backwards compatibility purposes, as the name |
|
174 |
:class:`pyramid.view.bfg_view`. |
|
175 |
|
8eb19b
|
176 |
:class:`pyramid.view.view_config` supports the following keyword |
e8c66a
|
177 |
arguments: ``context``, ``exception``, ``permission``, ``name``, |
cf7d8b
|
178 |
``request_type``, ``route_name``, ``request_method``, ``request_param``, |
CM |
179 |
``containment``, ``xhr``, ``accept``, ``header``, ``path_info``, |
602ac1
|
180 |
``custom_predicates``, ``decorator``, ``mapper``, ``http_cache``, |
769da1
|
181 |
``require_csrf``, ``match_param``, ``check_csrf``, ``physical_path``, and |
MM |
182 |
``view_options``. |
5a7f9a
|
183 |
|
cf7d8b
|
184 |
The meanings of these arguments are the same as the arguments passed to |
4c29ef
|
185 |
:meth:`pyramid.config.Configurator.add_view`. If any argument is left |
CM |
186 |
out, its default will be the equivalent ``add_view`` default. |
0b0e74
|
187 |
|
2c5f77
|
188 |
Two additional keyword arguments which will be passed to the |
T |
189 |
:term:`venusian` ``attach`` function are ``_depth`` and ``_category``. |
|
190 |
|
|
191 |
``_depth`` is provided for people who wish to reuse this class from another |
|
192 |
decorator. The default value is ``0`` and should be specified relative to |
|
193 |
the ``view_config`` invocation. It will be passed in to the |
|
194 |
:term:`venusian` ``attach`` function as the depth of the callstack when |
|
195 |
Venusian checks if the decorator is being used in a class or module |
|
196 |
context. It's not often used, but it can be useful in this circumstance. |
|
197 |
|
|
198 |
``_category`` sets the decorator category name. It can be useful in |
|
199 |
combination with the ``category`` argument of ``scan`` to control which |
|
200 |
views should be processed. |
|
201 |
|
|
202 |
See the :py:func:`venusian.attach` function in Venusian for more information. |
2033ee
|
203 |
|
SP |
204 |
.. seealso:: |
|
205 |
|
|
206 |
See also :ref:`mapping_views_using_a_decorator_section` for |
|
207 |
details about using :class:`pyramid.view.view_config`. |
a8d71c
|
208 |
|
2033ee
|
209 |
.. warning:: |
SP |
210 |
|
|
211 |
``view_config`` will work ONLY on module top level members |
|
212 |
because of the limitation of ``venusian.Scanner.scan``. |
a8f669
|
213 |
|
5a7f9a
|
214 |
""" |
e6fa66
|
215 |
venusian = venusian # for testing injection |
8ec8e2
|
216 |
def __init__(self, **settings): |
CM |
217 |
if 'for_' in settings: |
|
218 |
if settings.get('context') is None: |
|
219 |
settings['context'] = settings['for_'] |
|
220 |
self.__dict__.update(settings) |
5a7f9a
|
221 |
|
CM |
222 |
def __call__(self, wrapped): |
e6fa66
|
223 |
settings = self.__dict__.copy() |
ed1419
|
224 |
depth = settings.pop('_depth', 0) |
2c5f77
|
225 |
category = settings.pop('_category', 'pyramid') |
e6fa66
|
226 |
|
CM |
227 |
def callback(context, name, ob): |
b2c4e0
|
228 |
config = context.config.with_package(info.module) |
CM |
229 |
config.add_view(view=ob, **settings) |
e6fa66
|
230 |
|
2c5f77
|
231 |
info = self.venusian.attach(wrapped, callback, category=category, |
ed1419
|
232 |
depth=depth + 1) |
e6fa66
|
233 |
|
CM |
234 |
if info.scope == 'class': |
32418e
|
235 |
# if the decorator was attached to a method in a class, or |
CM |
236 |
# otherwise executed at class scope, we need to set an |
|
237 |
# 'attr' into the settings if one isn't already in there |
4c29ef
|
238 |
if settings.get('attr') is None: |
e6fa66
|
239 |
settings['attr'] = wrapped.__name__ |
89968d
|
240 |
|
b2c4e0
|
241 |
settings['_info'] = info.codeinfo # fbo "action_method" |
c89bcb
|
242 |
return wrapped |
5a7f9a
|
243 |
|
33516a
|
244 |
bfg_view = view_config # bw compat (forever) |
5f4780
|
245 |
|
914abe
|
246 |
class view_defaults(view_config): |
4375cf
|
247 |
""" A class :term:`decorator` which, when applied to a class, will |
CM |
248 |
provide defaults for all view configurations that use the class. This |
|
249 |
decorator accepts all the arguments accepted by |
aaedf5
|
250 |
:meth:`pyramid.view.view_config`, and each has the same meaning. |
4375cf
|
251 |
|
CM |
252 |
See :ref:`view_defaults` for more information. |
|
253 |
""" |
a8f669
|
254 |
|
914abe
|
255 |
def __call__(self, wrapped): |
CM |
256 |
wrapped.__view_defaults__ = self.__dict__.copy() |
|
257 |
return wrapped |
|
258 |
|
d96ff9
|
259 |
class AppendSlashNotFoundViewFactory(object): |
CM |
260 |
""" There can only be one :term:`Not Found view` in any |
fd5ae9
|
261 |
:app:`Pyramid` application. Even if you use |
c81aad
|
262 |
:func:`pyramid.view.append_slash_notfound_view` as the Not |
fd5ae9
|
263 |
Found view, :app:`Pyramid` still must generate a ``404 Not |
d96ff9
|
264 |
Found`` response when it cannot redirect to a slash-appended URL; |
CM |
265 |
this not found response will be visible to site users. |
|
266 |
|
|
267 |
If you don't care what this 404 response looks like, and you only |
|
268 |
need redirections to slash-appended route URLs, you may use the |
c81aad
|
269 |
:func:`pyramid.view.append_slash_notfound_view` object as the |
d96ff9
|
270 |
Not Found view. However, if you wish to use a *custom* notfound |
CM |
271 |
view callable when a URL cannot be redirected to a slash-appended |
|
272 |
URL, you may wish to use an instance of this class as the Not |
|
273 |
Found view, supplying a :term:`view callable` to be used as the |
|
274 |
custom notfound view as the first argument to its constructor. |
|
275 |
For instance: |
|
276 |
|
|
277 |
.. code-block:: python |
|
278 |
|
99edc5
|
279 |
from pyramid.httpexceptions import HTTPNotFound |
c81aad
|
280 |
from pyramid.view import AppendSlashNotFoundViewFactory |
d96ff9
|
281 |
|
1b4360
|
282 |
def notfound_view(context, request): return HTTPNotFound('nope') |
d96ff9
|
283 |
|
CM |
284 |
custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view) |
a7e625
|
285 |
config.add_view(custom_append_slash, context=HTTPNotFound) |
d96ff9
|
286 |
|
CM |
287 |
The ``notfound_view`` supplied must adhere to the two-argument |
|
288 |
view callable calling convention of ``(context, request)`` |
|
289 |
(``context`` will be the exception object). |
|
290 |
|
2033ee
|
291 |
.. deprecated:: 1.3 |
SP |
292 |
|
d96ff9
|
293 |
""" |
12b6f5
|
294 |
def __init__(self, notfound_view=None, redirect_class=HTTPFound): |
d96ff9
|
295 |
if notfound_view is None: |
CM |
296 |
notfound_view = default_exceptionresponse_view |
|
297 |
self.notfound_view = notfound_view |
12b6f5
|
298 |
self.redirect_class = redirect_class |
d96ff9
|
299 |
|
CM |
300 |
def __call__(self, context, request): |
0db4a1
|
301 |
path = decode_path_info(request.environ['PATH_INFO'] or '/') |
30e64f
|
302 |
registry = request.registry |
d96ff9
|
303 |
mapper = registry.queryUtility(IRoutesMapper) |
CM |
304 |
if mapper is not None and not path.endswith('/'): |
|
305 |
slashpath = path + '/' |
|
306 |
for route in mapper.get_routes(): |
|
307 |
if route.match(slashpath) is not None: |
b596e1
|
308 |
qs = request.query_string |
CM |
309 |
if qs: |
0db4a1
|
310 |
qs = '?' + qs |
17279b
|
311 |
return self.redirect_class(location=request.path + '/' + qs) |
d96ff9
|
312 |
return self.notfound_view(context, request) |
a9454c
|
313 |
|
d96ff9
|
314 |
append_slash_notfound_view = AppendSlashNotFoundViewFactory() |
CM |
315 |
append_slash_notfound_view.__doc__ = """\ |
|
316 |
For behavior like Django's ``APPEND_SLASH=True``, use this view as the |
|
317 |
:term:`Not Found view` in your application. |
a9454c
|
318 |
|
87a85f
|
319 |
When this view is the Not Found view (indicating that no view was found), and |
CM |
320 |
any routes have been defined in the configuration of your application, if the |
|
321 |
value of the ``PATH_INFO`` WSGI environment variable does not already end in |
|
322 |
a slash, and if the value of ``PATH_INFO`` *plus* a slash matches any route's |
|
323 |
path, do an HTTP redirect to the slash-appended PATH_INFO. Note that this |
|
324 |
will *lose* ``POST`` data information (turning it into a GET), so you |
|
325 |
shouldn't rely on this to redirect POST requests. Note also that static |
|
326 |
routes are not considered when attempting to find a matching route. |
d66bfb
|
327 |
|
c1eb0c
|
328 |
Use the :meth:`pyramid.config.Configurator.add_view` method to configure this |
CM |
329 |
view as the Not Found view:: |
8b1f6e
|
330 |
|
99edc5
|
331 |
from pyramid.httpexceptions import HTTPNotFound |
c81aad
|
332 |
from pyramid.view import append_slash_notfound_view |
a7e625
|
333 |
config.add_view(append_slash_notfound_view, context=HTTPNotFound) |
8b1f6e
|
334 |
|
2033ee
|
335 |
.. deprecated:: 1.3 |
d66bfb
|
336 |
|
d96ff9
|
337 |
""" |
d66bfb
|
338 |
|
0db4a1
|
339 |
class notfound_view_config(object): |
CM |
340 |
""" |
05e928
|
341 |
.. versionadded:: 1.3 |
0db4a1
|
342 |
|
CM |
343 |
An analogue of :class:`pyramid.view.view_config` which registers a |
e8c66a
|
344 |
:term:`Not Found View` using |
MM |
345 |
:meth:`pyramid.config.Configurator.add_notfound_view`. |
0db4a1
|
346 |
|
e450ca
|
347 |
The ``notfound_view_config`` constructor accepts most of the same arguments |
0db4a1
|
348 |
as the constructor of :class:`pyramid.view.view_config`. It can be used |
CM |
349 |
in the same places, and behaves in largely the same way, except it always |
8ec8e2
|
350 |
registers a not found exception view instead of a 'normal' view. |
0db4a1
|
351 |
|
CM |
352 |
Example: |
|
353 |
|
|
354 |
.. code-block:: python |
|
355 |
|
|
356 |
from pyramid.view import notfound_view_config |
|
357 |
from pyramid.response import Response |
a8f669
|
358 |
|
15e3b1
|
359 |
@notfound_view_config() |
0db4a1
|
360 |
def notfound(request): |
c898dd
|
361 |
return Response('Not found!', status='404 Not Found') |
0db4a1
|
362 |
|
CM |
363 |
All arguments except ``append_slash`` have the same meaning as |
|
364 |
:meth:`pyramid.view.view_config` and each predicate |
|
365 |
argument restricts the set of circumstances under which this notfound |
|
366 |
view will be invoked. |
|
367 |
|
cec2b0
|
368 |
If ``append_slash`` is ``True``, when the Not Found View is invoked, and |
0db4a1
|
369 |
the current path info does not end in a slash, the notfound logic will |
CM |
370 |
attempt to find a :term:`route` that matches the request's path info |
|
371 |
suffixed with a slash. If such a route exists, Pyramid will issue a |
|
372 |
redirect to the URL implied by the route; if it does not, Pyramid will |
|
373 |
return the result of the view callable provided as ``view``, as normal. |
|
374 |
|
24358c
|
375 |
If the argument provided as ``append_slash`` is not a boolean but |
CM |
376 |
instead implements :class:`~pyramid.interfaces.IResponse`, the |
|
377 |
append_slash logic will behave as if ``append_slash=True`` was passed, |
|
378 |
but the provided class will be used as the response class instead of |
|
379 |
the default :class:`~pyramid.httpexceptions.HTTPFound` response class |
|
380 |
when a redirect is performed. For example: |
|
381 |
|
|
382 |
.. code-block:: python |
|
383 |
|
|
384 |
from pyramid.httpexceptions import ( |
|
385 |
HTTPMovedPermanently, |
|
386 |
HTTPNotFound |
|
387 |
) |
|
388 |
|
|
389 |
@notfound_view_config(append_slash=HTTPMovedPermanently) |
|
390 |
def aview(request): |
|
391 |
return HTTPNotFound('not found') |
|
392 |
|
|
393 |
The above means that a redirect to a slash-appended route will be |
|
394 |
attempted, but instead of :class:`~pyramid.httpexceptions.HTTPFound` |
|
395 |
being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will |
|
396 |
be used` for the redirect response if a slash-appended route is found. |
|
397 |
|
|
398 |
.. versionchanged:: 1.6 |
|
399 |
|
0db4a1
|
400 |
See :ref:`changing_the_notfound_view` for detailed usage information. |
CM |
401 |
|
|
402 |
""" |
|
403 |
|
|
404 |
venusian = venusian |
|
405 |
|
8ec8e2
|
406 |
def __init__(self, **settings): |
CM |
407 |
self.__dict__.update(settings) |
0db4a1
|
408 |
|
CM |
409 |
def __call__(self, wrapped): |
|
410 |
settings = self.__dict__.copy() |
|
411 |
|
|
412 |
def callback(context, name, ob): |
|
413 |
config = context.config.with_package(info.module) |
|
414 |
config.add_notfound_view(view=ob, **settings) |
|
415 |
|
|
416 |
info = self.venusian.attach(wrapped, callback, category='pyramid') |
|
417 |
|
|
418 |
if info.scope == 'class': |
|
419 |
# if the decorator was attached to a method in a class, or |
|
420 |
# otherwise executed at class scope, we need to set an |
|
421 |
# 'attr' into the settings if one isn't already in there |
|
422 |
if settings.get('attr') is None: |
|
423 |
settings['attr'] = wrapped.__name__ |
|
424 |
|
|
425 |
settings['_info'] = info.codeinfo # fbo "action_method" |
|
426 |
return wrapped |
|
427 |
|
a7fe30
|
428 |
class forbidden_view_config(object): |
CM |
429 |
""" |
05e928
|
430 |
.. versionadded:: 1.3 |
a7fe30
|
431 |
|
CM |
432 |
An analogue of :class:`pyramid.view.view_config` which registers a |
e8c66a
|
433 |
:term:`forbidden view` using |
MM |
434 |
:meth:`pyramid.config.Configurator.add_forbidden_view`. |
a7fe30
|
435 |
|
CM |
436 |
The forbidden_view_config constructor accepts most of the same arguments |
|
437 |
as the constructor of :class:`pyramid.view.view_config`. It can be used |
|
438 |
in the same places, and behaves in largely the same way, except it always |
8ec8e2
|
439 |
registers a forbidden exception view instead of a 'normal' view. |
a7fe30
|
440 |
|
CM |
441 |
Example: |
|
442 |
|
|
443 |
.. code-block:: python |
|
444 |
|
|
445 |
from pyramid.view import forbidden_view_config |
|
446 |
from pyramid.response import Response |
a8f669
|
447 |
|
15e3b1
|
448 |
@forbidden_view_config() |
bbd3ab
|
449 |
def forbidden(request): |
59e7cc
|
450 |
return Response('You are not allowed', status='403 Forbidden') |
a7fe30
|
451 |
|
8ec8e2
|
452 |
All arguments passed to this function have the same meaning as |
CM |
453 |
:meth:`pyramid.view.view_config` and each predicate argument restricts |
|
454 |
the set of circumstances under which this notfound view will be invoked. |
a7fe30
|
455 |
|
CM |
456 |
See :ref:`changing_the_forbidden_view` for detailed usage information. |
|
457 |
|
|
458 |
""" |
|
459 |
|
|
460 |
venusian = venusian |
|
461 |
|
8ec8e2
|
462 |
def __init__(self, **settings): |
CM |
463 |
self.__dict__.update(settings) |
a7fe30
|
464 |
|
CM |
465 |
def __call__(self, wrapped): |
|
466 |
settings = self.__dict__.copy() |
|
467 |
|
|
468 |
def callback(context, name, ob): |
|
469 |
config = context.config.with_package(info.module) |
|
470 |
config.add_forbidden_view(view=ob, **settings) |
|
471 |
|
|
472 |
info = self.venusian.attach(wrapped, callback, category='pyramid') |
|
473 |
|
|
474 |
if info.scope == 'class': |
|
475 |
# if the decorator was attached to a method in a class, or |
|
476 |
# otherwise executed at class scope, we need to set an |
|
477 |
# 'attr' into the settings if one isn't already in there |
|
478 |
if settings.get('attr') is None: |
|
479 |
settings['attr'] = wrapped.__name__ |
|
480 |
|
|
481 |
settings['_info'] = info.codeinfo # fbo "action_method" |
|
482 |
return wrapped |
a8f669
|
483 |
|
93c94b
|
484 |
class exception_view_config(object): |
AL |
485 |
""" |
|
486 |
.. versionadded:: 1.8 |
|
487 |
|
|
488 |
An analogue of :class:`pyramid.view.view_config` which registers an |
e8c66a
|
489 |
:term:`exception view` using |
MM |
490 |
:meth:`pyramid.config.Configurator.add_exception_view`. |
93c94b
|
491 |
|
e8c66a
|
492 |
The ``exception_view_config`` constructor requires an exception context, |
MM |
493 |
and additionally accepts most of the same arguments as the constructor of |
93c94b
|
494 |
:class:`pyramid.view.view_config`. It can be used in the same places, |
e8c66a
|
495 |
and behaves in largely the same way, except it always registers an |
160aab
|
496 |
exception view instead of a "normal" view that dispatches on the request |
e8c66a
|
497 |
:term:`context`. |
93c94b
|
498 |
|
AL |
499 |
Example: |
|
500 |
|
|
501 |
.. code-block:: python |
|
502 |
|
|
503 |
from pyramid.view import exception_view_config |
|
504 |
from pyramid.response import Response |
|
505 |
|
e8c66a
|
506 |
@exception_view_config(ValueError, renderer='json') |
MM |
507 |
def error_view(request): |
|
508 |
return {'error': str(request.exception)} |
93c94b
|
509 |
|
AL |
510 |
All arguments passed to this function have the same meaning as |
160aab
|
511 |
:meth:`pyramid.view.view_config`, and each predicate argument restricts |
93c94b
|
512 |
the set of circumstances under which this exception view will be invoked. |
e8c66a
|
513 |
|
93c94b
|
514 |
""" |
74842a
|
515 |
venusian = venusian |
93c94b
|
516 |
|
e8c66a
|
517 |
def __init__(self, *args, **settings): |
MM |
518 |
if 'context' not in settings and len(args) > 0: |
|
519 |
exception, args = args[0], args[1:] |
|
520 |
settings['context'] = exception |
|
521 |
if len(args) > 0: |
|
522 |
raise ConfigurationError('unknown positional arguments') |
93c94b
|
523 |
self.__dict__.update(settings) |
AL |
524 |
|
|
525 |
def __call__(self, wrapped): |
|
526 |
settings = self.__dict__.copy() |
|
527 |
|
|
528 |
def callback(context, name, ob): |
|
529 |
config = context.config.with_package(info.module) |
|
530 |
config.add_exception_view(view=ob, **settings) |
|
531 |
|
|
532 |
info = self.venusian.attach(wrapped, callback, category='pyramid') |
|
533 |
|
|
534 |
if info.scope == 'class': |
|
535 |
# if the decorator was attached to a method in a class, or |
|
536 |
# otherwise executed at class scope, we need to set an |
160aab
|
537 |
# 'attr' in the settings if one isn't already in there |
93c94b
|
538 |
if settings.get('attr') is None: |
AL |
539 |
settings['attr'] = wrapped.__name__ |
|
540 |
|
|
541 |
settings['_info'] = info.codeinfo # fbo "action_method" |
|
542 |
return wrapped |
|
543 |
|
17c7f4
|
544 |
def _find_views( |
CM |
545 |
registry, |
|
546 |
request_iface, |
|
547 |
context_iface, |
|
548 |
view_name, |
|
549 |
view_types=None, |
|
550 |
view_classifier=None, |
|
551 |
): |
03c11e
|
552 |
if view_types is None: |
17c7f4
|
553 |
view_types = (IView, ISecuredView, IMultiView) |
03c11e
|
554 |
if view_classifier is None: |
17c7f4
|
555 |
view_classifier = IViewClassifier |
fb2824
|
556 |
registered = registry.adapters.registered |
c15cbc
|
557 |
cache = registry._view_lookup_cache |
CM |
558 |
views = cache.get((request_iface, context_iface, view_name)) |
|
559 |
if views is None: |
|
560 |
views = [] |
|
561 |
for req_type, ctx_type in itertools.product( |
|
562 |
request_iface.__sro__, context_iface.__sro__ |
|
563 |
): |
17c7f4
|
564 |
source_ifaces = (view_classifier, req_type, ctx_type) |
c15cbc
|
565 |
for view_type in view_types: |
CM |
566 |
view_callable = registered( |
|
567 |
source_ifaces, |
|
568 |
view_type, |
|
569 |
name=view_name, |
|
570 |
) |
|
571 |
if view_callable is not None: |
|
572 |
views.append(view_callable) |
|
573 |
if views: |
|
574 |
# do not cache view lookup misses. rationale: dont allow cache to |
|
575 |
# grow without bound if somebody tries to hit the site with many |
|
576 |
# missing URLs. we could use an LRU cache instead, but then |
|
577 |
# purposeful misses by an attacker would just blow out the cache |
|
578 |
# anyway. downside: misses will almost always consume more CPU than |
|
579 |
# hits in steady state. |
|
580 |
with registry._lock: |
|
581 |
cache[(request_iface, context_iface, view_name)] = views |
849196
|
582 |
|
99bc0c
|
583 |
return views |
eb3ac8
|
584 |
|
849196
|
585 |
def _call_view( |
CM |
586 |
registry, |
|
587 |
request, |
|
588 |
context, |
|
589 |
context_iface, |
|
590 |
view_name, |
17c7f4
|
591 |
view_types=None, |
CM |
592 |
view_classifier=None, |
849196
|
593 |
secure=True, |
17c7f4
|
594 |
request_iface=None, |
849196
|
595 |
): |
17c7f4
|
596 |
if request_iface is None: |
CM |
597 |
request_iface = getattr(request, 'request_iface', IRequest) |
eb3ac8
|
598 |
view_callables = _find_views( |
CM |
599 |
registry, |
17c7f4
|
600 |
request_iface, |
eb3ac8
|
601 |
context_iface, |
CM |
602 |
view_name, |
17c7f4
|
603 |
view_types=view_types, |
CM |
604 |
view_classifier=view_classifier, |
eb3ac8
|
605 |
) |
CM |
606 |
|
|
607 |
pme = None |
|
608 |
response = None |
|
609 |
|
|
610 |
for view_callable in view_callables: |
|
611 |
# look for views that meet the predicate criteria |
|
612 |
try: |
849196
|
613 |
if not secure: |
CM |
614 |
# the view will have a __call_permissive__ attribute if it's |
|
615 |
# secured; otherwise it won't. |
|
616 |
view_callable = getattr( |
|
617 |
view_callable, |
|
618 |
'__call_permissive__', |
|
619 |
view_callable |
|
620 |
) |
|
621 |
|
|
622 |
# if this view is secured, it will raise a Forbidden |
|
623 |
# appropriately if the executing user does not have the proper |
|
624 |
# permission |
eb3ac8
|
625 |
response = view_callable(context, request) |
CM |
626 |
return response |
|
627 |
except PredicateMismatch as _pme: |
|
628 |
pme = _pme |
|
629 |
|
|
630 |
if pme is not None: |
|
631 |
raise pme |
|
632 |
|
|
633 |
return response |
531428
|
634 |
|
CM |
635 |
class ViewMethodsMixin(object): |
|
636 |
""" Request methods mixin for BaseRequest having to do with executing |
|
637 |
views """ |
|
638 |
def invoke_exception_view( |
|
639 |
self, |
e40ef2
|
640 |
exc_info=None, |
531428
|
641 |
request=None, |
6f2f04
|
642 |
secure=True, |
MM |
643 |
reraise=False, |
|
644 |
): |
e40ef2
|
645 |
""" Executes an exception view related to the request it's called upon. |
CM |
646 |
The arguments it takes are these: |
|
647 |
|
|
648 |
``exc_info`` |
|
649 |
|
|
650 |
If provided, should be a 3-tuple in the form provided by |
252fa5
|
651 |
``sys.exc_info()``. If not provided, |
CM |
652 |
``sys.exc_info()`` will be called to obtain the current |
e40ef2
|
653 |
interpreter exception information. Default: ``None``. |
CM |
654 |
|
|
655 |
``request`` |
|
656 |
|
|
657 |
If the request to be used is not the same one as the instance that |
|
658 |
this method is called upon, it may be passed here. Default: |
|
659 |
``None``. |
|
660 |
|
|
661 |
``secure`` |
|
662 |
|
|
663 |
If the exception view should not be rendered if the current user |
|
664 |
does not have the appropriate permission, this should be ``True``. |
|
665 |
Default: ``True``. |
|
666 |
|
6f2f04
|
667 |
``reraise`` |
e40ef2
|
668 |
|
6f2f04
|
669 |
A boolean indicating whether the original error should be reraised |
MM |
670 |
if a :term:`response` object could not be created. If ``False`` |
|
671 |
then an :class:`pyramid.httpexceptions.HTTPNotFound`` exception |
|
672 |
will be raised. Default: ``False``. |
e40ef2
|
673 |
|
3b886e
|
674 |
If a response is generated then ``request.exception`` and |
MM |
675 |
``request.exc_info`` will be left at the values used to render the |
|
676 |
response. Otherwise the previous values for ``request.exception`` and |
|
677 |
``request.exc_info`` will be restored. |
|
678 |
|
daf06d
|
679 |
.. versionadded:: 1.7 |
MM |
680 |
|
e2e51b
|
681 |
.. versionchanged:: 1.9 |
MM |
682 |
The ``request.exception`` and ``request.exc_info`` properties will |
|
683 |
reflect the exception used to render the response where previously |
|
684 |
they were reset to the values prior to invoking the method. |
6f2f04
|
685 |
|
MM |
686 |
Also added the ``reraise`` argument. |
e2e51b
|
687 |
|
3b886e
|
688 |
""" |
531428
|
689 |
if request is None: |
CM |
690 |
request = self |
|
691 |
registry = getattr(request, 'registry', None) |
|
692 |
if registry is None: |
|
693 |
registry = get_current_registry() |
dda9fa
|
694 |
|
BJR |
695 |
if registry is None: |
|
696 |
raise RuntimeError("Unable to retrieve registry") |
|
697 |
|
e40ef2
|
698 |
if exc_info is None: |
252fa5
|
699 |
exc_info = sys.exc_info() |
dda9fa
|
700 |
|
d52257
|
701 |
exc = exc_info[1] |
ca529f
|
702 |
attrs = request.__dict__ |
d52257
|
703 |
context_iface = providedBy(exc) |
19016b
|
704 |
|
MM |
705 |
# clear old generated request.response, if any; it may |
|
706 |
# have been mutated by the view, and its state is not |
|
707 |
# sane (e.g. caching headers) |
3b886e
|
708 |
with hide_attrs(request, 'response', 'exc_info', 'exception'): |
d52257
|
709 |
attrs['exception'] = exc |
68b303
|
710 |
attrs['exc_info'] = exc_info |
19016b
|
711 |
# we use .get instead of .__getitem__ below due to |
MM |
712 |
# https://github.com/Pylons/pyramid/issues/700 |
|
713 |
request_iface = attrs.get('request_iface', IRequest) |
dda9fa
|
714 |
|
4f6635
|
715 |
manager.push({'request': request, 'registry': registry}) |
dda9fa
|
716 |
|
4f6635
|
717 |
try: |
dda9fa
|
718 |
response = _call_view( |
BJR |
719 |
registry, |
|
720 |
request, |
|
721 |
exc, |
|
722 |
context_iface, |
|
723 |
'', |
|
724 |
view_types=None, |
|
725 |
view_classifier=IExceptionViewClassifier, |
|
726 |
secure=secure, |
|
727 |
request_iface=request_iface.combined, |
|
728 |
) |
6f2f04
|
729 |
except: |
MM |
730 |
if reraise: |
|
731 |
reraise_(*exc_info) |
|
732 |
raise |
dda9fa
|
733 |
finally: |
BJR |
734 |
manager.pop() |
3b886e
|
735 |
|
MM |
736 |
if response is None: |
6f2f04
|
737 |
if reraise: |
MM |
738 |
reraise_(*exc_info) |
3b886e
|
739 |
raise HTTPNotFound |
MM |
740 |
|
|
741 |
# successful response, overwrite exception/exc_info |
|
742 |
attrs['exception'] = exc |
|
743 |
attrs['exc_info'] = exc_info |
|
744 |
return response |