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