diff --git a/manage.py b/manage.py index 69c5b25..ead3625 100644 --- a/manage.py +++ b/manage.py @@ -1,18 +1,43 @@ from gevent import wsgi -from realms import config, app, manager -from flask.ext.script import Server - -manager.add_command("runserver", Server(host="0.0.0.0", port=5000)) +from realms import config, app, cli, db +from werkzeug.serving import run_with_reloader +import click +import os -@manager.command +@cli.command() +@click.option('--port', default=5000) +def runserver(port): + """ Run development server + """ + click.secho("Starting development server", fg='green') + app.run(host="0.0.0.0", + port=port, + debug=True) + + +@cli.command() def run(): + """ Run production server """ - Run production ready server - """ - print "Server started. Env: %s Port: %s" % (config.ENV, config.PORT) + click.echo("Server started. Env: %s Port: %s" % (config.ENV, config.PORT)) wsgi.WSGIServer(('', int(config.PORT)), app).serve_forever() +@cli.command() +def create_db(): + """ Creates DB tables + """ + click.echo("Creating all tables") + db.create_all() + + +@cli.command() +def drop_db(): + """ Drops DB tables + """ + click.echo("Dropping all tables") + db.drop_all() + if __name__ == '__main__': - manager.run() + cli() diff --git a/realms/__init__.py b/realms/__init__.py index 29db79b..16fb987 100644 --- a/realms/__init__.py +++ b/realms/__init__.py @@ -1,6 +1,6 @@ # Monkey patch stdlib. import gevent.monkey -gevent.monkey.patch_all(aggressive=False) +gevent.monkey.patch_all(aggressive=False, subprocess=True) # Set default encoding to UTF-8 import sys @@ -14,9 +14,9 @@ import sys import json import httplib import traceback +import click from flask import Flask, request, render_template, url_for, redirect, g from flask.ext.cache import Cache -from flask.ext.script import Manager from flask.ext.login import LoginManager, current_user from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.assets import Environment, Bundle @@ -67,9 +67,9 @@ class Application(Flask): if hasattr(sources, 'views'): self.register_blueprint(sources.views.blueprint) - # Flask-Script + # Click if hasattr(sources, 'commands'): - manager.add_command(module_name, sources.commands.manager) + cli.add_command(sources.commands.cli, name=module_name) print >> sys.stderr, ' * Ready in %.2fms' % (1000.0 * (time.time() - start_time)) @@ -142,40 +142,39 @@ def error_handler(e): return response, status_code -def create_app(): - app = Application(__name__) - app.config.from_object('realms.config') - app.url_map.converters['regex'] = RegexConverter - app.url_map.strict_slashes = False - for status_code in httplib.responses: - if status_code >= 400: - app.register_error_handler(status_code, error_handler) +app = Application(__name__) +app.config.from_object('realms.config') +app.url_map.converters['regex'] = RegexConverter +app.url_map.strict_slashes = False - @app.before_request - def init_g(): - g.assets = dict(css=['main.css'], js=['main.js']) +for status_code in httplib.responses: + if status_code >= 400: + app.register_error_handler(status_code, error_handler) - @app.template_filter('datetime') - def _jinja2_filter_datetime(ts): - return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts)) +@app.before_request +def init_g(): + g.assets = dict(css=['main.css'], js=['main.js']) - @app.errorhandler(404) - def page_not_found(e): - return render_template('errors/404.html'), 404 +@app.template_filter('datetime') +def _jinja2_filter_datetime(ts): + return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts)) - if config.RELATIVE_PATH: - @app.route("/") - def root(): - return redirect(url_for(config.ROOT_ENDPOINT)) +@app.errorhandler(404) +def page_not_found(e): + return render_template('errors/404.html'), 404 - return app +if config.RELATIVE_PATH: + @app.route("/") + def root(): + return redirect(url_for(config.ROOT_ENDPOINT)) -app = create_app() + +@click.group() +def cli(): + pass # Init plugins here if possible -manager = Manager(app) - login_manager = LoginManager(app) login_manager.login_view = 'auth.login' @@ -204,7 +203,7 @@ assets.register('main.css', app.discover() -# Should be called explicitly during install? +# This will be removed at some point db.create_all() diff --git a/realms/config/__init__.py b/realms/config/__init__.py index 6d0e059..4ea398d 100644 --- a/realms/config/__init__.py +++ b/realms/config/__init__.py @@ -37,19 +37,31 @@ RECAPTCHA_OPTIONS = {} SECRET_KEY = 'K3dRq1q9eN72GJDkgvyshFVwlqHHCyPI' +# Path on file system where wiki data will reside WIKI_PATH = os.path.join(APP_PATH, 'wiki') + +# Name of page that will act as home WIKI_HOME = 'home' + ALLOW_ANON = True +REGISTRATION_ENABLED = True + +# Used by Flask-Login LOGIN_DISABLED = ALLOW_ANON # Page names that can't be modified -LOCKED = [] +WIKI_LOCKED_PAGES = [] +# Depreciated variable name +LOCKED = WIKI_LOCKED_PAGES ROOT_ENDPOINT = 'wiki.page' try: with open(os.path.join(APP_PATH, 'config.json')) as f: __settings = json.load(f) + for k in ['APP_PATH', 'USER_HOME']: + if k in __settings: + del __settings[k] globals().update(__settings) except IOError: pass diff --git a/realms/lib/util.py b/realms/lib/util.py index 1106337..bb8e97b 100644 --- a/realms/lib/util.py +++ b/realms/lib/util.py @@ -2,6 +2,8 @@ import re import os import hashlib import json +import string +import random class AttrDict(dict): @@ -10,6 +12,10 @@ class AttrDict(dict): self.__dict__ = self +def random_string(size=6, chars=string.ascii_lowercase + string.ascii_uppercase + string.digits): + return ''.join(random.choice(chars) for _ in range(size)) + + def to_json(data): return json.dumps(to_dict(data), separators=(',', ':')) diff --git a/realms/modules/auth/commands.py b/realms/modules/auth/commands.py new file mode 100644 index 0000000..2e9c97a --- /dev/null +++ b/realms/modules/auth/commands.py @@ -0,0 +1,35 @@ +import click +from realms.lib.util import random_string +from realms.modules.auth.models import User + + +@click.group() +def cli(): + pass + + +@cli.command() +@click.argument('username') +@click.argument('email') +@click.option('--password', help='Leave blank for random password') +def create_user(username, email, password): + """ Create a new user + """ + show_pass = not password + + if not password: + password = random_string(12) + + if User.get_by_username(username): + click.secho("Username %s already exists" % username, fg='red') + return + + if User.get_by_email(email): + click.secho("Email %s already exists" % email, fg='red') + return + + User.create(username, email, password) + click.secho("User %s created" % username, fg='green') + + if show_pass: + click.secho("Password: %s" % password, fg='yellow') diff --git a/realms/modules/auth/forms.py b/realms/modules/auth/forms.py index 1ba7eee..b7074ee 100644 --- a/realms/modules/auth/forms.py +++ b/realms/modules/auth/forms.py @@ -2,6 +2,7 @@ from flask_wtf import Form, RecaptchaField from wtforms import StringField, PasswordField, validators from realms import config + class RegistrationForm(Form): username = StringField('Username', [validators.Length(min=4, max=25)]) email = StringField('Email Address', [validators.Length(min=6, max=35)]) diff --git a/realms/modules/auth/views.py b/realms/modules/auth/views.py index caebad5..5eb3639 100644 --- a/realms/modules/auth/views.py +++ b/realms/modules/auth/views.py @@ -33,6 +33,11 @@ def login(): @blueprint.route("/register", methods=['GET', 'POST']) def register(): + + if not config.REGISTRATION_ENABLED: + flash("Registration is disabled") + return redirect(url_for(config.ROOT_ENDPOINT)) + form = RegistrationForm() if request.method == "POST": @@ -61,6 +66,7 @@ def register(): def settings(): return render_template("auth/settings.html") + @blueprint.route("/logout") def logout(): User.logout() diff --git a/realms/modules/wiki/views.py b/realms/modules/wiki/views.py index 7f4b9da..99db906 100644 --- a/realms/modules/wiki/views.py +++ b/realms/modules/wiki/views.py @@ -33,7 +33,7 @@ def revert(): commit = request.form.get('commit') cname = to_canonical(name) - if cname in config.LOCKED: + if cname in config.WIKI_LOCKED_PAGES: flash("Page is locked") return redirect(url_for(config.ROOT_ENDPOINT)) @@ -56,7 +56,7 @@ def edit(name): if request.method == 'POST': edit_cname = to_canonical(request.form['name']) - if edit_cname in config.LOCKED: + if edit_cname in config.WIKI_LOCKED_PAGES: return redirect(url_for(config.ROOT_ENDPOINT)) if edit_cname.lower() != cname.lower(): @@ -91,7 +91,7 @@ def create(name): if request.method == 'POST': cname = to_canonical(request.form['name']) - if cname in config.LOCKED: + if cname in config.WIKI_LOCKED_PAGES: return redirect(url_for("wiki.create")) if not cname: diff --git a/realms/templates/layout.html b/realms/templates/layout.html index 1baeaad..280f93d 100644 --- a/realms/templates/layout.html +++ b/realms/templates/layout.html @@ -59,8 +59,10 @@ {% else %} -
  •  Login
  • +
  •  Login
  • + {% if config.REGISTRATION_ENABLED %}
  •  Register
  • + {% endif %} {% endif %} diff --git a/requirements.txt b/requirements.txt index 0bb6d6b..903a8d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ Flask Flask-Assets Flask-Cache Flask-Login -Flask-Script Flask-SQLAlchemy Flask-WTF beautifulsoup4 @@ -15,3 +14,4 @@ lxml markdown2 simplejson PyYAML +click