yolo
This commit is contained in:
parent
99ce4acd00
commit
98c753aba6
|
@ -1,13 +1,20 @@
|
||||||
from gevent import monkey, wsgi
|
from gevent import wsgi
|
||||||
from realms import config, app, manager
|
from realms import config, app, manager
|
||||||
|
|
||||||
monkey.patch_all()
|
|
||||||
|
|
||||||
|
|
||||||
@manager.command
|
@manager.command
|
||||||
def server(port=10000):
|
def server(port=10000):
|
||||||
print "Server started (%s)" % config.ENV
|
print "Server started (%s)" % config.ENV
|
||||||
wsgi.WSGIServer(('', int(port)), app).serve_forever()
|
wsgi.WSGIServer(('', int(port)), app).serve_forever()
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def init_db():
|
||||||
|
from realms import db
|
||||||
|
import realms.models
|
||||||
|
print "Init DB"
|
||||||
|
db.drop_all()
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
manager.run()
|
manager.run()
|
|
@ -1,32 +1,46 @@
|
||||||
|
# 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.
|
||||||
import logging
|
import logging
|
||||||
|
logging.getLogger().setLevel(logging.INFO)
|
||||||
|
logging.getLogger('raven').setLevel(logging.WARNING)
|
||||||
|
logging.getLogger('requests').setLevel(logging.WARNING)
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import httplib
|
||||||
from flask import Flask, request, render_template, url_for, redirect, session, g
|
import traceback
|
||||||
|
from flask import Flask, request, render_template, url_for, redirect, session, flash, g
|
||||||
from flask.ctx import _AppCtxGlobals
|
from flask.ctx import _AppCtxGlobals
|
||||||
from flask.ext.script import Manager
|
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
|
||||||
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
from realms import config
|
from realms import config
|
||||||
|
from realms.lib.services import db
|
||||||
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
|
||||||
from realms.lib.util import to_canonical, remove_ext, mkdir_safe, gravatar_url
|
from realms.lib.util import to_canonical, remove_ext, mkdir_safe, gravatar_url, to_dict
|
||||||
from realms.lib.services import db
|
from realms.models import User, CurrentUser, Site
|
||||||
from realms.models import User, CurrentUser
|
|
||||||
|
|
||||||
|
|
||||||
sites = {}
|
sites = {}
|
||||||
|
|
||||||
|
|
||||||
class Site(object):
|
|
||||||
wiki = None
|
|
||||||
|
|
||||||
|
|
||||||
class AppCtxGlobals(_AppCtxGlobals):
|
class AppCtxGlobals(_AppCtxGlobals):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
@ -40,7 +54,7 @@ class AppCtxGlobals(_AppCtxGlobals):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not sites.get(subdomain):
|
if not sites.get(subdomain):
|
||||||
sites[subdomain] = Site()
|
sites[subdomain] = to_dict(Site.get_by_name(subdomain))
|
||||||
sites[subdomain].wiki = Wiki("%s/%s" % (config.REPO_DIR, subdomain))
|
sites[subdomain].wiki = Wiki("%s/%s" % (config.REPO_DIR, subdomain))
|
||||||
|
|
||||||
return sites[subdomain]
|
return sites[subdomain]
|
||||||
|
@ -149,9 +163,11 @@ app.url_map.converters['regex'] = RegexConverter
|
||||||
app.url_map.strict_slashes = False
|
app.url_map.strict_slashes = False
|
||||||
app.debug = True
|
app.debug = True
|
||||||
|
|
||||||
|
# Flask-SQLAlchemy
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
manager = Manager(app)
|
manager = Manager(app)
|
||||||
|
|
||||||
# Flask extension objects
|
|
||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
login_manager.init_app(app)
|
login_manager.init_app(app)
|
||||||
login_manager.login_view = 'auth.login'
|
login_manager.login_view = 'auth.login'
|
||||||
|
@ -161,6 +177,38 @@ login_manager.login_view = 'auth.login'
|
||||||
def load_user(user_id):
|
def load_user(user_id):
|
||||||
return CurrentUser(user_id)
|
return CurrentUser(user_id)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
assets = Environment()
|
assets = Environment()
|
||||||
assets.init_app(app)
|
assets.init_app(app)
|
||||||
if config.ENV is 'PROD':
|
if config.ENV is 'PROD':
|
||||||
|
@ -221,16 +269,25 @@ def page_not_found(e):
|
||||||
return render_template('errors/404.html'), 404
|
return render_template('errors/404.html'), 404
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(500)
|
|
||||||
def page_error(e):
|
|
||||||
logging.exception(e)
|
|
||||||
return render_template('errors/500.html'), 500
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def root():
|
def root():
|
||||||
return redirect(url_for(config.ROOT_ENDPOINT))
|
return redirect(url_for(config.ROOT_ENDPOINT))
|
||||||
|
|
||||||
|
@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')
|
||||||
|
|
||||||
|
|
||||||
@app.route("/_account/")
|
@app.route("/_account/")
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -240,5 +297,3 @@ def account():
|
||||||
if 'devserver' not in sys.argv or os.environ.get('WERKZEUG_RUN_MAIN'):
|
if 'devserver' not in sys.argv or os.environ.get('WERKZEUG_RUN_MAIN'):
|
||||||
app.discover()
|
app.discover()
|
||||||
|
|
||||||
print app.url_map
|
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,9 @@ import socket
|
||||||
|
|
||||||
HOSTNAME = socket.gethostname()
|
HOSTNAME = socket.gethostname()
|
||||||
|
|
||||||
DOMAIN = 'realms.dev'
|
|
||||||
ENV = 'DEV'
|
ENV = 'DEV'
|
||||||
|
|
||||||
DB_URI = 'postgresql://realms:dbpassword@localhost:5432/realms'
|
SQLALCHEMY_DATABASE_URI = 'postgresql://deploy:dbpassword@localhost:5432/realms'
|
||||||
|
|
||||||
REDIS_HOST = '127.0.0.1'
|
REDIS_HOST = '127.0.0.1'
|
||||||
REDIS_PORT = 6379
|
REDIS_PORT = 6379
|
||||||
|
@ -17,7 +16,6 @@ REPO_MAIN_NAME = '_'
|
||||||
REPO_FORBIDDEN_NAMES = ['api', 'www']
|
REPO_FORBIDDEN_NAMES = ['api', 'www']
|
||||||
REPO_ENABLE_SUBDOMAIN = True
|
REPO_ENABLE_SUBDOMAIN = True
|
||||||
|
|
||||||
|
|
||||||
RECAPTCHA_PUBLIC_KEY = '6LfoxeESAAAAAGNaeWnISh0GTgDk0fBnr6Bo2Tfk'
|
RECAPTCHA_PUBLIC_KEY = '6LfoxeESAAAAAGNaeWnISh0GTgDk0fBnr6Bo2Tfk'
|
||||||
RECAPTCHA_PRIVATE_KEY = '6LfoxeESAAAAABFzdCs0hNIIyeb42mofV-Ndd2_2'
|
RECAPTCHA_PRIVATE_KEY = '6LfoxeESAAAAABFzdCs0hNIIyeb42mofV-Ndd2_2'
|
||||||
RECAPTCHA_OPTIONS = {'theme': 'clean'}
|
RECAPTCHA_OPTIONS = {'theme': 'clean'}
|
||||||
|
@ -31,10 +29,12 @@ MODULES = [
|
||||||
]
|
]
|
||||||
|
|
||||||
if ENV is 'PROD':
|
if ENV is 'PROD':
|
||||||
SERVER_NAME = 'realms.io'
|
#SERVER_NAME = 'realms.io'
|
||||||
PORT = 80
|
PORT = 80
|
||||||
|
DOMAIN = 'realms.io'
|
||||||
else:
|
else:
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
ASSETS_DEBUG = True
|
ASSETS_DEBUG = True
|
||||||
SERVER_NAME = 'realms.dev:8000'
|
#SERVER_NAME = 'realms.dev:8000'
|
||||||
|
DOMAIN = 'realms.dev'
|
||||||
PORT = 8000
|
PORT = 8000
|
|
@ -1,10 +1,8 @@
|
||||||
import redis
|
import redis
|
||||||
from sqlalchemy import create_engine
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
# Default DB connection
|
|
||||||
from realms import config
|
from realms import config
|
||||||
|
|
||||||
db = create_engine(config.DB_URI, encoding='utf8', echo=True)
|
db = SQLAlchemy()
|
||||||
|
|
||||||
# Default Cache connection
|
# Default Cache connection
|
||||||
cache = redis.StrictRedis(host=config.REDIS_HOST, port=config.REDIS_PORT)
|
cache = redis.StrictRedis(host=config.REDIS_HOST, port=config.REDIS_PORT)
|
|
@ -10,6 +10,34 @@ from realms import config
|
||||||
from realms.lib.services import cache
|
from realms.lib.services import cache
|
||||||
|
|
||||||
|
|
||||||
|
class AttrDict(dict):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(AttrDict, self).__init__(*args, **kwargs)
|
||||||
|
self.__dict__ = self
|
||||||
|
|
||||||
|
|
||||||
|
def to_json(data):
|
||||||
|
return json.dumps(to_dict(data), separators=(',', ':'))
|
||||||
|
|
||||||
|
|
||||||
|
def to_dict(data):
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return AttrDict()
|
||||||
|
|
||||||
|
def row2dict(row):
|
||||||
|
d = AttrDict()
|
||||||
|
for column in row.__table__.columns:
|
||||||
|
d[column.name] = getattr(row, column.name)
|
||||||
|
|
||||||
|
return d
|
||||||
|
|
||||||
|
if isinstance(data, list):
|
||||||
|
return [row2dict(x) for x in data]
|
||||||
|
else:
|
||||||
|
return row2dict(data)
|
||||||
|
|
||||||
|
|
||||||
def cache_it(fn):
|
def cache_it(fn):
|
||||||
def wrap(*args, **kw):
|
def wrap(*args, **kw):
|
||||||
key = "%s:%s" % (args[0].table, args[1])
|
key = "%s:%s" % (args[0].table, args[1])
|
||||||
|
@ -40,26 +68,6 @@ def cache_it(fn):
|
||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
def to_json(res, first=False):
|
|
||||||
"""
|
|
||||||
Jsonify query result.
|
|
||||||
"""
|
|
||||||
res = to_dict(res, first)
|
|
||||||
return json.dumps(res, separators=(',', ':'))
|
|
||||||
|
|
||||||
|
|
||||||
def to_dict(cur, first=False):
|
|
||||||
if not cur:
|
|
||||||
return None
|
|
||||||
ret = []
|
|
||||||
for row in cur:
|
|
||||||
ret.append(row)
|
|
||||||
if ret and first:
|
|
||||||
return ret[0]
|
|
||||||
else:
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def validate_captcha():
|
def validate_captcha():
|
||||||
response = captcha.submit(
|
response = captcha.submit(
|
||||||
request.form['recaptcha_challenge_field'],
|
request.form['recaptcha_challenge_field'],
|
||||||
|
|
|
@ -63,11 +63,6 @@ class Wiki():
|
||||||
|
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_registered(name):
|
|
||||||
s = Site()
|
|
||||||
return True if s.get_by_name(name) else False
|
|
||||||
|
|
||||||
def revert_page(self, name, commit_sha, message, username):
|
def revert_page(self, name, commit_sha, message, username):
|
||||||
page = self.get_page(name, commit_sha)
|
page = self.get_page(name, commit_sha)
|
||||||
if not page:
|
if not page:
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
import bcrypt
|
import bcrypt
|
||||||
from sqlalchemy import Column, Integer, String, Time
|
from sqlalchemy import Column, Integer, String, Time
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
from flask import session, flash
|
from flask import session, flash
|
||||||
from flask.ext.login import login_user, logout_user
|
from flask.ext.login import login_user, logout_user
|
||||||
from realms.lib.util import gravatar_url, to_dict
|
|
||||||
from realms.lib.services import db
|
from realms.lib.services import db
|
||||||
|
from realms.lib.util import gravatar_url, to_dict
|
||||||
|
|
||||||
Base = declarative_base()
|
|
||||||
|
class ModelMixin(object):
|
||||||
|
def __getitem__(self, k):
|
||||||
|
return self.__getattribute__(k)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, **kwargs):
|
||||||
|
obj = cls(**kwargs)
|
||||||
|
db.session.add(obj)
|
||||||
|
db.session.commit()
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class CurrentUser():
|
class CurrentUser():
|
||||||
|
@ -15,8 +24,7 @@ class CurrentUser():
|
||||||
def __init__(self, id):
|
def __init__(self, id):
|
||||||
self.id = id
|
self.id = id
|
||||||
if id:
|
if id:
|
||||||
user = User()
|
session['user'] = to_dict(User.query.filter_by(id=id).first())
|
||||||
session['user'] = user.get_by_id(id)
|
|
||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
return self.id
|
return self.id
|
||||||
|
@ -38,28 +46,38 @@ class CurrentUser():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Site(Base):
|
class Site(ModelMixin, db.Model):
|
||||||
__tablename__ = 'sites'
|
__tablename__ = 'sites'
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
name = Column(String(100))
|
name = Column(String(100))
|
||||||
pages = Column(Integer)
|
pages = Column(Integer)
|
||||||
views = Column(Integer)
|
views = Column(Integer)
|
||||||
created = Column(Time)
|
founder = Column(Integer)
|
||||||
|
created_at = Column(Time)
|
||||||
|
updated_at = Column(Time)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_name(cls, name):
|
||||||
|
return Site.query.filter_by(name=name).first()
|
||||||
|
|
||||||
|
|
||||||
class User(Base):
|
class User(db.Model, ModelMixin):
|
||||||
__tablename__ = 'users'
|
__tablename__ = 'users'
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
username = Column(String(100))
|
username = Column(String(100))
|
||||||
email = Column(String(255))
|
email = Column(String(255))
|
||||||
|
avatar = Column(String(255))
|
||||||
password = Column(String(255))
|
password = Column(String(255))
|
||||||
joined = Column(Time)
|
created_at = Column(Time)
|
||||||
|
updated_at = Column(Time)
|
||||||
|
|
||||||
def get_by_email(self, email):
|
@classmethod
|
||||||
return to_dict(self.get_one(email, 'email'), True)
|
def get_by_email(cls, email):
|
||||||
|
return User.query.filter_by(email=email).first()
|
||||||
|
|
||||||
def get_by_username(self, username):
|
@classmethod
|
||||||
return to_dict(self.get_one(username, 'username'), True)
|
def get_by_username(cls, username):
|
||||||
|
return User.query.filter_by(username=username).first()
|
||||||
|
|
||||||
def login(self, login, password):
|
def login(self, login, password):
|
||||||
pass
|
pass
|
||||||
|
@ -84,6 +102,7 @@ class User(Base):
|
||||||
if user.get_by_email(email):
|
if user.get_by_email(email):
|
||||||
flash('Email is already taken')
|
flash('Email is already taken')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if user.get_by_username(username):
|
if user.get_by_username(username):
|
||||||
flash('Username is already taken')
|
flash('Username is already taken')
|
||||||
return False
|
return False
|
||||||
|
@ -93,7 +112,6 @@ class User(Base):
|
||||||
username=username,
|
username=username,
|
||||||
password=bcrypt.hashpw(password, bcrypt.gensalt(10)),
|
password=bcrypt.hashpw(password, bcrypt.gensalt(10)),
|
||||||
avatar=gravatar_url(email))
|
avatar=gravatar_url(email))
|
||||||
|
|
||||||
User.login(u.id)
|
User.login(u.id)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -1,30 +1,10 @@
|
||||||
from flask import g, render_template, request, redirect, Blueprint, flash, url_for
|
from flask import g, render_template, request, redirect, Blueprint, flash, url_for
|
||||||
from flask.ext.login import login_required
|
from flask.ext.login import login_required
|
||||||
from realms import redirect_url, config
|
|
||||||
from realms.lib.util import to_canonical, remove_ext
|
from realms.lib.util import to_canonical, remove_ext
|
||||||
from realms.lib.wiki import Wiki
|
|
||||||
from realms.models import Site
|
|
||||||
|
|
||||||
blueprint = Blueprint('wiki', __name__, url_prefix='/wiki')
|
blueprint = Blueprint('wiki', __name__, url_prefix='/wiki')
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/new/", methods=['GET', 'POST'])
|
|
||||||
@login_required
|
|
||||||
def new():
|
|
||||||
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("/_commit/<sha>/<name>")
|
@blueprint.route("/_commit/<sha>/<name>")
|
||||||
def commit(name, sha):
|
def commit(name, sha):
|
||||||
cname = to_canonical(name)
|
cname = to_canonical(name)
|
||||||
|
|
17
realms/templates/errors/error.html
Normal file
17
realms/templates/errors/error.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}{{ title|escape }}{% endblock %}
|
||||||
|
{% block scripts %}
|
||||||
|
{% if traceback %}
|
||||||
|
<link rel="stylesheet" href="{{ static_url('vendor/highlightjs/default.css') }}" />
|
||||||
|
<script src="{{ static_url('vendor/highlightjs/highlight.pack.js') }}"></script>
|
||||||
|
<script>hljs.initHighlightingOnLoad();</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="page-header"><h1>{{ title }} <small>{{ status_code }}</small></h1></div>
|
||||||
|
{% if message %}<div class="alert alert-error">{{ message|escape }}</div>{% endif %}
|
||||||
|
{% if traceback %}<pre><code>{{ traceback|escape }}</code></pre>{% endif %}
|
||||||
|
<a href="{{ url_for('home.index') }}" class="btn btn-danger btn-large">← Back to Safety</a>
|
||||||
|
<hr />
|
||||||
|
<div class="help-block">Think you know what happened? Please <a href="mailto:support@realms.io">email</a> us.</div>
|
||||||
|
{% endblock %}
|
|
@ -48,9 +48,6 @@
|
||||||
<li><a href="{{ url_for('wiki.edit', name=name) }}">Edit Page</a></li>
|
<li><a href="{{ url_for('wiki.edit', name=name) }}">Edit Page</a></li>
|
||||||
<li><a href="{{ url_for('wiki.history', name=name) }}">History</a></li>
|
<li><a href="{{ url_for('wiki.history', name=name) }}">History</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li class="divider"></li>
|
|
||||||
<li class="dropdown-header">Site Options</li>
|
|
||||||
<li><a href="{{ url_for('wiki.new') }}">Create New Site</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ 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
|
Flask-Script==0.6.3
|
||||||
|
Flask-SQLAlchemy==1.0
|
||||||
beautifulsoup4==4.3.2
|
beautifulsoup4==4.3.2
|
||||||
boto==2.17.0
|
boto==2.17.0
|
||||||
closure==20121212
|
closure==20121212
|
||||||
|
@ -16,7 +17,6 @@ pyzmq==14.0.0
|
||||||
recaptcha==1.0rc1
|
recaptcha==1.0rc1
|
||||||
recaptcha-client==1.0.6
|
recaptcha-client==1.0.6
|
||||||
redis==2.8.0
|
redis==2.8.0
|
||||||
rethinkdb==1.10.0-0
|
|
||||||
simplejson==3.3.1
|
simplejson==3.3.1
|
||||||
sockjs-tornado==1.0.0
|
sockjs-tornado==1.0.0
|
||||||
supervisor==3.0
|
supervisor==3.0
|
||||||
|
|
Loading…
Reference in a new issue