commit | author | age
|
52fde9
|
1 |
import functools |
5bf23f
|
2 |
import inspect |
d0bd5f
|
3 |
import posixpath |
392937
|
4 |
import operator |
2f665d
|
5 |
import os |
b01f1d
|
6 |
import warnings |
5bf23f
|
7 |
|
30f79d
|
8 |
from webob.acceptparse import Accept |
0c29cf
|
9 |
from zope.interface import Interface, implementedBy, implementer |
1b2651
|
10 |
from zope.interface.interfaces import IInterface |
5bf23f
|
11 |
|
ee117e
|
12 |
from pyramid.interfaces import ( |
121f45
|
13 |
IAcceptOrder, |
ee117e
|
14 |
IExceptionViewClassifier, |
e8c66a
|
15 |
IException, |
ee117e
|
16 |
IMultiView, |
d0bd5f
|
17 |
IPackageOverrides, |
ee117e
|
18 |
IRendererFactory, |
CM |
19 |
IRequest, |
|
20 |
IResponse, |
|
21 |
IRouteRequest, |
|
22 |
ISecuredView, |
|
23 |
IStaticURLInfo, |
|
24 |
IView, |
|
25 |
IViewClassifier, |
1d5a99
|
26 |
IViewDerivers, |
007600
|
27 |
IViewDeriverInfo, |
ee117e
|
28 |
IViewMapperFactory, |
CM |
29 |
PHASE1_CONFIG, |
0c29cf
|
30 |
) |
5bf23f
|
31 |
|
1b2651
|
32 |
from pyramid import renderers |
ee117e
|
33 |
|
5e3439
|
34 |
from pyramid.asset import resolve_asset_spec |
ee117e
|
35 |
from pyramid.compat import ( |
CM |
36 |
string_types, |
|
37 |
urlparse, |
|
38 |
url_quote, |
5c5857
|
39 |
WIN, |
bc26de
|
40 |
is_nonstr_iter, |
0c29cf
|
41 |
) |
007600
|
42 |
|
MM |
43 |
from pyramid.decorator import reify |
ee117e
|
44 |
|
0c29cf
|
45 |
from pyramid.exceptions import ConfigurationError, PredicateMismatch |
ee117e
|
46 |
|
CM |
47 |
from pyramid.httpexceptions import ( |
|
48 |
HTTPForbidden, |
|
49 |
HTTPNotFound, |
f10d1e
|
50 |
default_exceptionresponse_view, |
0c29cf
|
51 |
) |
ee117e
|
52 |
|
52fde9
|
53 |
from pyramid.registry import Deferred |
d98612
|
54 |
|
5bf23f
|
55 |
from pyramid.security import NO_PERMISSION_REQUIRED |
53d9d4
|
56 |
from pyramid.static import static_view |
0db4a1
|
57 |
|
af3134
|
58 |
from pyramid.url import parse_url_overrides |
0db4a1
|
59 |
|
bf40a3
|
60 |
from pyramid.view import AppendSlashNotFoundViewFactory |
0db4a1
|
61 |
|
0c29cf
|
62 |
from pyramid.util import as_sorted_tuple, TopologicalSorter |
5bf23f
|
63 |
|
c7974f
|
64 |
import pyramid.predicates |
ecc9d8
|
65 |
import pyramid.viewderivers |
c2c589
|
66 |
|
ecc9d8
|
67 |
from pyramid.viewderivers import ( |
a3db3c
|
68 |
INGRESS, |
c231d8
|
69 |
VIEW, |
c2c589
|
70 |
preserve_view_attrs, |
CD |
71 |
view_description, |
|
72 |
requestonly, |
|
73 |
DefaultViewMapper, |
bd6798
|
74 |
wraps_view, |
c2c589
|
75 |
) |
a00621
|
76 |
|
d579f2
|
77 |
from pyramid.config.actions import action_method |
MM |
78 |
from pyramid.config.predicates import ( |
ee117e
|
79 |
DEFAULT_PHASH, |
CM |
80 |
MAX_ORDER, |
4a9f4f
|
81 |
normalize_accept_offer, |
52fde9
|
82 |
predvalseq, |
f2294f
|
83 |
sort_accept_offers, |
0c29cf
|
84 |
) |
5bf23f
|
85 |
|
e6c2d2
|
86 |
urljoin = urlparse.urljoin |
16a5d3
|
87 |
url_parse = urlparse.urlparse |
bf40a3
|
88 |
|
0c29cf
|
89 |
DefaultViewMapper = DefaultViewMapper # bw-compat |
MM |
90 |
preserve_view_attrs = preserve_view_attrs # bw-compat |
|
91 |
requestonly = requestonly # bw-compat |
|
92 |
view_description = view_description # bw-compat |
|
93 |
|
5bf23f
|
94 |
|
3b7334
|
95 |
@implementer(IMultiView) |
5bf23f
|
96 |
class MultiView(object): |
CM |
97 |
def __init__(self, name): |
|
98 |
self.name = name |
|
99 |
self.media_views = {} |
|
100 |
self.views = [] |
|
101 |
self.accepts = [] |
|
102 |
|
ba2a3f
|
103 |
def __discriminator__(self, context, request): |
CM |
104 |
# used by introspection systems like so: |
|
105 |
# view = adapters.lookup(....) |
|
106 |
# view.__discriminator__(context, request) -> view's discriminator |
|
107 |
# so that superdynamic systems can feed the discriminator to |
|
108 |
# the introspection system to get info about it |
|
109 |
view = self.match(context, request) |
|
110 |
return view.__discriminator__(context, request) |
|
111 |
|
30f79d
|
112 |
def add(self, view, order, phash=None, accept=None, accept_order=None): |
5bf23f
|
113 |
if phash is not None: |
CM |
114 |
for i, (s, v, h) in enumerate(list(self.views)): |
|
115 |
if phash == h: |
|
116 |
self.views[i] = (order, view, phash) |
|
117 |
return |
|
118 |
|
4a9f4f
|
119 |
if accept is None or '*' in accept: |
5bf23f
|
120 |
self.views.append((order, view, phash)) |
392937
|
121 |
self.views.sort(key=operator.itemgetter(0)) |
5bf23f
|
122 |
else: |
CM |
123 |
subset = self.media_views.setdefault(accept, []) |
8ea3f3
|
124 |
for i, (s, v, h) in enumerate(list(subset)): |
f4b7fd
|
125 |
if phash == h: |
8ea3f3
|
126 |
subset[i] = (order, view, phash) |
DN |
127 |
return |
|
128 |
else: |
|
129 |
subset.append((order, view, phash)) |
a9289d
|
130 |
subset.sort(key=operator.itemgetter(0)) |
121f45
|
131 |
# dedupe accepts and sort appropriately |
5bf23f
|
132 |
accepts = set(self.accepts) |
CM |
133 |
accepts.add(accept) |
30f79d
|
134 |
if accept_order: |
bd82c8
|
135 |
accept_order = [v for _, v in accept_order.sorted()] |
30f79d
|
136 |
self.accepts = sort_accept_offers(accepts, accept_order) |
5bf23f
|
137 |
|
CM |
138 |
def get_views(self, request): |
|
139 |
if self.accepts and hasattr(request, 'accept'): |
|
140 |
views = [] |
121f45
|
141 |
for offer, _ in request.accept.acceptable_offers(self.accepts): |
MM |
142 |
views.extend(self.media_views[offer]) |
5bf23f
|
143 |
views.extend(self.views) |
CM |
144 |
return views |
|
145 |
return self.views |
|
146 |
|
|
147 |
def match(self, context, request): |
|
148 |
for order, view, phash in self.get_views(request): |
|
149 |
if not hasattr(view, '__predicated__'): |
|
150 |
return view |
|
151 |
if view.__predicated__(context, request): |
|
152 |
return view |
|
153 |
raise PredicateMismatch(self.name) |
|
154 |
|
|
155 |
def __permitted__(self, context, request): |
|
156 |
view = self.match(context, request) |
|
157 |
if hasattr(view, '__permitted__'): |
|
158 |
return view.__permitted__(context, request) |
|
159 |
return True |
|
160 |
|
|
161 |
def __call_permissive__(self, context, request): |
|
162 |
view = self.match(context, request) |
|
163 |
view = getattr(view, '__call_permissive__', view) |
|
164 |
return view(context, request) |
|
165 |
|
|
166 |
def __call__(self, context, request): |
|
167 |
for order, view, phash in self.get_views(request): |
|
168 |
try: |
|
169 |
return view(context, request) |
|
170 |
except PredicateMismatch: |
|
171 |
continue |
|
172 |
raise PredicateMismatch(self.name) |
52fde9
|
173 |
|
0c29cf
|
174 |
|
52fde9
|
175 |
def attr_wrapped_view(view, info): |
0c29cf
|
176 |
accept, order, phash = ( |
MM |
177 |
info.options.get('accept', None), |
|
178 |
getattr(info, 'order', MAX_ORDER), |
|
179 |
getattr(info, 'phash', DEFAULT_PHASH), |
|
180 |
) |
52fde9
|
181 |
# this is a little silly but we don't want to decorate the original |
MM |
182 |
# function with attributes that indicate accept, order, and phash, |
|
183 |
# so we use a wrapper |
0c29cf
|
184 |
if (accept is None) and (order == MAX_ORDER) and (phash == DEFAULT_PHASH): |
MM |
185 |
return view # defaults |
|
186 |
|
52fde9
|
187 |
def attr_view(context, request): |
MM |
188 |
return view(context, request) |
0c29cf
|
189 |
|
52fde9
|
190 |
attr_view.__accept__ = accept |
MM |
191 |
attr_view.__order__ = order |
|
192 |
attr_view.__phash__ = phash |
|
193 |
attr_view.__view_attr__ = info.options.get('attr') |
|
194 |
attr_view.__permission__ = info.options.get('permission') |
|
195 |
return attr_view |
|
196 |
|
0c29cf
|
197 |
|
52fde9
|
198 |
attr_wrapped_view.options = ('accept', 'attr', 'permission') |
0c29cf
|
199 |
|
52fde9
|
200 |
|
MM |
201 |
def predicated_view(view, info): |
|
202 |
preds = info.predicates |
|
203 |
if not preds: |
|
204 |
return view |
0c29cf
|
205 |
|
52fde9
|
206 |
def predicate_wrapper(context, request): |
MM |
207 |
for predicate in preds: |
|
208 |
if not predicate(context, request): |
|
209 |
view_name = getattr(view, '__name__', view) |
|
210 |
raise PredicateMismatch( |
0c29cf
|
211 |
'predicate mismatch for view %s (%s)' |
MM |
212 |
% (view_name, predicate.text()) |
|
213 |
) |
52fde9
|
214 |
return view(context, request) |
0c29cf
|
215 |
|
52fde9
|
216 |
def checker(context, request): |
0c29cf
|
217 |
return all((predicate(context, request) for predicate in preds)) |
MM |
218 |
|
52fde9
|
219 |
predicate_wrapper.__predicated__ = checker |
MM |
220 |
predicate_wrapper.__predicates__ = preds |
|
221 |
return predicate_wrapper |
0c29cf
|
222 |
|
52fde9
|
223 |
|
MM |
224 |
def viewdefaults(wrapped): |
|
225 |
""" Decorator for add_view-like methods which takes into account |
|
226 |
__view_defaults__ attached to view it is passed. Not a documented API but |
|
227 |
used by some external systems.""" |
0c29cf
|
228 |
|
52fde9
|
229 |
def wrapper(self, *arg, **kw): |
MM |
230 |
defaults = {} |
|
231 |
if arg: |
|
232 |
view = arg[0] |
|
233 |
else: |
|
234 |
view = kw.get('view') |
|
235 |
view = self.maybe_dotted(view) |
|
236 |
if inspect.isclass(view): |
|
237 |
defaults = getattr(view, '__view_defaults__', {}).copy() |
|
238 |
if '_backframes' not in kw: |
0c29cf
|
239 |
kw['_backframes'] = 1 # for action_method |
52fde9
|
240 |
defaults.update(kw) |
MM |
241 |
return wrapped(self, *arg, **defaults) |
0c29cf
|
242 |
|
52fde9
|
243 |
return functools.wraps(wrapped)(wrapper) |
0c29cf
|
244 |
|
30f79d
|
245 |
|
MM |
246 |
def combine_decorators(*decorators): |
|
247 |
def decorated(view_callable): |
|
248 |
# reversed() allows a more natural ordering in the api |
|
249 |
for decorator in reversed(decorators): |
|
250 |
view_callable = decorator(view_callable) |
|
251 |
return view_callable |
0c29cf
|
252 |
|
30f79d
|
253 |
return decorated |
0c29cf
|
254 |
|
bb462e
|
255 |
|
5bf23f
|
256 |
class ViewsConfiguratorMixin(object): |
bb462e
|
257 |
@viewdefaults |
5bf23f
|
258 |
@action_method |
9c8ec5
|
259 |
def add_view( |
CM |
260 |
self, |
|
261 |
view=None, |
|
262 |
name="", |
|
263 |
for_=None, |
|
264 |
permission=None, |
|
265 |
request_type=None, |
|
266 |
route_name=None, |
|
267 |
request_method=None, |
|
268 |
request_param=None, |
|
269 |
containment=None, |
|
270 |
attr=None, |
|
271 |
renderer=None, |
|
272 |
wrapper=None, |
|
273 |
xhr=None, |
|
274 |
accept=None, |
|
275 |
header=None, |
|
276 |
path_info=None, |
|
277 |
custom_predicates=(), |
|
278 |
context=None, |
|
279 |
decorator=None, |
|
280 |
mapper=None, |
|
281 |
http_cache=None, |
|
282 |
match_param=None, |
643a83
|
283 |
check_csrf=None, |
9e9fa9
|
284 |
require_csrf=None, |
b0d20b
|
285 |
exception_only=False, |
0c29cf
|
286 |
**view_options |
MM |
287 |
): |
5bf23f
|
288 |
""" Add a :term:`view configuration` to the current |
CM |
289 |
configuration state. Arguments to ``add_view`` are broken |
|
290 |
down below into *predicate* arguments and *non-predicate* |
|
291 |
arguments. Predicate arguments narrow the circumstances in |
|
292 |
which the view callable will be invoked when a request is |
|
293 |
presented to :app:`Pyramid`; non-predicate arguments are |
|
294 |
informational. |
|
295 |
|
|
296 |
Non-Predicate Arguments |
|
297 |
|
|
298 |
view |
|
299 |
|
|
300 |
A :term:`view callable` or a :term:`dotted Python name` |
|
301 |
which refers to a view callable. This argument is required |
|
302 |
unless a ``renderer`` argument also exists. If a |
|
303 |
``renderer`` argument is passed, and a ``view`` argument is |
|
304 |
not provided, the view callable defaults to a callable that |
|
305 |
returns an empty dictionary (see |
|
306 |
:ref:`views_which_use_a_renderer`). |
|
307 |
|
|
308 |
permission |
|
309 |
|
9c8ec5
|
310 |
A :term:`permission` that the user must possess in order to invoke |
CM |
311 |
the :term:`view callable`. See :ref:`view_security_section` for |
|
312 |
more information about view security and permissions. This is |
|
313 |
often a string like ``view`` or ``edit``. |
|
314 |
|
|
315 |
If ``permission`` is omitted, a *default* permission may be used |
|
316 |
for this view registration if one was named as the |
5bf23f
|
317 |
:class:`pyramid.config.Configurator` constructor's |
CM |
318 |
``default_permission`` argument, or if |
9c8ec5
|
319 |
:meth:`pyramid.config.Configurator.set_default_permission` was used |
CM |
320 |
prior to this view registration. Pass the value |
|
321 |
:data:`pyramid.security.NO_PERMISSION_REQUIRED` as the permission |
|
322 |
argument to explicitly indicate that the view should always be |
|
323 |
executable by entirely anonymous users, regardless of the default |
|
324 |
permission, bypassing any :term:`authorization policy` that may be |
|
325 |
in effect. |
5bf23f
|
326 |
|
CM |
327 |
attr |
9c8ec5
|
328 |
|
CM |
329 |
This knob is most useful when the view definition is a class. |
5bf23f
|
330 |
|
CM |
331 |
The view machinery defaults to using the ``__call__`` method |
|
332 |
of the :term:`view callable` (or the function itself, if the |
|
333 |
view callable is a function) to obtain a response. The |
|
334 |
``attr`` value allows you to vary the method attribute used |
|
335 |
to obtain the response. For example, if your view was a |
|
336 |
class, and the class has a method named ``index`` and you |
|
337 |
wanted to use this method instead of the class' ``__call__`` |
|
338 |
method to return the response, you'd say ``attr="index"`` in the |
9c8ec5
|
339 |
view configuration for the view. |
5bf23f
|
340 |
|
CM |
341 |
renderer |
|
342 |
|
|
343 |
This is either a single string term (e.g. ``json``) or a |
|
344 |
string implying a path or :term:`asset specification` |
|
345 |
(e.g. ``templates/views.pt``) naming a :term:`renderer` |
|
346 |
implementation. If the ``renderer`` value does not contain |
|
347 |
a dot ``.``, the specified string will be used to look up a |
|
348 |
renderer implementation, and that renderer implementation |
|
349 |
will be used to construct a response from the view return |
|
350 |
value. If the ``renderer`` value contains a dot (``.``), |
|
351 |
the specified term will be treated as a path, and the |
|
352 |
filename extension of the last element in the path will be |
|
353 |
used to look up the renderer implementation, which will be |
|
354 |
passed the full path. The renderer implementation will be |
|
355 |
used to construct a :term:`response` from the view return |
|
356 |
value. |
|
357 |
|
|
358 |
Note that if the view itself returns a :term:`response` (see |
|
359 |
:ref:`the_response`), the specified renderer implementation |
|
360 |
is never called. |
|
361 |
|
|
362 |
When the renderer is a path, although a path is usually just |
|
363 |
a simple relative pathname (e.g. ``templates/foo.pt``, |
|
364 |
implying that a template named "foo.pt" is in the |
|
365 |
"templates" directory relative to the directory of the |
|
366 |
current :term:`package` of the Configurator), a path can be |
|
367 |
absolute, starting with a slash on UNIX or a drive letter |
|
368 |
prefix on Windows. The path can alternately be a |
|
369 |
:term:`asset specification` in the form |
|
370 |
``some.dotted.package_name:relative/path``, making it |
|
371 |
possible to address template assets which live in a |
|
372 |
separate package. |
|
373 |
|
|
374 |
The ``renderer`` attribute is optional. If it is not |
|
375 |
defined, the "null" renderer is assumed (no rendering is |
|
376 |
performed and the value is passed back to the upstream |
|
377 |
:app:`Pyramid` machinery unmodified). |
|
378 |
|
|
379 |
http_cache |
|
380 |
|
0b23b3
|
381 |
.. versionadded:: 1.1 |
5bf23f
|
382 |
|
CM |
383 |
When you supply an ``http_cache`` value to a view configuration, |
|
384 |
the ``Expires`` and ``Cache-Control`` headers of a response |
|
385 |
generated by the associated view callable are modified. The value |
|
386 |
for ``http_cache`` may be one of the following: |
|
387 |
|
|
388 |
- A nonzero integer. If it's a nonzero integer, it's treated as a |
|
389 |
number of seconds. This number of seconds will be used to |
|
390 |
compute the ``Expires`` header and the ``Cache-Control: |
|
391 |
max-age`` parameter of responses to requests which call this view. |
|
392 |
For example: ``http_cache=3600`` instructs the requesting browser |
|
393 |
to 'cache this response for an hour, please'. |
|
394 |
|
|
395 |
- A ``datetime.timedelta`` instance. If it's a |
|
396 |
``datetime.timedelta`` instance, it will be converted into a |
|
397 |
number of seconds, and that number of seconds will be used to |
|
398 |
compute the ``Expires`` header and the ``Cache-Control: |
|
399 |
max-age`` parameter of responses to requests which call this view. |
|
400 |
For example: ``http_cache=datetime.timedelta(days=1)`` instructs |
|
401 |
the requesting browser to 'cache this response for a day, please'. |
|
402 |
|
|
403 |
- Zero (``0``). If the value is zero, the ``Cache-Control`` and |
|
404 |
``Expires`` headers present in all responses from this view will |
|
405 |
be composed such that client browser cache (and any intermediate |
|
406 |
caches) are instructed to never cache the response. |
|
407 |
|
|
408 |
- A two-tuple. If it's a two tuple (e.g. ``http_cache=(1, |
|
409 |
{'public':True})``), the first value in the tuple may be a |
|
410 |
nonzero integer or a ``datetime.timedelta`` instance; in either |
|
411 |
case this value will be used as the number of seconds to cache |
|
412 |
the response. The second value in the tuple must be a |
|
413 |
dictionary. The values present in the dictionary will be used as |
|
414 |
input to the ``Cache-Control`` response header. For example: |
|
415 |
``http_cache=(3600, {'public':True})`` means 'cache for an hour, |
|
416 |
and add ``public`` to the Cache-Control header of the response'. |
|
417 |
All keys and values supported by the |
|
418 |
``webob.cachecontrol.CacheControl`` interface may be added to the |
|
419 |
dictionary. Supplying ``{'public':True}`` is equivalent to |
|
420 |
calling ``response.cache_control.public = True``. |
|
421 |
|
|
422 |
Providing a non-tuple value as ``http_cache`` is equivalent to |
|
423 |
calling ``response.cache_expires(value)`` within your view's body. |
|
424 |
|
|
425 |
Providing a two-tuple value as ``http_cache`` is equivalent to |
|
426 |
calling ``response.cache_expires(value[0], **value[1])`` within your |
|
427 |
view's body. |
|
428 |
|
|
429 |
If you wish to avoid influencing, the ``Expires`` header, and |
|
430 |
instead wish to only influence ``Cache-Control`` headers, pass a |
|
431 |
tuple as ``http_cache`` with the first element of ``None``, e.g.: |
|
432 |
``(None, {'public':True})``. |
|
433 |
|
|
434 |
If you wish to prevent a view that uses ``http_cache`` in its |
|
435 |
configuration from having its caching response headers changed by |
|
436 |
this machinery, set ``response.cache_control.prevent_auto = True`` |
|
437 |
before returning the response from the view. This effectively |
|
438 |
disables any HTTP caching done by ``http_cache`` for that response. |
9e9fa9
|
439 |
|
MM |
440 |
require_csrf |
|
441 |
|
|
442 |
.. versionadded:: 1.7 |
|
443 |
|
de3d0c
|
444 |
A boolean option or ``None``. Default: ``None``. |
MM |
445 |
|
|
446 |
If this option is set to ``True`` then CSRF checks will be enabled |
|
447 |
for requests to this view. The required token or header default to |
|
448 |
``csrf_token`` and ``X-CSRF-Token``, respectively. |
|
449 |
|
|
450 |
CSRF checks only affect "unsafe" methods as defined by RFC2616. By |
|
451 |
default, these methods are anything except |
|
452 |
``GET``, ``HEAD``, ``OPTIONS``, and ``TRACE``. |
|
453 |
|
|
454 |
The defaults here may be overridden by |
|
455 |
:meth:`pyramid.config.Configurator.set_default_csrf_options`. |
9e9fa9
|
456 |
|
6b35eb
|
457 |
This feature requires a configured :term:`session factory`. |
9e9fa9
|
458 |
|
6b35eb
|
459 |
If this option is set to ``False`` then CSRF checks will be disabled |
de3d0c
|
460 |
regardless of the default ``require_csrf`` setting passed |
MM |
461 |
to ``set_default_csrf_options``. |
6b35eb
|
462 |
|
MM |
463 |
See :ref:`auto_csrf_checking` for more information. |
5bf23f
|
464 |
|
CM |
465 |
wrapper |
|
466 |
|
|
467 |
The :term:`view name` of a different :term:`view |
|
468 |
configuration` which will receive the response body of this |
|
469 |
view as the ``request.wrapped_body`` attribute of its own |
|
470 |
:term:`request`, and the :term:`response` returned by this |
|
471 |
view as the ``request.wrapped_response`` attribute of its |
|
472 |
own request. Using a wrapper makes it possible to "chain" |
|
473 |
views together to form a composite response. The response |
|
474 |
of the outermost wrapper view will be returned to the user. |
|
475 |
The wrapper view will be found as any view is found: see |
|
476 |
:ref:`view_lookup`. The "best" wrapper view will be found |
|
477 |
based on the lookup ordering: "under the hood" this wrapper |
|
478 |
view is looked up via |
|
479 |
``pyramid.view.render_view_to_response(context, request, |
|
480 |
'wrapper_viewname')``. The context and request of a wrapper |
|
481 |
view is the same context and request of the inner view. If |
|
482 |
this attribute is unspecified, no view wrapping is done. |
|
483 |
|
|
484 |
decorator |
|
485 |
|
76c9c2
|
486 |
A :term:`dotted Python name` to function (or the function itself, |
f194db
|
487 |
or an iterable of the aforementioned) which will be used to |
MM |
488 |
decorate the registered :term:`view callable`. The decorator |
|
489 |
function(s) will be called with the view callable as a single |
|
490 |
argument. The view callable it is passed will accept |
|
491 |
``(context, request)``. The decorator(s) must return a |
5bf23f
|
492 |
replacement view callable which also accepts ``(context, |
CM |
493 |
request)``. |
012b97
|
494 |
|
f194db
|
495 |
If decorator is an iterable, the callables will be combined and |
MM |
496 |
used in the order provided as a decorator. |
9aac76
|
497 |
For example:: |
R |
498 |
|
ee0e41
|
499 |
@view_config(..., |
PJ |
500 |
decorator=(decorator2, |
|
501 |
decorator1)) |
9aac76
|
502 |
def myview(request): |
R |
503 |
.... |
|
504 |
|
|
505 |
Is similar to doing:: |
|
506 |
|
|
507 |
@view_config(...) |
|
508 |
@decorator2 |
|
509 |
@decorator1 |
|
510 |
def myview(request): |
|
511 |
... |
|
512 |
|
|
513 |
Except with the existing benefits of ``decorator=`` (having a common |
|
514 |
decorator syntax for all view calling conventions and not having to |
|
515 |
think about preserving function attributes such as ``__name__`` and |
|
516 |
``__module__`` within decorator logic). |
|
517 |
|
663c26
|
518 |
An important distinction is that each decorator will receive a |
MM |
519 |
response object implementing :class:`pyramid.interfaces.IResponse` |
|
520 |
instead of the raw value returned from the view callable. All |
|
521 |
decorators in the chain must return a response object or raise an |
|
522 |
exception: |
6e0f02
|
523 |
|
MM |
524 |
.. code-block:: python |
|
525 |
|
|
526 |
def log_timer(wrapped): |
|
527 |
def wrapper(context, request): |
|
528 |
start = time.time() |
|
529 |
response = wrapped(context, request) |
|
530 |
duration = time.time() - start |
|
531 |
response.headers['X-View-Time'] = '%.3f' % (duration,) |
|
532 |
log.info('view took %.3f seconds', duration) |
|
533 |
return response |
|
534 |
return wrapper |
|
535 |
|
40dbf4
|
536 |
.. versionchanged:: 1.4a4 |
TL |
537 |
Passing an iterable. |
170124
|
538 |
|
5bf23f
|
539 |
mapper |
CM |
540 |
|
|
541 |
A Python object or :term:`dotted Python name` which refers to a |
|
542 |
:term:`view mapper`, or ``None``. By default it is ``None``, which |
|
543 |
indicates that the view should use the default view mapper. This |
|
544 |
plug-point is useful for Pyramid extension developers, but it's not |
|
545 |
very useful for 'civilians' who are just developing stock Pyramid |
|
546 |
applications. Pay no attention to the man behind the curtain. |
012b97
|
547 |
|
4cc3a6
|
548 |
accept |
MM |
549 |
|
|
550 |
A :term:`media type` that will be matched against the ``Accept`` |
|
551 |
HTTP request header. If this value is specified, it must be a |
|
552 |
specific media type such as ``text/html`` or ``text/html;level=1``. |
|
553 |
If the media type is acceptable by the ``Accept`` header of the |
|
554 |
request, or if the ``Accept`` header isn't set at all in the request, |
|
555 |
this predicate will match. If this does not match the ``Accept`` |
|
556 |
header of the request, view matching continues. |
|
557 |
|
|
558 |
If ``accept`` is not specified, the ``HTTP_ACCEPT`` HTTP header is |
|
559 |
not taken into consideration when deciding whether or not to invoke |
|
560 |
the associated view callable. |
|
561 |
|
|
562 |
The ``accept`` argument is technically not a predicate and does |
|
563 |
not support wrapping with :func:`pyramid.config.not_`. |
|
564 |
|
|
565 |
See :ref:`accept_content_negotiation` for more information. |
|
566 |
|
|
567 |
.. versionchanged:: 1.10 |
|
568 |
|
|
569 |
Specifying a media range is deprecated and will be removed in |
|
570 |
:app:`Pyramid` 2.0. Use explicit media types to avoid any |
|
571 |
ambiguities in content negotiation. |
|
572 |
|
121f45
|
573 |
exception_only |
d7c9f0
|
574 |
|
121f45
|
575 |
.. versionadded:: 1.8 |
MM |
576 |
|
|
577 |
When this value is ``True``, the ``context`` argument must be |
|
578 |
a subclass of ``Exception``. This flag indicates that only an |
|
579 |
:term:`exception view` should be created, and that this view should |
|
580 |
not match if the traversal :term:`context` matches the ``context`` |
|
581 |
argument. If the ``context`` is a subclass of ``Exception`` and |
|
582 |
this value is ``False`` (the default), then a view will be |
|
583 |
registered to match the traversal :term:`context` as well. |
d7c9f0
|
584 |
|
5bf23f
|
585 |
Predicate Arguments |
CM |
586 |
|
|
587 |
name |
|
588 |
|
|
589 |
The :term:`view name`. Read :ref:`traversal_chapter` to |
|
590 |
understand the concept of a view name. |
|
591 |
|
|
592 |
context |
|
593 |
|
|
594 |
An object or a :term:`dotted Python name` referring to an |
|
595 |
interface or class object that the :term:`context` must be |
|
596 |
an instance of, *or* the :term:`interface` that the |
|
597 |
:term:`context` must provide in order for this view to be |
|
598 |
found and called. This predicate is true when the |
|
599 |
:term:`context` is an instance of the represented class or |
|
600 |
if the :term:`context` provides the represented interface; |
|
601 |
it is otherwise false. This argument may also be provided |
|
602 |
to ``add_view`` as ``for_`` (an older, still-supported |
160aab
|
603 |
spelling). If the view should *only* match when handling |
SP |
604 |
exceptions, then set the ``exception_only`` to ``True``. |
5bf23f
|
605 |
|
CM |
606 |
route_name |
|
607 |
|
|
608 |
This value must match the ``name`` of a :term:`route |
|
609 |
configuration` declaration (see :ref:`urldispatch_chapter`) |
|
610 |
that must match before this view will be called. |
|
611 |
|
|
612 |
request_type |
|
613 |
|
|
614 |
This value should be an :term:`interface` that the |
|
615 |
:term:`request` must provide in order for this view to be |
|
616 |
found and called. This value exists only for backwards |
|
617 |
compatibility purposes. |
|
618 |
|
|
619 |
request_method |
|
620 |
|
306c29
|
621 |
This value can be either a string (such as ``"GET"``, ``"POST"``, |
MM |
622 |
``"PUT"``, ``"DELETE"``, ``"HEAD"`` or ``"OPTIONS"``) representing |
|
623 |
an HTTP ``REQUEST_METHOD``, or a tuple containing one or more of |
|
624 |
these strings. A view declaration with this argument ensures that |
|
625 |
the view will only be called when the ``method`` attribute of the |
b4e59b
|
626 |
request (aka the ``REQUEST_METHOD`` of the WSGI environment) matches |
TS |
627 |
a supplied value. Note that use of ``GET`` also implies that the |
360f25
|
628 |
view will respond to ``HEAD`` as of Pyramid 1.4. |
49f082
|
629 |
|
0b23b3
|
630 |
.. versionchanged:: 1.2 |
40dbf4
|
631 |
The ability to pass a tuple of items as ``request_method``. |
TL |
632 |
Previous versions allowed only a string. |
5bf23f
|
633 |
|
CM |
634 |
request_param |
|
635 |
|
b64851
|
636 |
This value can be any string or any sequence of strings. A view |
CR |
637 |
declaration with this argument ensures that the view will only be |
06a904
|
638 |
called when the :term:`request` has a key in the ``request.params`` |
5bf23f
|
639 |
dictionary (an HTTP ``GET`` or ``POST`` variable) that has a |
06a904
|
640 |
name which matches the supplied value (if the value is a string) |
CM |
641 |
or values (if the value is a tuple). If any value |
5bf23f
|
642 |
supplied has a ``=`` sign in it, |
CM |
643 |
e.g. ``request_param="foo=123"``, then the key (``foo``) |
|
644 |
must both exist in the ``request.params`` dictionary, *and* |
|
645 |
the value must match the right hand side of the expression |
|
646 |
(``123``) for the view to "match" the current request. |
|
647 |
|
718a44
|
648 |
match_param |
MM |
649 |
|
0b23b3
|
650 |
.. versionadded:: 1.2 |
718a44
|
651 |
|
835d48
|
652 |
This value can be a string of the format "key=value" or a tuple |
MM |
653 |
containing one or more of these strings. |
718a44
|
654 |
|
MM |
655 |
A view declaration with this argument ensures that the view will |
c0e6e6
|
656 |
only be called when the :term:`request` has key/value pairs in its |
CM |
657 |
:term:`matchdict` that equal those supplied in the predicate. |
896c77
|
658 |
e.g. ``match_param="action=edit"`` would require the ``action`` |
835d48
|
659 |
parameter in the :term:`matchdict` match the right hand side of |
c0e6e6
|
660 |
the expression (``edit``) for the view to "match" the current |
CM |
661 |
request. |
718a44
|
662 |
|
835d48
|
663 |
If the ``match_param`` is a tuple, every key/value pair must match |
718a44
|
664 |
for the predicate to pass. |
MM |
665 |
|
5bf23f
|
666 |
containment |
CM |
667 |
|
|
668 |
This value should be a Python class or :term:`interface` (or a |
|
669 |
:term:`dotted Python name`) that an object in the |
|
670 |
:term:`lineage` of the context must provide in order for this view |
|
671 |
to be found and called. The nodes in your object graph must be |
|
672 |
"location-aware" to use this feature. See |
|
673 |
:ref:`location_aware` for more information about |
|
674 |
location-awareness. |
|
675 |
|
|
676 |
xhr |
|
677 |
|
|
678 |
This value should be either ``True`` or ``False``. If this |
|
679 |
value is specified and is ``True``, the :term:`request` |
|
680 |
must possess an ``HTTP_X_REQUESTED_WITH`` (aka |
|
681 |
``X-Requested-With``) header that has the value |
|
682 |
``XMLHttpRequest`` for this view to be found and called. |
|
683 |
This is useful for detecting AJAX requests issued from |
|
684 |
jQuery, Prototype and other Javascript libraries. |
|
685 |
|
|
686 |
header |
|
687 |
|
|
688 |
This value represents an HTTP header name or a header |
|
689 |
name/value pair. If the value contains a ``:`` (colon), it |
|
690 |
will be considered a name/value pair |
|
691 |
(e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). The |
|
692 |
value portion should be a regular expression. If the value |
|
693 |
does not contain a colon, the entire value will be |
|
694 |
considered to be the header name |
|
695 |
(e.g. ``If-Modified-Since``). If the value evaluates to a |
|
696 |
header name only without a value, the header specified by |
|
697 |
the name must be present in the request for this predicate |
|
698 |
to be true. If the value evaluates to a header name/value |
|
699 |
pair, the header specified by the name must be present in |
|
700 |
the request *and* the regular expression specified as the |
|
701 |
value must match the header value. Whether or not the value |
|
702 |
represents a header name or a header name/value pair, the |
|
703 |
case of the header name is not significant. |
|
704 |
|
|
705 |
path_info |
|
706 |
|
|
707 |
This value represents a regular expression pattern that will |
|
708 |
be tested against the ``PATH_INFO`` WSGI environment |
|
709 |
variable. If the regex matches, this predicate will be |
|
710 |
``True``. |
|
711 |
|
643a83
|
712 |
check_csrf |
CM |
713 |
|
15b97d
|
714 |
.. deprecated:: 1.7 |
MM |
715 |
Use the ``require_csrf`` option or see :ref:`auto_csrf_checking` |
|
716 |
instead to have :class:`pyramid.exceptions.BadCSRFToken` |
|
717 |
exceptions raised. |
|
718 |
|
643a83
|
719 |
If specified, this value should be one of ``None``, ``True``, |
CM |
720 |
``False``, or a string representing the 'check name'. If the value |
|
721 |
is ``True`` or a string, CSRF checking will be performed. If the |
|
722 |
value is ``False`` or ``None``, CSRF checking will not be performed. |
|
723 |
|
|
724 |
If the value provided is a string, that string will be used as the |
|
725 |
'check name'. If the value provided is ``True``, ``csrf_token`` will |
|
726 |
be used as the check name. |
|
727 |
|
a2c7c7
|
728 |
If CSRF checking is performed, the checked value will be the value of |
MW |
729 |
``request.params[check_name]``. This value will be compared against |
313c25
|
730 |
the value of ``policy.get_csrf_token()`` (where ``policy`` is an |
a54bc1
|
731 |
implementation of :meth:`pyramid.interfaces.ICSRFStoragePolicy`), and |
MM |
732 |
the check will pass if these two values are the same. If the check |
a2c7c7
|
733 |
passes, the associated view will be permitted to execute. If the |
643a83
|
734 |
check fails, the associated view will not be permitted to execute. |
b64851
|
735 |
|
643a83
|
736 |
.. versionadded:: 1.4a2 |
CM |
737 |
|
2ded2f
|
738 |
.. versionchanged:: 1.9 |
MW |
739 |
This feature requires either a :term:`session factory` to have been |
|
740 |
configured, or a :term:`CSRF storage policy` other than the default |
|
741 |
to be in use. |
|
742 |
|
|
743 |
|
c25a8f
|
744 |
physical_path |
CM |
745 |
|
|
746 |
If specified, this value should be a string or a tuple representing |
|
747 |
the :term:`physical path` of the context found via traversal for this |
|
748 |
predicate to match as true. For example: ``physical_path='/'`` or |
|
749 |
``physical_path='/a/b/c'`` or ``physical_path=('', 'a', 'b', 'c')``. |
|
750 |
This is not a path prefix match or a regex, it's a whole-path match. |
|
751 |
It's useful when you want to always potentially show a view when some |
|
752 |
object is traversed to, but you can't be sure about what kind of |
|
753 |
object it will be, so you can't use the ``context`` predicate. The |
|
754 |
individual path elements inbetween slash characters or in tuple |
|
755 |
elements should be the Unicode representation of the name of the |
|
756 |
resource and should not be encoded in any way. |
|
757 |
|
|
758 |
.. versionadded:: 1.4a3 |
|
759 |
|
c7337b
|
760 |
effective_principals |
CM |
761 |
|
|
762 |
If specified, this value should be a :term:`principal` identifier or |
|
763 |
a sequence of principal identifiers. If the |
0184b5
|
764 |
:attr:`pyramid.request.Request.effective_principals` property |
CM |
765 |
indicates that every principal named in the argument list is present |
|
766 |
in the current request, this predicate will return True; otherwise it |
|
767 |
will return False. For example: |
c7337b
|
768 |
``effective_principals=pyramid.security.Authenticated`` or |
CM |
769 |
``effective_principals=('fred', 'group:admins')``. |
|
770 |
|
|
771 |
.. versionadded:: 1.4a4 |
|
772 |
|
5bf23f
|
773 |
custom_predicates |
CM |
774 |
|
2033ee
|
775 |
.. deprecated:: 1.5 |
SP |
776 |
This value should be a sequence of references to custom |
|
777 |
predicate callables. Use custom predicates when no set of |
|
778 |
predefined predicates do what you need. Custom predicates |
b64851
|
779 |
can be combined with predefined predicates as necessary. |
2033ee
|
780 |
Each custom predicate callable should accept two arguments: |
SP |
781 |
``context`` and ``request`` and should return either |
|
782 |
``True`` or ``False`` after doing arbitrary evaluation of |
|
783 |
the context and/or the request. The ``predicates`` argument |
|
784 |
to this method and the ability to register third-party view |
|
785 |
predicates via |
|
786 |
:meth:`pyramid.config.Configurator.add_view_predicate` |
|
787 |
obsoletes this argument, but it is kept around for backwards |
|
788 |
compatibility. |
9c8ec5
|
789 |
|
e8c66a
|
790 |
view_options |
9c8ec5
|
791 |
|
7fb6f9
|
792 |
Pass a key/value pair here to use a third-party predicate or set a |
fdd1f8
|
793 |
value for a view deriver. See |
MM |
794 |
:meth:`pyramid.config.Configurator.add_view_predicate` and |
|
795 |
:meth:`pyramid.config.Configurator.add_view_deriver`. See |
95f766
|
796 |
:ref:`view_and_route_predicates` for more information about |
fdd1f8
|
797 |
third-party predicates and :ref:`view_derivers` for information |
MM |
798 |
about view derivers. |
643a83
|
799 |
|
007600
|
800 |
.. versionadded: 1.4a1 |
MM |
801 |
|
|
802 |
.. versionchanged: 1.7 |
|
803 |
|
fdd1f8
|
804 |
Support setting view deriver options. Previously, only custom |
MM |
805 |
view predicate values could be supplied. |
b0d20b
|
806 |
|
5bf23f
|
807 |
""" |
b01f1d
|
808 |
if custom_predicates: |
CM |
809 |
warnings.warn( |
0c29cf
|
810 |
( |
a54bc1
|
811 |
'The "custom_predicates" argument to ' |
MM |
812 |
'Configurator.add_view is deprecated as of Pyramid 1.5. ' |
|
813 |
'Use "config.add_view_predicate" and use the registered ' |
|
814 |
'view predicate as a predicate argument to add_view ' |
|
815 |
'instead. See "Adding A Third Party View, Route, or ' |
|
816 |
'Subscriber Predicate" in the "Hooks" chapter of the ' |
|
817 |
'documentation for more information.' |
0c29cf
|
818 |
), |
c151ad
|
819 |
DeprecationWarning, |
15b97d
|
820 |
stacklevel=4, |
0c29cf
|
821 |
) |
15b97d
|
822 |
|
MM |
823 |
if check_csrf is not None: |
|
824 |
warnings.warn( |
0c29cf
|
825 |
( |
MM |
826 |
'The "check_csrf" argument to Configurator.add_view is ' |
a54bc1
|
827 |
'deprecated as of Pyramid 1.7. Use the "require_csrf" ' |
MM |
828 |
'option instead or see "Checking CSRF Tokens ' |
|
829 |
'Automatically" in the "Sessions" chapter of the ' |
|
830 |
'documentation for more information.' |
0c29cf
|
831 |
), |
c151ad
|
832 |
DeprecationWarning, |
15b97d
|
833 |
stacklevel=4, |
0c29cf
|
834 |
) |
b64851
|
835 |
|
121f45
|
836 |
if accept is not None: |
30f79d
|
837 |
if is_nonstr_iter(accept): |
MM |
838 |
raise ConfigurationError( |
0c29cf
|
839 |
'A list is not supported in the "accept" view predicate.' |
30f79d
|
840 |
) |
4a9f4f
|
841 |
if '*' in accept: |
MM |
842 |
warnings.warn( |
0c29cf
|
843 |
( |
MM |
844 |
'Passing a media range to the "accept" argument of ' |
a54bc1
|
845 |
'Configurator.add_view is deprecated as of ' |
MM |
846 |
'Pyramid 1.10. Use explicit media types to avoid ' |
|
847 |
'ambiguities in content negotiation that may impact ' |
|
848 |
'your users.' |
0c29cf
|
849 |
), |
4a9f4f
|
850 |
DeprecationWarning, |
MM |
851 |
stacklevel=4, |
0c29cf
|
852 |
) |
19eef8
|
853 |
# XXX when media ranges are gone, switch allow_range=False |
MM |
854 |
accept = normalize_accept_offer(accept, allow_range=True) |
121f45
|
855 |
|
5bf23f
|
856 |
view = self.maybe_dotted(view) |
CM |
857 |
context = self.maybe_dotted(context) |
|
858 |
for_ = self.maybe_dotted(for_) |
|
859 |
containment = self.maybe_dotted(containment) |
|
860 |
mapper = self.maybe_dotted(mapper) |
76c9c2
|
861 |
|
f194db
|
862 |
if is_nonstr_iter(decorator): |
30f79d
|
863 |
decorator = combine_decorators(*map(self.maybe_dotted, decorator)) |
76c9c2
|
864 |
else: |
R |
865 |
decorator = self.maybe_dotted(decorator) |
5bf23f
|
866 |
|
CM |
867 |
if not view: |
|
868 |
if renderer: |
0c29cf
|
869 |
|
5bf23f
|
870 |
def view(context, request): |
CM |
871 |
return {} |
0c29cf
|
872 |
|
5bf23f
|
873 |
else: |
0c29cf
|
874 |
raise ConfigurationError( |
MM |
875 |
'"view" was not specified and ' 'no "renderer" specified' |
|
876 |
) |
5bf23f
|
877 |
|
CM |
878 |
if request_type is not None: |
|
879 |
request_type = self.maybe_dotted(request_type) |
|
880 |
if not IInterface.providedBy(request_type): |
|
881 |
raise ConfigurationError( |
0c29cf
|
882 |
'request_type must be an interface, not %s' % request_type |
MM |
883 |
) |
b0d20b
|
884 |
|
5bf23f
|
885 |
if context is None: |
CM |
886 |
context = for_ |
e8c66a
|
887 |
|
MM |
888 |
isexc = isexception(context) |
|
889 |
if exception_only and not isexc: |
|
890 |
raise ConfigurationError( |
|
891 |
'view "context" must be an exception type when ' |
0c29cf
|
892 |
'"exception_only" is True' |
MM |
893 |
) |
5bf23f
|
894 |
|
CM |
895 |
r_context = context |
|
896 |
if r_context is None: |
|
897 |
r_context = Interface |
|
898 |
if not IInterface.providedBy(r_context): |
|
899 |
r_context = implementedBy(r_context) |
|
900 |
|
475532
|
901 |
if isinstance(renderer, string_types): |
5bf23f
|
902 |
renderer = renderers.RendererHelper( |
0c29cf
|
903 |
name=renderer, package=self.package, registry=self.registry |
MM |
904 |
) |
04373f
|
905 |
|
8a32e3
|
906 |
introspectables = [] |
7fb6f9
|
907 |
ovals = view_options.copy() |
0c29cf
|
908 |
ovals.update( |
MM |
909 |
dict( |
|
910 |
xhr=xhr, |
|
911 |
request_method=request_method, |
|
912 |
path_info=path_info, |
|
913 |
request_param=request_param, |
|
914 |
header=header, |
|
915 |
accept=accept, |
|
916 |
containment=containment, |
|
917 |
request_type=request_type, |
|
918 |
match_param=match_param, |
|
919 |
check_csrf=check_csrf, |
|
920 |
custom=predvalseq(custom_predicates), |
|
921 |
) |
|
922 |
) |
9c8ec5
|
923 |
|
CM |
924 |
def discrim_func(): |
|
925 |
# We need to defer the discriminator until we know what the phash |
|
926 |
# is. It can't be computed any sooner because thirdparty |
46fd86
|
927 |
# predicates/view derivers may not yet exist when add_view is |
7fb6f9
|
928 |
# called. |
e8c66a
|
929 |
predlist = self.get_predlist('view') |
7fb6f9
|
930 |
valid_predicates = predlist.names() |
BJR |
931 |
pvals = {} |
e4b931
|
932 |
dvals = {} |
7fb6f9
|
933 |
|
BJR |
934 |
for (k, v) in ovals.items(): |
|
935 |
if k in valid_predicates: |
|
936 |
pvals[k] = v |
e4b931
|
937 |
else: |
MM |
938 |
dvals[k] = v |
|
939 |
|
|
940 |
self._check_view_options(**dvals) |
7fb6f9
|
941 |
|
9c8ec5
|
942 |
order, preds, phash = predlist.make(self, **pvals) |
7fb6f9
|
943 |
|
0c29cf
|
944 |
view_intr.update( |
MM |
945 |
{'phash': phash, 'order': order, 'predicates': preds} |
|
946 |
) |
9c8ec5
|
947 |
return ('view', context, name, route_name, phash) |
CM |
948 |
|
|
949 |
discriminator = Deferred(discrim_func) |
|
950 |
|
8b6f09
|
951 |
if inspect.isclass(view) and attr: |
CM |
952 |
view_desc = 'method %r of %s' % ( |
0c29cf
|
953 |
attr, |
MM |
954 |
self.object_description(view), |
|
955 |
) |
8b6f09
|
956 |
else: |
CM |
957 |
view_desc = self.object_description(view) |
3d42aa
|
958 |
|
CM |
959 |
tmpl_intr = None |
b64851
|
960 |
|
0c29cf
|
961 |
view_intr = self.introspectable( |
MM |
962 |
'views', discriminator, view_desc, 'view' |
|
963 |
) |
|
964 |
view_intr.update( |
|
965 |
dict( |
|
966 |
name=name, |
|
967 |
context=context, |
|
968 |
exception_only=exception_only, |
|
969 |
containment=containment, |
|
970 |
request_param=request_param, |
|
971 |
request_methods=request_method, |
|
972 |
route_name=route_name, |
|
973 |
attr=attr, |
|
974 |
xhr=xhr, |
|
975 |
accept=accept, |
|
976 |
header=header, |
|
977 |
path_info=path_info, |
|
978 |
match_param=match_param, |
|
979 |
check_csrf=check_csrf, |
|
980 |
http_cache=http_cache, |
|
981 |
require_csrf=require_csrf, |
|
982 |
callable=view, |
|
983 |
mapper=mapper, |
|
984 |
decorator=decorator, |
|
985 |
) |
|
986 |
) |
007600
|
987 |
view_intr.update(view_options) |
8a32e3
|
988 |
introspectables.append(view_intr) |
CM |
989 |
|
5bf23f
|
990 |
def register(permission=permission, renderer=renderer): |
b9f2f5
|
991 |
request_iface = IRequest |
MM |
992 |
if route_name is not None: |
0c29cf
|
993 |
request_iface = self.registry.queryUtility( |
MM |
994 |
IRouteRequest, name=route_name |
|
995 |
) |
b9f2f5
|
996 |
if request_iface is None: |
381de3
|
997 |
# route configuration should have already happened in |
CM |
998 |
# phase 2 |
b9f2f5
|
999 |
raise ConfigurationError( |
0c29cf
|
1000 |
'No route named %s found for view registration' |
MM |
1001 |
% route_name |
|
1002 |
) |
b9f2f5
|
1003 |
|
5bf23f
|
1004 |
if renderer is None: |
eb2fee
|
1005 |
# use default renderer if one exists (reg'd in phase 1) |
5bf23f
|
1006 |
if self.registry.queryUtility(IRendererFactory) is not None: |
CM |
1007 |
renderer = renderers.RendererHelper( |
0c29cf
|
1008 |
name=None, package=self.package, registry=self.registry |
MM |
1009 |
) |
5bf23f
|
1010 |
|
e8c66a
|
1011 |
renderer_type = getattr(renderer, 'type', None) |
MM |
1012 |
intrspc = self.introspector |
|
1013 |
if ( |
0c29cf
|
1014 |
renderer_type is not None |
MM |
1015 |
and tmpl_intr is not None |
|
1016 |
and intrspc is not None |
|
1017 |
and intrspc.get('renderer factories', renderer_type) |
|
1018 |
is not None |
|
1019 |
): |
e8c66a
|
1020 |
# allow failure of registered template factories to be deferred |
MM |
1021 |
# until view execution, like other bad renderer factories; if |
|
1022 |
# we tried to relate this to an existing renderer factory |
160aab
|
1023 |
# without checking if the factory actually existed, we'd end |
e8c66a
|
1024 |
# up with a KeyError at startup time, which is inconsistent |
MM |
1025 |
# with how other bad renderer registrations behave (they throw |
|
1026 |
# a ValueError at view execution time) |
|
1027 |
tmpl_intr.relate('renderer factories', renderer.type) |
|
1028 |
|
|
1029 |
# make a new view separately for normal and exception paths |
|
1030 |
if not exception_only: |
|
1031 |
derived_view = derive_view(False, renderer) |
|
1032 |
register_view(IViewClassifier, request_iface, derived_view) |
|
1033 |
if isexc: |
|
1034 |
derived_exc_view = derive_view(True, renderer) |
0c29cf
|
1035 |
register_view( |
MM |
1036 |
IExceptionViewClassifier, request_iface, derived_exc_view |
|
1037 |
) |
e8c66a
|
1038 |
|
MM |
1039 |
if exception_only: |
|
1040 |
derived_view = derived_exc_view |
|
1041 |
|
|
1042 |
# if there are two derived views, combine them into one for |
|
1043 |
# introspection purposes |
|
1044 |
if not exception_only and isexc: |
|
1045 |
derived_view = runtime_exc_view(derived_view, derived_exc_view) |
|
1046 |
|
|
1047 |
derived_view.__discriminator__ = lambda *arg: discriminator |
|
1048 |
# __discriminator__ is used by superdynamic systems |
|
1049 |
# that require it for introspection after manual view lookup; |
|
1050 |
# see also MultiView.__discriminator__ |
|
1051 |
view_intr['derived_callable'] = derived_view |
|
1052 |
|
|
1053 |
self.registry._clear_view_lookup_cache() |
|
1054 |
|
|
1055 |
def derive_view(isexc_only, renderer): |
9c8ec5
|
1056 |
# added by discrim_func above during conflict resolving |
CM |
1057 |
preds = view_intr['predicates'] |
|
1058 |
order = view_intr['order'] |
|
1059 |
phash = view_intr['phash'] |
|
1060 |
|
007600
|
1061 |
derived_view = self._derive_view( |
bf40a3
|
1062 |
view, |
6439ce
|
1063 |
route_name=route_name, |
9c8ec5
|
1064 |
permission=permission, |
CM |
1065 |
predicates=preds, |
|
1066 |
attr=attr, |
a59312
|
1067 |
context=context, |
e8c66a
|
1068 |
exception_only=isexc_only, |
9c8ec5
|
1069 |
renderer=renderer, |
CM |
1070 |
wrapper_viewname=wrapper, |
|
1071 |
viewname=name, |
|
1072 |
accept=accept, |
|
1073 |
order=order, |
|
1074 |
phash=phash, |
|
1075 |
decorator=decorator, |
007600
|
1076 |
mapper=mapper, |
9c8ec5
|
1077 |
http_cache=http_cache, |
9e9fa9
|
1078 |
require_csrf=require_csrf, |
ff8c19
|
1079 |
extra_options=ovals, |
007600
|
1080 |
) |
e8c66a
|
1081 |
return derived_view |
5bf23f
|
1082 |
|
e8c66a
|
1083 |
def register_view(classifier, request_iface, derived_view): |
5bf23f
|
1084 |
# A multiviews is a set of views which are registered for |
CM |
1085 |
# exactly the same context type/request type/name triad. Each |
999bda
|
1086 |
# constituent view in a multiview differs only by the |
5bf23f
|
1087 |
# predicates which it possesses. |
CM |
1088 |
|
|
1089 |
# To find a previously registered view for a context |
|
1090 |
# type/request type/name triad, we need to use the |
|
1091 |
# ``registered`` method of the adapter registry rather than |
|
1092 |
# ``lookup``. ``registered`` ignores interface inheritance |
|
1093 |
# for the required and provided arguments, returning only a |
|
1094 |
# view registered previously with the *exact* triad we pass |
|
1095 |
# in. |
|
1096 |
|
|
1097 |
# We need to do this three times, because we use three |
|
1098 |
# different interfaces as the ``provided`` interface while |
|
1099 |
# doing registrations, and ``registered`` performs exact |
|
1100 |
# matches on all the arguments it receives. |
|
1101 |
|
|
1102 |
old_view = None |
e8c66a
|
1103 |
order, phash = view_intr['order'], view_intr['phash'] |
MM |
1104 |
registered = self.registry.adapters.registered |
5bf23f
|
1105 |
|
CM |
1106 |
for view_type in (IView, ISecuredView, IMultiView): |
e8c66a
|
1107 |
old_view = registered( |
0c29cf
|
1108 |
(classifier, request_iface, r_context), view_type, name |
MM |
1109 |
) |
5bf23f
|
1110 |
if old_view is not None: |
CM |
1111 |
break |
|
1112 |
|
30f79d
|
1113 |
old_phash = getattr(old_view, '__phash__', DEFAULT_PHASH) |
MM |
1114 |
is_multiview = IMultiView.providedBy(old_view) |
|
1115 |
want_multiview = ( |
|
1116 |
is_multiview |
|
1117 |
# no component was yet registered for exactly this triad |
|
1118 |
# or only one was registered but with the same phash, meaning |
|
1119 |
# that this view is an override |
|
1120 |
or (old_view is not None and old_phash != phash) |
|
1121 |
) |
|
1122 |
|
|
1123 |
if not want_multiview: |
5bf23f
|
1124 |
if hasattr(derived_view, '__call_permissive__'): |
CM |
1125 |
view_iface = ISecuredView |
|
1126 |
else: |
|
1127 |
view_iface = IView |
e8c66a
|
1128 |
self.registry.registerAdapter( |
MM |
1129 |
derived_view, |
|
1130 |
(classifier, request_iface, context), |
|
1131 |
view_iface, |
0c29cf
|
1132 |
name, |
MM |
1133 |
) |
5bf23f
|
1134 |
|
CM |
1135 |
else: |
|
1136 |
# - A view or multiview was already registered for this |
|
1137 |
# triad, and the new view is not an override. |
|
1138 |
|
|
1139 |
# XXX we could try to be more efficient here and register |
|
1140 |
# a non-secured view for a multiview if none of the |
de0719
|
1141 |
# multiview's constituent views have a permission |
5bf23f
|
1142 |
# associated with them, but this code is getting pretty |
CM |
1143 |
# rough already |
|
1144 |
if is_multiview: |
|
1145 |
multiview = old_view |
|
1146 |
else: |
|
1147 |
multiview = MultiView(name) |
13b74d
|
1148 |
old_accept = getattr(old_view, '__accept__', None) |
MM |
1149 |
old_order = getattr(old_view, '__order__', MAX_ORDER) |
|
1150 |
# don't bother passing accept_order here as we know we're |
|
1151 |
# adding another one right after which will re-sort |
30f79d
|
1152 |
multiview.add(old_view, old_order, old_phash, old_accept) |
121f45
|
1153 |
accept_order = self.registry.queryUtility(IAcceptOrder) |
30f79d
|
1154 |
multiview.add(derived_view, order, phash, accept, accept_order) |
5bf23f
|
1155 |
for view_type in (IView, ISecuredView): |
CM |
1156 |
# unregister any existing views |
|
1157 |
self.registry.adapters.unregister( |
e8c66a
|
1158 |
(classifier, request_iface, r_context), |
0c29cf
|
1159 |
view_type, |
MM |
1160 |
name=name, |
|
1161 |
) |
5bf23f
|
1162 |
self.registry.registerAdapter( |
CM |
1163 |
multiview, |
e8c66a
|
1164 |
(classifier, request_iface, context), |
0c29cf
|
1165 |
IMultiView, |
MM |
1166 |
name=name, |
|
1167 |
) |
5bf23f
|
1168 |
|
522405
|
1169 |
if mapper: |
9c8ec5
|
1170 |
mapper_intr = self.introspectable( |
CM |
1171 |
'view mappers', |
|
1172 |
discriminator, |
|
1173 |
'view mapper for %s' % view_desc, |
0c29cf
|
1174 |
'view mapper', |
MM |
1175 |
) |
522405
|
1176 |
mapper_intr['mapper'] = mapper |
CM |
1177 |
mapper_intr.relate('views', discriminator) |
|
1178 |
introspectables.append(mapper_intr) |
3b5ccb
|
1179 |
if route_name: |
0c29cf
|
1180 |
view_intr.relate('routes', route_name) # see add_route |
3b5ccb
|
1181 |
if renderer is not None and renderer.name and '.' in renderer.name: |
9c8ec5
|
1182 |
# the renderer is a template |
CM |
1183 |
tmpl_intr = self.introspectable( |
0c29cf
|
1184 |
'templates', discriminator, renderer.name, 'template' |
MM |
1185 |
) |
5e92f3
|
1186 |
tmpl_intr.relate('views', discriminator) |
8a32e3
|
1187 |
tmpl_intr['name'] = renderer.name |
CM |
1188 |
tmpl_intr['type'] = renderer.type |
|
1189 |
tmpl_intr['renderer'] = renderer |
3b5ccb
|
1190 |
introspectables.append(tmpl_intr) |
CM |
1191 |
if permission is not None: |
9c8ec5
|
1192 |
# if a permission exists, register a permission introspectable |
CM |
1193 |
perm_intr = self.introspectable( |
0c29cf
|
1194 |
'permissions', permission, permission, 'permission' |
MM |
1195 |
) |
8a32e3
|
1196 |
perm_intr['value'] = permission |
5e92f3
|
1197 |
perm_intr.relate('views', discriminator) |
3b5ccb
|
1198 |
introspectables.append(perm_intr) |
CM |
1199 |
self.action(discriminator, register, introspectables=introspectables) |
012b97
|
1200 |
|
e4b931
|
1201 |
def _check_view_options(self, **kw): |
MM |
1202 |
# we only need to validate deriver options because the predicates |
|
1203 |
# were checked by the predlist |
|
1204 |
derivers = self.registry.getUtility(IViewDerivers) |
|
1205 |
for deriver in derivers.values(): |
|
1206 |
for opt in getattr(deriver, 'options', []): |
|
1207 |
kw.pop(opt, None) |
|
1208 |
if kw: |
|
1209 |
raise ConfigurationError('Unknown view options: %s' % (kw,)) |
|
1210 |
|
ceb1f2
|
1211 |
def _apply_view_derivers(self, info): |
a3db3c
|
1212 |
# These derivers are not really derivers and so have fixed order |
0c29cf
|
1213 |
outer_derivers = [ |
MM |
1214 |
('attr_wrapped_view', attr_wrapped_view), |
|
1215 |
('predicated_view', predicated_view), |
|
1216 |
] |
3a20b7
|
1217 |
|
a610d0
|
1218 |
view = info.original_view |
46fd86
|
1219 |
derivers = self.registry.getUtility(IViewDerivers) |
a3db3c
|
1220 |
for name, deriver in reversed(outer_derivers + derivers.sorted()): |
46fd86
|
1221 |
view = wraps_view(deriver)(view, info) |
c2c589
|
1222 |
return view |
CD |
1223 |
|
a00621
|
1224 |
@action_method |
0c29cf
|
1225 |
def add_view_predicate( |
MM |
1226 |
self, name, factory, weighs_more_than=None, weighs_less_than=None |
|
1227 |
): |
0b23b3
|
1228 |
""" |
TL |
1229 |
.. versionadded:: 1.4 |
|
1230 |
|
|
1231 |
Adds a view predicate factory. The associated view predicate can |
9c8ec5
|
1232 |
later be named as a keyword argument to |
CM |
1233 |
:meth:`pyramid.config.Configurator.add_view` in the |
8ec8e2
|
1234 |
``predicates`` anonyous keyword argument dictionary. |
a00621
|
1235 |
|
CM |
1236 |
``name`` should be the name of the predicate. It must be a valid |
|
1237 |
Python identifier (it will be used as a keyword argument to |
9c8ec5
|
1238 |
``add_view`` by others). |
a00621
|
1239 |
|
d71aca
|
1240 |
``factory`` should be a :term:`predicate factory` or :term:`dotted |
BJR |
1241 |
Python name` which refers to a predicate factory. |
5664c4
|
1242 |
|
95f766
|
1243 |
See :ref:`view_and_route_predicates` for more information. |
a00621
|
1244 |
""" |
95f766
|
1245 |
self._add_predicate( |
CM |
1246 |
'view', |
|
1247 |
name, |
|
1248 |
factory, |
|
1249 |
weighs_more_than=weighs_more_than, |
0c29cf
|
1250 |
weighs_less_than=weighs_less_than, |
MM |
1251 |
) |
a00621
|
1252 |
|
CM |
1253 |
def add_default_view_predicates(self): |
c7974f
|
1254 |
p = pyramid.predicates |
9c8ec5
|
1255 |
for (name, factory) in ( |
8ec8e2
|
1256 |
('xhr', p.XHRPredicate), |
CM |
1257 |
('request_method', p.RequestMethodPredicate), |
|
1258 |
('path_info', p.PathInfoPredicate), |
|
1259 |
('request_param', p.RequestParamPredicate), |
|
1260 |
('header', p.HeaderPredicate), |
|
1261 |
('accept', p.AcceptPredicate), |
|
1262 |
('containment', p.ContainmentPredicate), |
|
1263 |
('request_type', p.RequestTypePredicate), |
|
1264 |
('match_param', p.MatchParamPredicate), |
643a83
|
1265 |
('check_csrf', p.CheckCSRFTokenPredicate), |
c25a8f
|
1266 |
('physical_path', p.PhysicalPathPredicate), |
c7337b
|
1267 |
('effective_principals', p.EffectivePrincipalsPredicate), |
8ec8e2
|
1268 |
('custom', p.CustomPredicate), |
0c29cf
|
1269 |
): |
9c8ec5
|
1270 |
self.add_view_predicate(name, factory) |
c2c589
|
1271 |
|
121f45
|
1272 |
def add_default_accept_view_order(self): |
MM |
1273 |
for accept in ( |
|
1274 |
'text/html', |
|
1275 |
'application/xhtml+xml', |
|
1276 |
'application/xml', |
|
1277 |
'text/xml', |
ed6ddc
|
1278 |
'text/plain', |
a3d3a2
|
1279 |
'application/json', |
121f45
|
1280 |
): |
MM |
1281 |
self.add_accept_view_order(accept) |
|
1282 |
|
|
1283 |
@action_method |
|
1284 |
def add_accept_view_order( |
0c29cf
|
1285 |
self, value, weighs_more_than=None, weighs_less_than=None |
121f45
|
1286 |
): |
MM |
1287 |
""" |
|
1288 |
Specify an ordering preference for the ``accept`` view option used |
|
1289 |
during :term:`view lookup`. |
|
1290 |
|
|
1291 |
By default, if two views have different ``accept`` options and a |
|
1292 |
request specifies ``Accept: */*`` or omits the header entirely then |
|
1293 |
it is random which view will be selected. This method provides a way |
|
1294 |
to specify a server-side, relative ordering between accept media types. |
|
1295 |
|
|
1296 |
``value`` should be a :term:`media type` as specified by |
30f79d
|
1297 |
:rfc:`7231#section-5.3.2`. For example, ``text/plain;charset=utf8``, |
121f45
|
1298 |
``application/json`` or ``text/html``. |
MM |
1299 |
|
|
1300 |
``weighs_more_than`` and ``weighs_less_than`` control the ordering |
bd82c8
|
1301 |
of media types. Each value may be a string or a list of strings. If |
MM |
1302 |
all options for ``weighs_more_than`` (or ``weighs_less_than``) cannot |
|
1303 |
be found, it is an error. |
|
1304 |
|
|
1305 |
Earlier calls to ``add_accept_view_order`` are given higher priority |
|
1306 |
over later calls, assuming similar constraints but standard conflict |
|
1307 |
resolution mechanisms can be used to override constraints. |
121f45
|
1308 |
|
f081ae
|
1309 |
See :ref:`accept_content_negotiation` for more information. |
121f45
|
1310 |
|
MM |
1311 |
.. versionadded:: 1.10 |
|
1312 |
|
|
1313 |
""" |
0c29cf
|
1314 |
|
30f79d
|
1315 |
def check_type(than): |
MM |
1316 |
than_type, than_subtype, than_params = Accept.parse_offer(than) |
4a9f4f
|
1317 |
# text/plain vs text/html;charset=utf8 |
MM |
1318 |
if bool(offer_params) ^ bool(than_params): |
30f79d
|
1319 |
raise ConfigurationError( |
4a9f4f
|
1320 |
'cannot compare a media type with params to one without ' |
0c29cf
|
1321 |
'params' |
MM |
1322 |
) |
30f79d
|
1323 |
# text/plain;charset=utf8 vs text/html;charset=utf8 |
MM |
1324 |
if offer_params and ( |
|
1325 |
offer_subtype != than_subtype or offer_type != than_type |
|
1326 |
): |
|
1327 |
raise ConfigurationError( |
0c29cf
|
1328 |
'cannot compare params across different media types' |
MM |
1329 |
) |
30f79d
|
1330 |
|
4a9f4f
|
1331 |
def normalize_types(thans): |
19eef8
|
1332 |
thans = [normalize_accept_offer(than) for than in thans] |
MM |
1333 |
for than in thans: |
|
1334 |
check_type(than) |
4a9f4f
|
1335 |
return thans |
MM |
1336 |
|
19eef8
|
1337 |
value = normalize_accept_offer(value) |
30f79d
|
1338 |
offer_type, offer_subtype, offer_params = Accept.parse_offer(value) |
MM |
1339 |
|
|
1340 |
if weighs_more_than: |
|
1341 |
if not is_nonstr_iter(weighs_more_than): |
|
1342 |
weighs_more_than = [weighs_more_than] |
4a9f4f
|
1343 |
weighs_more_than = normalize_types(weighs_more_than) |
30f79d
|
1344 |
|
MM |
1345 |
if weighs_less_than: |
|
1346 |
if not is_nonstr_iter(weighs_less_than): |
|
1347 |
weighs_less_than = [weighs_less_than] |
4a9f4f
|
1348 |
weighs_less_than = normalize_types(weighs_less_than) |
30f79d
|
1349 |
|
121f45
|
1350 |
discriminator = ('accept view order', value) |
MM |
1351 |
intr = self.introspectable( |
0c29cf
|
1352 |
'accept view order', value, value, 'accept view order' |
MM |
1353 |
) |
121f45
|
1354 |
intr['value'] = value |
MM |
1355 |
intr['weighs_more_than'] = weighs_more_than |
|
1356 |
intr['weighs_less_than'] = weighs_less_than |
0c29cf
|
1357 |
|
121f45
|
1358 |
def register(): |
MM |
1359 |
sorter = self.registry.queryUtility(IAcceptOrder) |
|
1360 |
if sorter is None: |
|
1361 |
sorter = TopologicalSorter() |
|
1362 |
self.registry.registerUtility(sorter, IAcceptOrder) |
|
1363 |
sorter.add( |
0c29cf
|
1364 |
value, value, before=weighs_more_than, after=weighs_less_than |
121f45
|
1365 |
) |
0c29cf
|
1366 |
|
MM |
1367 |
self.action( |
|
1368 |
discriminator, |
|
1369 |
register, |
|
1370 |
introspectables=(intr,), |
|
1371 |
order=PHASE1_CONFIG, |
|
1372 |
) # must be registered before add_view |
121f45
|
1373 |
|
c2c589
|
1374 |
@action_method |
a3db3c
|
1375 |
def add_view_deriver(self, deriver, name=None, under=None, over=None): |
35e632
|
1376 |
""" |
MM |
1377 |
.. versionadded:: 1.7 |
|
1378 |
|
|
1379 |
Add a :term:`view deriver` to the view pipeline. View derivers are |
|
1380 |
a feature used by extension authors to wrap views in custom code |
|
1381 |
controllable by view-specific options. |
|
1382 |
|
|
1383 |
``deriver`` should be a callable conforming to the |
|
1384 |
:class:`pyramid.interfaces.IViewDeriver` interface. |
|
1385 |
|
|
1386 |
``name`` should be the name of the view deriver. There are no |
|
1387 |
restrictions on the name of a view deriver. If left unspecified, the |
|
1388 |
name will be constructed from the name of the ``deriver``. |
|
1389 |
|
a3db3c
|
1390 |
The ``under`` and ``over`` options can be used to control the ordering |
35e632
|
1391 |
of view derivers by providing hints about where in the view pipeline |
a3db3c
|
1392 |
the deriver is used. Each option may be a string or a list of strings. |
MM |
1393 |
At least one view deriver in each, the over and under directions, must |
|
1394 |
exist to fully satisfy the constraints. |
35e632
|
1395 |
|
cf9dcb
|
1396 |
``under`` means closer to the user-defined :term:`view callable`, |
MM |
1397 |
and ``over`` means closer to view pipeline ingress. |
35e632
|
1398 |
|
c231d8
|
1399 |
The default value for ``over`` is ``rendered_view`` and ``under`` is |
MM |
1400 |
``decorated_view``. This places the deriver somewhere between the two |
|
1401 |
in the view pipeline. If the deriver should be placed elsewhere in the |
|
1402 |
pipeline, such as above ``decorated_view``, then you MUST also specify |
|
1403 |
``under`` to something earlier in the order, or a |
|
1404 |
``CyclicDependencyError`` will be raised when trying to sort the |
35e632
|
1405 |
derivers. |
MM |
1406 |
|
|
1407 |
See :ref:`view_derivers` for more information. |
|
1408 |
|
|
1409 |
""" |
cbf686
|
1410 |
deriver = self.maybe_dotted(deriver) |
MM |
1411 |
|
a3db3c
|
1412 |
if name is None: |
MM |
1413 |
name = deriver.__name__ |
|
1414 |
|
c231d8
|
1415 |
if name in (INGRESS, VIEW): |
0c29cf
|
1416 |
raise ConfigurationError( |
MM |
1417 |
'%s is a reserved view deriver name' % name |
|
1418 |
) |
a3db3c
|
1419 |
|
c231d8
|
1420 |
if under is None: |
cf9dcb
|
1421 |
under = 'decorated_view' |
c231d8
|
1422 |
|
MM |
1423 |
if over is None: |
cf9dcb
|
1424 |
over = 'rendered_view' |
a3db3c
|
1425 |
|
c231d8
|
1426 |
over = as_sorted_tuple(over) |
MM |
1427 |
under = as_sorted_tuple(under) |
a3db3c
|
1428 |
|
c231d8
|
1429 |
if INGRESS in over: |
MM |
1430 |
raise ConfigurationError('%s cannot be over INGRESS' % name) |
a3db3c
|
1431 |
|
c231d8
|
1432 |
# ensure everything is always over mapped_view |
MM |
1433 |
if VIEW in over and name != 'mapped_view': |
|
1434 |
over = as_sorted_tuple(over + ('mapped_view',)) |
a3db3c
|
1435 |
|
c231d8
|
1436 |
if VIEW in under: |
MM |
1437 |
raise ConfigurationError('%s cannot be under VIEW' % name) |
|
1438 |
if 'mapped_view' in under: |
|
1439 |
raise ConfigurationError('%s cannot be under "mapped_view"' % name) |
a3794d
|
1440 |
|
ceb1f2
|
1441 |
discriminator = ('view deriver', name) |
0c29cf
|
1442 |
intr = self.introspectable('view derivers', name, name, 'view deriver') |
c2c589
|
1443 |
intr['name'] = name |
ceb1f2
|
1444 |
intr['deriver'] = deriver |
c15fe7
|
1445 |
intr['under'] = under |
BJR |
1446 |
intr['over'] = over |
0c29cf
|
1447 |
|
c2c589
|
1448 |
def register(): |
1d5a99
|
1449 |
derivers = self.registry.queryUtility(IViewDerivers) |
AL |
1450 |
if derivers is None: |
a3db3c
|
1451 |
derivers = TopologicalSorter( |
MM |
1452 |
default_before=None, |
|
1453 |
default_after=INGRESS, |
|
1454 |
first=INGRESS, |
c231d8
|
1455 |
last=VIEW, |
a3db3c
|
1456 |
) |
1d5a99
|
1457 |
self.registry.registerUtility(derivers, IViewDerivers) |
cf9dcb
|
1458 |
derivers.add(name, deriver, before=over, after=under) |
0c29cf
|
1459 |
|
MM |
1460 |
self.action( |
|
1461 |
discriminator, |
|
1462 |
register, |
|
1463 |
introspectables=(intr,), |
|
1464 |
order=PHASE1_CONFIG, |
|
1465 |
) # must be registered before add_view |
c2c589
|
1466 |
|
ceb1f2
|
1467 |
def add_default_view_derivers(self): |
ecc9d8
|
1468 |
d = pyramid.viewderivers |
c2c589
|
1469 |
derivers = [ |
cf9dcb
|
1470 |
('secured_view', d.secured_view), |
MM |
1471 |
('owrapped_view', d.owrapped_view), |
|
1472 |
('http_cached_view', d.http_cached_view), |
|
1473 |
('decorated_view', d.decorated_view), |
a3db3c
|
1474 |
('rendered_view', d.rendered_view), |
c231d8
|
1475 |
('mapped_view', d.mapped_view), |
c2c589
|
1476 |
] |
a3db3c
|
1477 |
last = INGRESS |
c2c589
|
1478 |
for name, deriver in derivers: |
0c29cf
|
1479 |
self.add_view_deriver(deriver, name=name, under=last, over=VIEW) |
cf9dcb
|
1480 |
last = name |
a3794d
|
1481 |
|
de3d0c
|
1482 |
# leave the csrf_view loosely coupled to the rest of the pipeline |
MM |
1483 |
# by ensuring nothing in the default pipeline depends on the order |
|
1484 |
# of the csrf_view |
|
1485 |
self.add_view_deriver( |
|
1486 |
d.csrf_view, |
|
1487 |
'csrf_view', |
|
1488 |
under='secured_view', |
|
1489 |
over='owrapped_view', |
|
1490 |
) |
|
1491 |
|
5bf23f
|
1492 |
def derive_view(self, view, attr=None, renderer=None): |
CM |
1493 |
""" |
|
1494 |
Create a :term:`view callable` using the function, instance, |
|
1495 |
or class (or :term:`dotted Python name` referring to the same) |
|
1496 |
provided as ``view`` object. |
|
1497 |
|
012b97
|
1498 |
.. warning:: |
M |
1499 |
|
|
1500 |
This method is typically only used by :app:`Pyramid` framework |
|
1501 |
extension authors, not by :app:`Pyramid` application developers. |
5bf23f
|
1502 |
|
CM |
1503 |
This is API is useful to framework extenders who create |
|
1504 |
pluggable systems which need to register 'proxy' view |
|
1505 |
callables for functions, instances, or classes which meet the |
|
1506 |
requirements of being a :app:`Pyramid` view callable. For |
|
1507 |
example, a ``some_other_framework`` function in another |
|
1508 |
framework may want to allow a user to supply a view callable, |
|
1509 |
but he may want to wrap the view callable in his own before |
|
1510 |
registering the wrapper as a :app:`Pyramid` view callable. |
|
1511 |
Because a :app:`Pyramid` view callable can be any of a |
|
1512 |
number of valid objects, the framework extender will not know |
|
1513 |
how to call the user-supplied object. Running it through |
|
1514 |
``derive_view`` normalizes it to a callable which accepts two |
|
1515 |
arguments: ``context`` and ``request``. |
|
1516 |
|
|
1517 |
For example: |
|
1518 |
|
|
1519 |
.. code-block:: python |
|
1520 |
|
|
1521 |
def some_other_framework(user_supplied_view): |
|
1522 |
config = Configurator(reg) |
|
1523 |
proxy_view = config.derive_view(user_supplied_view) |
|
1524 |
def my_wrapper(context, request): |
|
1525 |
do_something_that_mutates(request) |
|
1526 |
return proxy_view(context, request) |
|
1527 |
config.add_view(my_wrapper) |
|
1528 |
|
|
1529 |
The ``view`` object provided should be one of the following: |
|
1530 |
|
|
1531 |
- A function or another non-class callable object that accepts |
|
1532 |
a :term:`request` as a single positional argument and which |
|
1533 |
returns a :term:`response` object. |
|
1534 |
|
|
1535 |
- A function or other non-class callable object that accepts |
|
1536 |
two positional arguments, ``context, request`` and which |
|
1537 |
returns a :term:`response` object. |
|
1538 |
|
|
1539 |
- A class which accepts a single positional argument in its |
|
1540 |
constructor named ``request``, and which has a ``__call__`` |
|
1541 |
method that accepts no arguments that returns a |
|
1542 |
:term:`response` object. |
|
1543 |
|
|
1544 |
- A class which accepts two positional arguments named |
|
1545 |
``context, request``, and which has a ``__call__`` method |
|
1546 |
that accepts no arguments that returns a :term:`response` |
|
1547 |
object. |
|
1548 |
|
|
1549 |
- A :term:`dotted Python name` which refers to any of the |
|
1550 |
kinds of objects above. |
|
1551 |
|
|
1552 |
This API returns a callable which accepts the arguments |
|
1553 |
``context, request`` and which returns the result of calling |
|
1554 |
the provided ``view`` object. |
|
1555 |
|
|
1556 |
The ``attr`` keyword argument is most useful when the view |
|
1557 |
object is a class. It names the method that should be used as |
|
1558 |
the callable. If ``attr`` is not provided, the attribute |
|
1559 |
effectively defaults to ``__call__``. See |
|
1560 |
:ref:`class_as_view` for more information. |
|
1561 |
|
|
1562 |
The ``renderer`` keyword argument should be a renderer |
|
1563 |
name. If supplied, it will cause the returned callable to use |
|
1564 |
a :term:`renderer` to convert the user-supplied view result to |
|
1565 |
a :term:`response` object. If a ``renderer`` argument is not |
|
1566 |
supplied, the user-supplied view must itself return a |
|
1567 |
:term:`response` object. """ |
|
1568 |
return self._derive_view(view, attr=attr, renderer=renderer) |
|
1569 |
|
|
1570 |
# b/w compat |
0c29cf
|
1571 |
def _derive_view( |
MM |
1572 |
self, |
|
1573 |
view, |
|
1574 |
permission=None, |
|
1575 |
predicates=(), |
|
1576 |
attr=None, |
|
1577 |
renderer=None, |
|
1578 |
wrapper_viewname=None, |
|
1579 |
viewname=None, |
|
1580 |
accept=None, |
|
1581 |
order=MAX_ORDER, |
|
1582 |
phash=DEFAULT_PHASH, |
|
1583 |
decorator=None, |
|
1584 |
route_name=None, |
|
1585 |
mapper=None, |
|
1586 |
http_cache=None, |
|
1587 |
context=None, |
|
1588 |
require_csrf=None, |
|
1589 |
exception_only=False, |
|
1590 |
extra_options=None, |
|
1591 |
): |
5bf23f
|
1592 |
view = self.maybe_dotted(view) |
CM |
1593 |
mapper = self.maybe_dotted(mapper) |
8e606d
|
1594 |
if isinstance(renderer, string_types): |
5bf23f
|
1595 |
renderer = renderers.RendererHelper( |
0c29cf
|
1596 |
name=renderer, package=self.package, registry=self.registry |
MM |
1597 |
) |
5bf23f
|
1598 |
if renderer is None: |
CM |
1599 |
# use default renderer if one exists |
|
1600 |
if self.registry.queryUtility(IRendererFactory) is not None: |
|
1601 |
renderer = renderers.RendererHelper( |
0c29cf
|
1602 |
name=None, package=self.package, registry=self.registry |
MM |
1603 |
) |
5bf23f
|
1604 |
|
007600
|
1605 |
options = dict( |
a610d0
|
1606 |
view=view, |
a59312
|
1607 |
context=context, |
c2c589
|
1608 |
permission=permission, |
CD |
1609 |
attr=attr, |
|
1610 |
renderer=renderer, |
e292cc
|
1611 |
wrapper=wrapper_viewname, |
MM |
1612 |
name=viewname, |
c2c589
|
1613 |
accept=accept, |
CD |
1614 |
mapper=mapper, |
|
1615 |
decorator=decorator, |
bf40a3
|
1616 |
http_cache=http_cache, |
9e9fa9
|
1617 |
require_csrf=require_csrf, |
0c29cf
|
1618 |
route_name=route_name, |
007600
|
1619 |
) |
MM |
1620 |
if extra_options: |
|
1621 |
options.update(extra_options) |
|
1622 |
|
|
1623 |
info = ViewDeriverInfo( |
|
1624 |
view=view, |
|
1625 |
registry=self.registry, |
|
1626 |
package=self.package, |
|
1627 |
predicates=predicates, |
e8c66a
|
1628 |
exception_only=exception_only, |
c6dffc
|
1629 |
options=options, |
bf40a3
|
1630 |
) |
007600
|
1631 |
|
dad950
|
1632 |
# order and phash are only necessary for the predicated view and |
ceb1f2
|
1633 |
# are not really view deriver options |
dad950
|
1634 |
info.order = order |
MM |
1635 |
info.phash = phash |
|
1636 |
|
ceb1f2
|
1637 |
return self._apply_view_derivers(info) |
5bf23f
|
1638 |
|
7c9624
|
1639 |
@viewdefaults |
5bf23f
|
1640 |
@action_method |
a7fe30
|
1641 |
def add_forbidden_view( |
8ec8e2
|
1642 |
self, |
CM |
1643 |
view=None, |
|
1644 |
attr=None, |
|
1645 |
renderer=None, |
|
1646 |
wrapper=None, |
|
1647 |
route_name=None, |
|
1648 |
request_type=None, |
b64851
|
1649 |
request_method=None, |
8ec8e2
|
1650 |
request_param=None, |
CM |
1651 |
containment=None, |
|
1652 |
xhr=None, |
|
1653 |
accept=None, |
|
1654 |
header=None, |
|
1655 |
path_info=None, |
|
1656 |
custom_predicates=(), |
|
1657 |
decorator=None, |
|
1658 |
mapper=None, |
|
1659 |
match_param=None, |
2efc82
|
1660 |
**view_options |
0c29cf
|
1661 |
): |
a7fe30
|
1662 |
""" Add a forbidden view to the current configuration state. The |
CM |
1663 |
view will be called when Pyramid or application code raises a |
|
1664 |
:exc:`pyramid.httpexceptions.HTTPForbidden` exception and the set of |
|
1665 |
circumstances implied by the predicates provided are matched. The |
|
1666 |
simplest example is: |
5bf23f
|
1667 |
|
a7fe30
|
1668 |
.. code-block:: python |
012b97
|
1669 |
|
a7fe30
|
1670 |
def forbidden(request): |
CM |
1671 |
return Response('Forbidden', status='403 Forbidden') |
5bf23f
|
1672 |
|
a7fe30
|
1673 |
config.add_forbidden_view(forbidden) |
5bf23f
|
1674 |
|
dfa449
|
1675 |
If ``view`` argument is not provided, the view callable defaults to |
DK |
1676 |
:func:`~pyramid.httpexceptions.default_exceptionresponse_view`. |
|
1677 |
|
a7fe30
|
1678 |
All arguments have the same meaning as |
CM |
1679 |
:meth:`pyramid.config.Configurator.add_view` and each predicate |
dfa449
|
1680 |
argument restricts the set of circumstances under which this forbidden |
8ec8e2
|
1681 |
view will be invoked. Unlike |
CM |
1682 |
:meth:`pyramid.config.Configurator.add_view`, this method will raise |
e8c66a
|
1683 |
an exception if passed ``name``, ``permission``, ``require_csrf``, |
0fdafb
|
1684 |
``context``, ``for_``, or ``exception_only`` keyword arguments. These |
e8c66a
|
1685 |
argument values make no sense in the context of a forbidden |
MM |
1686 |
:term:`exception view`. |
5bf23f
|
1687 |
|
0b23b3
|
1688 |
.. versionadded:: 1.3 |
e8c66a
|
1689 |
|
MM |
1690 |
.. versionchanged:: 1.8 |
|
1691 |
|
|
1692 |
The view is created using ``exception_only=True``. |
a7fe30
|
1693 |
""" |
2160ce
|
1694 |
for arg in ( |
0c29cf
|
1695 |
'name', |
MM |
1696 |
'permission', |
|
1697 |
'context', |
|
1698 |
'for_', |
|
1699 |
'require_csrf', |
e8c66a
|
1700 |
'exception_only', |
2160ce
|
1701 |
): |
2efc82
|
1702 |
if arg in view_options: |
8ec8e2
|
1703 |
raise ConfigurationError( |
CM |
1704 |
'%s may not be used as an argument to add_forbidden_view' |
0c29cf
|
1705 |
% (arg,) |
MM |
1706 |
) |
b64851
|
1707 |
|
dfa449
|
1708 |
if view is None: |
DK |
1709 |
view = default_exceptionresponse_view |
|
1710 |
|
a7fe30
|
1711 |
settings = dict( |
CM |
1712 |
view=view, |
|
1713 |
context=HTTPForbidden, |
e8c66a
|
1714 |
exception_only=True, |
a7fe30
|
1715 |
wrapper=wrapper, |
CM |
1716 |
request_type=request_type, |
|
1717 |
request_method=request_method, |
|
1718 |
request_param=request_param, |
|
1719 |
containment=containment, |
|
1720 |
xhr=xhr, |
|
1721 |
accept=accept, |
b64851
|
1722 |
header=header, |
a7fe30
|
1723 |
path_info=path_info, |
CM |
1724 |
custom_predicates=custom_predicates, |
|
1725 |
decorator=decorator, |
|
1726 |
mapper=mapper, |
|
1727 |
match_param=match_param, |
|
1728 |
route_name=route_name, |
|
1729 |
permission=NO_PERMISSION_REQUIRED, |
2160ce
|
1730 |
require_csrf=False, |
a7fe30
|
1731 |
attr=attr, |
CM |
1732 |
renderer=renderer, |
0c29cf
|
1733 |
) |
2efc82
|
1734 |
settings.update(view_options) |
a7fe30
|
1735 |
return self.add_view(**settings) |
5bf23f
|
1736 |
|
0c29cf
|
1737 |
set_forbidden_view = add_forbidden_view # deprecated sorta-bw-compat alias |
b64851
|
1738 |
|
7c9624
|
1739 |
@viewdefaults |
5bf23f
|
1740 |
@action_method |
0db4a1
|
1741 |
def add_notfound_view( |
8ec8e2
|
1742 |
self, |
CM |
1743 |
view=None, |
|
1744 |
attr=None, |
|
1745 |
renderer=None, |
|
1746 |
wrapper=None, |
|
1747 |
route_name=None, |
|
1748 |
request_type=None, |
b64851
|
1749 |
request_method=None, |
8ec8e2
|
1750 |
request_param=None, |
CM |
1751 |
containment=None, |
|
1752 |
xhr=None, |
|
1753 |
accept=None, |
|
1754 |
header=None, |
|
1755 |
path_info=None, |
|
1756 |
custom_predicates=(), |
|
1757 |
decorator=None, |
|
1758 |
mapper=None, |
|
1759 |
match_param=None, |
|
1760 |
append_slash=False, |
2efc82
|
1761 |
**view_options |
0c29cf
|
1762 |
): |
e8c66a
|
1763 |
""" Add a default :term:`Not Found View` to the current configuration |
MM |
1764 |
state. The view will be called when Pyramid or application code raises |
160aab
|
1765 |
an :exc:`pyramid.httpexceptions.HTTPNotFound` exception (e.g., when a |
a7fe30
|
1766 |
view cannot be found for the request). The simplest example is: |
5bf23f
|
1767 |
|
0db4a1
|
1768 |
.. code-block:: python |
012b97
|
1769 |
|
a7fe30
|
1770 |
def notfound(request): |
CM |
1771 |
return Response('Not Found', status='404 Not Found') |
|
1772 |
|
|
1773 |
config.add_notfound_view(notfound) |
5bf23f
|
1774 |
|
f10d1e
|
1775 |
If ``view`` argument is not provided, the view callable defaults to |
DK |
1776 |
:func:`~pyramid.httpexceptions.default_exceptionresponse_view`. |
|
1777 |
|
0db4a1
|
1778 |
All arguments except ``append_slash`` have the same meaning as |
CM |
1779 |
:meth:`pyramid.config.Configurator.add_view` and each predicate |
|
1780 |
argument restricts the set of circumstances under which this notfound |
8ec8e2
|
1781 |
view will be invoked. Unlike |
CM |
1782 |
:meth:`pyramid.config.Configurator.add_view`, this method will raise |
e8c66a
|
1783 |
an exception if passed ``name``, ``permission``, ``require_csrf``, |
MM |
1784 |
``context``, ``for_``, or ``exception_only`` keyword arguments. These |
|
1785 |
argument values make no sense in the context of a Not Found View. |
5bf23f
|
1786 |
|
cec2b0
|
1787 |
If ``append_slash`` is ``True``, when this Not Found View is invoked, |
0db4a1
|
1788 |
and the current path info does not end in a slash, the notfound logic |
CM |
1789 |
will attempt to find a :term:`route` that matches the request's path |
|
1790 |
info suffixed with a slash. If such a route exists, Pyramid will |
|
1791 |
issue a redirect to the URL implied by the route; if it does not, |
|
1792 |
Pyramid will return the result of the view callable provided as |
|
1793 |
``view``, as normal. |
5bf23f
|
1794 |
|
24358c
|
1795 |
If the argument provided as ``append_slash`` is not a boolean but |
CM |
1796 |
instead implements :class:`~pyramid.interfaces.IResponse`, the |
|
1797 |
append_slash logic will behave as if ``append_slash=True`` was passed, |
|
1798 |
but the provided class will be used as the response class instead of |
b5422e
|
1799 |
the default :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect` |
DK |
1800 |
response class when a redirect is performed. For example: |
12b6f5
|
1801 |
|
24358c
|
1802 |
.. code-block:: python |
CM |
1803 |
|
|
1804 |
from pyramid.httpexceptions import HTTPMovedPermanently |
|
1805 |
config.add_notfound_view(append_slash=HTTPMovedPermanently) |
|
1806 |
|
|
1807 |
The above means that a redirect to a slash-appended route will be |
a54bc1
|
1808 |
attempted, but instead of |
MM |
1809 |
:class:`~pyramid.httpexceptions.HTTPTemporaryRedirect` |
24358c
|
1810 |
being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will |
CM |
1811 |
be used` for the redirect response if a slash-appended route is found. |
b5422e
|
1812 |
|
DK |
1813 |
:class:`~pyramid.httpexceptions.HTTPTemporaryRedirect` class is used |
|
1814 |
as default response, which is equivalent to |
|
1815 |
:class:`~pyramid.httpexceptions.HTTPFound` with addition of redirecting |
|
1816 |
with the same HTTP method (useful when doing POST requests). |
24358c
|
1817 |
|
CM |
1818 |
.. versionadded:: 1.3 |
e8c66a
|
1819 |
|
MM |
1820 |
.. versionchanged:: 1.6 |
|
1821 |
|
b05765
|
1822 |
The ``append_slash`` argument was modified to allow any object that |
MM |
1823 |
implements the ``IResponse`` interface to specify the response class |
|
1824 |
used when a redirect is performed. |
|
1825 |
|
e8c66a
|
1826 |
.. versionchanged:: 1.8 |
MM |
1827 |
|
|
1828 |
The view is created using ``exception_only=True``. |
b5422e
|
1829 |
|
DK |
1830 |
.. versionchanged: 1.10 |
|
1831 |
|
a54bc1
|
1832 |
Default response was changed from |
MM |
1833 |
:class:`~pyramid.httpexceptions.HTTPFound` |
b5422e
|
1834 |
to :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect`. |
DK |
1835 |
|
5bf23f
|
1836 |
""" |
2160ce
|
1837 |
for arg in ( |
0c29cf
|
1838 |
'name', |
MM |
1839 |
'permission', |
|
1840 |
'context', |
|
1841 |
'for_', |
|
1842 |
'require_csrf', |
e8c66a
|
1843 |
'exception_only', |
2160ce
|
1844 |
): |
2efc82
|
1845 |
if arg in view_options: |
8ec8e2
|
1846 |
raise ConfigurationError( |
CM |
1847 |
'%s may not be used as an argument to add_notfound_view' |
0c29cf
|
1848 |
% (arg,) |
MM |
1849 |
) |
b64851
|
1850 |
|
41ba4d
|
1851 |
if view is None: |
f10d1e
|
1852 |
view = default_exceptionresponse_view |
DK |
1853 |
|
0db4a1
|
1854 |
settings = dict( |
CM |
1855 |
view=view, |
|
1856 |
context=HTTPNotFound, |
e8c66a
|
1857 |
exception_only=True, |
0db4a1
|
1858 |
wrapper=wrapper, |
CM |
1859 |
request_type=request_type, |
|
1860 |
request_method=request_method, |
|
1861 |
request_param=request_param, |
|
1862 |
containment=containment, |
|
1863 |
xhr=xhr, |
|
1864 |
accept=accept, |
b64851
|
1865 |
header=header, |
0db4a1
|
1866 |
path_info=path_info, |
CM |
1867 |
custom_predicates=custom_predicates, |
|
1868 |
decorator=decorator, |
|
1869 |
mapper=mapper, |
|
1870 |
match_param=match_param, |
90a458
|
1871 |
route_name=route_name, |
CM |
1872 |
permission=NO_PERMISSION_REQUIRED, |
2160ce
|
1873 |
require_csrf=False, |
0c29cf
|
1874 |
) |
2efc82
|
1875 |
settings.update(view_options) |
0db4a1
|
1876 |
if append_slash: |
CM |
1877 |
view = self._derive_view(view, attr=attr, renderer=renderer) |
12b6f5
|
1878 |
if IResponse.implementedBy(append_slash): |
DS |
1879 |
view = AppendSlashNotFoundViewFactory( |
0c29cf
|
1880 |
view, redirect_class=append_slash |
12b6f5
|
1881 |
) |
DS |
1882 |
else: |
|
1883 |
view = AppendSlashNotFoundViewFactory(view) |
0db4a1
|
1884 |
settings['view'] = view |
CM |
1885 |
else: |
|
1886 |
settings['attr'] = attr |
|
1887 |
settings['renderer'] = renderer |
|
1888 |
return self.add_view(**settings) |
|
1889 |
|
0c29cf
|
1890 |
set_notfound_view = add_notfound_view # deprecated sorta-bw-compat alias |
5bf23f
|
1891 |
|
d6c90d
|
1892 |
@viewdefaults |
AL |
1893 |
@action_method |
|
1894 |
def add_exception_view( |
|
1895 |
self, |
|
1896 |
view=None, |
|
1897 |
context=None, |
e8c66a
|
1898 |
# force all other arguments to be specified as key=value |
d6c90d
|
1899 |
**view_options |
0c29cf
|
1900 |
): |
e8c66a
|
1901 |
""" Add an :term:`exception view` for the specified ``exception`` to |
MM |
1902 |
the current configuration state. The view will be called when Pyramid |
|
1903 |
or application code raises the given exception. |
|
1904 |
|
160aab
|
1905 |
This method accepts almost all of the same arguments as |
e8c66a
|
1906 |
:meth:`pyramid.config.Configurator.add_view` except for ``name``, |
160aab
|
1907 |
``permission``, ``for_``, ``require_csrf``, and ``exception_only``. |
e8c66a
|
1908 |
|
160aab
|
1909 |
By default, this method will set ``context=Exception``, thus |
e8c66a
|
1910 |
registering for most default Python exceptions. Any subclass of |
MM |
1911 |
``Exception`` may be specified. |
d6c90d
|
1912 |
|
AL |
1913 |
.. versionadded:: 1.8 |
|
1914 |
""" |
|
1915 |
for arg in ( |
0c29cf
|
1916 |
'name', |
MM |
1917 |
'for_', |
|
1918 |
'exception_only', |
|
1919 |
'require_csrf', |
|
1920 |
'permission', |
d6c90d
|
1921 |
): |
AL |
1922 |
if arg in view_options: |
|
1923 |
raise ConfigurationError( |
|
1924 |
'%s may not be used as an argument to add_exception_view' |
0c29cf
|
1925 |
% (arg,) |
MM |
1926 |
) |
d6c90d
|
1927 |
if context is None: |
e8c66a
|
1928 |
context = Exception |
0c29cf
|
1929 |
view_options.update( |
MM |
1930 |
dict( |
|
1931 |
view=view, |
|
1932 |
context=context, |
|
1933 |
exception_only=True, |
|
1934 |
permission=NO_PERMISSION_REQUIRED, |
|
1935 |
require_csrf=False, |
|
1936 |
) |
|
1937 |
) |
e8c66a
|
1938 |
return self.add_view(**view_options) |
d6c90d
|
1939 |
|
5bf23f
|
1940 |
@action_method |
CM |
1941 |
def set_view_mapper(self, mapper): |
|
1942 |
""" |
|
1943 |
Setting a :term:`view mapper` makes it possible to make use of |
|
1944 |
:term:`view callable` objects which implement different call |
|
1945 |
signatures than the ones supported by :app:`Pyramid` as described in |
|
1946 |
its narrative documentation. |
|
1947 |
|
1fefda
|
1948 |
The ``mapper`` argument should be an object implementing |
5bf23f
|
1949 |
:class:`pyramid.interfaces.IViewMapperFactory` or a :term:`dotted |
adfc23
|
1950 |
Python name` to such an object. The provided ``mapper`` will become |
CM |
1951 |
the default view mapper to be used by all subsequent :term:`view |
|
1952 |
configuration` registrations. |
5bf23f
|
1953 |
|
2033ee
|
1954 |
.. seealso:: |
SP |
1955 |
|
|
1956 |
See also :ref:`using_a_view_mapper`. |
adfc23
|
1957 |
|
012b97
|
1958 |
.. note:: |
M |
1959 |
|
|
1960 |
Using the ``default_view_mapper`` argument to the |
adfc23
|
1961 |
:class:`pyramid.config.Configurator` constructor |
CM |
1962 |
can be used to achieve the same purpose. |
5bf23f
|
1963 |
""" |
CM |
1964 |
mapper = self.maybe_dotted(mapper) |
0c29cf
|
1965 |
|
eb2fee
|
1966 |
def register(): |
CM |
1967 |
self.registry.registerUtility(mapper, IViewMapperFactory) |
0c29cf
|
1968 |
|
eb2fee
|
1969 |
# IViewMapperFactory is looked up as the result of view config |
CM |
1970 |
# in phase 3 |
0c29cf
|
1971 |
intr = self.introspectable( |
MM |
1972 |
'view mappers', |
|
1973 |
IViewMapperFactory, |
|
1974 |
self.object_description(mapper), |
|
1975 |
'default view mapper', |
|
1976 |
) |
522405
|
1977 |
intr['mapper'] = mapper |
0c29cf
|
1978 |
self.action( |
MM |
1979 |
IViewMapperFactory, |
|
1980 |
register, |
|
1981 |
order=PHASE1_CONFIG, |
|
1982 |
introspectables=(intr,), |
|
1983 |
) |
5bf23f
|
1984 |
|
CM |
1985 |
@action_method |
|
1986 |
def add_static_view(self, name, path, **kw): |
|
1987 |
""" Add a view used to render static assets such as images |
|
1988 |
and CSS files. |
|
1989 |
|
|
1990 |
The ``name`` argument is a string representing an |
|
1991 |
application-relative local URL prefix. It may alternately be a full |
|
1992 |
URL. |
|
1993 |
|
|
1994 |
The ``path`` argument is the path on disk where the static files |
|
1995 |
reside. This can be an absolute path, a package-relative path, or a |
|
1996 |
:term:`asset specification`. |
|
1997 |
|
|
1998 |
The ``cache_max_age`` keyword argument is input to set the |
|
1999 |
``Expires`` and ``Cache-Control`` headers for static assets served. |
|
2000 |
Note that this argument has no effect when the ``name`` is a *url |
|
2001 |
prefix*. By default, this argument is ``None``, meaning that no |
6e29b4
|
2002 |
particular Expires or Cache-Control headers are set in the response. |
5bf23f
|
2003 |
|
CM |
2004 |
The ``permission`` keyword argument is used to specify the |
|
2005 |
:term:`permission` required by a user to execute the static view. By |
|
2006 |
default, it is the string |
|
2007 |
:data:`pyramid.security.NO_PERMISSION_REQUIRED`, a special sentinel |
|
2008 |
which indicates that, even if a :term:`default permission` exists for |
|
2009 |
the current application, the static view should be renderered to |
|
2010 |
completely anonymous users. This default value is permissive |
|
2011 |
because, in most web apps, static assets seldom need protection from |
|
2012 |
viewing. If ``permission`` is specified, the security checking will |
|
2013 |
be performed against the default root factory ACL. |
|
2014 |
|
|
2015 |
Any other keyword arguments sent to ``add_static_view`` are passed on |
adfc23
|
2016 |
to :meth:`pyramid.config.Configurator.add_route` (e.g. ``factory``, |
5bf23f
|
2017 |
perhaps to define a custom factory with a custom ACL for this static |
CM |
2018 |
view). |
|
2019 |
|
|
2020 |
*Usage* |
|
2021 |
|
|
2022 |
The ``add_static_view`` function is typically used in conjunction |
|
2023 |
with the :meth:`pyramid.request.Request.static_url` method. |
|
2024 |
``add_static_view`` adds a view which renders a static asset when |
|
2025 |
some URL is visited; :meth:`pyramid.request.Request.static_url` |
|
2026 |
generates a URL to that asset. |
|
2027 |
|
5c5857
|
2028 |
The ``name`` argument to ``add_static_view`` is usually a simple URL |
CM |
2029 |
prefix (e.g. ``'images'``). When this is the case, the |
5bf23f
|
2030 |
:meth:`pyramid.request.Request.static_url` API will generate a URL |
CM |
2031 |
which points to a Pyramid view, which will serve up a set of assets |
|
2032 |
that live in the package itself. For example: |
|
2033 |
|
|
2034 |
.. code-block:: python |
|
2035 |
|
|
2036 |
add_static_view('images', 'mypackage:images/') |
|
2037 |
|
|
2038 |
Code that registers such a view can generate URLs to the view via |
|
2039 |
:meth:`pyramid.request.Request.static_url`: |
|
2040 |
|
|
2041 |
.. code-block:: python |
|
2042 |
|
|
2043 |
request.static_url('mypackage:images/logo.png') |
|
2044 |
|
|
2045 |
When ``add_static_view`` is called with a ``name`` argument that |
|
2046 |
represents a URL prefix, as it is above, subsequent calls to |
|
2047 |
:meth:`pyramid.request.Request.static_url` with paths that start with |
|
2048 |
the ``path`` argument passed to ``add_static_view`` will generate a |
|
2049 |
URL something like ``http://<Pyramid app URL>/images/logo.png``, |
|
2050 |
which will cause the ``logo.png`` file in the ``images`` subdirectory |
|
2051 |
of the ``mypackage`` package to be served. |
|
2052 |
|
|
2053 |
``add_static_view`` can alternately be used with a ``name`` argument |
|
2054 |
which is a *URL*, causing static assets to be served from an external |
|
2055 |
webserver. This happens when the ``name`` argument is a fully |
|
2056 |
qualified URL (e.g. starts with ``http://`` or similar). In this |
|
2057 |
mode, the ``name`` is used as the prefix of the full URL when |
|
2058 |
generating a URL using :meth:`pyramid.request.Request.static_url`. |
ff41f8
|
2059 |
Furthermore, if a protocol-relative URL (e.g. ``//example.com/images``) |
WS |
2060 |
is used as the ``name`` argument, the generated URL will use the |
|
2061 |
protocol of the request (http or https, respectively). |
|
2062 |
|
5bf23f
|
2063 |
For example, if ``add_static_view`` is called like so: |
CM |
2064 |
|
|
2065 |
.. code-block:: python |
|
2066 |
|
|
2067 |
add_static_view('http://example.com/images', 'mypackage:images/') |
|
2068 |
|
|
2069 |
Subsequently, the URLs generated by |
|
2070 |
:meth:`pyramid.request.Request.static_url` for that static view will |
ff41f8
|
2071 |
be prefixed with ``http://example.com/images`` (the external webserver |
WS |
2072 |
listening on ``example.com`` must be itself configured to respond |
|
2073 |
properly to such a request.): |
5bf23f
|
2074 |
|
CM |
2075 |
.. code-block:: python |
|
2076 |
|
|
2077 |
static_url('mypackage:images/logo.png', request) |
|
2078 |
|
|
2079 |
See :ref:`static_assets_section` for more information. |
|
2080 |
""" |
|
2081 |
spec = self._make_spec(path) |
6e29b4
|
2082 |
info = self._get_static_info() |
MM |
2083 |
info.add(self, name, spec, **kw) |
|
2084 |
|
4d19b8
|
2085 |
def add_cache_buster(self, path, cachebust, explicit=False): |
6e29b4
|
2086 |
""" |
6923ca
|
2087 |
Add a cache buster to a set of files on disk. |
MM |
2088 |
|
|
2089 |
The ``path`` should be the path on disk where the static files |
|
2090 |
reside. This can be an absolute path, a package-relative path, or a |
|
2091 |
:term:`asset specification`. |
|
2092 |
|
|
2093 |
The ``cachebust`` argument may be set to cause |
6e29b4
|
2094 |
:meth:`~pyramid.request.Request.static_url` to use cache busting when |
MM |
2095 |
generating URLs. See :ref:`cache_busting` for general information |
|
2096 |
about cache busting. The value of the ``cachebust`` argument must |
|
2097 |
be an object which implements |
6923ca
|
2098 |
:class:`~pyramid.interfaces.ICacheBuster`. |
6e29b4
|
2099 |
|
4d19b8
|
2100 |
If ``explicit`` is set to ``True`` then the ``path`` for the cache |
MM |
2101 |
buster will be matched based on the ``rawspec`` instead of the |
|
2102 |
``pathspec`` as defined in the |
|
2103 |
:class:`~pyramid.interfaces.ICacheBuster` interface. |
|
2104 |
Default: ``False``. |
|
2105 |
|
6e29b4
|
2106 |
""" |
MM |
2107 |
spec = self._make_spec(path) |
|
2108 |
info = self._get_static_info() |
4d19b8
|
2109 |
info.add_cache_buster(self, spec, cachebust, explicit=explicit) |
6e29b4
|
2110 |
|
MM |
2111 |
def _get_static_info(self): |
5bf23f
|
2112 |
info = self.registry.queryUtility(IStaticURLInfo) |
CM |
2113 |
if info is None: |
cda7f6
|
2114 |
info = StaticURLInfo() |
5bf23f
|
2115 |
self.registry.registerUtility(info, IStaticURLInfo) |
6e29b4
|
2116 |
return info |
5bf23f
|
2117 |
|
0c29cf
|
2118 |
|
5bf23f
|
2119 |
def isexception(o): |
CM |
2120 |
if IInterface.providedBy(o): |
|
2121 |
if IException.isEqualOrExtendedBy(o): |
|
2122 |
return True |
0c29cf
|
2123 |
return isinstance(o, Exception) or ( |
MM |
2124 |
inspect.isclass(o) and (issubclass(o, Exception)) |
|
2125 |
) |
|
2126 |
|
5bf23f
|
2127 |
|
e8c66a
|
2128 |
def runtime_exc_view(view, excview): |
MM |
2129 |
# create a view callable which can pretend to be both a normal view |
|
2130 |
# and an exception view, dispatching to the appropriate one based |
|
2131 |
# on the state of request.exception |
|
2132 |
def wrapper_view(context, request): |
|
2133 |
if getattr(request, 'exception', None): |
|
2134 |
return excview(context, request) |
|
2135 |
return view(context, request) |
|
2136 |
|
|
2137 |
# these constants are the same between the two views |
|
2138 |
wrapper_view.__wraps__ = wrapper_view |
|
2139 |
wrapper_view.__original_view__ = getattr(view, '__original_view__', view) |
|
2140 |
wrapper_view.__module__ = view.__module__ |
|
2141 |
wrapper_view.__doc__ = view.__doc__ |
|
2142 |
wrapper_view.__name__ = view.__name__ |
|
2143 |
|
|
2144 |
wrapper_view.__accept__ = getattr(view, '__accept__', None) |
|
2145 |
wrapper_view.__order__ = getattr(view, '__order__', MAX_ORDER) |
|
2146 |
wrapper_view.__phash__ = getattr(view, '__phash__', DEFAULT_PHASH) |
|
2147 |
wrapper_view.__view_attr__ = getattr(view, '__view_attr__', None) |
|
2148 |
wrapper_view.__permission__ = getattr(view, '__permission__', None) |
|
2149 |
|
|
2150 |
def wrap_fn(attr): |
|
2151 |
def wrapper(context, request): |
|
2152 |
if getattr(request, 'exception', None): |
|
2153 |
selected_view = excview |
|
2154 |
else: |
|
2155 |
selected_view = view |
|
2156 |
fn = getattr(selected_view, attr, None) |
|
2157 |
if fn is not None: |
|
2158 |
return fn(context, request) |
0c29cf
|
2159 |
|
e8c66a
|
2160 |
return wrapper |
MM |
2161 |
|
|
2162 |
# these methods are dynamic per-request and should dispatch to their |
|
2163 |
# respective views based on whether it's an exception or not |
|
2164 |
wrapper_view.__call_permissive__ = wrap_fn('__call_permissive__') |
|
2165 |
wrapper_view.__permitted__ = wrap_fn('__permitted__') |
|
2166 |
wrapper_view.__predicated__ = wrap_fn('__predicated__') |
|
2167 |
wrapper_view.__predicates__ = wrap_fn('__predicates__') |
|
2168 |
return wrapper_view |
|
2169 |
|
0c29cf
|
2170 |
|
007600
|
2171 |
@implementer(IViewDeriverInfo) |
MM |
2172 |
class ViewDeriverInfo(object): |
0c29cf
|
2173 |
def __init__( |
MM |
2174 |
self, view, registry, package, predicates, exception_only, options |
|
2175 |
): |
a610d0
|
2176 |
self.original_view = view |
007600
|
2177 |
self.registry = registry |
MM |
2178 |
self.package = package |
|
2179 |
self.predicates = predicates or [] |
|
2180 |
self.options = options or {} |
e8c66a
|
2181 |
self.exception_only = exception_only |
007600
|
2182 |
|
MM |
2183 |
@reify |
|
2184 |
def settings(self): |
|
2185 |
return self.registry.settings |
53d9d4
|
2186 |
|
0c29cf
|
2187 |
|
3b7334
|
2188 |
@implementer(IStaticURLInfo) |
53d9d4
|
2189 |
class StaticURLInfo(object): |
6e29b4
|
2190 |
def __init__(self): |
MM |
2191 |
self.registrations = [] |
|
2192 |
self.cache_busters = [] |
53d9d4
|
2193 |
|
CM |
2194 |
def generate(self, path, request, **kw): |
6e29b4
|
2195 |
for (url, spec, route_name) in self.registrations: |
53d9d4
|
2196 |
if path.startswith(spec): |
0c29cf
|
2197 |
subpath = path[len(spec) :] |
MM |
2198 |
if WIN: # pragma: no cover |
|
2199 |
subpath = subpath.replace('\\', '/') # windows |
5e3439
|
2200 |
if self.cache_busters: |
aecb47
|
2201 |
subpath, kw = self._bust_asset_path( |
0c29cf
|
2202 |
request, spec, subpath, kw |
MM |
2203 |
) |
bc9357
|
2204 |
if url is None: |
53d9d4
|
2205 |
kw['subpath'] = subpath |
bc9357
|
2206 |
return request.route_url(route_name, **kw) |
CM |
2207 |
else: |
498342
|
2208 |
app_url, qs, anchor = parse_url_overrides(request, kw) |
ff41f8
|
2209 |
parsed = url_parse(url) |
WS |
2210 |
if not parsed.scheme: |
0c29cf
|
2211 |
url = urlparse.urlunparse( |
MM |
2212 |
parsed._replace( |
|
2213 |
scheme=request.environ['wsgi.url_scheme'] |
|
2214 |
) |
|
2215 |
) |
05f462
|
2216 |
subpath = url_quote(subpath) |
cd5ab5
|
2217 |
result = urljoin(url, subpath) |
af3134
|
2218 |
return result + qs + anchor |
53d9d4
|
2219 |
|
CM |
2220 |
raise ValueError('No static URL definition matching %s' % path) |
|
2221 |
|
cda7f6
|
2222 |
def add(self, config, name, spec, **extra): |
53d9d4
|
2223 |
# This feature only allows for the serving of a directory and |
CM |
2224 |
# the files contained within, not of a single asset; |
|
2225 |
# appending a slash here if the spec doesn't have one is |
|
2226 |
# required for proper prefix matching done in ``generate`` |
|
2227 |
# (``subpath = path[len(spec):]``). |
0c29cf
|
2228 |
if os.path.isabs(spec): # FBO windows |
2f665d
|
2229 |
sep = os.sep |
CM |
2230 |
else: |
|
2231 |
sep = '/' |
e7745a
|
2232 |
if not spec.endswith(sep) and not spec.endswith(':'): |
2f665d
|
2233 |
spec = spec + sep |
53d9d4
|
2234 |
|
CM |
2235 |
# we also make sure the name ends with a slash, purely as a |
|
2236 |
# convenience: a name that is a url is required to end in a |
|
2237 |
# slash, so that ``urljoin(name, subpath))`` will work above |
|
2238 |
# when the name is a URL, and it doesn't hurt things for it to |
|
2239 |
# have a name that ends in a slash if it's used as a route |
|
2240 |
# name instead of a URL. |
|
2241 |
if not name.endswith('/'): |
|
2242 |
# make sure it ends with a slash |
|
2243 |
name = name + '/' |
|
2244 |
|
ff41f8
|
2245 |
if url_parse(name).netloc: |
53d9d4
|
2246 |
# it's a URL |
bc9357
|
2247 |
# url, spec, route_name |
CM |
2248 |
url = name |
|
2249 |
route_name = None |
53d9d4
|
2250 |
else: |
CM |
2251 |
# it's a view name |
bc9357
|
2252 |
url = None |
6e29b4
|
2253 |
cache_max_age = extra.pop('cache_max_age', None) |
0445bf
|
2254 |
|
53d9d4
|
2255 |
# create a view |
0c29cf
|
2256 |
view = static_view( |
MM |
2257 |
spec, cache_max_age=cache_max_age, use_subpath=True |
|
2258 |
) |
53d9d4
|
2259 |
|
CM |
2260 |
# Mutate extra to allow factory, etc to be passed through here. |
|
2261 |
# Treat permission specially because we'd like to default to |
fdf30b
|
2262 |
# permissiveness (see docs of config.add_static_view). |
CM |
2263 |
permission = extra.pop('permission', None) |
53d9d4
|
2264 |
if permission is None: |
CM |
2265 |
permission = NO_PERMISSION_REQUIRED |
|
2266 |
|
fdf30b
|
2267 |
context = extra.pop('context', None) |
53d9d4
|
2268 |
if context is None: |
CM |
2269 |
context = extra.pop('for_', None) |
|
2270 |
|
fdf30b
|
2271 |
renderer = extra.pop('renderer', None) |
53d9d4
|
2272 |
|
012b97
|
2273 |
# register a route using the computed view, permission, and |
53d9d4
|
2274 |
# pattern, plus any extras passed to us via add_static_view |
0c29cf
|
2275 |
pattern = "%s*subpath" % name # name already ends with slash |
bc9357
|
2276 |
if config.route_prefix: |
CM |
2277 |
route_name = '__%s/%s' % (config.route_prefix, name) |
|
2278 |
else: |
|
2279 |
route_name = '__%s' % name |
|
2280 |
config.add_route(route_name, pattern, **extra) |
cda7f6
|
2281 |
config.add_view( |
bc9357
|
2282 |
route_name=route_name, |
cda7f6
|
2283 |
view=view, |
CM |
2284 |
permission=permission, |
|
2285 |
context=context, |
|
2286 |
renderer=renderer, |
25c64c
|
2287 |
) |
bc9357
|
2288 |
|
CM |
2289 |
def register(): |
6e29b4
|
2290 |
registrations = self.registrations |
bc9357
|
2291 |
|
25c64c
|
2292 |
names = [t[0] for t in registrations] |
bc9357
|
2293 |
|
CM |
2294 |
if name in names: |
|
2295 |
idx = names.index(name) |
|
2296 |
registrations.pop(idx) |
|
2297 |
|
|
2298 |
# url, spec, route_name |
6e29b4
|
2299 |
registrations.append((url, spec, route_name)) |
bc9357
|
2300 |
|
0c29cf
|
2301 |
intr = config.introspectable( |
MM |
2302 |
'static views', name, 'static view for %r' % name, 'static view' |
|
2303 |
) |
773948
|
2304 |
intr['name'] = name |
CM |
2305 |
intr['spec'] = spec |
|
2306 |
|
|
2307 |
config.action(None, callable=register, introspectables=(intr,)) |
9d521e
|
2308 |
|
4d19b8
|
2309 |
def add_cache_buster(self, config, spec, cachebust, explicit=False): |
6923ca
|
2310 |
# ensure the spec always has a trailing slash as we only support |
MM |
2311 |
# adding cache busters to folders, not files |
0c29cf
|
2312 |
if os.path.isabs(spec): # FBO windows |
6923ca
|
2313 |
sep = os.sep |
MM |
2314 |
else: |
|
2315 |
sep = '/' |
|
2316 |
if not spec.endswith(sep) and not spec.endswith(':'): |
|
2317 |
spec = spec + sep |
|
2318 |
|
6e29b4
|
2319 |
def register(): |
ca573e
|
2320 |
if config.registry.settings.get('pyramid.prevent_cachebust'): |
MM |
2321 |
return |
|
2322 |
|
6e29b4
|
2323 |
cache_busters = self.cache_busters |
MM |
2324 |
|
4d19b8
|
2325 |
# find duplicate cache buster (old_idx) |
MM |
2326 |
# and insertion location (new_idx) |
|
2327 |
new_idx, old_idx = len(cache_busters), None |
|
2328 |
for idx, (spec_, cb_, explicit_) in enumerate(cache_busters): |
|
2329 |
# if we find an identical (spec, explicit) then use it |
|
2330 |
if spec == spec_ and explicit == explicit_: |
|
2331 |
old_idx = new_idx = idx |
|
2332 |
break |
6e29b4
|
2333 |
|
4d19b8
|
2334 |
# past all explicit==False specs then add to the end |
MM |
2335 |
elif not explicit and explicit_: |
|
2336 |
new_idx = idx |
|
2337 |
break |
|
2338 |
|
|
2339 |
# explicit matches and spec is shorter |
|
2340 |
elif explicit == explicit_ and len(spec) < len(spec_): |
|
2341 |
new_idx = idx |
|
2342 |
break |
|
2343 |
|
|
2344 |
if old_idx is not None: |
|
2345 |
cache_busters.pop(old_idx) |
ca573e
|
2346 |
|
4d19b8
|
2347 |
cache_busters.insert(new_idx, (spec, cachebust, explicit)) |
6e29b4
|
2348 |
|
0c29cf
|
2349 |
intr = config.introspectable( |
MM |
2350 |
'cache busters', spec, 'cache buster for %r' % spec, 'cache buster' |
|
2351 |
) |
6e29b4
|
2352 |
intr['cachebust'] = cachebust |
4d19b8
|
2353 |
intr['path'] = spec |
MM |
2354 |
intr['explicit'] = explicit |
6e29b4
|
2355 |
|
MM |
2356 |
config.action(None, callable=register, introspectables=(intr,)) |
|
2357 |
|
4d19b8
|
2358 |
def _bust_asset_path(self, request, spec, subpath, kw): |
MM |
2359 |
registry = request.registry |
5e3439
|
2360 |
pkg_name, pkg_subpath = resolve_asset_spec(spec) |
ffad12
|
2361 |
rawspec = None |
5e3439
|
2362 |
|
MM |
2363 |
if pkg_name is not None: |
4d19b8
|
2364 |
pathspec = '{0}:{1}{2}'.format(pkg_name, pkg_subpath, subpath) |
5e3439
|
2365 |
overrides = registry.queryUtility(IPackageOverrides, name=pkg_name) |
MM |
2366 |
if overrides is not None: |
|
2367 |
resource_name = posixpath.join(pkg_subpath, subpath) |
|
2368 |
sources = overrides.filtered_sources(resource_name) |
|
2369 |
for source, filtered_path in sources: |
|
2370 |
rawspec = source.get_path(filtered_path) |
|
2371 |
if hasattr(source, 'pkg_name'): |
|
2372 |
rawspec = '{0}:{1}'.format(source.pkg_name, rawspec) |
|
2373 |
break |
|
2374 |
|
4d19b8
|
2375 |
else: |
MM |
2376 |
pathspec = pkg_subpath + subpath |
d0bd5f
|
2377 |
|
ffad12
|
2378 |
if rawspec is None: |
4d19b8
|
2379 |
rawspec = pathspec |
ffad12
|
2380 |
|
4d19b8
|
2381 |
kw['pathspec'] = pathspec |
MM |
2382 |
kw['rawspec'] = rawspec |
|
2383 |
for spec_, cachebust, explicit in reversed(self.cache_busters): |
0c29cf
|
2384 |
if (explicit and rawspec.startswith(spec_)) or ( |
MM |
2385 |
not explicit and pathspec.startswith(spec_) |
4d19b8
|
2386 |
): |
MM |
2387 |
subpath, kw = cachebust(request, subpath, kw) |
d0bd5f
|
2388 |
break |
MM |
2389 |
return subpath, kw |