many things have occured

This commit is contained in:
Matthew Scragg 2013-12-03 14:09:57 -06:00
parent 02b6b7d592
commit db70df22a2
23 changed files with 281 additions and 167 deletions

3
app.py
View file

@ -1,7 +1,8 @@
from gevent import monkey, pywsgi from gevent import monkey, pywsgi
from realms import config, app
monkey.patch_all() monkey.patch_all()
import logging import logging
from realms import app, config
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -1,16 +1,17 @@
import logging import logging
import os
import time import time
from tldextract import tldextract import sys
import os
from flask import Flask, g, request, render_template, url_for, redirect, flash, session, current_app from flask import Flask, request, render_template, url_for, redirect, session
from flask.ctx import _AppCtxGlobals from flask.ctx import _AppCtxGlobals
from flask.ext.script import Manager
from flask.ext.login import LoginManager, login_required from flask.ext.login import LoginManager, login_required
from flask.ext.assets import Environment, Bundle from flask.ext.assets import Environment, Bundle
from werkzeug.routing import BaseConverter from werkzeug.routing import BaseConverter
from werkzeug.utils import cached_property from werkzeug.utils import cached_property
import config from realms import config
from realms.lib.ratelimit import get_view_rate_limit, ratelimiter from realms.lib.ratelimit import get_view_rate_limit, ratelimiter
from realms.lib.session import RedisSessionInterface from realms.lib.session import RedisSessionInterface
from realms.lib.wiki import Wiki from realms.lib.wiki import Wiki
@ -19,13 +20,26 @@ from realms.lib.services import db
from models import Site, User, CurrentUser from models import Site, User, CurrentUser
wikis = {}
class AppCtxGlobals(_AppCtxGlobals): class AppCtxGlobals(_AppCtxGlobals):
@cached_property
def current_wiki(self):
subdomain = format_subdomain(self.current_site)
if not subdomain:
subdomain = "_"
if not wikis.get(subdomain):
wikis[subdomain] = Wiki("%s/%s" % (config.REPO_DIR, subdomain))
return wikis[subdomain]
@cached_property @cached_property
def current_site(self): def current_site(self):
ext = tldextract.extract(request.host) host = request.host.split(':')[0]
print ext return host[:-len(config.DOMAIN)].rstrip('.')
return ext.subdomain
@cached_property @cached_property
def current_user(self): def current_user(self):
@ -53,6 +67,41 @@ class Application(Flask):
return super(Application, self).__call__(environ, start_response) return super(Application, self).__call__(environ, start_response)
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))
def init_db(dbname): def init_db(dbname):
""" """
@ -77,23 +126,24 @@ def redirect_url(referrer=None):
def format_subdomain(s): def format_subdomain(s):
if not config.repos['enable_subrepos']: if not config.REPO_ENABLE_SUBDOMAIN:
return "" return ""
s = s.lower() s = s.lower()
s = to_canonical(s) s = to_canonical(s)
if s in config.repos['forbidden_subrepos']: if s in config.REPO_FORBIDDEN_NAMES:
# Not allowed # Not allowed
s = "" s = ""
return s return s
app = Application(__name__) app = Application(__name__)
app.config.update(config.FLASK) app.config.from_object('realms.config')
app.debug = (config.ENV is not 'PROD')
app.secret_key = config.SECRET_KEY
app.static_path = os.sep + 'static'
app.session_interface = RedisSessionInterface() app.session_interface = RedisSessionInterface()
app.url_map.converters['regex'] = RegexConverter app.url_map.converters['regex'] = RegexConverter
app.url_map.strict_slashes = False
app.debug = True
manager = Manager(app)
# Flask extension objects # Flask extension objects
login_manager = LoginManager() login_manager = LoginManager()
@ -137,8 +187,6 @@ else:
filters='closure_js', output='packed-editor.js') filters='closure_js', output='packed-editor.js')
assets.register('js_editor', js) assets.register('js_editor', js)
repo_dir = config.REPO_DIR
@app.after_request @app.after_request
def inject_x_rate_headers(response): def inject_x_rate_headers(response):
@ -155,6 +203,7 @@ def inject_x_rate_headers(response):
def _jinja2_filter_datetime(ts): def _jinja2_filter_datetime(ts):
return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts)) return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts))
@app.errorhandler(404) @app.errorhandler(404)
def page_not_found(e): def page_not_found(e):
return render_template('errors/404.html'), 404 return render_template('errors/404.html'), 404
@ -167,160 +216,18 @@ def page_error(e):
@app.route("/") @app.route("/")
@ratelimiter(limit=50, per=60)
def root(): def root():
return g.current_site return redirect(url_for(config.ROOT_ENDPOINT))
return render('home')
@app.route("/home")
def home():
return redirect(url_for('root'))
@app.route("/_account/") @app.route("/_account/")
@login_required @login_required
def account(): def account():
return render_template('account/index.html') return render_template('account/index.html')
if 'devserver' not in sys.argv or os.environ.get('WERKZEUG_RUN_MAIN'):
app.discover()
@app.route("/_new/", methods=['GET', 'POST']) print app.url_map
@login_required
def new_wiki():
if request.method == 'POST':
wiki_name = to_canonical(request.form['name'])
if Wiki.is_registered(wiki_name):
flash("Site already exists")
return redirect(redirect_url())
else:
s = Site()
s.create(name=wiki_name, repo=wiki_name, founder=g.current_user.get('id'))
return redirect('http://%s.%s' % (wiki_name, config.hostname))
else:
return render_template('_new/index.html')
@app.route("/_logout/")
def logout():
User.logout()
return redirect(url_for('root'))
@app.route("/_commit/<sha>/<name>")
def commit_sha(name, sha):
cname = to_canonical(name)
data = Wiki.get_page(cname, sha=sha)
if data:
return render_template('page/page.html', name=name, page=data, commit=sha)
else:
return redirect('/_create/'+cname)
@app.route("/_compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>")
def compare(name, fsha, dots, lsha):
diff = Wiki.compare(name, fsha, lsha)
return render_template('page/compare.html', name=name, diff=diff, old=fsha, new=lsha)
@app.route("/_revert", methods=['POST'])
def revert():
if request.method == 'POST':
name = request.form.get('name')
commit = request.form.get('commit')
cname = to_canonical(name)
Wiki.revert_page(name, commit, message="Reverting %s" % cname, username=g.current_user.get('username'))
flash('Page reverted', 'success')
return redirect("/" + cname)
@app.route("/_register", methods=['GET', 'POST'])
def register():
if request.method == 'POST':
if User.register(request.form.get('username'), request.form.get('email'), request.form.get('password')):
return redirect(url_for('root'))
else:
# Login failed
return redirect(url_for('register'))
else:
return render_template('account/register.html')
@app.route("/_login", methods=['GET', 'POST'])
def login():
if request.method == 'POST':
if User.auth(request.form['email'], request.form['password']):
return redirect(redirect_url(referrer=url_for('root')))
else:
flash("Email or Password invalid")
return redirect("/_login")
else:
return render_template('account/login.html')
@app.route("/_history/<name>")
def history(name):
history = Wiki.get_history(name)
return render_template('page/history.html', name=name, history=history)
@app.route("/_edit/<name>", methods=['GET', 'POST'])
def edit(name):
data = Wiki.get_page(name)
cname = to_canonical(name)
if request.method == 'POST':
edit_cname = to_canonical(request.form['name'])
if edit_cname.lower() != cname.lower():
Wiki.rename_page(cname, edit_cname)
Wiki.write_page(edit_cname,
request.form['content'],
message=request.form['message'],
username=g.current_user.get('username'))
return redirect("/" + edit_cname)
else:
if data:
name = remove_ext(data['name'])
content = data['data']
return render_template('page/edit.html', name=name, content=content)
else:
return redirect('/_create/'+cname)
@app.route("/_delete/<name>", methods=['POST'])
@login_required
def delete(name):
pass
@app.route("/_create/", methods=['GET', 'POST'])
@app.route("/_create/<name>", methods=['GET', 'POST'])
def create(name=None):
cname = ""
if name:
cname = to_canonical(name)
if Wiki.get_page(cname):
# Page exists, edit instead
return redirect("/edit/" + cname)
if request.method == 'POST':
Wiki.write_page(request.form['name'],
request.form['content'],
message=request.form['message'],
create=True,
username=g.current_user.get('username'))
return redirect("/" + cname)
else:
return render_template('page/edit.html', name=cname, content="")
@app.route("/<name>")
def render(name):
cname = to_canonical(name)
if cname != name:
return redirect('/' + cname)
data = Wiki.get_page(cname)
if data:
return render_template('page/page.html', name=cname, page=data)
else:
return redirect('/_create/'+cname)

