[3] | 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'] |
---|