realms-wiki/realms/__init__.py

300 lines
8.6 KiB
Python
Raw Normal View History

2014-01-14 01:07:13 +02:00
# Monkey patch stdlib.
import gevent.monkey
gevent.monkey.patch_all(aggressive=False)
# Set default encoding to UTF-8
import sys
reload(sys)
# noinspection PyUnresolvedReferences
sys.setdefaultencoding('utf-8')
# Silence Sentry and Requests.
2013-09-26 16:51:15 +03:00
import logging
2014-01-14 01:07:13 +02:00
logging.getLogger().setLevel(logging.INFO)
logging.getLogger('raven').setLevel(logging.WARNING)
logging.getLogger('requests').setLevel(logging.WARNING)
2013-10-02 04:50:48 +03:00
import time
2013-12-03 22:09:57 +02:00
import sys
import os
2014-01-14 01:07:13 +02:00
import httplib
import traceback
from flask import Flask, request, render_template, url_for, redirect, session, flash, g
2013-11-08 20:20:40 +02:00
from flask.ctx import _AppCtxGlobals
2013-12-03 22:09:57 +02:00
from flask.ext.script import Manager
2013-10-05 00:42:45 +03:00
from flask.ext.login import LoginManager, login_required
2013-10-10 00:35:06 +03:00
from flask.ext.assets import Environment, Bundle
2013-10-03 17:58:07 +03:00
from werkzeug.routing import BaseConverter
2013-11-08 20:20:40 +02:00
from werkzeug.utils import cached_property
2014-01-14 01:07:13 +02:00
from werkzeug.exceptions import HTTPException
2013-10-05 00:42:45 +03:00
2013-12-03 22:09:57 +02:00
from realms import config
2014-01-14 01:07:13 +02:00
from realms.lib.services import db
2013-11-08 20:20:40 +02:00
from realms.lib.ratelimit import get_view_rate_limit, ratelimiter
from realms.lib.session import RedisSessionInterface
from realms.lib.wiki import Wiki
2014-01-14 01:07:13 +02:00
from realms.lib.util import to_canonical, remove_ext, mkdir_safe, gravatar_url, to_dict
from realms.models import User, CurrentUser, Site
2013-11-08 20:20:40 +02:00
2013-10-05 00:42:45 +03:00
2013-12-09 22:24:22 +02:00
sites = {}
2013-11-08 20:20:40 +02:00
class AppCtxGlobals(_AppCtxGlobals):
2013-12-03 22:09:57 +02:00
@cached_property
2013-12-09 22:24:22 +02:00
def current_site(self):
subdomain = format_subdomain(self.current_subdomain)
2013-12-03 22:09:57 +02:00
if not subdomain:
2013-12-09 22:24:22 +02:00
subdomain = "www"
2013-12-03 22:09:57 +02:00
2013-12-09 22:24:22 +02:00
if subdomain is "www" and self.current_subdomain:
# Invalid sub domain
return False
2013-12-03 22:09:57 +02:00
2013-12-09 22:24:22 +02:00
if not sites.get(subdomain):
2014-01-14 01:07:13 +02:00
sites[subdomain] = to_dict(Site.get_by_name(subdomain))
2013-12-09 22:24:22 +02:00
sites[subdomain].wiki = Wiki("%s/%s" % (config.REPO_DIR, subdomain))
return sites[subdomain]
2013-12-03 22:09:57 +02:00
2013-12-03 01:50:19 +02:00
@cached_property
2013-12-09 22:24:22 +02:00
def current_wiki(self):
return g.current_site.wiki
@cached_property
def current_subdomain(self):
2013-12-03 22:09:57 +02:00
host = request.host.split(':')[0]
return host[:-len(config.DOMAIN)].rstrip('.')
2013-12-03 01:50:19 +02:00
2013-11-08 20:20:40 +02:00
@cached_property
def current_user(self):
return session.get('user') if session.get('user') else {'username': 'Anon'}
class Application(Flask):
app_ctx_globals_class = AppCtxGlobals
2013-12-03 01:50:19 +02:00
def __call__(self, environ, start_response):
path_info = environ.get('PATH_INFO')
2013-11-08 20:20:40 +02:00
2013-12-03 01:50:19 +02:00
if path_info and len(path_info) > 1 and path_info.endswith('/'):
environ['PATH_INFO'] = path_info[:-1]
2013-10-05 00:42:45 +03:00
2013-12-03 01:50:19 +02:00
scheme = environ.get('HTTP_X_SCHEME')
2013-10-05 00:42:45 +03:00
2013-12-03 01:50:19 +02:00
if scheme:
environ['wsgi.url_scheme'] = scheme
2013-10-05 00:42:45 +03:00
2013-12-03 01:50:19 +02:00
real_ip = environ.get('HTTP_X_REAL_IP')
2013-10-05 00:42:45 +03:00
2013-12-03 01:50:19 +02:00
if real_ip:
environ['REMOTE_ADDR'] = real_ip
2013-10-05 00:42:45 +03:00
2013-12-03 01:50:19 +02:00
return super(Application, self).__call__(environ, start_response)
2013-10-05 00:52:41 +03:00
2013-12-03 22:09:57 +02:00
def discover(self):
"""
Pattern taken from guildwork.com
"""
IMPORT_NAME = 'realms.modules'
FROMLIST = (
'assets',
'models',
'search',
'perms',
'broadcasts',
'commands',
'notifications',
'requests',
'tasks',
'views',
)
start_time = time.time()
__import__(IMPORT_NAME, fromlist=FROMLIST)
for module_name in self.config['MODULES']:
sources = __import__('%s.%s' % (IMPORT_NAME, module_name), fromlist=FROMLIST)
# Blueprint
if hasattr(sources, 'views'):
self.register_blueprint(sources.views.blueprint)
# Flask-Script
if hasattr(sources, 'commands'):
manager.add_command(module_name, sources.commands.manager)
print >> sys.stderr, ' * Ready in %.2fms' % (1000.0 * (time.time() - start_time))
2013-10-05 00:52:41 +03:00
2013-10-03 17:58:07 +03:00
class RegexConverter(BaseConverter):
2013-10-10 00:35:06 +03:00
"""
Enables Regex matching on endpoints
"""
2013-10-03 17:58:07 +03:00
def __init__(self, url_map, *items):
super(RegexConverter, self).__init__(url_map)
self.regex = items[0]
2013-09-26 16:51:15 +03:00
2013-10-05 00:42:45 +03:00
def redirect_url(referrer=None):
if not referrer:
referrer = request.referrer
return request.args.get('next') or referrer or url_for('index')
2013-10-04 05:57:19 +03:00
2013-09-26 16:51:15 +03:00
2013-10-10 00:35:06 +03:00
def format_subdomain(s):
2013-12-03 22:09:57 +02:00
if not config.REPO_ENABLE_SUBDOMAIN:
2013-11-08 20:20:40 +02:00
return ""
2013-10-10 00:35:06 +03:00
s = s.lower()
s = to_canonical(s)
2013-12-03 22:09:57 +02:00
if s in config.REPO_FORBIDDEN_NAMES:
2013-10-10 00:35:06 +03:00
# Not allowed
s = ""
return s
2013-12-03 01:50:19 +02:00
app = Application(__name__)
2013-12-03 22:09:57 +02:00
app.config.from_object('realms.config')
2013-12-03 01:50:19 +02:00
app.session_interface = RedisSessionInterface()
app.url_map.converters['regex'] = RegexConverter
2013-12-03 22:09:57 +02:00
app.url_map.strict_slashes = False
app.debug = True
2014-01-14 01:07:13 +02:00
# Flask-SQLAlchemy
db.init_app(app)
2013-12-03 22:09:57 +02:00
manager = Manager(app)
2013-12-03 01:50:19 +02:00
login_manager = LoginManager()
login_manager.init_app(app)
2013-12-04 00:28:16 +02:00
login_manager.login_view = 'auth.login'
2013-10-05 00:42:45 +03:00
2013-12-03 01:50:19 +02:00
@login_manager.user_loader
def load_user(user_id):
return CurrentUser(user_id)
2013-09-26 16:51:15 +03:00
2014-01-14 01:07:13 +02:00
def error_handler(e):
try:
if isinstance(e, HTTPException):
status_code = e.code
message = e.description if e.description != type(e).description else None
tb = None
else:
status_code = httplib.INTERNAL_SERVER_ERROR
message = None
tb = traceback.format_exc() if g.current_user.staff else None
if request.is_xhr or request.accept_mimetypes.best in ['application/json', 'text/javascript']:
response = {
'message': message,
'traceback': tb,
}
else:
response = render_template('errors/error.html',
title=httplib.responses[status_code],
status_code=status_code,
message=message,
traceback=tb)
except HTTPException as e2:
return error_handler(e2)
return response, status_code
for status_code in httplib.responses:
if status_code >= 400:
app.register_error_handler(status_code, error_handler)
2013-12-03 01:50:19 +02:00
assets = Environment()
assets.init_app(app)
if config.ENV is 'PROD':
if 'js_common' not in assets._named_bundles:
assets.register('js_common', Bundle('packed-common.js'))
if 'js_editor' not in assets._named_bundles:
assets.register('js_editor', Bundle('packed-editor.js'))
else:
if 'js_common' not in assets._named_bundles:
js = Bundle(
Bundle('vendor/jquery/jquery.js',
'vendor/components-underscore/underscore.js',
'vendor/components-bootstrap/js/bootstrap.js',
'vendor/handlebars/handlebars.js',
'vendor/showdown/src/showdown.js',
'vendor/showdown/src/extensions/table.js',
'js/wmd.js',
filters='closure_js'),
'js/html-sanitizer-minified.js',
'vendor/highlightjs/highlight.pack.js',
Bundle('js/main.js', filters='closure_js'),
output='packed-common.js')
assets.register('js_common', js)
if 'js_editor' not in assets._named_bundles:
js = Bundle('js/ace/ace.js',
'js/ace/mode-markdown.js',
'vendor/keymaster/keymaster.js',
'js/dillinger.js',
filters='closure_js', output='packed-editor.js')
assets.register('js_editor', js)
2013-12-09 22:24:22 +02:00
@app.before_request
def check_subdomain():
if not g.current_site:
return redirect('http://%s' % config.SERVER_NAME)
2013-12-03 01:50:19 +02:00
@app.after_request
def inject_x_rate_headers(response):
limit = get_view_rate_limit()
if limit and limit.send_x_headers:
h = response.headers
h.add('X-RateLimit-Remaining', str(limit.remaining))
h.add('X-RateLimit-Limit', str(limit.limit))
h.add('X-RateLimit-Reset', str(limit.reset))
return response
@app.template_filter('datetime')
def _jinja2_filter_datetime(ts):
return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts))
2013-12-03 22:09:57 +02:00
2013-12-03 01:50:19 +02:00
@app.errorhandler(404)
def page_not_found(e):
return render_template('errors/404.html'), 404
@app.route("/")
def root():
2013-12-03 22:09:57 +02:00
return redirect(url_for(config.ROOT_ENDPOINT))
2013-12-03 01:50:19 +02:00
2014-01-14 01:07:13 +02:00
@app.route("/new/", methods=['GET', 'POST'])
@login_required
def new():
if request.method == 'POST':
site_name = to_canonical(request.form['name'])
if Site.get_by_name(site_name):
flash("Site already exists")
return redirect(redirect_url())
else:
Site.create(name=site_name, founder=g.current_user.id)
return redirect('http://%s.%s' % (site_name, config.HOSTNAME))
else:
return render_template('wiki/new.html')
2013-12-03 01:50:19 +02:00
@app.route("/_account/")
@login_required
def account():
return render_template('account/index.html')
2013-12-03 22:09:57 +02:00
if 'devserver' not in sys.argv or os.environ.get('WERKZEUG_RUN_MAIN'):
app.discover()
2013-12-03 01:50:19 +02:00