38
realms/config/__init__.py Normal file
View file

@ -0,0 +1,38 @@
import socket
HOSTNAME = socket.gethostname()
DOMAIN = 'realms.dev'
ENV = 'DEV'
PORT = 10000
DB_URI = 'postgresql://realms:dbpassword@localhost:5432/realms'
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
SECRET_KEY = 'K3dRq1q9eN72GJDkgvyshFVwlqHHCyPI'
REPO_DIR = '/home/deploy/repos'
REPO_MAIN_NAME = '_'
REPO_FORBIDDEN_NAMES = ['api', 'www']
REPO_ENABLE_SUBDOMAIN = True
RECAPTCHA_PUBLIC_KEY = '6LfoxeESAAAAAGNaeWnISh0GTgDk0fBnr6Bo2Tfk'
RECAPTCHA_PRIVATE_KEY = '6LfoxeESAAAAABFzdCs0hNIIyeb42mofV-Ndd2_2'
RECAPTCHA_OPTIONS = {'theme': 'clean'}
ROOT_ENDPOINT = 'wiki.page'
WIKI_HOME = 'home'
MODULES = [
'wiki',
'auth'
]
if ENV is 'PROD':
pass
else:
DEBUG = True
ASSETS_DEBUG = True

View file

@ -1,8 +1,9 @@
import redis import redis
from realms import config
from sqlalchemy import create_engine from sqlalchemy import create_engine
# Default DB connection # Default DB connection
from realms import config
db = create_engine(config.DB_URI, encoding='utf8', echo=True) db = create_engine(config.DB_URI, encoding='utf8', echo=True)
# Default Cache connection # Default Cache connection

