commit
9de1e4bdd8
|
@ -1,4 +1,5 @@
|
||||||
include requirements.txt VERSION LICENSE README.md
|
include requirements.txt LICENSE README.md
|
||||||
|
recursive-include realms/static/img *
|
||||||
recursive-include realms/static/fonts *
|
recursive-include realms/static/fonts *
|
||||||
recursive-include realms/static/css *
|
recursive-include realms/static/css *
|
||||||
recursive-include realms/static/js *
|
recursive-include realms/static/js *
|
||||||
|
|
10
realms-wiki
10
realms-wiki
|
@ -1,6 +1,14 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
import pip
|
||||||
from realms.cli import cli
|
from realms.cli import cli
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
print
|
||||||
|
print "----------------------------------"
|
||||||
|
print "This script is for development.\n" \
|
||||||
|
"If you installed via pip, " \
|
||||||
|
"you should have realms-wiki in your PATH that should be used instead."
|
||||||
|
print "----------------------------------"
|
||||||
|
print
|
||||||
|
|
||||||
cli()
|
cli()
|
||||||
|
|
|
@ -18,9 +18,10 @@ from flask.ext.assets import Environment, Bundle
|
||||||
from werkzeug.routing import BaseConverter
|
from werkzeug.routing import BaseConverter
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
from realms.lib.util import to_canonical, remove_ext, mkdir_safe, gravatar_url, to_dict
|
from .lib.util import to_canonical, remove_ext, mkdir_safe, gravatar_url, to_dict
|
||||||
from realms.lib.hook import HookModelMeta
|
from .lib.hook import HookModelMeta
|
||||||
from realms.lib.util import is_su, in_virtualenv
|
from .lib.util import is_su, in_virtualenv
|
||||||
|
from .version import __version__
|
||||||
|
|
||||||
|
|
||||||
class Application(Flask):
|
class Application(Flask):
|
||||||
|
@ -60,14 +61,25 @@ class Application(Flask):
|
||||||
for module_name in self.config['MODULES']:
|
for module_name in self.config['MODULES']:
|
||||||
sources = __import__('%s.%s' % (import_name, module_name), fromlist=fromlist)
|
sources = __import__('%s.%s' % (import_name, module_name), fromlist=fromlist)
|
||||||
|
|
||||||
|
if hasattr(sources, 'init'):
|
||||||
|
sources.init(self)
|
||||||
|
|
||||||
# Blueprint
|
# Blueprint
|
||||||
if hasattr(sources, 'views'):
|
if hasattr(sources, 'views'):
|
||||||
self.register_blueprint(sources.views.blueprint)
|
self.register_blueprint(sources.views.blueprint, url_prefix=self.config['RELATIVE_PATH'])
|
||||||
|
|
||||||
# Click
|
# Click
|
||||||
if hasattr(sources, 'commands'):
|
if hasattr(sources, 'commands'):
|
||||||
cli.add_command(sources.commands.cli, name=module_name)
|
cli.add_command(sources.commands.cli, name=module_name)
|
||||||
|
|
||||||
|
# Hooks
|
||||||
|
if hasattr(sources, 'hooks'):
|
||||||
|
if hasattr(sources.hooks, 'before_request'):
|
||||||
|
self.before_request(sources.hooks.before_request)
|
||||||
|
|
||||||
|
if hasattr(sources.hooks, 'before_first_request'):
|
||||||
|
self.before_first_request(sources.hooks.before_first_request)
|
||||||
|
|
||||||
# print >> sys.stderr, ' * Ready in %.2fms' % (1000.0 * (time.time() - start_time))
|
# print >> sys.stderr, ' * Ready in %.2fms' % (1000.0 * (time.time() - start_time))
|
||||||
|
|
||||||
def make_response(self, rv):
|
def make_response(self, rv):
|
||||||
|
@ -148,27 +160,29 @@ def error_handler(e):
|
||||||
return response, status_code
|
return response, status_code
|
||||||
|
|
||||||
|
|
||||||
|
def create_app(config=None):
|
||||||
app = Application(__name__)
|
app = Application(__name__)
|
||||||
app.config.from_object('realms.config')
|
app.config.from_object('realms.config')
|
||||||
app.url_map.converters['regex'] = RegexConverter
|
app.url_map.converters['regex'] = RegexConverter
|
||||||
app.url_map.strict_slashes = False
|
app.url_map.strict_slashes = False
|
||||||
|
|
||||||
|
login_manager.init_app(app)
|
||||||
|
db.init_app(app)
|
||||||
|
cache.init_app(app)
|
||||||
|
assets.init_app(app)
|
||||||
|
|
||||||
for status_code in httplib.responses:
|
for status_code in httplib.responses:
|
||||||
if status_code >= 400:
|
if status_code >= 400:
|
||||||
app.register_error_handler(status_code, error_handler)
|
app.register_error_handler(status_code, error_handler)
|
||||||
|
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def init_g():
|
def init_g():
|
||||||
g.assets = dict(css=['main.css'], js=['main.js'])
|
g.assets = dict(css=['main.css'], js=['main.js'])
|
||||||
|
|
||||||
|
|
||||||
@app.template_filter('datetime')
|
@app.template_filter('datetime')
|
||||||
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
|
||||||
|
@ -178,26 +192,23 @@ if app.config['RELATIVE_PATH']:
|
||||||
def root():
|
def root():
|
||||||
return redirect(url_for(app.config['ROOT_ENDPOINT']))
|
return redirect(url_for(app.config['ROOT_ENDPOINT']))
|
||||||
|
|
||||||
|
app.discover()
|
||||||
|
|
||||||
|
# This will be removed at some point
|
||||||
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
@click.group()
|
|
||||||
@click.pass_context
|
|
||||||
def cli(ctx):
|
|
||||||
# This could probably done better
|
|
||||||
if ctx.invoked_subcommand in ['setup', 'setup_upstart', 'pip']:
|
|
||||||
if not in_virtualenv() and not is_su():
|
|
||||||
# This does not account for people the have user level python installs
|
|
||||||
# that aren't virtual environments! Should be rare I think
|
|
||||||
click.secho("This command requires root privileges, use sudo or run as root.", fg='red')
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
# Init plugins here if possible
|
# Init plugins here if possible
|
||||||
login_manager = LoginManager(app)
|
login_manager = LoginManager()
|
||||||
login_manager.login_view = 'auth.login'
|
login_manager.login_view = 'auth.login'
|
||||||
|
|
||||||
db = MySQLAlchemy(app)
|
db = MySQLAlchemy()
|
||||||
cache = Cache(app)
|
cache = Cache()
|
||||||
|
assets = Assets()
|
||||||
|
|
||||||
assets = Assets(app)
|
|
||||||
assets.register('main.js',
|
assets.register('main.js',
|
||||||
'vendor/jquery/dist/jquery.js',
|
'vendor/jquery/dist/jquery.js',
|
||||||
'vendor/components-bootstrap/js/bootstrap.js',
|
'vendor/components-bootstrap/js/bootstrap.js',
|
||||||
|
@ -220,13 +231,7 @@ assets.register('main.css',
|
||||||
'vendor/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css',
|
'vendor/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css',
|
||||||
'css/style.css')
|
'css/style.css')
|
||||||
|
|
||||||
app.discover()
|
|
||||||
|
|
||||||
# This will be removed at some point
|
|
||||||
db.create_all()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
def cli():
|
||||||
|
pass
|
||||||
|
|
|
@ -1,11 +1,40 @@
|
||||||
from realms import config, app, db, cli
|
from realms import config, create_app, db, cli as cli_, __version__
|
||||||
from realms.lib.util import random_string, in_virtualenv
|
from realms.lib.util import is_su, random_string, in_virtualenv, green, yellow, red
|
||||||
from subprocess import call, Popen
|
from subprocess import call, Popen
|
||||||
from multiprocessing import cpu_count
|
from multiprocessing import cpu_count
|
||||||
import click
|
import click
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import pip
|
||||||
|
|
||||||
|
|
||||||
|
def print_version(ctx, param, value):
|
||||||
|
if not value or ctx.resilient_parsing:
|
||||||
|
return
|
||||||
|
green(__version__)
|
||||||
|
ctx.exit()
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
@click.option('--version', is_flag=True, callback=print_version,
|
||||||
|
expose_value=False, is_eager=True)
|
||||||
|
@click.pass_context
|
||||||
|
def cli(ctx):
|
||||||
|
# This could probably done better
|
||||||
|
if ctx.invoked_subcommand in ['setup', 'pip']:
|
||||||
|
if not in_virtualenv() and not is_su():
|
||||||
|
# This does not account for people the have user level python installs
|
||||||
|
# that aren't virtual environments! Should be rare I think
|
||||||
|
click.secho("This command requires root privileges, use sudo or run as root.", fg='red')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if ctx.invoked_subcommand in ['setup_upstart']:
|
||||||
|
if not is_su():
|
||||||
|
click.secho("This command requires root privileges, use sudo or run as root.", fg='red')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
cli.add_command(cli_)
|
||||||
|
|
||||||
|
|
||||||
def get_user():
|
def get_user():
|
||||||
|
@ -33,18 +62,6 @@ def module_exists(module_name):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def green(s):
|
|
||||||
click.secho(s, fg='green')
|
|
||||||
|
|
||||||
|
|
||||||
def yellow(s):
|
|
||||||
click.secho(s, fg='yellow')
|
|
||||||
|
|
||||||
|
|
||||||
def red(s):
|
|
||||||
click.secho(s, fg='red')
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.option('--site-title',
|
@click.option('--site-title',
|
||||||
default=config.SITE_TITLE,
|
default=config.SITE_TITLE,
|
||||||
|
@ -126,37 +143,28 @@ def get_prefix():
|
||||||
return sys.prefix
|
return sys.prefix
|
||||||
|
|
||||||
|
|
||||||
def get_pip():
|
@cli.command(name='pip')
|
||||||
""" Get virtualenv path for pip
|
|
||||||
"""
|
|
||||||
if in_virtualenv():
|
|
||||||
return get_prefix() + '/bin/pip'
|
|
||||||
else:
|
|
||||||
return 'pip'
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
|
||||||
@click.argument('cmd', nargs=-1)
|
@click.argument('cmd', nargs=-1)
|
||||||
def pip(cmd):
|
def pip_(cmd):
|
||||||
""" Execute pip commands, useful for virtualenvs
|
""" Execute pip commands, useful for virtualenvs
|
||||||
"""
|
"""
|
||||||
call(get_pip() + ' ' + ' '.join(cmd), shell=True)
|
pip.main(cmd)
|
||||||
|
|
||||||
|
|
||||||
def install_redis():
|
def install_redis():
|
||||||
call([get_pip(), 'install', 'redis'])
|
pip.main(['install', 'redis'])
|
||||||
|
|
||||||
|
|
||||||
def install_mysql():
|
def install_mysql():
|
||||||
call([get_pip(), 'install', 'MySQL-Python'])
|
pip.main(['install', 'MySQL-Python'])
|
||||||
|
|
||||||
|
|
||||||
def install_postgres():
|
def install_postgres():
|
||||||
call([get_pip(), 'install', 'psycopg2'])
|
pip.main(['install', 'psycopg2'])
|
||||||
|
|
||||||
|
|
||||||
def install_memcached():
|
def install_memcached():
|
||||||
call([get_pip(), 'install', 'python-memcached'])
|
pip.main(['install', 'python-memcached'])
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
|
@ -229,7 +237,7 @@ def dev(port):
|
||||||
""" Run development server
|
""" Run development server
|
||||||
"""
|
"""
|
||||||
green("Starting development server")
|
green("Starting development server")
|
||||||
app.run(host="0.0.0.0",
|
create_app().run(host="0.0.0.0",
|
||||||
port=port,
|
port=port,
|
||||||
debug=True)
|
debug=True)
|
||||||
|
|
||||||
|
@ -243,7 +251,7 @@ def start_server():
|
||||||
|
|
||||||
green("Server started. Port: %s" % config.PORT)
|
green("Server started. Port: %s" % config.PORT)
|
||||||
|
|
||||||
Popen('gunicorn realms:app -b 0.0.0.0:%s -k gevent %s' %
|
Popen("gunicorn 'realms:create_app()' -b 0.0.0.0:%s -k gevent %s" %
|
||||||
(config.PORT, flags), shell=True, executable='/bin/bash')
|
(config.PORT, flags), shell=True, executable='/bin/bash')
|
||||||
|
|
||||||
|
|
||||||
|
@ -317,21 +325,20 @@ def drop_db():
|
||||||
def test():
|
def test():
|
||||||
""" Run tests
|
""" Run tests
|
||||||
"""
|
"""
|
||||||
for mod in [('flask.ext.testing', 'Flask-Testing'), ('nose', 'nose')]:
|
for mod in [('flask.ext.testing', 'Flask-Testing'), ('nose', 'nose'), ('blinker', 'blinker')]:
|
||||||
if not module_exists(mod[0]):
|
if not module_exists(mod[0]):
|
||||||
call([get_pip(), 'install', mod[1]])
|
pip.main(['install', mod[1]])
|
||||||
|
|
||||||
nosetests = get_prefix() + "/bin/nosetests" if in_virtualenv() else "nosetests"
|
nosetests = get_prefix() + "/bin/nosetests" if in_virtualenv() else "nosetests"
|
||||||
|
|
||||||
call([nosetests, config.APP_PATH])
|
call([nosetests, 'realms'])
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
def version():
|
def version():
|
||||||
""" Output version
|
""" Output version
|
||||||
"""
|
"""
|
||||||
with open(os.path.join(config.APP_PATH, 'VERSION')) as f:
|
green(__version__)
|
||||||
click.echo(f.read().strip())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -42,7 +42,6 @@ class HookMixin(object):
|
||||||
return f
|
return f
|
||||||
return outer
|
return outer
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def before(cls, method_name):
|
def before(cls, method_name):
|
||||||
def outer(f, *args, **kwargs):
|
def outer(f, *args, **kwargs):
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import json
|
import json
|
||||||
from realms import db
|
|
||||||
from sqlalchemy import not_
|
from sqlalchemy import not_
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
|
||||||
class Model(db.Model):
|
class Model(db.Model):
|
||||||
|
|
23
realms/lib/test.py
Normal file
23
realms/lib/test.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from flask.ext.testing import TestCase
|
||||||
|
from realms.lib.util import random_string
|
||||||
|
from realms import create_app
|
||||||
|
from subprocess import call
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTest(TestCase):
|
||||||
|
|
||||||
|
def create_app(self):
|
||||||
|
app = create_app()
|
||||||
|
app.config['TESTING'] = True
|
||||||
|
app.config['PRESERVE_CONTEXT_ON_EXCEPTION'] = False
|
||||||
|
app.config['WIKI_PATH'] = '/tmp/%s' % random_string(12)
|
||||||
|
app.config['DB_URI'] = 'sqlite:////tmp/%s.db' % random_string(12)
|
||||||
|
app.config.update(self.configure())
|
||||||
|
return app
|
||||||
|
|
||||||
|
def configure(self):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
call(['rm', '-rf', self.app.config['WIKI_PATH']])
|
||||||
|
call(['rm', '-f', self.app.config['DB_URI'][10:]])
|
|
@ -1,3 +1,4 @@
|
||||||
|
import click
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import hashlib
|
import hashlib
|
||||||
|
@ -109,6 +110,18 @@ def is_su():
|
||||||
return os.geteuid() == 0
|
return os.geteuid() == 0
|
||||||
|
|
||||||
|
|
||||||
|
def green(s):
|
||||||
|
click.secho(s, fg='green')
|
||||||
|
|
||||||
|
|
||||||
|
def yellow(s):
|
||||||
|
click.secho(s, fg='yellow')
|
||||||
|
|
||||||
|
|
||||||
|
def red(s):
|
||||||
|
click.secho(s, fg='red')
|
||||||
|
|
||||||
|
|
||||||
def upstart_script(user='root', app_dir=None, port=5000, workers=2, path=None):
|
def upstart_script(user='root', app_dir=None, port=5000, workers=2, path=None):
|
||||||
script = """
|
script = """
|
||||||
limit nofile 65335 65335
|
limit nofile 65335 65335
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import click
|
import click
|
||||||
from realms.lib.util import random_string
|
from realms.lib.util import random_string
|
||||||
from realms.modules.auth.models import User
|
from realms.modules.auth.models import User
|
||||||
|
from realms.lib.util import green, red, yellow
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
|
@ -21,15 +22,15 @@ def create_user(username, email, password):
|
||||||
password = random_string(12)
|
password = random_string(12)
|
||||||
|
|
||||||
if User.get_by_username(username):
|
if User.get_by_username(username):
|
||||||
click.secho("Username %s already exists" % username, fg='red')
|
red("Username %s already exists" % username)
|
||||||
return
|
return
|
||||||
|
|
||||||
if User.get_by_email(email):
|
if User.get_by_email(email):
|
||||||
click.secho("Email %s already exists" % email, fg='red')
|
red("Email %s already exists" % email)
|
||||||
return
|
return
|
||||||
|
|
||||||
User.create(username, email, password)
|
User.create(username, email, password)
|
||||||
click.secho("User %s created" % username, fg='green')
|
green("User %s created" % username)
|
||||||
|
|
||||||
if show_pass:
|
if show_pass:
|
||||||
click.secho("Password: %s" % password, fg='yellow')
|
yellow("Password: %s" % password)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from flask_wtf import Form, RecaptchaField
|
from flask_wtf import Form
|
||||||
from wtforms import StringField, PasswordField, validators
|
from wtforms import StringField, PasswordField, validators
|
||||||
from realms import app
|
|
||||||
|
|
||||||
|
|
||||||
class RegistrationForm(Form):
|
class RegistrationForm(Form):
|
||||||
|
@ -12,9 +11,6 @@ class RegistrationForm(Form):
|
||||||
])
|
])
|
||||||
confirm = PasswordField('Repeat Password')
|
confirm = PasswordField('Repeat Password')
|
||||||
|
|
||||||
if app.config['RECAPTCHA_ENABLE']:
|
|
||||||
setattr(RegistrationForm, 'recaptcha', RecaptchaField("You Human?"))
|
|
||||||
|
|
||||||
|
|
||||||
class LoginForm(Form):
|
class LoginForm(Form):
|
||||||
email = StringField('Email', [validators.DataRequired()])
|
email = StringField('Email', [validators.DataRequired()])
|
||||||
|
|
8
realms/modules/auth/hooks.py
Normal file
8
realms/modules/auth/hooks.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from flask import current_app
|
||||||
|
from flask_wtf import RecaptchaField
|
||||||
|
from .forms import RegistrationForm
|
||||||
|
|
||||||
|
|
||||||
|
def before_first_request():
|
||||||
|
if current_app.config['RECAPTCHA_ENABLE']:
|
||||||
|
setattr(RegistrationForm, 'recaptcha', RecaptchaField("You Human?"))
|
|
@ -1,5 +1,6 @@
|
||||||
|
from flask import current_app
|
||||||
from flask.ext.login import UserMixin, logout_user, login_user, AnonymousUserMixin
|
from flask.ext.login import UserMixin, logout_user, login_user, AnonymousUserMixin
|
||||||
from realms import config, login_manager, db
|
from realms import login_manager, db
|
||||||
from realms.lib.model import Model
|
from realms.lib.model import Model
|
||||||
from realms.lib.util import gravatar_url
|
from realms.lib.util import gravatar_url
|
||||||
from itsdangerous import URLSafeSerializer, BadSignature
|
from itsdangerous import URLSafeSerializer, BadSignature
|
||||||
|
@ -15,7 +16,7 @@ def load_user(user_id):
|
||||||
@login_manager.token_loader
|
@login_manager.token_loader
|
||||||
def load_token(token):
|
def load_token(token):
|
||||||
# Load unsafe because payload is needed for sig
|
# Load unsafe because payload is needed for sig
|
||||||
sig_okay, payload = URLSafeSerializer(config.SECRET_KEY).loads_unsafe(token)
|
sig_okay, payload = URLSafeSerializer(current_app.config['SECRET_KEY']).loads_unsafe(token)
|
||||||
|
|
||||||
if not payload:
|
if not payload:
|
||||||
return None
|
return None
|
||||||
|
@ -81,7 +82,7 @@ class User(Model, UserMixin):
|
||||||
"""
|
"""
|
||||||
Signed with app secret salted with sha256 of password hash of user (client secret)
|
Signed with app secret salted with sha256 of password hash of user (client secret)
|
||||||
"""
|
"""
|
||||||
return URLSafeSerializer(config.SECRET_KEY + salt)
|
return URLSafeSerializer(current_app.config['SECRET_KEY'] + salt)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def auth(email, password):
|
def auth(email, password):
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
from flask import g, render_template, request, redirect, Blueprint, flash, url_for
|
from flask import current_app, render_template, request, redirect, Blueprint, flash, url_for
|
||||||
from realms.modules.auth.models import User
|
from realms.modules.auth.models import User
|
||||||
from realms.modules.auth.forms import LoginForm, RegistrationForm
|
from realms.modules.auth.forms import LoginForm, RegistrationForm
|
||||||
from realms import app
|
|
||||||
|
|
||||||
blueprint = Blueprint('auth', __name__, url_prefix=app.config['RELATIVE_PATH'])
|
blueprint = Blueprint('auth', __name__)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/logout")
|
@blueprint.route("/logout")
|
||||||
def logout_page():
|
def logout_page():
|
||||||
User.logout()
|
User.logout()
|
||||||
flash("You are now logged out")
|
flash("You are now logged out")
|
||||||
return redirect(url_for(app.config['ROOT_ENDPOINT']))
|
return redirect(url_for(current_app.config['ROOT_ENDPOINT']))
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/login", methods=['GET', 'POST'])
|
@blueprint.route("/login", methods=['GET', 'POST'])
|
||||||
|
@ -23,7 +22,7 @@ def login():
|
||||||
return redirect(url_for('auth.login'))
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
if User.auth(request.form['email'], request.form['password']):
|
if User.auth(request.form['email'], request.form['password']):
|
||||||
return redirect(request.args.get("next") or url_for(app.config['ROOT_ENDPOINT']))
|
return redirect(request.args.get("next") or url_for(current_app.config['ROOT_ENDPOINT']))
|
||||||
else:
|
else:
|
||||||
flash('Email or Password Incorrect', 'warning')
|
flash('Email or Password Incorrect', 'warning')
|
||||||
return redirect(url_for('auth.login'))
|
return redirect(url_for('auth.login'))
|
||||||
|
@ -34,9 +33,9 @@ def login():
|
||||||
@blueprint.route("/register", methods=['GET', 'POST'])
|
@blueprint.route("/register", methods=['GET', 'POST'])
|
||||||
def register():
|
def register():
|
||||||
|
|
||||||
if not app.config['REGISTRATION_ENABLED']:
|
if not current_app.config['REGISTRATION_ENABLED']:
|
||||||
flash("Registration is disabled")
|
flash("Registration is disabled")
|
||||||
return redirect(url_for(app.config['ROOT_ENDPOINT']))
|
return redirect(url_for(current_app.config['ROOT_ENDPOINT']))
|
||||||
|
|
||||||
form = RegistrationForm()
|
form = RegistrationForm()
|
||||||
|
|
||||||
|
@ -57,7 +56,7 @@ def register():
|
||||||
User.create(request.form['username'], request.form['email'], request.form['password'])
|
User.create(request.form['username'], request.form['email'], request.form['password'])
|
||||||
User.auth(request.form['email'], request.form['password'])
|
User.auth(request.form['email'], request.form['password'])
|
||||||
|
|
||||||
return redirect(request.args.get("next") or url_for(app.config['ROOT_ENDPOINT']))
|
return redirect(request.args.get("next") or url_for(current_app.config['ROOT_ENDPOINT']))
|
||||||
|
|
||||||
return render_template("auth/register.html", form=form)
|
return render_template("auth/register.html", form=form)
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from realms import app
|
|
||||||
from realms.modules.wiki.models import Wiki
|
from realms.modules.wiki.models import Wiki
|
||||||
|
|
||||||
|
|
||||||
|
def init(app):
|
||||||
# Init Wiki
|
# Init Wiki
|
||||||
Wiki(app.config['WIKI_PATH'])
|
Wiki(app.config['WIKI_PATH'])
|
||||||
|
|
||||||
|
|
6
realms/modules/wiki/hooks.py
Normal file
6
realms/modules/wiki/hooks.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from flask import g, current_app
|
||||||
|
from .models import Wiki
|
||||||
|
|
||||||
|
|
||||||
|
def before_request():
|
||||||
|
g.current_wiki = Wiki(current_app.config['WIKI_PATH'])
|
|
@ -20,7 +20,7 @@ def cname_to_filename(cname):
|
||||||
:return: str -- Filename
|
:return: str -- Filename
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return cname.lower() + ".md"
|
return cname + ".md"
|
||||||
|
|
||||||
|
|
||||||
def filename_to_cname(filename):
|
def filename_to_cname(filename):
|
||||||
|
@ -62,25 +62,35 @@ class Wiki(HookMixin):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "Wiki: %s" % self.path
|
return "Wiki: %s" % self.path
|
||||||
|
|
||||||
def revert_page(self, name, commit_sha, message, username):
|
def _get_user(self, username, email):
|
||||||
|
if not username:
|
||||||
|
username = self.default_committer_name
|
||||||
|
|
||||||
|
if not email:
|
||||||
|
email = self.default_committer_email
|
||||||
|
|
||||||
|
return username, email
|
||||||
|
|
||||||
|
def revert_page(self, name, commit_sha, message, username, email):
|
||||||
"""Revert page to passed commit sha1
|
"""Revert page to passed commit sha1
|
||||||
|
|
||||||
:param name: Name of page to revert.
|
:param name: Name of page to revert.
|
||||||
:param commit_sha: Commit Sha1 to revert to.
|
:param commit_sha: Commit Sha1 to revert to.
|
||||||
:param message: Commit message.
|
:param message: Commit message.
|
||||||
:param username:
|
:param username: Committer name.
|
||||||
|
:param email: Committer email.
|
||||||
:return: Git commit sha1
|
:return: Git commit sha1
|
||||||
|
|
||||||
"""
|
"""
|
||||||
page = self.get_page(name, commit_sha)
|
page = self.get_page(name, commit_sha)
|
||||||
if not page:
|
if not page:
|
||||||
raise PageNotFound()
|
raise PageNotFound('Commit not found')
|
||||||
|
|
||||||
if not message:
|
if not message:
|
||||||
commit_info = gittle.utils.git.commit_info(self.gittle[commit_sha.encode('latin-1')])
|
commit_info = gittle.utils.git.commit_info(self.gittle[commit_sha.encode('latin-1')])
|
||||||
message = commit_info['message']
|
message = commit_info['message']
|
||||||
|
|
||||||
return self.write_page(name, page['data'], message=message, username=username)
|
return self.write_page(name, page['data'], message=message, username=username, email=email)
|
||||||
|
|
||||||
def write_page(self, name, content, message=None, create=False, username=None, email=None):
|
def write_page(self, name, content, message=None, create=False, username=None, email=None):
|
||||||
"""Write page to git repo
|
"""Write page to git repo
|
||||||
|
@ -108,11 +118,7 @@ class Wiki(HookMixin):
|
||||||
if not message:
|
if not message:
|
||||||
message = "Updated %s" % name
|
message = "Updated %s" % name
|
||||||
|
|
||||||
if not username:
|
username, email = self._get_user(username, email)
|
||||||
username = self.default_committer_name
|
|
||||||
|
|
||||||
if not email:
|
|
||||||
email = self.default_committer_email
|
|
||||||
|
|
||||||
ret = self.gittle.commit(name=username,
|
ret = self.gittle.commit(name=username,
|
||||||
email=email,
|
email=email,
|
||||||
|
@ -168,12 +174,13 @@ class Wiki(HookMixin):
|
||||||
content = re.sub(r"```(.*?)```", unescape_repl, content, flags=re.DOTALL)
|
content = re.sub(r"```(.*?)```", unescape_repl, content, flags=re.DOTALL)
|
||||||
return content
|
return content
|
||||||
|
|
||||||
def rename_page(self, old_name, new_name, user=None):
|
def rename_page(self, old_name, new_name, username=None, email=None, message=None):
|
||||||
"""Rename page.
|
"""Rename page.
|
||||||
|
|
||||||
:param old_name: Page that will be renamed.
|
:param old_name: Page that will be renamed.
|
||||||
:param new_name: New name of page.
|
:param new_name: New name of page.
|
||||||
:param user: User object if any.
|
:param username: Committer name
|
||||||
|
:param email: Committer email
|
||||||
:return: str -- Commit sha1
|
:return: str -- Commit sha1
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -186,32 +193,45 @@ class Wiki(HookMixin):
|
||||||
# file is being overwritten, but that is ok, it's git!
|
# file is being overwritten, but that is ok, it's git!
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
username, email = self._get_user(username, email)
|
||||||
|
|
||||||
|
if not message:
|
||||||
|
message = "Moved %s to %s" % (old_name, new_name)
|
||||||
|
|
||||||
os.rename(os.path.join(self.path, old_filename), os.path.join(self.path, new_filename))
|
os.rename(os.path.join(self.path, old_filename), os.path.join(self.path, new_filename))
|
||||||
|
|
||||||
self.gittle.add(new_filename)
|
self.gittle.add(new_filename)
|
||||||
self.gittle.rm(old_filename)
|
self.gittle.rm(old_filename)
|
||||||
|
|
||||||
commit = self.gittle.commit(name=getattr(user, 'username', self.default_committer_name),
|
commit = self.gittle.commit(name=username,
|
||||||
email=getattr(user, 'email', self.default_committer_email),
|
email=email,
|
||||||
message="Moved %s to %s" % (old_name, new_name),
|
message=message,
|
||||||
files=[old_filename, new_filename])
|
files=[old_filename, new_filename])
|
||||||
|
|
||||||
cache.delete_many(old_filename, new_filename)
|
cache.delete_many(old_name, new_name)
|
||||||
|
|
||||||
return commit
|
return commit
|
||||||
|
|
||||||
def delete_page(self, name, user=None):
|
def delete_page(self, name, username=None, email=None, message=None):
|
||||||
"""Delete page.
|
"""Delete page.
|
||||||
:param name: Page that will be deleted
|
:param name: Page that will be deleted
|
||||||
:param user: User object if any
|
:param username: Committer name
|
||||||
|
:param email: Committer email
|
||||||
:return: str -- Commit sha1
|
:return: str -- Commit sha1
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.gittle.rm(name)
|
|
||||||
commit = self.gittle.commit(name=getattr(user, 'username', self.default_committer_name),
|
username, email = self._get_user(username, email)
|
||||||
email=getattr(user, 'email', self.default_committer_email),
|
|
||||||
message="Deleted %s" % name,
|
if not message:
|
||||||
files=[name])
|
message = "Deleted %s" % name
|
||||||
|
|
||||||
|
filename = cname_to_filename(name)
|
||||||
|
self.gittle.rm(filename)
|
||||||
|
commit = self.gittle.commit(name=username,
|
||||||
|
email=email,
|
||||||
|
message=message,
|
||||||
|
files=[str(filename)])
|
||||||
cache.delete_many(name)
|
cache.delete_many(name)
|
||||||
return commit
|
return commit
|
||||||
|
|
||||||
|
|
|
@ -1,34 +1,72 @@
|
||||||
|
import json
|
||||||
from nose.tools import *
|
from nose.tools import *
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from realms import app, g
|
from realms.modules.wiki.models import cname_to_filename, filename_to_cname
|
||||||
from realms.modules.wiki.models import Wiki, cname_to_filename, filename_to_cname
|
from realms.lib.test import BaseTest
|
||||||
from flask.ext.testing import TestCase
|
|
||||||
|
|
||||||
|
|
||||||
class WikiTest(TestCase):
|
class WikiBaseTest(BaseTest):
|
||||||
|
def write_page(self, name, message=None, content=None):
|
||||||
|
return self.client.post(url_for('wiki.page_write', name=name),
|
||||||
|
data=dict(message=message, content=content))
|
||||||
|
|
||||||
def create_app(self):
|
|
||||||
app.config['TESTING'] = True
|
|
||||||
return app
|
|
||||||
|
|
||||||
def test_wiki_routes(self):
|
|
||||||
|
|
||||||
self.assert_200(self.client.get(url_for("wiki.create")))
|
|
||||||
|
|
||||||
""" Create a test page first!
|
|
||||||
for route in ['page', 'edit', 'history', 'index']:
|
|
||||||
rv = self.client.get(url_for("wiki.%s" % route, name='test'))
|
|
||||||
self.assert_200(rv, "wiki.%s: %s" % (route, rv.status_code))
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_write_page(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_revert(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
class UtilTest(WikiBaseTest):
|
||||||
def test_cname_to_filename(self):
|
def test_cname_to_filename(self):
|
||||||
eq_(cname_to_filename('test'), 'test.md')
|
eq_(cname_to_filename('test'), 'test.md')
|
||||||
|
|
||||||
def test_filename_to_cname(self):
|
def test_filename_to_cname(self):
|
||||||
eq_(filename_to_cname('test-1-2-3.md'), 'test-1-2-3')
|
eq_(filename_to_cname('test-1-2-3.md'), 'test-1-2-3')
|
||||||
|
|
||||||
|
|
||||||
|
class WikiTest(WikiBaseTest):
|
||||||
|
def test_routes(self):
|
||||||
|
self.assert_200(self.client.get(url_for("wiki.create")))
|
||||||
|
self.write_page('test', message='test message', content='testing')
|
||||||
|
|
||||||
|
for route in ['page', 'edit', 'history']:
|
||||||
|
rv = self.client.get(url_for("wiki.%s" % route, name='test'))
|
||||||
|
self.assert_200(rv, "wiki.%s: %s" % (route, rv.status_code))
|
||||||
|
|
||||||
|
self.assert_200(self.client.get(url_for('wiki.index')))
|
||||||
|
|
||||||
|
def test_write_page(self):
|
||||||
|
self.assert_200(self.write_page('test', message='test message', content='testing'))
|
||||||
|
|
||||||
|
rv = self.client.get(url_for('wiki.page', name='test'))
|
||||||
|
self.assert_200(rv)
|
||||||
|
|
||||||
|
self.assert_context('name', 'test')
|
||||||
|
eq_(self.get_context_variable('page')['info']['message'], 'test message')
|
||||||
|
eq_(self.get_context_variable('page')['data'], 'testing')
|
||||||
|
|
||||||
|
def test_history(self):
|
||||||
|
self.assert_200(self.client.get(url_for('wiki.history', name='test')))
|
||||||
|
|
||||||
|
def test_delete_page(self):
|
||||||
|
self.app.config['WIKI_LOCKED_PAGES'] = ['test']
|
||||||
|
self.assert_403(self.client.delete(url_for('wiki.page_write', name='test')))
|
||||||
|
self.app.config['WIKI_LOCKED_PAGES'] = []
|
||||||
|
|
||||||
|
self.assert_200(self.client.delete(url_for('wiki.page_write', name='test')))
|
||||||
|
|
||||||
|
rv = self.client.get(url_for('wiki.page', name='test'))
|
||||||
|
self.assert_status(rv, 302)
|
||||||
|
|
||||||
|
def test_revert(self):
|
||||||
|
rv1 = self.write_page('test', message='test message', content='testing_old')
|
||||||
|
self.write_page('test', message='test message', content='testing_new')
|
||||||
|
data = json.loads(rv1.data)
|
||||||
|
self.client.post(url_for('wiki.revert'), data=dict(name='test', commit=data['sha']))
|
||||||
|
self.client.get(url_for('wiki.page', name='test'))
|
||||||
|
eq_(self.get_context_variable('page')['data'], 'testing_old')
|
||||||
|
self.assert_404(self.client.post(url_for('wiki.revert'), data=dict(name='test', commit='does not exist')))
|
||||||
|
|
||||||
|
self.app.config['WIKI_LOCKED_PAGES'] = ['test']
|
||||||
|
self.assert_403(self.client.post(url_for('wiki.revert'), data=dict(name='test', commit=data['sha'])))
|
||||||
|
self.app.config['WIKI_LOCKED_PAGES'] = []
|
||||||
|
|
||||||
|
|
||||||
|
class RelativePathTest(WikiTest):
|
||||||
|
def configure(self):
|
||||||
|
return dict(RELATIVE_PATH='wiki')
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
from flask import abort, g, render_template, request, redirect, Blueprint, flash, url_for, current_app
|
from flask import abort, g, render_template, request, redirect, Blueprint, flash, url_for, current_app
|
||||||
from flask.ext.login import login_required
|
from flask.ext.login import login_required, current_user
|
||||||
from realms.lib.util import to_canonical, remove_ext
|
from realms.lib.util import to_canonical, remove_ext
|
||||||
from realms.modules.wiki.models import Wiki
|
from .models import PageNotFound
|
||||||
from realms import current_user, app
|
|
||||||
|
|
||||||
blueprint = Blueprint('wiki', __name__, url_prefix=app.config['RELATIVE_PATH'])
|
blueprint = Blueprint('wiki', __name__)
|
||||||
|
|
||||||
|
|
||||||
@app.before_request
|
|
||||||
def init_wiki():
|
|
||||||
g.current_wiki = Wiki(app.config['WIKI_PATH'])
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/_commit/<sha>/<name>")
|
@blueprint.route("/_commit/<sha>/<name>")
|
||||||
|
@ -27,22 +21,29 @@ def commit(name, sha):
|
||||||
@blueprint.route("/_compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>")
|
@blueprint.route("/_compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>")
|
||||||
def compare(name, fsha, dots, lsha):
|
def compare(name, fsha, dots, lsha):
|
||||||
diff = g.current_wiki.compare(name, fsha, lsha)
|
diff = g.current_wiki.compare(name, fsha, lsha)
|
||||||
return render_template('wiki/compare.html', name=name, diff=diff, old=fsha, new=lsha)
|
return render_template('wiki/compare.html',
|
||||||
|
name=name, diff=diff, old=fsha, new=lsha)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/_revert", methods=['POST'])
|
@blueprint.route("/_revert", methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def revert():
|
def revert():
|
||||||
name = request.form.get('name')
|
cname = to_canonical(request.form.get('name'))
|
||||||
commit = request.form.get('commit')
|
commit = request.form.get('commit')
|
||||||
cname = to_canonical(name)
|
|
||||||
message = request.form.get('message', "Reverting %s" % cname)
|
message = request.form.get('message', "Reverting %s" % cname)
|
||||||
|
|
||||||
if cname in app.config.get('WIKI_LOCKED_PAGES'):
|
if cname in current_app.config.get('WIKI_LOCKED_PAGES'):
|
||||||
return dict(error=True, message="Page is locked")
|
return dict(error=True, message="Page is locked"), 403
|
||||||
|
|
||||||
|
try:
|
||||||
|
sha = g.current_wiki.revert_page(cname,
|
||||||
|
commit,
|
||||||
|
message=message,
|
||||||
|
username=current_user.username,
|
||||||
|
email=current_user.email)
|
||||||
|
except PageNotFound as e:
|
||||||
|
return dict(error=True, message=e.message), 404
|
||||||
|
|
||||||
sha = g.current_wiki.revert_page(name, commit, message=message,
|
|
||||||
username=current_user.username)
|
|
||||||
if sha:
|
if sha:
|
||||||
flash("Page reverted")
|
flash("Page reverted")
|
||||||
|
|
||||||
|
@ -105,20 +106,21 @@ def page_write(name):
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
# Create
|
# Create
|
||||||
if cname in app.config.get('WIKI_LOCKED_PAGES'):
|
if cname in current_app.config.get('WIKI_LOCKED_PAGES'):
|
||||||
return dict(error=True, message="Page is locked")
|
return dict(error=True, message="Page is locked"), 403
|
||||||
|
|
||||||
sha = g.current_wiki.write_page(cname,
|
sha = g.current_wiki.write_page(cname,
|
||||||
request.form['content'],
|
request.form['content'],
|
||||||
message=request.form['message'],
|
message=request.form['message'],
|
||||||
create=True,
|
create=True,
|
||||||
username=current_user.username)
|
username=current_user.username,
|
||||||
|
email=current_user.email)
|
||||||
|
|
||||||
elif request.method == 'PUT':
|
elif request.method == 'PUT':
|
||||||
edit_cname = to_canonical(request.form['name'])
|
edit_cname = to_canonical(request.form['name'])
|
||||||
|
|
||||||
if edit_cname in app.config.get('WIKI_LOCKED_PAGES'):
|
if edit_cname in current_app.config.get('WIKI_LOCKED_PAGES'):
|
||||||
return dict(error=True, message="Page is locked")
|
return dict(error=True, message="Page is locked"), 403
|
||||||
|
|
||||||
if edit_cname != cname.lower():
|
if edit_cname != cname.lower():
|
||||||
g.current_wiki.rename_page(cname, edit_cname)
|
g.current_wiki.rename_page(cname, edit_cname)
|
||||||
|
@ -126,13 +128,19 @@ def page_write(name):
|
||||||
sha = g.current_wiki.write_page(edit_cname,
|
sha = g.current_wiki.write_page(edit_cname,
|
||||||
request.form['content'],
|
request.form['content'],
|
||||||
message=request.form['message'],
|
message=request.form['message'],
|
||||||
username=current_user.username)
|
username=current_user.username,
|
||||||
|
email=current_user.email)
|
||||||
|
|
||||||
return dict(sha=sha)
|
return dict(sha=sha)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# DELETE
|
# DELETE
|
||||||
sha = g.current_wiki.delete_page(name, user=current_user)
|
if cname in current_app.config.get('WIKI_LOCKED_PAGES'):
|
||||||
|
return dict(error=True, message="Page is locked"), 403
|
||||||
|
|
||||||
|
sha = g.current_wiki.delete_page(name,
|
||||||
|
username=current_user.username,
|
||||||
|
email=current_user.email)
|
||||||
|
|
||||||
return dict(sha=sha)
|
return dict(sha=sha)
|
||||||
|
|
||||||
|
|
1
realms/version.py
Normal file
1
realms/version.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
__version__ = '0.3.24'
|
|
@ -1,3 +1,4 @@
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
Flask-Testing==0.4.2
|
Flask-Testing==0.4.2
|
||||||
nose==1.3.4
|
nose==1.3.4
|
||||||
|
blinker==1.3
|
||||||
|
|
|
@ -1,18 +1 @@
|
||||||
Flask==0.10.1
|
-e .
|
||||||
Flask-Assets==0.10
|
|
||||||
Flask-Cache==0.13.1
|
|
||||||
Flask-Login==0.2.11
|
|
||||||
Flask-SQLAlchemy==2.0
|
|
||||||
Flask-WTF==0.10.2
|
|
||||||
PyYAML==3.11
|
|
||||||
bcrypt==1.0.2
|
|
||||||
beautifulsoup4==4.3.2
|
|
||||||
click==3.3
|
|
||||||
gevent==1.0.1
|
|
||||||
ghdiff==0.4
|
|
||||||
gittle==0.4.0
|
|
||||||
gunicorn==19.1.1
|
|
||||||
itsdangerous==0.24
|
|
||||||
lxml==3.4.0
|
|
||||||
markdown2==2.3.0
|
|
||||||
simplejson==3.6.3
|
|
||||||
|
|
28
setup.py
28
setup.py
|
@ -12,8 +12,8 @@ with open('README.md') as f:
|
||||||
with open('requirements.txt') as f:
|
with open('requirements.txt') as f:
|
||||||
required = f.read().splitlines()
|
required = f.read().splitlines()
|
||||||
|
|
||||||
with open('VERSION') as f:
|
__version__ = None
|
||||||
VERSION = f.read().strip()
|
exec(open('realms/version.py').read())
|
||||||
|
|
||||||
CLASSIFIERS = [
|
CLASSIFIERS = [
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
|
@ -23,10 +23,28 @@ CLASSIFIERS = [
|
||||||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content']
|
'Topic :: Internet :: WWW/HTTP :: Dynamic Content']
|
||||||
|
|
||||||
setup(name='realms-wiki',
|
setup(name='realms-wiki',
|
||||||
version=VERSION,
|
version=__version__,
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
install_requires=required,
|
install_requires=[
|
||||||
#scripts=['realms-wiki'],
|
'Flask==0.10.1',
|
||||||
|
'Flask-Assets==0.10',
|
||||||
|
'Flask-Cache==0.13.1',
|
||||||
|
'Flask-Login==0.2.11',
|
||||||
|
'Flask-SQLAlchemy==2.0',
|
||||||
|
'Flask-WTF==0.10.2',
|
||||||
|
'PyYAML==3.11',
|
||||||
|
'bcrypt==1.0.2',
|
||||||
|
'beautifulsoup4==4.3.2',
|
||||||
|
'click==3.3',
|
||||||
|
'gevent==1.0.1',
|
||||||
|
'ghdiff==0.4',
|
||||||
|
'gittle==0.4.0',
|
||||||
|
'gunicorn==19.1.1',
|
||||||
|
'itsdangerous==0.24',
|
||||||
|
'lxml==3.4.0',
|
||||||
|
'markdown2==2.3.0',
|
||||||
|
'simplejson==3.6.3'
|
||||||
|
],
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
'realms-wiki = realms.cli:cli'
|
'realms-wiki = realms.cli:cli'
|
||||||
|
|
Loading…
Reference in a new issue