0001"""URL Helpers"""
0002
0003
0004import re
0005import urllib
0006
0007from routes import url_for, request_config
0008
0009import tags
0010from asset_tag import compute_public_path
0011from javascript import *
0012from webhelpers.util import html_escape
0013
0014def get_url(url):
0015 if callable(url):
0016 return url()
0017 else:
0018 return url
0019
0020def url(*args, **kargs):
0021 """
0022 Lazily evaluates url_for() arguments
0023
0024 Used instead of url_for() for functions so that the function will be evaluated
0025 in a lazy manner rather than at initial function call.
0026 """
0027 args = args
0028 kargs = kargs
0029 def call():
0030 return url_for(*args, **kargs)
0031 return call
0032
0033def link_to(name, url='', **html_options):
0034 """
0035 Creates a link tag of the given ``name`` using an URL created by the set of ``options``.
0036
0037 See the valid options in the documentation for Routes url_for.
0038
0039 The html_options has three special features. One for creating javascript confirm alerts where if you pass
0040 ``confirm='Are you sure?'`` , the link will be guarded with a JS popup asking that question. If the user
0041 accepts, the link is processed, otherwise not.
0042
0043 Another for creating a popup window, which is done by either passing ``popup`` with True or the options
0044 of the window in Javascript form.
0045
0046 And a third for making the link do a POST request (instead of the regular GET) through a dynamically added
0047 form element that is instantly submitted. Note that if the user has turned off Javascript, the request will
0048 fall back on the GET. So its your responsibility to determine what the action should be once it arrives at
0049 the controller. The POST form is turned on by passing ``post`` as True. Note, it's not possible to use POST
0050 requests and popup targets at the same time (an exception will be thrown).
0051
0052 Examples::
0053
0054 >> link_to("Delete this page", url(action="destroy", id=4), confirm="Are you sure?")
0055 >> link_to("Help", url(action="help"), popup=True)
0056 >> link_to("Busy loop", url(action="busy"), popup=['new_window', 'height=300,width=600'])
0057 >> link_to("Destroy account", url(action="destroy"), confirm="Are you sure?", method='delete')
0058 """
0059 if html_options:
0060 html_options = convert_options_to_javascript(**html_options)
0061 tag_op = tags.tag_options(**html_options)
0062 else:
0063 tag_op = ''
0064 if callable(url):
0065 url = url()
0066 else:
0067 url = html_escape(url)
0068 return "<a href=\"%s\"%s>%s</a>" % (url, tag_op, name or url)
0069
0070def button_to(name, url='', **html_options):
0071 """
0072 Generates a form containing a sole button that submits to the
0073 URL given by ``url``.
0074
0075 Use this method instead of ``link_to`` for actions that do not have the safe HTTP GET semantics
0076 implied by using a hypertext link.
0077
0078 The parameters are the same as for ``link_to``. Any ``html_options`` that you pass will be
0079 applied to the inner ``input`` element.
0080 In particular, pass
0081
0082 disabled = True/False
0083
0084 as part of ``html_options`` to control whether the button is
0085 disabled. The generated form element is given the class
0086 'button-to', to which you can attach CSS styles for display
0087 purposes.
0088
0089 The submit button itself will be displayed as an image if you provide both
0090 ``type`` and ``src`` as followed:
0091
0092 type='image', src='icon_delete.gif'
0093
0094 The ``src`` path will be computed as the image_tag() computes it's ``source``
0095 argument.
0096
0097 Example 1::
0098
0099 # inside of controller for "feeds"
0100 >> button_to("Edit", url(action='edit', id=3))
0101 <form method="POST" action="/feeds/edit/3" class="button-to">
0102 <div><input value="Edit" type="submit" /></div>
0103 </form>
0104
0105 Example 2::
0106
0107 >> button_to("Destroy", url(action='destroy', id=3), confirm="Are you sure?", method='DELETE')
0108 <form method="POST" action="/feeds/destroy/3" class="button-to">
0109 <div>
0110 <input type="hidden" name="_method" value="DELETE" />
0111 <input onclick="return confirm('Are you sure?');" value="Destroy" type="submit" />
0112 </div>
0113 </form>
0114
0115 Example 3::
0116
0117 # Button as an image.
0118 >> button_to("Edit", url(action='edit', id=3), type='image', src='icon_delete.gif')
0119 <form method="POST" action="/feeds/edit/3" class="button-to">
0120 <div><input alt="Edit" src="/images/icon_delete.gif" type="image" value="Edit" /></div>
0121 </form>
0122
0123 *NOTE*: This method generates HTML code that represents a form.
0124 Forms are "block" content, which means that you should not try to
0125 insert them into your HTML where only inline content is expected.
0126 For example, you can legally insert a form inside of a ``div`` or
0127 ``td`` element or in between ``p`` elements, but not in the middle of
0128 a run of text, nor can you place a form within another form.
0129 (Bottom line: Always validate your HTML before going public.)
0130 """
0131 if html_options:
0132 convert_boolean_attributes(html_options, ['disabled'])
0133
0134 method_tag = ''
0135 method = html_options.pop('method', '')
0136 if method.upper() in ['PUT', 'DELETE']:
0137 method_tag = tags.tag('input', type_='hidden', id='_method', name_='_method',
0138 value=method)
0139
0140 form_method = (method.upper() == 'GET' and method) or 'POST'
0141
0142 confirm = html_options.get('confirm')
0143 if confirm:
0144 del html_options['confirm']
0145 html_options['onclick'] = "return %s;" % confirm_javascript_function(confirm)
0146
0147 if callable(url):
0148 ur = url()
0149 url, name = ur, name or tags.escape_once(ur)
0150 else:
0151 url, name = url, name or url
0152
0153 submit_type = html_options.get('type')
0154 img_source = html_options.get('src')
0155 if submit_type == 'image' and img_source:
0156 html_options.update(dict(type=submit_type, value=name,
0157 alt=html_options.get('alt', name)))
0158 html_options['src'] = compute_public_path(img_source, 'images', 'png')
0159 else:
0160 html_options.update(dict(type='submit', value=name))
0161
0162 return """<form method="%s" action="%s" class="button-to"><div>""" % (form_method, tags.escape_once(url)) + method_tag + tags.tag("input", **html_options) + "</div></form>"
0165
0166def link_to_unless_current(name, url, **html_options):
0167 """
0168 Conditionally create a link tag of the given ``name`` using the ``url``
0169
0170 If the current request uri is the same as the link's only the name is returned. This is useful
0171 for creating link bars where you don't want to link to the page currently being viewed.
0172 """
0173 return link_to_unless(current_page(url), name, url, **html_options)
0174
0175def link_to_unless(condition, name, url, **html_options):
0176 """
0177 Conditionally create a link tag of the given ``name`` using the ``url``
0178
0179 If ``condition`` is True only the name is returned.
0180 """
0181 if condition:
0182 return name
0183 else:
0184 return link_to(name, url, **html_options)
0185
0186def link_to_if(condition, name, url, **html_options):
0187 """
0188 Conditionally create a link tag of the given ``name`` using the ``url``
0189
0190 If ``condition`` is True only the name is returned.
0191 """
0192 return link_to_unless(not condition, name, url, **html_options)
0193
0194def current_page(url):
0195 """
0196 Returns true if the current page uri is equivalent to ``url``
0197 """
0198 currl = current_url()
0199 if callable(url):
0200 return url() == currl
0201 else:
0202 return url == currl
0203
0204def current_url(*args, **kwargs):
0205 """
0206 Returns the current page's url.
0207 """
0208 config = request_config()
0209 environ = config.environ
0210 qs = environ.get('QUERY_STRING', '')
0211 if qs:
0212 qs = '?' + qs
0213 return url_for(*args, **kwargs) + qs
0214
0215def convert_options_to_javascript(confirm=None, popup=None, post=None, method=None, **html_options):
0216 if post and not method:
0217 method = 'POST'
0218
0219 if popup and method:
0220 raise ValueError("You can't use popup and post in the same link")
0221 elif confirm and popup:
0222 oc = "if (%s) { %s };return false;" % (confirm_javascript_function(confirm),
0223 popup_javascript_function(popup))
0224 elif confirm and method:
0225 oc = "if (%s) { %s };return false;" % (confirm_javascript_function(confirm),
0226 method_javascript_function(method))
0227 elif confirm:
0228 oc = "return %s;" % confirm_javascript_function(confirm)
0229 elif method:
0230 oc = "%sreturn false;" % method_javascript_function(method)
0231 elif popup:
0232 oc = popup_javascript_function(popup) + 'return false;'
0233 else:
0234 oc = html_options.get('onclick')
0235 html_options['onclick'] = oc
0236 return html_options
0237
0238def convert_boolean_attributes(html_options, bool_attrs):
0239 for attr in bool_attrs:
0240 if html_options.has_key(attr) and html_options[attr]:
0241 html_options[attr] = attr
0242 elif html_options.has_key(attr):
0243 del html_options[attr]
0244
0245def confirm_javascript_function(confirm):
0246 return "confirm('%s')" % escape_javascript(confirm)
0247
0248def popup_javascript_function(popup):
0249 if isinstance(popup, list):
0250 return "window.open(this.href,'%s','%s');" % (popup[0], popup[-1])
0251 else:
0252 return "window.open(this.href);"
0253
0254def method_javascript_function(method):
0255 submit_function = "var f = document.createElement('form'); f.style.display = 'none'; " + "this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;"
0257
0258 if method.upper() != 'POST':
0259 submit_function += "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); "
0260 submit_function += "m.setAttribute('name', '_method'); m.setAttribute('value', '%s'); f.appendChild(m);" % method
0261
0262 return submit_function + "f.submit();"
0263
0264
0265def mail_to(email_address, name=None, cc=None, bcc=None, subject=None,
0266 body=None, replace_at=None, replace_dot=None, encode=None, **html_options):
0267 """
0268 Creates a link tag for starting an email to the specified
0269 ``email_address``, which is also used as the name of the link unless
0270 ``name`` is specified. Additional HTML options, such as class or id, can be
0271 passed in the ``html_options`` hash.
0272
0273 You can also make it difficult for spiders to harvest email address by
0274 obfuscating them.
0275
0276 Examples::
0277
0278 >>> mail_to("me@domain.com", "My email", encode = "javascript")
0279 '<script type="text/javascript">\\n//<![CDATA[\\neval(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\\'))\\n//]]>\\n</script>'
0280
0281 >>> mail_to("me@domain.com", "My email", encode = "hex")
0282 '<a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>'
0283
0284 You can also specify the cc address, bcc address, subject, and body parts
0285 of the message header to create a complex e-mail using the corresponding
0286 ``cc``, ``bcc``, ``subject``, and ``body`` keyword arguments. Each of these
0287 options are URI escaped and then appended to the ``email_address`` before
0288 being output. **Be aware that javascript keywords will not be escaped and
0289 may break this feature when encoding with javascript.**
0290
0291 Examples::
0292
0293 >>> mail_to("me@domain.com", "My email", cc="ccaddress@domain.com", bcc="bccaddress@domain.com", subject="This is an example email", body= "This is the body of the message.")
0294 '<a href="mailto:me@domain.com?cc=ccaddress%40domain.com&body=This%20is%20the%20body%20of%20the%20message.&subject=This%20is%20an%20example%20email&bcc=bccaddress%40domain.com">My email</a>'
0295 """
0296 extras = {}
0297 for key, option in ('cc', cc), ('bcc', bcc), ('subject', subject), ('body', body):
0298 if option:
0299 extras[key] = option
0300 options_query = urllib.urlencode(extras).replace("+", "%20")
0301 protocol = 'mailto:'
0302
0303 email_address_obfuscated = email_address
0304 if replace_at:
0305 email_address_obfuscated = email_address_obfuscated.replace('@', replace_at)
0306 if replace_dot:
0307 email_address_obfuscated = email_address_obfuscated.replace('.', replace_dot)
0308
0309 if encode == 'hex':
0310 email_address_obfuscated = ''.join(['&#%d;' % ord(x) for x in email_address_obfuscated])
0311 protocol = ''.join(['&#%d;' % ord(x) for x in protocol])
0312
0313 word_re = re.compile('\w')
0314 encoded_parts = []
0315 for x in email_address:
0316 if word_re.match(x):
0317 encoded_parts.append('%%%x' % ord(x))
0318 else:
0319 encoded_parts.append(x)
0320 email_address = ''.join(encoded_parts)
0321
0322 url = protocol + email_address
0323 if options_query:
0324 url += '?' + options_query
0325 html_options['href'] = url
0326
0327 tag = tags.content_tag('a', name or email_address_obfuscated, **html_options)
0328
0329 if encode == 'javascript':
0330 tmp = "document.write('%s');" % tag
0331 string = ''.join(['%%%x' % ord(x) for x in tmp])
0332 return javascript_tag("eval(unescape('%s'))" % string)
0333 else :
0334 return tag
0335
0336def js_obfuscate(data):
0337 """Obfuscates data in a Javascript tag
0338
0339 Example::
0340
0341 >>> js_obfuscate("<input type='hidden' name='check' value='valid' />")
0342 '<script type="text/javascript">\\n//<![CDATA[\\neval(unescape(\\'%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%69%6e%70%75%74%20%74%79%70%65%3d%27%68%69%64%64%65%6e%27%20%6e%61%6d%65%3d%27%63%68%65%63%6b%27%20%76%61%6c%75%65%3d%27%76%61%6c%69%64%27%20%2f%3e%27%29%3b\\'))\\n//]]>\\n</script>'
0343 """
0344 tmp = "document.write('%s');" % data
0345 string = ''.join(['%%%x' % ord(x) for x in tmp])
0346 return javascript_tag("eval(unescape('%s'))" % string)
0347
0348__all__ = ['url', 'link_to', 'button_to', 'link_to_unless_current', 'link_to_unless', 'link_to_if',
0349 'current_page', 'current_url', 'mail_to', 'js_obfuscate']