8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS |
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS |
10 # FOR A PARTICULAR PURPOSE. |
10 # FOR A PARTICULAR PURPOSE. |
11 # |
11 # |
12 |
12 |
|
13 """PyAMS_utils.url module |
|
14 |
|
15 This module provides several functions, adapters and TALES extensions which can be used to |
|
16 generate object's URLs. |
|
17 |
|
18 Three kinds of URLs can be used: |
|
19 - an absolute URL, which is the standard way to access an object via it's physical path |
|
20 - a canonical URL; this URL is the "preferred" one used to access an object, and is typically |
|
21 used by search engines to index contents |
|
22 - a relative URL; some contents can use this kind of URL to get access to an object from another |
|
23 context. |
|
24 """ |
|
25 |
13 from pyramid.encode import url_quote, urlencode |
26 from pyramid.encode import url_quote, urlencode |
14 from pyramid.url import QUERY_SAFE, resource_url |
27 from pyramid.url import QUERY_SAFE, resource_url |
15 from pyramid_zope_request import PyramidPublisherRequest |
28 from pyramid_zope_request import PyramidPublisherRequest |
16 from zope.interface import Interface |
29 from zope.interface import Interface |
17 |
30 |
36 >>> from pyams_utils.url import generate_url |
49 >>> from pyams_utils.url import generate_url |
37 >>> generate_url('This is my test') |
50 >>> generate_url('This is my test') |
38 'this-is-my-test' |
51 'this-is-my-test' |
39 |
52 |
40 Single letters are removed from generated URLs: |
53 Single letters are removed from generated URLs: |
|
54 |
41 >>> generate_url('This word has a single a') |
55 >>> generate_url('This word has a single a') |
42 'this-word-has-single' |
56 'this-word-has-single' |
43 |
57 |
44 But you can define the minimum length of word: |
58 But you can define the minimum length of word: |
|
59 |
45 >>> generate_url('This word has a single a', min_word_length=4) |
60 >>> generate_url('This word has a single a', min_word_length=4) |
46 'this-word-single' |
61 'this-word-single' |
47 |
62 |
48 If input text contains slashes, they are replaced with hyphens: |
63 If input text contains slashes, they are replaced with hyphens: |
|
64 |
49 >>> generate_url('This string contains/slash') |
65 >>> generate_url('This string contains/slash') |
50 'this-string-contains-slash' |
66 'this-string-contains-slash' |
51 |
67 |
52 Punctation and special characters are completely removed: |
68 Punctation and special characters are completely removed: |
|
69 |
53 >>> generate_url('This is a string with a point. And why not?') |
70 >>> generate_url('This is a string with a point. And why not?') |
54 'this-is-string-with-point-and-why-not' |
71 'this-is-string-with-point-and-why-not' |
55 """ |
72 """ |
56 return '-'.join(filter(lambda x: len(x) >= min_word_length, |
73 return '-'.join(filter(lambda x: len(x) >= min_word_length, |
57 translate_string(title.replace('/', '-'), escape_slashes=False, |
74 translate_string(title.replace('/', '-'), escape_slashes=False, |
62 # |
79 # |
63 # Request display context |
80 # Request display context |
64 # |
81 # |
65 |
82 |
66 def get_display_context(request): |
83 def get_display_context(request): |
|
84 """Get current display context |
|
85 |
|
86 The display context can be used when we generate a page to display an object in the context |
|
87 of another one; PyAMS_content package is using this feature to display "shared" contents as |
|
88 is they were located inside another site or folder... |
|
89 """ |
67 return request.annotations.get(DISPLAY_CONTEXT, request.context) |
90 return request.annotations.get(DISPLAY_CONTEXT, request.context) |
68 |
91 |
69 |
92 |
70 # |
93 # |
71 # Absolute URLs management |
94 # Absolute URLs management |
97 if view_name.startswith('#'): |
120 if view_name.startswith('#'): |
98 result += view_name |
121 result += view_name |
99 else: |
122 else: |
100 result += '/' + view_name |
123 result += '/' + view_name |
101 if query: |
124 if query: |
102 qs = '' |
125 qstr = '' |
103 if isinstance(query, str): |
126 if isinstance(query, str): |
104 qs = '?' + url_quote(query, QUERY_SAFE) |
127 qstr = '?' + url_quote(query, QUERY_SAFE) |
105 elif query: |
128 elif query: |
106 qs = '?' + urlencode(query, doseq=True) |
129 qstr = '?' + urlencode(query, doseq=True) |
107 result += qs |
130 result += qstr |
108 return result |
131 return result |
109 |
132 |
110 |
133 |
111 @adapter_config(name='absolute_url', context=(Interface, Interface, Interface), |
134 @adapter_config(name='absolute_url', context=(Interface, Interface, Interface), |
112 provides=ITALESExtension) |
135 provides=ITALESExtension) |
115 |
138 |
116 A PyAMS TALES extension used to get access to an object URL from a page template. |
139 A PyAMS TALES extension used to get access to an object URL from a page template. |
117 """ |
140 """ |
118 |
141 |
119 def render(self, context=None, view_name=None): |
142 def render(self, context=None, view_name=None): |
|
143 """Extension rendering; see |
|
144 :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>` |
|
145 """ |
120 if context is None: |
146 if context is None: |
121 context = self.context |
147 context = self.context |
122 return absolute_url(context, self.request, view_name) |
148 return absolute_url(context, self.request, view_name) |
123 |
149 |
124 |
150 |
125 # |
151 # |
126 # Canonical URLs management |
152 # Canonical URLs management |
127 # |
153 # |
128 |
154 |
129 def canonical_url(context, request, view_name=None, query=None): |
155 def canonical_url(context, request, view_name=None, query=None): |
130 """Get resource canonical URL""" |
156 """Get resource canonical URL |
|
157 |
|
158 We look for an :py:class:`ICanonicalURL <pyams_utils.interfaces.url.ICanonicalURL>` adapter; |
|
159 if none is found, we use the absolute_url. |
|
160 """ |
131 |
161 |
132 # if we pass a string to canonical_url(), argument is returned as-is! |
162 # if we pass a string to canonical_url(), argument is returned as-is! |
133 if isinstance(context, str): |
163 if isinstance(context, str): |
134 return context |
164 return context |
135 |
165 |
137 if url_adapter is None: |
167 if url_adapter is None: |
138 url_adapter = request.registry.queryAdapter(context, ICanonicalURL) |
168 url_adapter = request.registry.queryAdapter(context, ICanonicalURL) |
139 |
169 |
140 if url_adapter is not None: |
170 if url_adapter is not None: |
141 return url_adapter.get_url(view_name, query) |
171 return url_adapter.get_url(view_name, query) |
142 else: |
172 return absolute_url(context, request, view_name, query) |
143 return absolute_url(context, request, view_name, query) |
|
144 |
173 |
145 |
174 |
146 @adapter_config(name='canonical_url', context=(Interface, Interface, Interface), |
175 @adapter_config(name='canonical_url', context=(Interface, Interface, Interface), |
147 provides=ITALESExtension) |
176 provides=ITALESExtension) |
148 class CanonicalUrlTalesExtension(ContextRequestViewAdapter): |
177 class CanonicalUrlTalesExtension(ContextRequestViewAdapter): |
150 |
179 |
151 A PyAMS TALES extension used to get access to an object's canonical URL from a page template. |
180 A PyAMS TALES extension used to get access to an object's canonical URL from a page template. |
152 """ |
181 """ |
153 |
182 |
154 def render(self, context=None, view_name=None): |
183 def render(self, context=None, view_name=None): |
|
184 """Render TALES extension; see |
|
185 :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>` |
|
186 """ |
155 if context is None: |
187 if context is None: |
156 context = self.context |
188 context = self.context |
157 return canonical_url(context, self.request, view_name) |
189 return canonical_url(context, self.request, view_name) |
158 |
190 |
159 |
191 |
164 @adapter_config(context=(Interface, Interface), provides=IRelativeURL) |
196 @adapter_config(context=(Interface, Interface), provides=IRelativeURL) |
165 class DefaultRelativeURLAdapter(ContextRequestAdapter): |
197 class DefaultRelativeURLAdapter(ContextRequestAdapter): |
166 """Default relative URL adapter""" |
198 """Default relative URL adapter""" |
167 |
199 |
168 def get_url(self, display_context=None, view_name=None, query=None): |
200 def get_url(self, display_context=None, view_name=None, query=None): |
|
201 # pylint: disable=unused-argument |
|
202 """Default adapter returns absolute URL""" |
169 return absolute_url(self.context, self.request, view_name, query) |
203 return absolute_url(self.context, self.request, view_name, query) |
170 |
204 |
171 |
205 |
172 def relative_url(context, request, display_context=None, view_name=None, query=None): |
206 def relative_url(context, request, display_context=None, view_name=None, query=None): |
173 """Get resource URL relative to given context""" |
207 """Get resource URL relative to given context""" |
174 if isinstance(request, PyramidPublisherRequest): |
208 if isinstance(request, PyramidPublisherRequest): |
175 request = request._request |
209 request = request._request # pylint: disable=protected-access |
176 if display_context is None: |
210 if display_context is None: |
177 display_context = request.annotations.get(DISPLAY_CONTEXT, request.context) |
211 display_context = request.annotations.get(DISPLAY_CONTEXT, request.context) |
178 adapter = request.registry.getMultiAdapter((context, request), IRelativeURL) |
212 adapter = request.registry.getMultiAdapter((context, request), IRelativeURL) |
179 return adapter.get_url(display_context, view_name, query) |
213 return adapter.get_url(display_context, view_name, query) |
180 |
214 |
187 A PyAMS TALES extension used to get an object's relative URL based on current request display |
221 A PyAMS TALES extension used to get an object's relative URL based on current request display |
188 context. |
222 context. |
189 """ |
223 """ |
190 |
224 |
191 def render(self, context=None, view_name=None, query=None): |
225 def render(self, context=None, view_name=None, query=None): |
|
226 """Rander TALES extension; |
|
227 see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>` |
|
228 """ |
192 if context is None: |
229 if context is None: |
193 context = self.context |
230 context = self.context |
194 return relative_url(context, self.request, view_name=view_name, query=query) |
231 return relative_url(context, self.request, view_name=view_name, query=query) |