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