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