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