1 | """URL Helpers""" |
---|
2 | # Last synced with Rails copy at Revision 4374 on Aug 20th, 2006. |
---|
3 | |
---|
4 | import cgi |
---|
5 | import urllib |
---|
6 | |
---|
7 | from webhelpers.util import html_escape |
---|
8 | |
---|
9 | from routes import url_for, request_config |
---|
10 | from javascript import * |
---|
11 | import tags |
---|
12 | |
---|
13 | def get_url(url): |
---|
14 | if callable(url): |
---|
15 | return url() |
---|
16 | else: |
---|
17 | return url |
---|
18 | |
---|
19 | def url(*args, **kargs): |
---|
20 | """ |
---|
21 | Lazily evaluates url_for() arguments |
---|
22 | |
---|
23 | Used instead of url_for() for functions so that the function will be evaluated |
---|
24 | in a lazy manner rather than at initial function call. |
---|
25 | """ |
---|
26 | args = args |
---|
27 | kargs = kargs |
---|
28 | def call(): |
---|
29 | return url_for(*args, **kargs) |
---|
30 | return call |
---|
31 | |
---|
32 | def link_to(name, url='', **html_options): |
---|
33 | """ |
---|
34 | Creates a link tag of the given ``name`` using an URL created by the set of ``options``. |
---|
35 | |
---|
36 | See the valid options in the documentation for Routes url_for. |
---|
37 | |
---|
38 | The html_options has three special features. One for creating javascript confirm alerts where if you pass |
---|
39 | ``confirm='Are you sure?'`` , the link will be guarded with a JS popup asking that question. If the user |
---|
40 | accepts, the link is processed, otherwise not. |
---|
41 | |
---|
42 | Another for creating a popup window, which is done by either passing ``popup`` with True or the options |
---|
43 | of the window in Javascript form. |
---|
44 | |
---|
45 | And a third for making the link do a POST request (instead of the regular GET) through a dynamically added |
---|
46 | form element that is instantly submitted. Note that if the user has turned off Javascript, the request will |
---|
47 | fall back on the GET. So its your responsibility to determine what the action should be once it arrives at |
---|
48 | the controller. The POST form is turned on by passing ``post`` as True. Note, it's not possible to use POST |
---|
49 | requests and popup targets at the same time (an exception will be thrown). |
---|
50 | |
---|
51 | Examples:: |
---|
52 | |
---|
53 | >>> link_to("Delete this page", url(action="destroy", id=4), confirm="Are you sure?") |
---|
54 | >>> link_to("Help", url(action="help"), popup=True) |
---|
55 | >>> link_to("Busy loop", url(action="busy"), popup=['new_window', 'height=300,width=600']) |
---|
56 | >>> link_to("Destroy account", url(action="destroy"), confirm="Are you sure?", method='delete') |
---|
57 | """ |
---|
58 | if html_options: |
---|
59 | html_options = convert_options_to_javascript(**html_options) |
---|
60 | tag_op = tags.tag_options(**html_options) |
---|
61 | else: |
---|
62 | tag_op = '' |
---|
63 | if callable(url): |
---|
64 | url = url() |
---|
65 | else: |
---|
66 | url = html_escape(url) |
---|
67 | return "<a href=\"%s\"%s>%s</a>" % (url, tag_op, name or url) |
---|
68 | |
---|
69 | def button_to(name, url='', **html_options): |
---|
70 | """ |
---|
71 | Generates a form containing a sole button that submits to the |
---|
72 | URL given by ``url``. |
---|
73 | |
---|
74 | Use this method instead of ``link_to`` for actions that do not have the safe HTTP GET semantics |
---|
75 | implied by using a hypertext link. |
---|
76 | |
---|
77 | The parameters are the same as for ``link_to``. Any ``html_options`` that you pass will be |
---|
78 | applied to the inner ``input`` element. |
---|
79 | In particular, pass |
---|
80 | |
---|
81 | disabled = True/False |
---|
82 | |
---|
83 | as part of ``html_options`` to control whether the button is |
---|
84 | disabled. The generated form element is given the class |
---|
85 | 'button-to', to which you can attach CSS styles for display |
---|
86 | purposes. |
---|
87 | |
---|
88 | Example 1:: |
---|
89 | |
---|
90 | # inside of controller for "feeds" |
---|
91 | >>> button_to("Edit", url(action='edit', id=3)) |
---|
92 | <form method="post" action="/feeds/edit/3" class="button-to"> |
---|
93 | <div><input value="Edit" type="submit" /></div> |
---|
94 | </form> |
---|
95 | |
---|
96 | Example 2:: |
---|
97 | |
---|
98 | >> button_to("Destroy", url(action='destroy', id=3), confirm="Are you sure?") |
---|
99 | <form method="post" action="/feeds/destroy/3" class="button-to"> |
---|
100 | <div><input onclick="return confirm('Are you sure?');" value="Destroy" type="submit" /> |
---|
101 | </div> |
---|
102 | </form> |
---|
103 | |
---|
104 | *NOTE*: This method generates HTML code that represents a form. |
---|
105 | Forms are "block" content, which means that you should not try to |
---|
106 | insert them into your HTML where only inline content is expected. |
---|
107 | For example, you can legally insert a form inside of a ``div`` or |
---|
108 | ``td`` element or in between ``p`` elements, but not in the middle of |
---|
109 | a run of text, nor can you place a form within another form. |
---|
110 | (Bottom line: Always validate your HTML before going public.) |
---|
111 | """ |
---|
112 | if html_options: |
---|
113 | convert_boolean_attributes(html_options, ['disabled']) |
---|
114 | |
---|
115 | confirm = html_options.get('confirm') |
---|
116 | if confirm: |
---|
117 | del html_options['confirm'] |
---|
118 | html_options['onclick'] = "return %s;" % confirm_javascript_function(confirm) |
---|
119 | |
---|
120 | if callable(url): |
---|
121 | ur = url() |
---|
122 | url, name = ur, name or html_escape(ur) |
---|
123 | else: |
---|
124 | url, name = url, name or url |
---|
125 | |
---|
126 | html_options.update(dict(type='submit', value=name)) |
---|
127 | |
---|
128 | return """<form method="post" action="%s" class="button-to"><div>""" % html_escape(url) + \ |
---|
129 | tags.tag("input", **html_options) + "</div></form>" |
---|
130 | |
---|
131 | def link_to_unless_current(name, url, **html_options): |
---|
132 | """ |
---|
133 | Conditionally create a link tag of the given ``name`` using the ``url`` |
---|
134 | |
---|
135 | If the current request uri is the same as the link's only the name is returned. This is useful |
---|
136 | for creating link bars where you don't want to link to the page currently being viewed. |
---|
137 | """ |
---|
138 | return link_to_unless(current_page(url), name, url, **html_options) |
---|
139 | |
---|
140 | def link_to_unless(condition, name, url, **html_options): |
---|
141 | """ |
---|
142 | Conditionally create a link tag of the given ``name`` using the ``url`` |
---|
143 | |
---|
144 | If ``condition`` is false only the name is returned. |
---|
145 | """ |
---|
146 | if condition: |
---|
147 | return name |
---|
148 | else: |
---|
149 | return link_to(name, url, **html_options) |
---|
150 | |
---|
151 | def link_to_if(condition, name, url, **html_options): |
---|
152 | """ |
---|
153 | Conditionally create a link tag of the given ``name`` using the ``url`` |
---|
154 | |
---|
155 | If ``condition`` is True only the name is returned. |
---|
156 | """ |
---|
157 | link_to_unless(not condition, name, url, **html_options) |
---|
158 | |
---|
159 | def parse_querystring(environ): |
---|
160 | """ |
---|
161 | Parses a query string into a list like ``[(name, value)]``. |
---|
162 | |
---|
163 | You can pass the result to ``dict()``, but be aware that keys that appear multiple |
---|
164 | times will be lost (only the last value will be preserved). |
---|
165 | |
---|
166 | This function is cut and pasted from paste.request.parse_querystring (minus its |
---|
167 | caching piece) to avoid requiring paste as a dependency. |
---|
168 | """ |
---|
169 | source = environ.get('QUERY_STRING', '') |
---|
170 | parsed = cgi.parse_qsl(source, keep_blank_values=True, |
---|
171 | strict_parsing=False) |
---|
172 | return parsed |
---|
173 | |
---|
174 | def current_page(url): |
---|
175 | """ |
---|
176 | Returns true if the current page uri is equivalent to ``url`` |
---|
177 | """ |
---|
178 | currl = current_url() |
---|
179 | if callable(url): |
---|
180 | return url() == currl |
---|
181 | else: |
---|
182 | return url == currl |
---|
183 | |
---|
184 | def current_url(): |
---|
185 | """ |
---|
186 | Returns the current page's url. |
---|
187 | """ |
---|
188 | config = request_config() |
---|
189 | environ = config.environ |
---|
190 | curopts = config.mapper_dict.copy() |
---|
191 | if environ.get('REQUEST_METHOD', 'GET') == 'GET': |
---|
192 | if environ.has_key('QUERY_STRING'): |
---|
193 | curopts.update(dict(parse_querystring(environ))) |
---|
194 | return url_for(**curopts) |
---|
195 | |
---|
196 | def convert_options_to_javascript(confirm=None, popup=None, post=None, method=None, **html_options): |
---|
197 | if method: method = method.lower() |
---|
198 | |
---|
199 | if post and not method: |
---|
200 | method = 'post' |
---|
201 | |
---|
202 | if popup and method: |
---|
203 | raise "You can't use popup and post in the same link" |
---|
204 | elif confirm and popup: |
---|
205 | oc = "if (%s) { %s };return false;" % (confirm_javascript_function(confirm), |
---|
206 | popup_javascript_function(popup)) |
---|
207 | elif confirm and method: |
---|
208 | oc = "if (%s) { %s };return false;" % (confirm_javascript_function(confirm), |
---|
209 | method_javascript_function(method)) |
---|
210 | elif confirm: |
---|
211 | oc = "return %s;" % confirm_javascript_function(confirm) |
---|
212 | elif method: |
---|
213 | oc = "%sreturn false;" % method_javascript_function(method) |
---|
214 | elif popup: |
---|
215 | oc = popup_javascript_function(popup) + 'return false;' |
---|
216 | else: |
---|
217 | oc = html_options.get('onclick') |
---|
218 | html_options['onclick'] = oc |
---|
219 | return html_options |
---|
220 | |
---|
221 | def convert_boolean_attributes(html_options, bool_attrs): |
---|
222 | for attr in bool_attrs: |
---|
223 | if html_options.has_key(attr) and html_options[attr]: |
---|
224 | html_options[attr] = attr |
---|
225 | elif html_options.has_key(attr): |
---|
226 | del html_options[attr] |
---|
227 | |
---|
228 | def confirm_javascript_function(confirm): |
---|
229 | return "confirm('%s')" % escape_javascript(confirm) |
---|
230 | |
---|
231 | def popup_javascript_function(popup): |
---|
232 | if isinstance(popup, list): |
---|
233 | return "window.open(this.href,'%s','%s');" % (popup[0], popup[-1]) |
---|
234 | else: |
---|
235 | return "window.open(this.href);" |
---|
236 | |
---|
237 | def method_javascript_function(method): |
---|
238 | submit_function = "var f = document.createElement('form'); f.style.display = 'none'; " + \ |
---|
239 | "this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;" |
---|
240 | |
---|
241 | if method != 'post': |
---|
242 | submit_function += "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); " |
---|
243 | submit_function += "m.setAttribute('name', '_method'); m.setAttribute('value', '%s'); f.appendChild(m);" % method |
---|
244 | |
---|
245 | return submit_function + "f.submit();" |
---|
246 | |
---|
247 | |
---|
248 | def mail_to(email_address, name=None, cc=None, bcc=None, subject=None, |
---|
249 | body=None, replace_at=None, replace_dot=None, encode=None, **html_options): |
---|
250 | """ |
---|
251 | Creates a link tag for starting an email to the specified |
---|
252 | ``email_address``, which is also used as the name of the link unless |
---|
253 | ``name`` is specified. Additional HTML options, such as class or id, can be |
---|
254 | passed in the ``html_options`` hash. |
---|
255 | |
---|
256 | You can also make it difficult for spiders to harvest email address by |
---|
257 | obfuscating them. |
---|
258 | |
---|
259 | Examples:: |
---|
260 | |
---|
261 | >>> mail_to("me@domain.com", "My email", encode = "javascript") |
---|
262 | <script type="text/javascript" language="javascript">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script> |
---|
263 | |
---|
264 | >>> mail_to("me@domain.com", "My email", encode = "hex") |
---|
265 | <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a> |
---|
266 | |
---|
267 | You can also specify the cc address, bcc address, subject, and body parts |
---|
268 | of the message header to create a complex e-mail using the corresponding |
---|
269 | ``cc``, ``bcc``, ``subject``, and ``body`` keyword arguments. Each of these |
---|
270 | options are URI escaped and then appended to the ``email_address`` before |
---|
271 | being output. **Be aware that javascript keywords will not be escaped and |
---|
272 | may break this feature when encoding with javascript.** |
---|
273 | |
---|
274 | Examples:: |
---|
275 | |
---|
276 | >>> mail_to("me@domain.com", "My email", cc="ccaddress@domain.com", bcc="bccaddress@domain.com", |
---|
277 | subject="This is an examjust ple email", body= "This is the body of the message.") |
---|
278 | <a href="mailto:me@domain.com?cc="ccaddress@domain.com"&bcc="bccaddress@domain.com"&body="This%20is%20the%20body%20of%20the%20message."&subject="This%20is%20an%20example%20email">My email</a> |
---|
279 | """ |
---|
280 | extras = {} |
---|
281 | for key, option in ('cc', cc), ('bcc', bcc), ('subject', subject), ('body', body): |
---|
282 | if option: |
---|
283 | extras[key] = option |
---|
284 | options_query = urllib.urlencode(extras).replace("+", "%20") |
---|
285 | |
---|
286 | email_address_obfuscated = email_address |
---|
287 | if replace_at: |
---|
288 | email_address_obfuscated = email_address_obfuscated.replace('@', replace_at) |
---|
289 | if replace_dot: |
---|
290 | email_address_obfuscated = email_address_obfuscated.replace('.', replace_dot) |
---|
291 | |
---|
292 | if encode=='hex': |
---|
293 | email_address = ''.join(['%%%x' % ord(x) for x in email_address]) |
---|
294 | |
---|
295 | url = 'mailto:' + email_address |
---|
296 | if options_query: |
---|
297 | url += '?' + options_query |
---|
298 | html_options['href'] = url |
---|
299 | |
---|
300 | tag = tags.content_tag('a', name or email_address_obfuscated, **html_options) |
---|
301 | |
---|
302 | if encode =='javascript': |
---|
303 | tmp = "document.write('%s');" % tag |
---|
304 | string = ''.join(['%%%x' % ord(x) for x in tmp]) |
---|
305 | return javascript_tag("eval(unescape('%s'))" % string) |
---|
306 | else : |
---|
307 | return tag |
---|
308 | |
---|
309 | |
---|
310 | __all__ = ['url', 'link_to', 'button_to', 'link_to_unless_current', 'link_to_unless', 'link_to_if', |
---|
311 | 'current_page', 'current_url', 'mail_to'] |
---|