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