View file

@ -59,11 +59,12 @@ def to_dict(cur, first=False):
else: else:
return ret return ret
def validate_captcha(): def validate_captcha():
response = captcha.submit( response = captcha.submit(
request.form['recaptcha_challenge_field'], request.form['recaptcha_challenge_field'],
request.form['recaptcha_response_field'], request.form['recaptcha_response_field'],
config.flask['RECAPTCHA_PRIVATE_KEY'], config.RECAPTCHA_PRIVATE_KEY,
request.remote_addr) request.remote_addr)
return response.is_valid return response.is_valid
@ -125,4 +126,4 @@ def to_canonical(s):
def gravatar_url(email): def gravatar_url(email):
return "https://www.gravatar.com/avatar/" + hashlib.md5(email).hexdigest() return "//www.gravatar.com/avatar/" + hashlib.md5(email).hexdigest()

View file

@ -1,6 +1,7 @@
import os import os
import re import re
import lxml.html import lxml.html
from lxml.html.clean import Cleaner
import ghdiff import ghdiff
import gittle.utils import gittle.utils
from gittle import Gittle from gittle import Gittle
@ -92,7 +93,9 @@ class Wiki():
tree = lxml.html.fromstring(content) tree = lxml.html.fromstring(content)
cleaner = lxml.html.Cleaner(remove_unknown_tags=False, kill_tags=set(['style']), safe_attrs_only=False) cleaner = Cleaner(remove_unknown_tags=False,
kill_tags=set(['style']),
safe_attrs_only=False)
tree = cleaner.clean_html(tree) tree = cleaner.clean_html(tree)
content = lxml.html.tostring(tree, encoding='utf-8', method='html') content = lxml.html.tostring(tree, encoding='utf-8', method='html')

View file

View file

View file

@ -0,0 +1,35 @@
from flask import render_template, redirect, request, url_for, flash, Blueprint
from realms import redirect_url
from realms.models import User
blueprint = Blueprint('auth', __name__)
@blueprint.route("/logout/")
def logout():
User.logout()
return redirect(url_for('root'))
@blueprint.route("/register/", methods=['GET', 'POST'])
def register():
if request.method == 'POST':
if User.register(request.form.get('username'), request.form.get('email'), request.form.get('password')):
return redirect(url_for('root'))
else:
# Login failed
return redirect(url_for('.register'))
else:
return render_template('auth/register.html')
@blueprint.route("/login/", methods=['GET', 'POST'])
def login():
if request.method == 'POST':
if User.auth(request.form['email'], request.form['password']):
return redirect(redirect_url(referrer=url_for('root')))
else:
flash("Email or Password invalid")
return redirect(url_for(".login"))
else:
return render_template('auth/login.html')

View file

View file

@ -0,0 +1,5 @@
import realms
c = realms.app.test_client()
print c.get('/wiki/_create')
print c.get('/wiki/_create/blah')

View file

