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 realms import config, app
monkey.patch_all()
import logging
from realms import app, config
if __name__ == '__main__':

View File

@ -1,16 +1,17 @@
import logging
import os
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.ext.script import Manager
from flask.ext.login import LoginManager, login_required
from flask.ext.assets import Environment, Bundle
from werkzeug.routing import BaseConverter
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.session import RedisSessionInterface
from realms.lib.wiki import Wiki
@ -19,13 +20,26 @@ from realms.lib.services import db
from models import Site, User, CurrentUser
wikis = {}
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
def current_site(self):
ext = tldextract.extract(request.host)
print ext
return ext.subdomain
host = request.host.split(':')[0]
return host[:-len(config.DOMAIN)].rstrip('.')
@cached_property
def current_user(self):
@ -53,6 +67,41 @@ class Application(Flask):
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):
"""
@ -77,23 +126,24 @@ def redirect_url(referrer=None):
def format_subdomain(s):
if not config.repos['enable_subrepos']:
if not config.REPO_ENABLE_SUBDOMAIN:
return ""
s = s.lower()
s = to_canonical(s)
if s in config.repos['forbidden_subrepos']:
if s in config.REPO_FORBIDDEN_NAMES:
# Not allowed
s = ""
return s
app = Application(__name__)
app.config.update(config.FLASK)
app.debug = (config.ENV is not 'PROD')
app.secret_key = config.SECRET_KEY
app.static_path = os.sep + 'static'
app.config.from_object('realms.config')
app.session_interface = RedisSessionInterface()
app.url_map.converters['regex'] = RegexConverter
app.url_map.strict_slashes = False
app.debug = True
manager = Manager(app)
# Flask extension objects
login_manager = LoginManager()
@ -137,8 +187,6 @@ else:
filters='closure_js', output='packed-editor.js')
assets.register('js_editor', js)
repo_dir = config.REPO_DIR
@app.after_request
def inject_x_rate_headers(response):
@ -155,6 +203,7 @@ def inject_x_rate_headers(response):
def _jinja2_filter_datetime(ts):
return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts))
@app.errorhandler(404)
def page_not_found(e):
return render_template('errors/404.html'), 404
@ -167,160 +216,18 @@ def page_error(e):
@app.route("/")
@ratelimiter(limit=50, per=60)
def root():
return g.current_site
return render('home')
return redirect(url_for(config.ROOT_ENDPOINT))
@app.route("/home")
def home():
return redirect(url_for('root'))
@app.route("/_account/")
@login_required
def account():
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'])
@login_required
def new_wiki():
if request.method == 'POST':
wiki_name = to_canonical(request.form['name'])
print app.url_map
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
from realms import config
from sqlalchemy import create_engine
# Default DB connection
from realms import config
db = create_engine(config.DB_URI, encoding='utf8', echo=True)
# Default Cache connection

View File

@ -59,11 +59,12 @@ def to_dict(cur, first=False):
else:
return ret
def validate_captcha():
response = captcha.submit(
request.form['recaptcha_challenge_field'],
request.form['recaptcha_response_field'],
config.flask['RECAPTCHA_PRIVATE_KEY'],
config.RECAPTCHA_PRIVATE_KEY,
request.remote_addr)
return response.is_valid
@ -125,4 +126,4 @@ def to_canonical(s):
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 re
import lxml.html
from lxml.html.clean import Cleaner
import ghdiff
import gittle.utils
from gittle import Gittle
@ -92,7 +93,9 @@ class Wiki():
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)
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="">
<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/font-awesome.min.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-Bcrypt==0.5.2
Flask-Login==0.2.7
Flask-Script==0.6.3
beautifulsoup4==4.3.2
boto==2.17.0
closure==20121212