commit | author | age
|
c81aad
|
1 |
""" Utility functions for dealing with URLs in pyramid """ |
7ae0c2
|
2 |
|
750ce4
|
3 |
import os |
7ae0c2
|
4 |
|
206188
|
5 |
from zope.deprecation import deprecated |
CM |
6 |
|
afc4bb
|
7 |
from repoze.lru import lru_cache |
CM |
8 |
|
b60bdb
|
9 |
from pyramid.interfaces import IContextURL |
CM |
10 |
from pyramid.interfaces import IRoutesMapper |
|
11 |
from pyramid.interfaces import IStaticURLInfo |
a97a70
|
12 |
|
b60bdb
|
13 |
from pyramid.encode import urlencode |
CM |
14 |
from pyramid.path import caller_package |
|
15 |
from pyramid.threadlocal import get_current_registry |
|
16 |
from pyramid.traversal import TraversalContextURL |
|
17 |
from pyramid.traversal import quote_path_segment |
7ae0c2
|
18 |
|
05c023
|
19 |
def route_url(route_name, request, *elements, **kw): |
fd5ae9
|
20 |
"""Generates a fully qualified URL for a named :app:`Pyramid` |
8b1f6e
|
21 |
:term:`route configuration`. |
820bd9
|
22 |
|
ffca4e
|
23 |
.. note:: Calling :meth:`pyramid.Request.route_url` can be used to |
36c159
|
24 |
achieve the same result as :func:`pyramid.url.route_url`. |
CM |
25 |
|
60996a
|
26 |
Use the route's ``name`` as the first positional argument. Use a |
CM |
27 |
request object as the second positional argument. Additional |
9a9af8
|
28 |
positional arguments are appended to the URL as path segments |
CM |
29 |
after it is generated. |
820bd9
|
30 |
|
05c023
|
31 |
Use keyword arguments to supply values which match any dynamic |
c6895b
|
32 |
path elements in the route definition. Raises a :exc:`KeyError` |
05c023
|
33 |
exception if the URL cannot be generated for any reason (not |
CM |
34 |
enough arguments, for example). |
dc405b
|
35 |
|
5a11c0
|
36 |
For example, if you've defined a route named "foobar" with the path |
c82eb9
|
37 |
``{foo}/{bar}/*traverse``:: |
dc405b
|
38 |
|
05c023
|
39 |
route_url('foobar', request, foo='1') => <KeyError exception> |
CM |
40 |
route_url('foobar', request, foo='1', bar='2') => <KeyError exception> |
|
41 |
route_url('foobar', request, foo='1', bar='2', |
820bd9
|
42 |
traverse=('a','b')) => http://e.com/1/2/a/b |
a8447a
|
43 |
route_url('foobar', request, foo='1', bar='2', |
820bd9
|
44 |
traverse='/a/b') => http://e.com/1/2/a/b |
a8447a
|
45 |
|
CM |
46 |
Values replacing ``:segment`` arguments can be passed as strings |
|
47 |
or Unicode objects. They will be encoded to UTF-8 and URL-quoted |
|
48 |
before being placed into the generated URL. |
|
49 |
|
|
50 |
Values replacing ``*remainder`` arguments can be passed as strings |
|
51 |
*or* tuples of Unicode/string values. If a tuple is passed as a |
|
52 |
``*remainder`` replacement value, its values are URL-quoted and |
|
53 |
encoded to UTF-8. The resulting strings are joined with slashes |
|
54 |
and rendered into the URL. If a string is passed as a |
|
55 |
``*remainder`` replacement value, it is tacked on to the URL |
|
56 |
untouched. |
dc405b
|
57 |
|
ae4039
|
58 |
If a keyword argument ``_query`` is present, it will be used to |
05c023
|
59 |
compose a query string that will be tacked on to the end of the |
c6895b
|
60 |
URL. The value of ``_query`` must be a sequence of two-tuples |
CM |
61 |
*or* a data structure with an ``.items()`` method that returns a |
05c023
|
62 |
sequence of two-tuples (presumably a dictionary). This data |
CM |
63 |
structure will be turned into a query string per the documentation |
c81aad
|
64 |
of :func:`pyramid.encode.urlencode` function. After the query |
c6895b
|
65 |
data is turned into a query string, a leading ``?`` is prepended, |
c5f24b
|
66 |
and the resulting string is appended to the generated URL. |
dc405b
|
67 |
|
05c023
|
68 |
.. note:: Python data structures that are passed as ``_query`` |
CM |
69 |
which are sequences or dictionaries are turned into a |
|
70 |
string under the same rules as when run through |
c6895b
|
71 |
:func:`urllib.urlencode` with the ``doseq`` argument |
CM |
72 |
equal to ``True``. This means that sequences can be |
|
73 |
passed as values, and a k=v pair will be placed into the |
|
74 |
query string for each value. |
05c023
|
75 |
|
CM |
76 |
If a keyword argument ``_anchor`` is present, its string |
|
77 |
representation will be used as a named anchor in the generated URL |
fb6a5c
|
78 |
(e.g. if ``_anchor`` is passed as ``foo`` and the route URL is |
CM |
79 |
``http://example.com/route/url``, the resulting generated URL will |
|
80 |
be ``http://example.com/route/url#foo``). |
05c023
|
81 |
|
CM |
82 |
.. note:: If ``_anchor`` is passed as a string, it should be UTF-8 |
7a65f9
|
83 |
encoded. If ``_anchor`` is passed as a Unicode object, it |
05c023
|
84 |
will be converted to UTF-8 before being appended to the |
CM |
85 |
URL. The anchor value is not quoted in any way before |
|
86 |
being appended to the generated URL. |
|
87 |
|
7a65f9
|
88 |
If both ``_anchor`` and ``_query`` are specified, the anchor |
CM |
89 |
element will always follow the query element, |
05c023
|
90 |
e.g. ``http://example.com?foo=1#bar``. |
CM |
91 |
|
b29429
|
92 |
If a keyword ``_app_url`` is present, it will be used as the |
CM |
93 |
protocol/hostname/port/leading path prefix of the generated URL. |
|
94 |
For example, using an ``_app_url`` of |
|
95 |
``http://example.com:8080/foo`` would cause the URL |
|
96 |
``http://example.com:8080/foo/fleeb/flub`` to be returned from |
|
97 |
this function if the expansion of the route pattern associated |
|
98 |
with the ``route_name`` expanded to ``/fleeb/flub``. If |
|
99 |
``_app_url`` is not specified, the result of |
|
100 |
``request.application_url`` will be used as the prefix (the |
|
101 |
default). |
6bc662
|
102 |
|
c6895b
|
103 |
This function raises a :exc:`KeyError` if the URL cannot be |
CM |
104 |
generated due to missing replacement names. Extra replacement |
|
105 |
names are ignored. |
70f1cd
|
106 |
|
CM |
107 |
If the route object which matches the ``route_name`` argument has |
|
108 |
a :term:`pregenerator`, the ``*elements`` and ``**kw`` arguments |
|
109 |
arguments passed to this function might be augmented or changed. |
36c159
|
110 |
|
dc405b
|
111 |
""" |
6fec21
|
112 |
try: |
CM |
113 |
reg = request.registry |
|
114 |
except AttributeError: |
|
115 |
reg = get_current_registry() # b/c |
|
116 |
mapper = reg.getUtility(IRoutesMapper) |
70f1cd
|
117 |
route = mapper.get_route(route_name) |
CM |
118 |
|
|
119 |
if route is None: |
|
120 |
raise KeyError('No such route named %s' % route_name) |
|
121 |
|
66051f
|
122 |
if route.pregenerator is not None: |
70f1cd
|
123 |
elements, kw = route.pregenerator(request, elements, kw) |
05c023
|
124 |
|
CM |
125 |
anchor = '' |
|
126 |
qs = '' |
b29429
|
127 |
app_url = None |
05c023
|
128 |
|
CM |
129 |
if '_query' in kw: |
f0b74a
|
130 |
qs = '?' + urlencode(kw.pop('_query'), doseq=True) |
05c023
|
131 |
|
CM |
132 |
if '_anchor' in kw: |
f0b74a
|
133 |
anchor = kw.pop('_anchor') |
05c023
|
134 |
if isinstance(anchor, unicode): |
CM |
135 |
anchor = anchor.encode('utf-8') |
|
136 |
anchor = '#' + anchor |
|
137 |
|
b29429
|
138 |
if '_app_url' in kw: |
CM |
139 |
app_url = kw.pop('_app_url') |
|
140 |
|
70f1cd
|
141 |
path = route.generate(kw) # raises KeyError if generate fails |
f0b74a
|
142 |
|
05c023
|
143 |
if elements: |
afc4bb
|
144 |
suffix = _join_elements(elements) |
05c023
|
145 |
if not path.endswith('/'): |
CM |
146 |
suffix = '/' + suffix |
|
147 |
else: |
|
148 |
suffix = '' |
|
149 |
|
b29429
|
150 |
if app_url is None: |
CM |
151 |
# we only defer lookup of application_url until here because |
|
152 |
# it's somewhat expensive; we won't need to do it if we've |
|
153 |
# been passed _app_url |
|
154 |
app_url = request.application_url |
|
155 |
|
|
156 |
return app_url + path + suffix + qs + anchor |
dc405b
|
157 |
|
2c9d14
|
158 |
def route_path(route_name, request, *elements, **kw): |
CM |
159 |
"""Generates a path (aka a 'relative URL', a URL minus the host, scheme, |
|
160 |
and port) for a named :app:`Pyramid` :term:`route configuration`. |
|
161 |
|
ffca4e
|
162 |
.. note:: Calling :meth:`pyramid.Request.route_path` can be used to |
2c9d14
|
163 |
achieve the same result as :func:`pyramid.url.route_path`. |
CM |
164 |
|
|
165 |
This function accepts the same argument as :func:`pyramid.url.route_url` |
|
166 |
and performs the same duty. It just omits the host, port, and scheme |
0a0edf
|
167 |
information in the return value; only the script_name, path, |
CM |
168 |
query parameters, and anchor data are present in the returned string. |
2c9d14
|
169 |
|
CM |
170 |
For example, if you've defined a route named 'foobar' with the path |
df3f64
|
171 |
``/{foo}/{bar}``, this call to ``route_path``:: |
2c9d14
|
172 |
|
CM |
173 |
route_path('foobar', request, foo='1', bar='2') |
|
174 |
|
|
175 |
Will return the string ``/1/2``. |
|
176 |
|
|
177 |
.. note:: Calling ``route_path('route', request)`` is the same as calling |
0a0edf
|
178 |
``route_url('route', request, _app_url=request.script_name)``. |
CM |
179 |
``route_path`` is, in fact, implemented in terms of ``route_url`` |
|
180 |
in just this way. As a result, any ``_app_url`` passed within the |
|
181 |
``**kw`` values to ``route_path`` will be ignored. |
2c9d14
|
182 |
""" |
0a0edf
|
183 |
kw['_app_url'] = request.script_name |
647815
|
184 |
return route_url(route_name, request, *elements, **kw) |
2c9d14
|
185 |
|
fb6a5c
|
186 |
def resource_url(resource, request, *elements, **kw): |
7ae0c2
|
187 |
""" |
fb6a5c
|
188 |
Generate a string representing the absolute URL of the :term:`resource` |
8b1f6e
|
189 |
object based on the ``wsgi.url_scheme``, ``HTTP_HOST`` or |
CM |
190 |
``SERVER_NAME`` in the ``request``, plus any ``SCRIPT_NAME``. The |
|
191 |
overall result of this function is always a UTF-8 encoded string |
c6895b
|
192 |
(never Unicode). |
36c159
|
193 |
|
ffca4e
|
194 |
.. note:: Calling :meth:`pyramid.Request.resource_url` can be used to |
fb6a5c
|
195 |
achieve the same result as :func:`pyramid.url.resource_url`. |
7ae0c2
|
196 |
|
c8d6ab
|
197 |
Examples:: |
CM |
198 |
|
fb6a5c
|
199 |
resource_url(context, request) => |
c8d6ab
|
200 |
|
CM |
201 |
http://example.com/ |
|
202 |
|
fb6a5c
|
203 |
resource_url(context, request, 'a.html') => |
c8d6ab
|
204 |
|
CM |
205 |
http://example.com/a.html |
|
206 |
|
fb6a5c
|
207 |
resource_url(context, request, 'a.html', query={'q':'1'}) => |
c8d6ab
|
208 |
|
CM |
209 |
http://example.com/a.html?q=1 |
|
210 |
|
fb6a5c
|
211 |
resource_url(context, request, 'a.html', anchor='abc') => |
c8d6ab
|
212 |
|
8b1f6e
|
213 |
http://example.com/a.html#abc |
c8d6ab
|
214 |
|
7ae0c2
|
215 |
Any positional arguments passed in as ``elements`` must be strings |
fcbd7b
|
216 |
Unicode objects, or integer objects. These will be joined by slashes and |
CM |
217 |
appended to the generated resource URL. Each of the elements passed in |
|
218 |
is URL-quoted before being appended; if any element is Unicode, it will |
|
219 |
converted to a UTF-8 bytestring before being URL-quoted. If any element |
|
220 |
is an integer, it will be converted to its string representation before |
|
221 |
being URL-quoted. |
7ae0c2
|
222 |
|
fb6a5c
|
223 |
.. warning:: if no ``elements`` arguments are specified, the resource |
7ae0c2
|
224 |
URL will end with a trailing slash. If any |
CM |
225 |
``elements`` are used, the generated URL will *not* |
|
226 |
end in trailing a slash. |
|
227 |
|
ae4039
|
228 |
If a keyword argument ``query`` is present, it will be used to |
7ae0c2
|
229 |
compose a query string that will be tacked on to the end of the |
CM |
230 |
URL. The value of ``query`` must be a sequence of two-tuples *or* |
|
231 |
a data structure with an ``.items()`` method that returns a |
|
232 |
sequence of two-tuples (presumably a dictionary). This data |
|
233 |
structure will be turned into a query string per the documentation |
169711
|
234 |
of ``pyramid.url.urlencode`` function. After the query data is |
7ae0c2
|
235 |
turned into a query string, a leading ``?`` is prepended, and the |
c5f24b
|
236 |
resulting string is appended to the generated URL. |
7ae0c2
|
237 |
|
CM |
238 |
.. note:: Python data structures that are passed as ``query`` |
e62e47
|
239 |
which are sequences or dictionaries are turned into a |
7ae0c2
|
240 |
string under the same rules as when run through |
c6895b
|
241 |
:func:`urllib.urlencode` with the ``doseq`` argument |
CM |
242 |
equal to ``True``. This means that sequences can be |
|
243 |
passed as values, and a k=v pair will be placed into the |
|
244 |
query string for each value. |
e693ce
|
245 |
|
MN |
246 |
If a keyword argument ``anchor`` is present, its string |
|
247 |
representation will be used as a named anchor in the generated URL |
fb6a5c
|
248 |
(e.g. if ``anchor`` is passed as ``foo`` and the resource URL is |
CM |
249 |
``http://example.com/resource/url``, the resulting generated URL will |
|
250 |
be ``http://example.com/resource/url#foo``). |
e693ce
|
251 |
|
MN |
252 |
.. note:: If ``anchor`` is passed as a string, it should be UTF-8 |
|
253 |
encoded. If ``anchor`` is passed as a Unicode object, it |
|
254 |
will be converted to UTF-8 before being appended to the |
|
255 |
URL. The anchor value is not quoted in any way before |
|
256 |
being appended to the generated URL. |
|
257 |
|
|
258 |
If both ``anchor`` and ``query`` are specified, the anchor element |
|
259 |
will always follow the query element, |
|
260 |
e.g. ``http://example.com?foo=1#bar``. |
8b1f6e
|
261 |
|
bac5b3
|
262 |
If the ``resource`` passed in has a ``__resource_url__`` method, it will |
c4d401
|
263 |
be used to generate the URL (scheme, host, port, path) that for the base |
CM |
264 |
resource which is operated upon by this function. See also |
bac5b3
|
265 |
:ref:`overriding_resource_url_generation`. |
CM |
266 |
|
fb6a5c
|
267 |
.. note:: If the :term:`resource` used is the result of a |
8b1f6e
|
268 |
:term:`traversal`, it must be :term:`location`-aware. |
fb6a5c
|
269 |
The resource can also be the context of a :term:`URL |
8b1f6e
|
270 |
dispatch`; contexts found this way do not need to be |
CM |
271 |
location-aware. |
|
272 |
|
|
273 |
.. note:: If a 'virtual root path' is present in the request |
|
274 |
environment (the value of the WSGI environ key |
fb6a5c
|
275 |
``HTTP_X_VHM_ROOT``), and the resource was obtained via |
8b1f6e
|
276 |
:term:`traversal`, the URL path will not include the |
CM |
277 |
virtual root prefix (it will be stripped off the |
|
278 |
left hand side of the generated URL). |
92c3e5
|
279 |
|
206188
|
280 |
.. note:: For backwards compatibility purposes, this function can also be |
CM |
281 |
imported as ``model_url``, although doing so will emit a deprecation |
|
282 |
warning. |
003908
|
283 |
""" |
6fec21
|
284 |
try: |
CM |
285 |
reg = request.registry |
|
286 |
except AttributeError: |
|
287 |
reg = get_current_registry() # b/c |
003908
|
288 |
|
fb6a5c
|
289 |
context_url = reg.queryMultiAdapter((resource, request), IContextURL) |
da442d
|
290 |
if context_url is None: |
fb6a5c
|
291 |
context_url = TraversalContextURL(resource, request) |
CM |
292 |
resource_url = context_url() |
925957
|
293 |
|
e693ce
|
294 |
qs = '' |
MN |
295 |
anchor = '' |
|
296 |
|
7ae0c2
|
297 |
if 'query' in kw: |
CM |
298 |
qs = '?' + urlencode(kw['query'], doseq=True) |
e693ce
|
299 |
|
MN |
300 |
if 'anchor' in kw: |
|
301 |
anchor = kw['anchor'] |
|
302 |
if isinstance(anchor, unicode): |
|
303 |
anchor = anchor.encode('utf-8') |
|
304 |
anchor = '#' + anchor |
925957
|
305 |
|
CM |
306 |
if elements: |
afc4bb
|
307 |
suffix = _join_elements(elements) |
925957
|
308 |
else: |
CM |
309 |
suffix = '' |
|
310 |
|
fb6a5c
|
311 |
return resource_url + suffix + qs + anchor |
CM |
312 |
|
92c3e5
|
313 |
model_url = resource_url # b/w compat (forever) |
7ae0c2
|
314 |
|
206188
|
315 |
deprecated( |
CM |
316 |
'model_url', |
|
317 |
'pyramid.url.model_url is deprecated as of Pyramid 1.0. Use' |
|
318 |
'``pyramid.url.resource_url`` instead (API-compat, simple ' |
|
319 |
'rename).') |
|
320 |
|
750ce4
|
321 |
def static_url(path, request, **kw): |
CM |
322 |
""" |
3e2f12
|
323 |
Generates a fully qualified URL for a static :term:`asset`. |
CM |
324 |
The asset must live within a location defined via the |
aff443
|
325 |
:meth:`pyramid.config.Configurator.add_static_view` |
c1eb0c
|
326 |
:term:`configuration declaration` (see :ref:`static_assets_section`). |
750ce4
|
327 |
|
ffca4e
|
328 |
.. note:: Calling :meth:`pyramid.Request.static_url` can be used to |
36c159
|
329 |
achieve the same result as :func:`pyramid.url.static_url`. |
CM |
330 |
|
06173d
|
331 |
Example:: |
CM |
332 |
|
|
333 |
static_url('mypackage:static/foo.css', request) => |
|
334 |
|
|
335 |
http://example.com/static/foo.css |
|
336 |
|
|
337 |
|
750ce4
|
338 |
The ``path`` argument points at a file or directory on disk which |
CM |
339 |
a URL should be generated for. The ``path`` may be either a |
3e2f12
|
340 |
relative path (e.g. ``static/foo.css``) or a :term:`asset |
750ce4
|
341 |
specification` (e.g. ``mypackage:static/foo.css``). A ``path`` |
c6895b
|
342 |
may not be an absolute filesystem path (a :exc:`ValueError` will |
CM |
343 |
be raised if this function is supplied with an absolute path). |
750ce4
|
344 |
|
8b1f6e
|
345 |
The ``request`` argument should be a :term:`request` object. |
750ce4
|
346 |
|
CM |
347 |
The purpose of the ``**kw`` argument is the same as the purpose of |
c81aad
|
348 |
the :func:`pyramid.url.route_url` ``**kw`` argument. See the |
c6895b
|
349 |
documentation for that function to understand the arguments which |
CM |
350 |
you can provide to it. However, typically, you don't need to pass |
3e2f12
|
351 |
anything as ``*kw`` when generating a static asset URL. |
750ce4
|
352 |
|
c6895b
|
353 |
This function raises a :exc:`ValueError` if a static view |
CM |
354 |
definition cannot be found which matches the path specification. |
750ce4
|
355 |
|
CM |
356 |
""" |
|
357 |
if os.path.isabs(path): |
|
358 |
raise ValueError('Absolute paths cannot be used to generate static ' |
3e2f12
|
359 |
'urls (use a package-relative path or an asset ' |
750ce4
|
360 |
'specification).') |
CM |
361 |
if not ':' in path: |
|
362 |
# if it's not a package:relative/name and it's not an |
|
363 |
# /absolute/path it's a relative/path; this means its relative |
|
364 |
# to the package in which the caller's module is defined. |
13c923
|
365 |
package = caller_package() |
750ce4
|
366 |
path = '%s:%s' % (package.__name__, path) |
6fec21
|
367 |
|
CM |
368 |
try: |
|
369 |
reg = request.registry |
|
370 |
except AttributeError: |
|
371 |
reg = get_current_registry() # b/c |
750ce4
|
372 |
|
b29429
|
373 |
info = reg.queryUtility(IStaticURLInfo) |
CM |
374 |
if info is None: |
|
375 |
raise ValueError('No static URL definition matching %s' % path) |
|
376 |
|
|
377 |
return info.generate(path, request, **kw) |
7ae0c2
|
378 |
|
5653d1
|
379 |
def current_route_url(request, *elements, **kw): |
b23e6e
|
380 |
"""Generates a fully qualified URL for a named :app:`Pyramid` |
5653d1
|
381 |
:term:`route configuration` based on the 'current route'. |
b23e6e
|
382 |
|
5653d1
|
383 |
This function supplements :func:`pyramid.url.route_url`. It presents an |
CM |
384 |
easy way to generate a URL for the 'current route' (defined as the route |
|
385 |
which matched when the request was generated). |
|
386 |
|
|
387 |
The arguments to this function have the same meaning as those with the |
|
388 |
same names passed to :func:`pyramid.url.route_url`. It also understands |
|
389 |
an extra argument which ``route_url`` does not named ``_route_name``. |
|
390 |
|
|
391 |
The route name used to generate a URL is taken from either the |
|
392 |
``_route_name`` keyword argument or the name of the route which is |
|
393 |
currently associated with the request if ``_route_name`` was not passed. |
|
394 |
Keys and values from the current request :term:`matchdict` are combined |
|
395 |
with the ``kw`` arguments to form a set of defaults named ``newkw``. |
|
396 |
Then ``route_url(route_name, request, *elements, **newkw)`` is called, |
|
397 |
returning a URL. |
|
398 |
|
|
399 |
Examples follow. |
|
400 |
|
|
401 |
If the 'current route' has the route pattern ``/foo/{page}`` and the |
|
402 |
current url path is ``/foo/1`` , the matchdict will be ``{'page':'1'}``. |
|
403 |
The result of ``current_route_url(request)`` in this situation will be |
|
404 |
``/foo/1``. |
|
405 |
|
|
406 |
If the 'current route' has the route pattern ``/foo/{page}`` and the |
bad27c
|
407 |
current url path is ``/foo/1``, the matchdict will be |
5653d1
|
408 |
``{'page':'1'}``. The result of ``current_route_url(request, page='2')`` |
CM |
409 |
in this situation will be ``/foo/2``. |
b23e6e
|
410 |
|
5653d1
|
411 |
Usage of the ``_route_name`` keyword argument: if our routing table |
CM |
412 |
defines routes ``/foo/{action}`` named 'foo' and ``/foo/{action}/{page}`` |
|
413 |
named ``fooaction``, and the current url pattern is ``/foo/view`` (which |
|
414 |
has matched the ``/foo/{action}`` route), we may want to use the |
|
415 |
matchdict args to generate a URL to the ``fooaction`` route. In this |
e6ba3c
|
416 |
scenario, ``current_route_url(request, _route_name='fooaction', page='5')`` |
5653d1
|
417 |
Will return string like: ``/foo/view/5``. |
b23e6e
|
418 |
""" |
5653d1
|
419 |
|
CM |
420 |
if '_route_name' in kw: |
|
421 |
route_name = kw.pop('_route_name') |
b23e6e
|
422 |
else: |
5653d1
|
423 |
route = getattr(request, 'matched_route', None) |
CM |
424 |
route_name = getattr(route, 'name', None) |
|
425 |
if route_name is None: |
|
426 |
raise ValueError('Current request matches no route') |
|
427 |
|
|
428 |
newkw = {} |
|
429 |
newkw.update(request.matchdict) |
|
430 |
newkw.update(kw) |
|
431 |
return route_url(route_name, request, *elements, **newkw) |
b23e6e
|
432 |
|
afc4bb
|
433 |
@lru_cache(1000) |
CM |
434 |
def _join_elements(elements): |
8cf91a
|
435 |
return '/'.join([quote_path_segment(s, safe=':@&+$,') for s in elements]) |