commit | author | age
|
fdaa89
|
1 |
import mimetypes |
e6fa66
|
2 |
import venusian |
CM |
3 |
|
4174e4
|
4 |
from zope.interface import providedBy |
5f4780
|
5 |
from zope.deprecation import deprecated |
7e2c6c
|
6 |
|
b60bdb
|
7 |
from pyramid.interfaces import IRoutesMapper |
CM |
8 |
from pyramid.interfaces import IView |
|
9 |
from pyramid.interfaces import IViewClassifier |
d66bfb
|
10 |
|
99edc5
|
11 |
from pyramid.httpexceptions import HTTPFound |
CM |
12 |
from pyramid.httpexceptions import default_exceptionresponse_view |
95c9f6
|
13 |
from pyramid.renderers import RendererHelper |
b33dca
|
14 |
from pyramid.static import static_view |
b60bdb
|
15 |
from pyramid.threadlocal import get_current_registry |
b29429
|
16 |
|
70d504
|
17 |
def init_mimetypes(mimetypes): |
CM |
18 |
# this is a function so it can be unittested |
|
19 |
if hasattr(mimetypes, 'init'): |
|
20 |
mimetypes.init() |
|
21 |
return True |
|
22 |
return False |
|
23 |
|
|
24 |
# See http://bugs.python.org/issue5853 which is a recursion bug |
|
25 |
# that seems to effect Python 2.6, Python 2.6.1, and 2.6.2 (a fix |
|
26 |
# has been applied on the Python 2 trunk). This workaround should |
|
27 |
# really be in Paste if anywhere, but it's easiest to just do it |
|
28 |
# here and get it over with to avoid needing to deal with any |
|
29 |
# fallout. |
|
30 |
init_mimetypes(mimetypes) |
|
31 |
|
87a85f
|
32 |
# Nasty BW compat hack: dont yet deprecate this (ever?) |
b33dca
|
33 |
class static(static_view): # only subclass for purposes of autodoc |
CM |
34 |
__doc__ = static_view.__doc__ |
d75fe7
|
35 |
|
f66290
|
36 |
_marker = object() |
85078f
|
37 |
|
7e2c6c
|
38 |
def render_view_to_response(context, request, name='', secure=True): |
b93d19
|
39 |
""" Call the :term:`view callable` configured with a :term:`view |
c5f24b
|
40 |
configuration` that matches the :term:`view name` ``name`` |
b93d19
|
41 |
registered against the specified ``context`` and ``request`` and |
CM |
42 |
return a :term:`response` object. This function will return |
|
43 |
``None`` if a corresponding :term:`view callable` cannot be found |
|
44 |
(when no :term:`view configuration` matches the combination of |
|
45 |
``name`` / ``context`` / and ``request``). |
8b1f6e
|
46 |
|
b93d19
|
47 |
If `secure`` is ``True``, and the :term:`view callable` found is |
99edc5
|
48 |
protected by a permission, the permission will be checked before calling |
CM |
49 |
the view function. If the permission check disallows view execution |
|
50 |
(based on the current :term:`authorization policy`), a |
|
51 |
:exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised. |
|
52 |
The exception's ``args`` attribute explains why the view access was |
|
53 |
disallowed. |
b93d19
|
54 |
|
7e2c6c
|
55 |
If ``secure`` is ``False``, no permission checking is done.""" |
ff1213
|
56 |
provides = [IViewClassifier] + map(providedBy, (request, context)) |
d0b398
|
57 |
try: |
CM |
58 |
reg = request.registry |
|
59 |
except AttributeError: |
|
60 |
reg = get_current_registry() |
1c0210
|
61 |
view = reg.adapters.lookup(provides, IView, name=name) |
d66bfb
|
62 |
if view is None: |
CM |
63 |
return None |
17ce57
|
64 |
|
d66bfb
|
65 |
if not secure: |
7850a4
|
66 |
# the view will have a __call_permissive__ attribute if it's |
d66bfb
|
67 |
# secured; otherwise it won't. |
CM |
68 |
view = getattr(view, '__call_permissive__', view) |
|
69 |
|
d75fe7
|
70 |
# if this view is secured, it will raise a Forbidden |
d66bfb
|
71 |
# appropriately if the executing user does not have the proper |
CM |
72 |
# permission |
|
73 |
return view(context, request) |
7e2c6c
|
74 |
|
CM |
75 |
def render_view_to_iterable(context, request, name='', secure=True): |
b93d19
|
76 |
""" Call the :term:`view callable` configured with a :term:`view |
c5f24b
|
77 |
configuration` that matches the :term:`view name` ``name`` |
b93d19
|
78 |
registered against the specified ``context`` and ``request`` and |
CM |
79 |
return an iterable object which represents the body of a response. |
|
80 |
This function will return ``None`` if a corresponding :term:`view |
|
81 |
callable` cannot be found (when no :term:`view configuration` |
|
82 |
matches the combination of ``name`` / ``context`` / and |
|
83 |
``request``). Additionally, this function will raise a |
|
84 |
:exc:`ValueError` if a view function is found and called but the |
|
85 |
view function's result does not have an ``app_iter`` attribute. |
8b1f6e
|
86 |
|
b93d19
|
87 |
You can usually get the string representation of the return value |
CM |
88 |
of this function by calling ``''.join(iterable)``, or just use |
c81aad
|
89 |
:func:`pyramid.view.render_view` instead. |
8b1f6e
|
90 |
|
99edc5
|
91 |
If ``secure`` is ``True``, and the view is protected by a permission, the |
CM |
92 |
permission will be checked before the view function is invoked. If the |
|
93 |
permission check disallows view execution (based on the current |
|
94 |
:term:`authentication policy`), a |
|
95 |
:exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised; its |
|
96 |
``args`` attribute explains why the view access was disallowed. |
b93d19
|
97 |
|
CM |
98 |
If ``secure`` is ``False``, no permission checking is |
|
99 |
done.""" |
7e2c6c
|
100 |
response = render_view_to_response(context, request, name, secure) |
CM |
101 |
if response is None: |
|
102 |
return None |
|
103 |
return response.app_iter |
|
104 |
|
885bfb
|
105 |
def render_view(context, request, name='', secure=True): |
b93d19
|
106 |
""" Call the :term:`view callable` configured with a :term:`view |
c5f24b
|
107 |
configuration` that matches the :term:`view name` ``name`` |
27d735
|
108 |
registered against the specified ``context`` and ``request`` |
c5f24b
|
109 |
and unwind the view response's ``app_iter`` (see |
b93d19
|
110 |
:ref:`the_response`) into a single string. This function will |
CM |
111 |
return ``None`` if a corresponding :term:`view callable` cannot be |
|
112 |
found (when no :term:`view configuration` matches the combination |
|
113 |
of ``name`` / ``context`` / and ``request``). Additionally, this |
|
114 |
function will raise a :exc:`ValueError` if a view function is |
|
115 |
found and called but the view function's result does not have an |
|
116 |
``app_iter`` attribute. This function will return ``None`` if a |
|
117 |
corresponding view cannot be found. |
8b1f6e
|
118 |
|
99edc5
|
119 |
If ``secure`` is ``True``, and the view is protected by a permission, the |
CM |
120 |
permission will be checked before the view is invoked. If the permission |
|
121 |
check disallows view execution (based on the current :term:`authorization |
|
122 |
policy`), a :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be |
|
123 |
raised; its ``args`` attribute explains why the view access was |
b93d19
|
124 |
disallowed. |
CM |
125 |
|
885bfb
|
126 |
If ``secure`` is ``False``, no permission checking is done.""" |
CM |
127 |
iterable = render_view_to_iterable(context, request, name, secure) |
|
128 |
if iterable is None: |
|
129 |
return None |
|
130 |
return ''.join(iterable) |
|
131 |
|
197f0c
|
132 |
class view_config(object): |
8b1f6e
|
133 |
""" A function, class or method :term:`decorator` which allows a |
CM |
134 |
developer to create view registrations nearer to a :term:`view |
c1eb0c
|
135 |
callable` definition than use :term:`imperative |
8b1f6e
|
136 |
configuration` to do the same. |
5a7f9a
|
137 |
|
8b1f6e
|
138 |
For example, this code in a module ``views.py``:: |
5a7f9a
|
139 |
|
3e2f12
|
140 |
from resources import MyResource |
5a7f9a
|
141 |
|
3e2f12
|
142 |
@view_config(name='my_view', context=MyResource, permission='read', |
197f0c
|
143 |
route_name='site1') |
5a7f9a
|
144 |
def my_view(context, request): |
8b1f6e
|
145 |
return 'OK' |
5a7f9a
|
146 |
|
b93d19
|
147 |
Might replace the following call to the |
aff443
|
148 |
:meth:`pyramid.config.Configurator.add_view` method:: |
8b1f6e
|
149 |
|
CM |
150 |
import views |
3e2f12
|
151 |
from resources import MyResource |
CM |
152 |
config.add_view(views.my_view, context=MyResource, name='my_view', |
8b1f6e
|
153 |
permission='read', 'route_name='site1') |
5a7f9a
|
154 |
|
197f0c
|
155 |
.. note: :class:`pyramid.view.view_config` is also importable, for |
CM |
156 |
backwards compatibility purposes, as the name |
|
157 |
:class:`pyramid.view.bfg_view`. |
|
158 |
|
8b1f6e
|
159 |
The following arguments are supported as arguments to |
cf7d8b
|
160 |
:class:`pyramid.view.view_config`: ``context``, ``permission``, ``name``, |
CM |
161 |
``request_type``, ``route_name``, ``request_method``, ``request_param``, |
|
162 |
``containment``, ``xhr``, ``accept``, ``header``, ``path_info``, |
0fa199
|
163 |
``custom_predicates``, ``decorator``, ``mapper``, and ``http_cache``. |
5a7f9a
|
164 |
|
cf7d8b
|
165 |
The meanings of these arguments are the same as the arguments passed to |
CM |
166 |
:meth:`pyramid.config.Configurator.add_view`. |
0b0e74
|
167 |
|
cb14cd
|
168 |
See :ref:`mapping_views_using_a_decorator_section` for details about |
CM |
169 |
using :class:`view_config`. |
5a7f9a
|
170 |
|
CM |
171 |
""" |
e6fa66
|
172 |
venusian = venusian # for testing injection |
dfc2b6
|
173 |
def __init__(self, name='', request_type=None, for_=None, permission=None, |
d66bfb
|
174 |
route_name=None, request_method=None, request_param=None, |
083422
|
175 |
containment=None, attr=None, renderer=None, wrapper=None, |
6225a2
|
176 |
xhr=False, accept=None, header=None, path_info=None, |
1d9ade
|
177 |
custom_predicates=(), context=None, decorator=None, |
0fa199
|
178 |
mapper=None, http_cache=None): |
5a7f9a
|
179 |
self.name = name |
CM |
180 |
self.request_type = request_type |
eecdbc
|
181 |
self.context = context or for_ |
5a7f9a
|
182 |
self.permission = permission |
dfc2b6
|
183 |
self.route_name = route_name |
d66bfb
|
184 |
self.request_method = request_method |
CM |
185 |
self.request_param = request_param |
|
186 |
self.containment = containment |
04e182
|
187 |
self.attr = attr |
a9fed7
|
188 |
self.renderer = renderer |
1dc390
|
189 |
self.wrapper = wrapper |
083422
|
190 |
self.xhr = xhr |
CM |
191 |
self.accept = accept |
|
192 |
self.header = header |
7a13fb
|
193 |
self.path_info = path_info |
6225a2
|
194 |
self.custom_predicates = custom_predicates |
1d9ade
|
195 |
self.decorator = decorator |
5e3d64
|
196 |
self.mapper = mapper |
0fa199
|
197 |
self.http_cache = http_cache |
5a7f9a
|
198 |
|
CM |
199 |
def __call__(self, wrapped): |
e6fa66
|
200 |
settings = self.__dict__.copy() |
CM |
201 |
|
|
202 |
def callback(context, name, ob): |
95c9f6
|
203 |
renderer = settings.get('renderer') |
CM |
204 |
if isinstance(renderer, basestring): |
|
205 |
renderer = RendererHelper(name=renderer, |
|
206 |
package=info.module, |
|
207 |
registry=context.config.registry) |
|
208 |
settings['renderer'] = renderer |
e6fa66
|
209 |
context.config.add_view(view=ob, **settings) |
CM |
210 |
|
cba2e1
|
211 |
info = self.venusian.attach(wrapped, callback, category='pyramid') |
e6fa66
|
212 |
|
CM |
213 |
if info.scope == 'class': |
32418e
|
214 |
# if the decorator was attached to a method in a class, or |
CM |
215 |
# otherwise executed at class scope, we need to set an |
|
216 |
# 'attr' into the settings if one isn't already in there |
e6fa66
|
217 |
if settings['attr'] is None: |
CM |
218 |
settings['attr'] = wrapped.__name__ |
89968d
|
219 |
|
14b78b
|
220 |
settings['_info'] = info.codeinfo |
c89bcb
|
221 |
return wrapped |
5a7f9a
|
222 |
|
87a85f
|
223 |
bfg_view = view_config |
197f0c
|
224 |
|
5f4780
|
225 |
deprecated( |
CM |
226 |
'bfg_view', |
|
227 |
'pyramid.view.bfg_view is deprecated as of Pyramid 1.0. Use ' |
|
228 |
'pyramid.view.view_config instead (API-compat, simple ' |
|
229 |
'rename).') |
|
230 |
|
d96ff9
|
231 |
class AppendSlashNotFoundViewFactory(object): |
CM |
232 |
""" There can only be one :term:`Not Found view` in any |
fd5ae9
|
233 |
:app:`Pyramid` application. Even if you use |
c81aad
|
234 |
:func:`pyramid.view.append_slash_notfound_view` as the Not |
fd5ae9
|
235 |
Found view, :app:`Pyramid` still must generate a ``404 Not |
d96ff9
|
236 |
Found`` response when it cannot redirect to a slash-appended URL; |
CM |
237 |
this not found response will be visible to site users. |
|
238 |
|
|
239 |
If you don't care what this 404 response looks like, and you only |
|
240 |
need redirections to slash-appended route URLs, you may use the |
c81aad
|
241 |
:func:`pyramid.view.append_slash_notfound_view` object as the |
d96ff9
|
242 |
Not Found view. However, if you wish to use a *custom* notfound |
CM |
243 |
view callable when a URL cannot be redirected to a slash-appended |
|
244 |
URL, you may wish to use an instance of this class as the Not |
|
245 |
Found view, supplying a :term:`view callable` to be used as the |
|
246 |
custom notfound view as the first argument to its constructor. |
|
247 |
For instance: |
|
248 |
|
|
249 |
.. code-block:: python |
|
250 |
|
99edc5
|
251 |
from pyramid.httpexceptions import HTTPNotFound |
c81aad
|
252 |
from pyramid.view import AppendSlashNotFoundViewFactory |
d96ff9
|
253 |
|
1b4360
|
254 |
def notfound_view(context, request): return HTTPNotFound('nope') |
d96ff9
|
255 |
|
CM |
256 |
custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view) |
a7e625
|
257 |
config.add_view(custom_append_slash, context=HTTPNotFound) |
d96ff9
|
258 |
|
CM |
259 |
The ``notfound_view`` supplied must adhere to the two-argument |
|
260 |
view callable calling convention of ``(context, request)`` |
|
261 |
(``context`` will be the exception object). |
|
262 |
|
|
263 |
""" |
|
264 |
def __init__(self, notfound_view=None): |
|
265 |
if notfound_view is None: |
|
266 |
notfound_view = default_exceptionresponse_view |
|
267 |
self.notfound_view = notfound_view |
|
268 |
|
|
269 |
def __call__(self, context, request): |
|
270 |
if not isinstance(context, Exception): |
|
271 |
# backwards compat for an append_notslash_view registered via |
|
272 |
# config.set_notfound_view instead of as a proper exception view |
8c2a9e
|
273 |
context = getattr(request, 'exception', None) or context |
b596e1
|
274 |
path = request.path |
30e64f
|
275 |
registry = request.registry |
d96ff9
|
276 |
mapper = registry.queryUtility(IRoutesMapper) |
CM |
277 |
if mapper is not None and not path.endswith('/'): |
|
278 |
slashpath = path + '/' |
|
279 |
for route in mapper.get_routes(): |
|
280 |
if route.match(slashpath) is not None: |
b596e1
|
281 |
qs = request.query_string |
CM |
282 |
if qs: |
|
283 |
slashpath += '?' + qs |
d96ff9
|
284 |
return HTTPFound(location=slashpath) |
CM |
285 |
return self.notfound_view(context, request) |
a9454c
|
286 |
|
d96ff9
|
287 |
append_slash_notfound_view = AppendSlashNotFoundViewFactory() |
CM |
288 |
append_slash_notfound_view.__doc__ = """\ |
|
289 |
For behavior like Django's ``APPEND_SLASH=True``, use this view as the |
|
290 |
:term:`Not Found view` in your application. |
a9454c
|
291 |
|
87a85f
|
292 |
When this view is the Not Found view (indicating that no view was found), and |
CM |
293 |
any routes have been defined in the configuration of your application, if the |
|
294 |
value of the ``PATH_INFO`` WSGI environment variable does not already end in |
|
295 |
a slash, and if the value of ``PATH_INFO`` *plus* a slash matches any route's |
|
296 |
path, do an HTTP redirect to the slash-appended PATH_INFO. Note that this |
|
297 |
will *lose* ``POST`` data information (turning it into a GET), so you |
|
298 |
shouldn't rely on this to redirect POST requests. Note also that static |
|
299 |
routes are not considered when attempting to find a matching route. |
d66bfb
|
300 |
|
c1eb0c
|
301 |
Use the :meth:`pyramid.config.Configurator.add_view` method to configure this |
CM |
302 |
view as the Not Found view:: |
8b1f6e
|
303 |
|
99edc5
|
304 |
from pyramid.httpexceptions import HTTPNotFound |
c81aad
|
305 |
from pyramid.view import append_slash_notfound_view |
a7e625
|
306 |
config.add_view(append_slash_notfound_view, context=HTTPNotFound) |
8b1f6e
|
307 |
|
d96ff9
|
308 |
See also :ref:`changing_the_notfound_view`. |
d66bfb
|
309 |
|
d96ff9
|
310 |
""" |
d66bfb
|
311 |
|
ce9b9b
|
312 |
def is_response(ob): |
CM |
313 |
""" Return ``True`` if ``ob`` implements the interface implied by |
|
314 |
:ref:`the_response`. ``False`` if not. |
|
315 |
|
920990
|
316 |
.. warning:: This function is deprecated as of :app:`Pyramid` 1.1. New |
CM |
317 |
code should not use it. Instead, new code should use the |
|
318 |
:func:`pyramid.request.Request.is_response` method.""" |
ce9b9b
|
319 |
if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and |
CM |
320 |
hasattr(ob, 'status') ): |
|
321 |
return True |
|
322 |
return False |
ab27bd
|
323 |
|
920990
|
324 |
deprecated( |
CM |
325 |
'is_response', |
|
326 |
'pyramid.view.is_response is deprecated as of Pyramid 1.1. Use ' |
|
327 |
'pyramid.request.Request.is_response instead.') |