Index: utils.py |
diff --git a/utils.py b/utils.py |
index e1d16f5d3eb84c4321a68f8f595e3d31606d365a..b689666218034c1cb1207d17b7f9c94618b33ba9 100644 |
--- a/utils.py |
+++ b/utils.py |
@@ -1,9 +1,18 @@ |
+ |
# Copyright (c) 2012 The Chromium Authors. All rights reserved. |
# Use of this source code is governed by a BSD-style license that can be |
# found in the LICENSE file. |
"""Utils.""" |
+import re |
+import time |
+import random |
+import logging |
+import sys |
+import string |
+import json |
+ |
from google.appengine.api import users |
@@ -35,3 +44,229 @@ def require_user(func): |
else: |
return func(self, *args, **kwargs) |
return decorated |
+ |
+ |
+############ |
+# Decorators |
+############ |
+ |
+def render(template_filename, jinja_environment): |
+ """Use a template to render results. The wrapped function is expected to |
+ return a dict.""" |
+ def _render(fn): |
+ def wrapper(self, *args, **kwargs): |
+ results = fn(self, *args, **kwargs) |
+ template = jinja_environment.get_template(template_filename) |
+ self.response.out.write(template.render(results)) |
+ return wrapper |
+ return _render |
+ |
+ |
+def render_iff_new_flag_set(template_filename, jinja_environment): |
+ """Use the given template if and only if the 'new' flag is set by: |
+ * The presence of the 'new' cookie. |
+ * 'new' is passed in as an url parameter.""" |
+ def _render(fn): |
+ def wrapper(self, *args, **kwargs): |
+ new = self.request.get('new') or self.request.cookies.get('new') |
+ kwargs.update({'new': new}) |
+ results = fn(self, *args, **kwargs) |
+ if new: |
+ template = jinja_environment.get_template(template_filename) |
+ try: |
+ self.response.out.write(template.render(results)) |
+ except Exception as e: |
+ logging.error('Caught exception while calling %s with template %s' % |
+ (self.__class__.__name__, template_filename)) |
+ raise e, None, sys.exc_info()[2] |
+ else: |
+ # Just treat the results as a large string blob. |
+ self.response.out.write(results) |
+ return wrapper |
+ return _render |
+ |
+ |
+def render_json(fn): |
+ """The function is expected to return a dict, and we want to render json.""" |
+ def wrapper(self, *args, **kwargs): |
+ results = fn(self, *args, **kwargs) |
+ self.response.out.write(json.dumps(results)) |
+ return wrapper |
+ |
+ |
+def maybe_render_json(template_filename, jinja_environment): |
+ """If the variable 'json' exists in the request, return a json object. |
+ Otherwise render the page using the template""" |
+ def _render(fn): |
+ def wrapper(self, *args, **kwargs): |
+ results = fn(self, *args, **kwargs) |
+ if self.request.get('json'): |
+ self.response.out.write(json.dumps(results)) |
+ else: |
+ template = jinja_environment.get_template(template_filename) |
+ self.response.out.write(template.render(results)) |
+ return wrapper |
+ return _render |
+ |
+ |
+def login_required(fn): |
+ """Redirect user to a login page.""" |
+ def wrapper(self, *args, **kwargs): |
+ user = users.get_current_user() |
+ if not user: |
+ self.redirect(users.create_login_url(self.request.uri)) |
+ return |
+ else: |
+ return fn(self, *args, **kwargs) |
+ return wrapper |
+ |
+ |
+def google_login_required(fn): |
+ """Return 403 unless the user is logged in from a @google.com domain.""" |
+ def wrapper(self, *args, **kwargs): |
+ user = users.get_current_user() |
+ if not user: |
+ self.redirect(users.create_login_url(self.request.uri)) |
+ return |
+ email_match = re.match('^(.*)@(.*)$', user.email()) |
+ if email_match: |
+ _, domain = email_match.groups() |
+ if domain == 'google.com': |
+ return fn(self, *args, **kwargs) |
+ self.error(403) # Unrecognized email or unauthroized domain. |
+ self.response.out.write('unauthroized email %s' % user.user_id()) |
+ return wrapper |
+ |
+ |
+def admin_required(fn): |
+ """Return 403 unless an admin is logged in.""" |
+ def wrapper(self, *args, **kwargs): |
+ user = users.get_current_user() |
+ if not user: |
+ self.redirect(users.create_login_url(self.request.uri)) |
+ return |
+ elif not users.is_current_user_admin(): |
+ self.error(403) |
+ return |
+ else: |
+ return fn(self, *args, **kwargs) |
+ return wrapper |
+ |
+ |
+def expect_request_param(*request_args): |
+ """Strips out the expected args from a request and feeds it into the function |
+ as the arguments. Optionally, typecast the argument from a string into a |
+ different class. Examples include: |
+ name (Get the request object called "name") |
+ time as timestamp (Get "time", pass it in as "timestamp") |
+ """ |
+ def _decorator(fn): |
+ def wrapper(self, *args, **kwargs): |
+ request_kwargs = {} |
+ for arg in request_args: |
+ # TODO(hinoka): Optional typecasting? |
+ arg_match = re.match(r'^(\((\w+)\))?\s*(\w+)( as (\w+))?$', arg) |
+ if arg_match: |
+ _, _, name, _, target_name = arg_match.groups() |
+ if not target_name: |
+ target_name = name |
+ request_item = self.request.get(name) |
+ request_kwargs[target_name] = request_item |
+ else: |
+ raise Exception('Incorrect format %s' % arg) |
+ kwargs.update(request_kwargs) |
+ return fn(self, *args, **kwargs) |
+ return wrapper |
+ return _decorator |
+ |
+ |
+############### |
+# Jinja filters |
+############### |
+ |
+def delta_time(delta): |
+ hours = int(delta/60/60) |
+ minutes = int((delta - hours * 3600)/60) |
+ seconds = int(delta - (hours * 3600) - (minutes * 60)) |
+ result = '' |
+ if hours > 1: |
+ result += '%d hrs, ' % hours |
+ elif hours: |
+ result += '%d hr, ' % hours |
+ if minutes > 1: |
+ result += '%d mins ' % minutes |
+ elif minutes: |
+ result += '%d min ' % minutes |
+ if not hours: |
+ if seconds > 1 or seconds == 0: |
+ result += '%d secs.' % seconds |
+ else: |
+ result += '%d sec.' % seconds |
+ return result |
+ |
+ |
+def time_since(timestamp): |
+ delta = time.time() - timestamp |
+ return delta_time(delta) |
+ |
+ |
+def nl2br(value): |
+ return value.replace('\n','<br>\n') |
+ |
+ |
+def rot13_email(value): |
+ nonce = ''.join(random.choice( |
+ string.ascii_uppercase + string.digits) for x in range(6)) |
+ rep = ('<span id="obf-%s"><script>document.getElementById("obf-%s").' |
+ 'innerHTML="<n uers=\\"znvygb:%s\\" gnetrg=\\"_oynax\\">%s</n>".' |
+ 'replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((' |
+ 'c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});</script>' |
+ '<noscript><span style="unicode-bidi:bidi-override;direction:rtl;"' |
+ '>%s</span></noscript></span>') |
+ return rep % (nonce, nonce, value.encode('rot13'), |
+ value.encode('rot13'), value[::-1]) |
+ |
+ |
+def _blockquote(value): |
+ """Wrap blockquote levels recursively.""" |
+ new_value = '' |
+ blockquote = False |
+ for line in value.splitlines(): |
+ if blockquote: |
+ if line.startswith('>'): |
+ new_value += '%s\n' % line[1:].strip() |
+ else: |
+ blockquote = False |
+ new_value += '</blockquote>%s\n' % line |
+ else: |
+ if line.startswith('>'): |
+ blockquote = True |
+ new_value += '<blockquote>%s\n' % line[1:].strip() |
+ else: |
+ new_value += '%s\n' % line |
+ if blockquote: |
+ new_value += '</blockquote>' |
+ if re.search(r'^>', new_value, re.M): |
+ return _blockquote(new_value) |
+ else: |
+ return new_value |
+ |
+ |
+def _resolve_crbug(match): |
+ results = [] |
+ bugs = match.group(1).split(',') |
+ for bug in bugs: |
+ results.append('<a href="http://crbug.com/%s">%s</a>' % (bug, bug)) |
+ return 'BUG=%s' % ','.join(results) |
+ |
+ |
+def cl_comment(value): |
+ """Add links to https:// addresses, BUG=####, and trim excessive newlines.""" |
+ value = re.sub(r'(https?://.*)', r'<a href="\1">\1</a>', value) |
+ value = re.sub(r'BUG=([\d,]+)', _resolve_crbug, value) |
+ # Add blockquotes. |
+ value = _blockquote(value) |
+ value = re.sub(r'\n', r'<br>', value) |
+ # Obfuscure email addresses with rot13 encoding. |
+ value = re.sub(r'(\w+@[\w.]+)', lambda m: rot13_email(m.group(1)), value) |
+ return value |