@ -0,0 +1,122 @@
from flask import g, render_template, request, redirect, Blueprint, flash, url_for
from flask.ext.login import login_required
from realms import app, redirect_url, config
from realms.lib.util import to_canonical, remove_ext
from realms.lib.wiki import Wiki
from realms.models import Site
blueprint = Blueprint('wiki', __name__)
@blueprint.route("/wiki/_new/", methods=['GET', 'POST'])
@login_required
def new_wiki():
if request.method == 'POST':
wiki_name = to_canonical(request.form['name'])
if Wiki.is_registered(wiki_name):
flash("Site already exists")
return redirect(redirect_url())
else:
s = Site()
s.create(name=wiki_name, repo=wiki_name, founder=g.current_user.get('id'))
return redirect('http://%s.%s' % (wiki_name, config.HOSTNAME))
else:
return render_template('wiki/new.html')
@blueprint.route("/wiki/_commit/<sha>/<name>")
def commit_sha(name, sha):
cname = to_canonical(name)
data = g.current_wiki.get_page(cname, sha=sha)
if data:
return render_template('wiki/page.html', name=name, page=data, commit=sha)
else:
return redirect(url_for('.create', name=cname))
@blueprint.route("/wiki/_compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>")
def compare(name, fsha, dots, lsha):
diff = g.current_wiki.compare(name, fsha, lsha)
return render_template('wiki/compare.html', name=name, diff=diff, old=fsha, new=lsha)
@blueprint.route("/wiki/_revert", methods=['POST'])
def revert():
if request.method == 'POST':
name = request.form.get('name')
commit = request.form.get('commit')
cname = to_canonical(name)
g.current_wiki.revert_page(name, commit, message="Reverting %s" % cname, username=g.current_user.get('username'))
flash('Page reverted', 'success')
return redirect(url_for('.page', name=cname))
@blueprint.route("/wiki/_history/<name>")
def history(name):
history = g.current_wiki.get_history(name)
return render_template('wiki/history.html', name=name, history=history)
@blueprint.route("/wiki/_edit/<name>", methods=['GET', 'POST'])
def edit(name):
data = g.current_wiki.get_page(name)
cname = to_canonical(name)
if request.method == 'POST':
edit_cname = to_canonical(request.form['name'])
if edit_cname.lower() != cname.lower():
g.current_wiki.rename_page(cname, edit_cname)
g.current_wiki.write_page(edit_cname,
request.form['content'],
message=request.form['message'],
username=g.current_user.get('username'))
return redirect(url_for('.page', name=edit_cname))
else:
if data:
name = remove_ext(data['name'])
content = data['data']
return render_template('wiki/edit.html', name=name, content=content)
else:
return redirect(url_for('.create', name=cname))
@blueprint.route("/wiki/_delete/<name>", methods=['POST'])
@login_required
def delete(name):
pass
@blueprint.route("/wiki/_create/", defaults={'name': None}, methods=['GET', 'POST'])
@blueprint.route("/wiki/_create/<name>", methods=['GET', 'POST'])
def create(name):
cname = ""
if name:
cname = to_canonical(name)
if g.current_wiki.get_page(cname):
# Page exists, edit instead
return redirect(url_for('.edit', name=cname))
if request.method == 'POST':
g.current_wiki.write_page(request.form['name'],
request.form['content'],
message=request.form['message'],
create=True,
username=g.current_user.get('username'))
return redirect(url_for('.page', name=cname))
else:
return render_template('wiki/edit.html', name=cname, content="")
@blueprint.route("/wiki/", defaults={'name': 'home'})
@blueprint.route("/wiki/<name>")
def page(name):
cname = to_canonical(name)
if cname != name:
return redirect(url_for('.page', name=cname))
data = g.current_wiki.get_page(cname)
if data:
return render_template('wiki/page.html', name=cname, page=data)
else:
return redirect(url_for('.create', name=cname))

View file

@ -7,7 +7,7 @@
<meta name="author" content=""> <meta name="author" content="">
<title>Realms</title> <title>Realms</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon.ico') }}">
<link href="/static/css/bootstrap/spacelab.css" rel="stylesheet"> <link href="/static/css/bootstrap/spacelab.css" rel="stylesheet">
<link href="/static/css/font-awesome.min.css" rel="stylesheet"> <link href="/static/css/font-awesome.min.css" rel="stylesheet">
<link href="/static/vendor/highlightjs/styles/github.css" rel="stylesheet"> <link href="/static/vendor/highlightjs/styles/github.css" rel="stylesheet">

View file

@ -2,6 +2,7 @@ Flask==0.10.1
Flask-Assets==0.8 Flask-Assets==0.8
Flask-Bcrypt==0.5.2 Flask-Bcrypt==0.5.2
Flask-Login==0.2.7 Flask-Login==0.2.7
Flask-Script==0.6.3
beautifulsoup4==4.3.2 beautifulsoup4==4.3.2
boto==2.17.0 boto==2.17.0
closure==20121212 closure==20121212