WIP
This commit is contained in:
		
							parent
							
								
									b02d3db684
								
							
						
					
					
						commit
						86f0549e44
					
				
					 24 changed files with 710 additions and 398 deletions
				
			
		
							
								
								
									
										1
									
								
								Vagrantfile
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								Vagrantfile
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -11,6 +11,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  config.vm.synced_folder "srv/", "/srv/"
 | 
					  config.vm.synced_folder "srv/", "/srv/"
 | 
				
			||||||
  config.vm.synced_folder ".", "/home/deploy/realms"
 | 
					  config.vm.synced_folder ".", "/home/deploy/realms"
 | 
				
			||||||
 | 
					  config.vm.synced_folder "~/.virtualenvs", "/home/deploy/virtualenvs"
 | 
				
			||||||
  config.vm.provision :salt do |salt|
 | 
					  config.vm.provision :salt do |salt|
 | 
				
			||||||
  	salt.minion_config = "srv/minion"
 | 
					  	salt.minion_config = "srv/minion"
 | 
				
			||||||
	salt.run_highstate = true
 | 
						salt.run_highstate = true
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,15 @@
 | 
				
			||||||
from gevent import wsgi
 | 
					from gevent import wsgi
 | 
				
			||||||
from realms import config, app, manager
 | 
					from realms import config, app, manager
 | 
				
			||||||
 | 
					from flask.ext.script import Server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					manager.add_command("runserver", Server(host="0.0.0.0", port=config.PORT))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@manager.command
 | 
					@manager.command
 | 
				
			||||||
def server():
 | 
					def run():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Run production ready server
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    print "Server started. Env: %s Port: %s" % (config.ENV, config.PORT)
 | 
					    print "Server started. Env: %s Port: %s" % (config.ENV, config.PORT)
 | 
				
			||||||
    wsgi.WSGIServer(('', int(config.PORT)), app).serve_forever()
 | 
					    wsgi.WSGIServer(('', int(config.PORT)), app).serve_forever()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,18 +20,18 @@ import sys
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
import httplib
 | 
					import httplib
 | 
				
			||||||
import traceback
 | 
					import traceback
 | 
				
			||||||
from flask import Flask, request, render_template, url_for, redirect, session, flash, g
 | 
					from flask import Flask, request, render_template, url_for, redirect, g
 | 
				
			||||||
from flask.ctx import _AppCtxGlobals
 | 
					from flask.ctx import _AppCtxGlobals
 | 
				
			||||||
 | 
					from flask.ext.cache import Cache
 | 
				
			||||||
from flask.ext.script import Manager
 | 
					from flask.ext.script import Manager
 | 
				
			||||||
from flask.ext.login import LoginManager
 | 
					from flask.ext.login import LoginManager, current_user
 | 
				
			||||||
 | 
					from flask.ext.sqlalchemy import SQLAlchemy
 | 
				
			||||||
 | 
					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 werkzeug.exceptions import HTTPException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from realms 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
 | 
					 | 
				
			||||||
from realms.lib.util import to_canonical, remove_ext, mkdir_safe, gravatar_url, to_dict
 | 
					from realms.lib.util import to_canonical, remove_ext, mkdir_safe, gravatar_url, to_dict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,11 +39,7 @@ class AppCtxGlobals(_AppCtxGlobals):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @cached_property
 | 
					    @cached_property
 | 
				
			||||||
    def current_user(self):
 | 
					    def current_user(self):
 | 
				
			||||||
        return session.get('user') if session.get('user') else {'username': 'Anon'}
 | 
					        return current_user
 | 
				
			||||||
 | 
					 | 
				
			||||||
    @cached_property
 | 
					 | 
				
			||||||
    def current_wiki(self):
 | 
					 | 
				
			||||||
        return Wiki(config.WIKI_PATH)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Application(Flask):
 | 
					class Application(Flask):
 | 
				
			||||||
| 
						 | 
					@ -68,8 +64,8 @@ class Application(Flask):
 | 
				
			||||||
        return super(Application, self).__call__(environ, start_response)
 | 
					        return super(Application, self).__call__(environ, start_response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def discover(self):
 | 
					    def discover(self):
 | 
				
			||||||
        IMPORT_NAME = 'realms.modules'
 | 
					        import_name = 'realms.modules'
 | 
				
			||||||
        FROMLIST = (
 | 
					        fromlist = (
 | 
				
			||||||
            'assets',
 | 
					            'assets',
 | 
				
			||||||
            'commands',
 | 
					            'commands',
 | 
				
			||||||
            'models',
 | 
					            'models',
 | 
				
			||||||
| 
						 | 
					@ -78,10 +74,10 @@ class Application(Flask):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        start_time = time.time()
 | 
					        start_time = time.time()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        __import__(IMPORT_NAME, fromlist=FROMLIST)
 | 
					        __import__(import_name, fromlist=fromlist)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Blueprint
 | 
					            # Blueprint
 | 
				
			||||||
            if hasattr(sources, 'views'):
 | 
					            if hasattr(sources, 'views'):
 | 
				
			||||||
| 
						 | 
					@ -107,6 +103,18 @@ class Application(Flask):
 | 
				
			||||||
        return super(Application, self).make_response(tuple(rv))
 | 
					        return super(Application, self).make_response(tuple(rv))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Assets(Environment):
 | 
				
			||||||
 | 
					    default_filters = {'js': 'uglifyjs', 'css': 'cssmin'}
 | 
				
			||||||
 | 
					    default_output = {'js': 'assets/%(version)s.js', 'css': 'assets/%(version)s.css'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def register(self, name, *args, **kwargs):
 | 
				
			||||||
 | 
					        ext = args[0].split('.')[-1]
 | 
				
			||||||
 | 
					        filters = kwargs.get('filters', self.default_filters[ext])
 | 
				
			||||||
 | 
					        output = kwargs.get('output', self.default_output[ext])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super(Assets, self).register(name, Bundle(*args, filters=filters, output=output))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RegexConverter(BaseConverter):
 | 
					class RegexConverter(BaseConverter):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Enables Regex matching on endpoints
 | 
					    Enables Regex matching on endpoints
 | 
				
			||||||
| 
						 | 
					@ -122,20 +130,6 @@ def redirect_url(referrer=None):
 | 
				
			||||||
    return request.args.get('next') or referrer or url_for('index')
 | 
					    return request.args.get('next') or referrer or url_for('index')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
app = Application(__name__)
 | 
					 | 
				
			||||||
app.config.from_object('realms.config')
 | 
					 | 
				
			||||||
app.session_interface = RedisSessionInterface()
 | 
					 | 
				
			||||||
app.url_map.converters['regex'] = RegexConverter
 | 
					 | 
				
			||||||
app.url_map.strict_slashes = False
 | 
					 | 
				
			||||||
app.debug = config.DEBUG
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
manager = Manager(app)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
login_manager = LoginManager()
 | 
					 | 
				
			||||||
login_manager.init_app(app)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def error_handler(e):
 | 
					def error_handler(e):
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        if isinstance(e, HTTPException):
 | 
					        if isinstance(e, HTTPException):
 | 
				
			||||||
| 
						 | 
					@ -150,7 +144,7 @@ def error_handler(e):
 | 
				
			||||||
        if request.is_xhr or request.accept_mimetypes.best in ['application/json', 'text/javascript']:
 | 
					        if request.is_xhr or request.accept_mimetypes.best in ['application/json', 'text/javascript']:
 | 
				
			||||||
            response = {
 | 
					            response = {
 | 
				
			||||||
                'message': message,
 | 
					                'message': message,
 | 
				
			||||||
                'traceback': tb,
 | 
					                'traceback': tb
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            response = render_template('errors/error.html',
 | 
					            response = render_template('errors/error.html',
 | 
				
			||||||
| 
						 | 
					@ -163,16 +157,49 @@ def error_handler(e):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return response, status_code
 | 
					    return response, status_code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for status_code in httplib.responses:
 | 
					
 | 
				
			||||||
 | 
					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:
 | 
					        if status_code >= 400:
 | 
				
			||||||
            app.register_error_handler(status_code, error_handler)
 | 
					            app.register_error_handler(status_code, error_handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from realms.lib.assets import register, assets
 | 
					    @app.before_request
 | 
				
			||||||
assets.init_app(app)
 | 
					    def init_g():
 | 
				
			||||||
assets.app = app
 | 
					        g.assets = ['main']
 | 
				
			||||||
assets.debug = config.DEBUG
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
register('main',
 | 
					    @app.template_filter('datetime')
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if config.RELATIVE_PATH:
 | 
				
			||||||
 | 
					        @app.route("/")
 | 
				
			||||||
 | 
					        def root():
 | 
				
			||||||
 | 
					            return redirect(url_for(config.ROOT_ENDPOINT))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app = create_app()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Init plugins here if possible
 | 
				
			||||||
 | 
					manager = Manager(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					login_manager = LoginManager(app)
 | 
				
			||||||
 | 
					login_manager.login_view = 'auth.login'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					db = SQLAlchemy(app)
 | 
				
			||||||
 | 
					cache = Cache(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					assets = Environment(app)
 | 
				
			||||||
 | 
					assets.register('main',
 | 
				
			||||||
                'vendor/jquery/jquery.js',
 | 
					                'vendor/jquery/jquery.js',
 | 
				
			||||||
                'vendor/components-underscore/underscore.js',
 | 
					                'vendor/components-underscore/underscore.js',
 | 
				
			||||||
                'vendor/components-bootstrap/js/bootstrap.js',
 | 
					                'vendor/components-bootstrap/js/bootstrap.js',
 | 
				
			||||||
| 
						 | 
					@ -180,43 +207,17 @@ register('main',
 | 
				
			||||||
                'vendor/showdown/src/showdown.js',
 | 
					                'vendor/showdown/src/showdown.js',
 | 
				
			||||||
                'vendor/showdown/src/extensions/table.js',
 | 
					                'vendor/showdown/src/extensions/table.js',
 | 
				
			||||||
                'js/wmd.js',
 | 
					                'js/wmd.js',
 | 
				
			||||||
         'js/html-sanitizer-minified.js',  # don't minify
 | 
					                'js/html-sanitizer-minified.js',  # don't minify?
 | 
				
			||||||
                'vendor/highlightjs/highlight.pack.js',
 | 
					                'vendor/highlightjs/highlight.pack.js',
 | 
				
			||||||
                'vendor/parsleyjs/dist/parsley.js',
 | 
					                'vendor/parsleyjs/dist/parsley.js',
 | 
				
			||||||
                'js/main.js')
 | 
					                'js/main.js')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.before_request
 | 
					 | 
				
			||||||
def init_g():
 | 
					 | 
				
			||||||
    g.assets = ['main']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.after_request
 | 
					 | 
				
			||||||
def inject_x_rate_headers(response):
 | 
					 | 
				
			||||||
    limit = get_view_rate_limit()
 | 
					 | 
				
			||||||
    if limit and limit.send_x_headers:
 | 
					 | 
				
			||||||
        h = response.headers
 | 
					 | 
				
			||||||
        h.add('X-RateLimit-Remaining', str(limit.remaining))
 | 
					 | 
				
			||||||
        h.add('X-RateLimit-Limit', str(limit.limit))
 | 
					 | 
				
			||||||
        h.add('X-RateLimit-Reset', str(limit.reset))
 | 
					 | 
				
			||||||
    return response
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.template_filter('datetime')
 | 
					 | 
				
			||||||
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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if config.RELATIVE_PATH:
 | 
					 | 
				
			||||||
    @app.route("/")
 | 
					 | 
				
			||||||
    def root():
 | 
					 | 
				
			||||||
        return redirect(url_for(config.ROOT_ENDPOINT))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.discover()
 | 
					app.discover()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					db.create_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,19 +7,35 @@ ENV = 'DEV'
 | 
				
			||||||
DEBUG = True
 | 
					DEBUG = True
 | 
				
			||||||
ASSETS_DEBUG = True
 | 
					ASSETS_DEBUG = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SQLALCHEMY_ECHO = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PORT = 80
 | 
					PORT = 80
 | 
				
			||||||
BASE_URL = 'http://realms.dev'
 | 
					BASE_URL = 'http://realms.dev'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
REDIS_HOST = '127.0.0.1'
 | 
					DB_URI = 'sqlite:////home/deploy/wiki.db'
 | 
				
			||||||
REDIS_PORT = 6379
 | 
					 | 
				
			||||||
REDIS_DB = '0'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
SECRET = 'K3dRq1q9eN72GJDkgvyshFVwlqHHCyPI'
 | 
					CACHE_TYPE = 'simple'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Redis Example
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					CACHE_TYPE = 'redis'
 | 
				
			||||||
 | 
					CACHE_REDIS_HOST = '127.0.0.1'
 | 
				
			||||||
 | 
					CACHE_REDIS_PORT = 6379
 | 
				
			||||||
 | 
					CACHE_REDIS_DB = '0'
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RECAPTCHA_ENABLE = True
 | 
				
			||||||
 | 
					RECAPTCHA_USE_SSL = False
 | 
				
			||||||
 | 
					RECAPTCHA_PUBLIC_KEY = "6LfYbPkSAAAAAB4a2lG2Y_Yjik7MG9l4TDzyKUao"
 | 
				
			||||||
 | 
					RECAPTCHA_PRIVATE_KEY = "6LfYbPkSAAAAAG-KlkwjZ8JLWgwc9T0ytkN7lWRE"
 | 
				
			||||||
 | 
					RECAPTCHA_OPTIONS = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SECRET_KEY = 'K3dRq1q9eN72GJDkgvyshFVwlqHHCyPI'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WIKI_PATH = '/home/deploy/wiki'
 | 
					WIKI_PATH = '/home/deploy/wiki'
 | 
				
			||||||
WIKI_HOME = 'home'
 | 
					WIKI_HOME = 'home'
 | 
				
			||||||
 | 
					 | 
				
			||||||
ALLOW_ANON = True
 | 
					ALLOW_ANON = True
 | 
				
			||||||
 | 
					LOGIN_DISABLED = ALLOW_ANON
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ROOT_ENDPOINT = 'wiki.page'
 | 
					ROOT_ENDPOINT = 'wiki.page'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,10 +43,11 @@ with open(os.path.join(os.path.dirname(__file__) + "/../../", 'config.json')) as
 | 
				
			||||||
    __settings = json.load(f)
 | 
					    __settings = json.load(f)
 | 
				
			||||||
    globals().update(__settings)
 | 
					    globals().update(__settings)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# String trailing slash
 | 
					 | 
				
			||||||
if BASE_URL.endswith('/'):
 | 
					if BASE_URL.endswith('/'):
 | 
				
			||||||
    BASE_URL = BASE_URL[-1]
 | 
					    BASE_URL = BASE_URL[-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SQLALCHEMY_DATABASE_URI = DB_URI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_url = urlparse(BASE_URL)
 | 
					_url = urlparse(BASE_URL)
 | 
				
			||||||
RELATIVE_PATH = _url.path
 | 
					RELATIVE_PATH = _url.path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +0,0 @@
 | 
				
			||||||
from flask.ext.assets import Bundle, Environment
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# This can be done better, make it better
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
assets = Environment()
 | 
					 | 
				
			||||||
filters = 'uglifyjs'
 | 
					 | 
				
			||||||
output = 'assets/%(version)s.js'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def register(name, *files):
 | 
					 | 
				
			||||||
    assets.register(name, Bundle(*files, filters=filters, output=output))
 | 
					 | 
				
			||||||
							
								
								
									
										287
									
								
								realms/lib/model.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								realms/lib/model.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,287 @@
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					from realms import db
 | 
				
			||||||
 | 
					from sqlalchemy import not_
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Model(db.Model):
 | 
				
			||||||
 | 
					    """Base SQLAlchemy Model for automatic serialization and
 | 
				
			||||||
 | 
					    deserialization of columns and nested relationships.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Source: https://gist.github.com/alanhamlett/6604662
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Usage::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        >>> class User(Model):
 | 
				
			||||||
 | 
					        >>>     id = db.Column(db.Integer(), primary_key=True)
 | 
				
			||||||
 | 
					        >>>     email = db.Column(db.String(), index=True)
 | 
				
			||||||
 | 
					        >>>     name = db.Column(db.String())
 | 
				
			||||||
 | 
					        >>>     password = db.Column(db.String())
 | 
				
			||||||
 | 
					        >>>     posts = db.relationship('Post', backref='user', lazy='dynamic')
 | 
				
			||||||
 | 
					        >>>     ...
 | 
				
			||||||
 | 
					        >>>     default_fields = ['email', 'name']
 | 
				
			||||||
 | 
					        >>>     hidden_fields = ['password']
 | 
				
			||||||
 | 
					        >>>     readonly_fields = ['email', 'password']
 | 
				
			||||||
 | 
					        >>>
 | 
				
			||||||
 | 
					        >>> class Post(Model):
 | 
				
			||||||
 | 
					        >>>     id = db.Column(db.Integer(), primary_key=True)
 | 
				
			||||||
 | 
					        >>>     user_id = db.Column(db.String(), db.ForeignKey('user.id'), nullable=False)
 | 
				
			||||||
 | 
					        >>>     title = db.Column(db.String())
 | 
				
			||||||
 | 
					        >>>     ...
 | 
				
			||||||
 | 
					        >>>     default_fields = ['title']
 | 
				
			||||||
 | 
					        >>>     readonly_fields = ['user_id']
 | 
				
			||||||
 | 
					        >>>
 | 
				
			||||||
 | 
					        >>> model = User(email='john@localhost')
 | 
				
			||||||
 | 
					        >>> db.session.add(model)
 | 
				
			||||||
 | 
					        >>> db.session.commit()
 | 
				
			||||||
 | 
					        >>>
 | 
				
			||||||
 | 
					        >>> # update name and create a new post
 | 
				
			||||||
 | 
					        >>> validated_input = {'name': 'John', 'posts': [{'title':'My First Post'}]}
 | 
				
			||||||
 | 
					        >>> model.set_columns(**validated_input)
 | 
				
			||||||
 | 
					        >>> db.session.commit()
 | 
				
			||||||
 | 
					        >>>
 | 
				
			||||||
 | 
					        >>> print(model.to_dict(show=['password', 'posts']))
 | 
				
			||||||
 | 
					        >>> {u'email': u'john@localhost', u'posts': [{u'id': 1, u'title': u'My First Post'}], u'name': u'John', u'id': 1}
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    __abstract__ = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Stores changes made to this model's attributes. Can be retrieved
 | 
				
			||||||
 | 
					    # with model.changes
 | 
				
			||||||
 | 
					    _changes = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, **kwargs):
 | 
				
			||||||
 | 
					        kwargs['_force'] = True
 | 
				
			||||||
 | 
					        self._set_columns(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _set_columns(self, **kwargs):
 | 
				
			||||||
 | 
					        force = kwargs.get('_force')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        readonly = []
 | 
				
			||||||
 | 
					        if hasattr(self, 'readonly_fields'):
 | 
				
			||||||
 | 
					            readonly = self.readonly_fields
 | 
				
			||||||
 | 
					        if hasattr(self, 'hidden_fields'):
 | 
				
			||||||
 | 
					            readonly += self.hidden_fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        readonly += [
 | 
				
			||||||
 | 
					            'id',
 | 
				
			||||||
 | 
					            'created',
 | 
				
			||||||
 | 
					            'updated',
 | 
				
			||||||
 | 
					            'modified',
 | 
				
			||||||
 | 
					            'created_at',
 | 
				
			||||||
 | 
					            'updated_at',
 | 
				
			||||||
 | 
					            'modified_at',
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        changes = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        columns = self.__table__.columns.keys()
 | 
				
			||||||
 | 
					        relationships = self.__mapper__.relationships.keys()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for key in columns:
 | 
				
			||||||
 | 
					            allowed = True if force or key not in readonly else False
 | 
				
			||||||
 | 
					            exists = True if key in kwargs else False
 | 
				
			||||||
 | 
					            if allowed and exists:
 | 
				
			||||||
 | 
					                val = getattr(self, key)
 | 
				
			||||||
 | 
					                if val != kwargs[key]:
 | 
				
			||||||
 | 
					                    changes[key] = {'old': val, 'new': kwargs[key]}
 | 
				
			||||||
 | 
					                    setattr(self, key, kwargs[key])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for rel in relationships:
 | 
				
			||||||
 | 
					            allowed = True if force or rel not in readonly else False
 | 
				
			||||||
 | 
					            exists = True if rel in kwargs else False
 | 
				
			||||||
 | 
					            if allowed and exists:
 | 
				
			||||||
 | 
					                is_list = self.__mapper__.relationships[rel].uselist
 | 
				
			||||||
 | 
					                if is_list:
 | 
				
			||||||
 | 
					                    valid_ids = []
 | 
				
			||||||
 | 
					                    query = getattr(self, rel)
 | 
				
			||||||
 | 
					                    cls = self.__mapper__.relationships[rel].argument()
 | 
				
			||||||
 | 
					                    for item in kwargs[rel]:
 | 
				
			||||||
 | 
					                        if 'id' in item and query.filter_by(id=item['id']).limit(1).count() == 1:
 | 
				
			||||||
 | 
					                            obj = cls.query.filter_by(id=item['id']).first()
 | 
				
			||||||
 | 
					                            col_changes = obj.set_columns(**item)
 | 
				
			||||||
 | 
					                            if col_changes:
 | 
				
			||||||
 | 
					                                col_changes['id'] = str(item['id'])
 | 
				
			||||||
 | 
					                                if rel in changes:
 | 
				
			||||||
 | 
					                                    changes[rel].append(col_changes)
 | 
				
			||||||
 | 
					                                else:
 | 
				
			||||||
 | 
					                                    changes.update({rel: [col_changes]})
 | 
				
			||||||
 | 
					                            valid_ids.append(str(item['id']))
 | 
				
			||||||
 | 
					                        else:
 | 
				
			||||||
 | 
					                            col = cls()
 | 
				
			||||||
 | 
					                            col_changes = col.set_columns(**item)
 | 
				
			||||||
 | 
					                            query.append(col)
 | 
				
			||||||
 | 
					                            db.session.flush()
 | 
				
			||||||
 | 
					                            if col_changes:
 | 
				
			||||||
 | 
					                                col_changes['id'] = str(col.id)
 | 
				
			||||||
 | 
					                                if rel in changes:
 | 
				
			||||||
 | 
					                                    changes[rel].append(col_changes)
 | 
				
			||||||
 | 
					                                else:
 | 
				
			||||||
 | 
					                                    changes.update({rel: [col_changes]})
 | 
				
			||||||
 | 
					                            valid_ids.append(str(col.id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # delete related rows that were not in kwargs[rel]
 | 
				
			||||||
 | 
					                    for item in query.filter(not_(cls.id.in_(valid_ids))).all():
 | 
				
			||||||
 | 
					                        col_changes = {
 | 
				
			||||||
 | 
					                            'id': str(item.id),
 | 
				
			||||||
 | 
					                            'deleted': True,
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        if rel in changes:
 | 
				
			||||||
 | 
					                            changes[rel].append(col_changes)
 | 
				
			||||||
 | 
					                        else:
 | 
				
			||||||
 | 
					                            changes.update({rel: [col_changes]})
 | 
				
			||||||
 | 
					                        db.session.delete(item)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    val = getattr(self, rel)
 | 
				
			||||||
 | 
					                    if self.__mapper__.relationships[rel].query_class is not None:
 | 
				
			||||||
 | 
					                        if val is not None:
 | 
				
			||||||
 | 
					                            col_changes = val.set_columns(**kwargs[rel])
 | 
				
			||||||
 | 
					                            if col_changes:
 | 
				
			||||||
 | 
					                                changes.update({rel: col_changes})
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        if val != kwargs[rel]:
 | 
				
			||||||
 | 
					                            setattr(self, rel, kwargs[rel])
 | 
				
			||||||
 | 
					                            changes[rel] = {'old': val, 'new': kwargs[rel]}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return changes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_columns(self, **kwargs):
 | 
				
			||||||
 | 
					        self._changes = self._set_columns(**kwargs)
 | 
				
			||||||
 | 
					        if 'modified' in self.__table__.columns:
 | 
				
			||||||
 | 
					            self.modified = datetime.utcnow()
 | 
				
			||||||
 | 
					        if 'updated' in self.__table__.columns:
 | 
				
			||||||
 | 
					            self.updated = datetime.utcnow()
 | 
				
			||||||
 | 
					        if 'modified_at' in self.__table__.columns:
 | 
				
			||||||
 | 
					            self.modified_at = datetime.utcnow()
 | 
				
			||||||
 | 
					        if 'updated_at' in self.__table__.columns:
 | 
				
			||||||
 | 
					            self.updated_at = datetime.utcnow()
 | 
				
			||||||
 | 
					        return self._changes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __repr__(self):
 | 
				
			||||||
 | 
					        if 'id' in self.__table__.columns.keys():
 | 
				
			||||||
 | 
					            return '%s(%s)' % (self.__class__.__name__, self.id)
 | 
				
			||||||
 | 
					        data = {}
 | 
				
			||||||
 | 
					        for key in self.__table__.columns.keys():
 | 
				
			||||||
 | 
					            val = getattr(self, key)
 | 
				
			||||||
 | 
					            if type(val) is datetime:
 | 
				
			||||||
 | 
					                val = val.strftime('%Y-%m-%dT%H:%M:%SZ')
 | 
				
			||||||
 | 
					            data[key] = val
 | 
				
			||||||
 | 
					        return json.dumps(data, use_decimal=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def changes(self):
 | 
				
			||||||
 | 
					        return self._changes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def reset_changes(self):
 | 
				
			||||||
 | 
					        self._changes = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def to_dict(self, show=None, hide=None, path=None, show_all=None):
 | 
				
			||||||
 | 
					        """ Return a dictionary representation of this model.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not show:
 | 
				
			||||||
 | 
					            show = []
 | 
				
			||||||
 | 
					        if not hide:
 | 
				
			||||||
 | 
					            hide = []
 | 
				
			||||||
 | 
					        hidden = []
 | 
				
			||||||
 | 
					        if hasattr(self, 'hidden_fields'):
 | 
				
			||||||
 | 
					            hidden = self.hidden_fields
 | 
				
			||||||
 | 
					        default = []
 | 
				
			||||||
 | 
					        if hasattr(self, 'default_fields'):
 | 
				
			||||||
 | 
					            default = self.default_fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ret_data = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not path:
 | 
				
			||||||
 | 
					            path = self.__tablename__.lower()
 | 
				
			||||||
 | 
					            def prepend_path(item):
 | 
				
			||||||
 | 
					                item = item.lower()
 | 
				
			||||||
 | 
					                if item.split('.', 1)[0] == path:
 | 
				
			||||||
 | 
					                    return item
 | 
				
			||||||
 | 
					                if len(item) == 0:
 | 
				
			||||||
 | 
					                    return item
 | 
				
			||||||
 | 
					                if item[0] != '.':
 | 
				
			||||||
 | 
					                    item = '.%s' % item
 | 
				
			||||||
 | 
					                item = '%s%s' % (path, item)
 | 
				
			||||||
 | 
					                return item
 | 
				
			||||||
 | 
					            show[:] = [prepend_path(x) for x in show]
 | 
				
			||||||
 | 
					            hide[:] = [prepend_path(x) for x in hide]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        columns = self.__table__.columns.keys()
 | 
				
			||||||
 | 
					        relationships = self.__mapper__.relationships.keys()
 | 
				
			||||||
 | 
					        properties = dir(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for key in columns:
 | 
				
			||||||
 | 
					            check = '%s.%s' % (path, key)
 | 
				
			||||||
 | 
					            if check in hide or key in hidden:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            if show_all or key is 'id' or check in show or key in default:
 | 
				
			||||||
 | 
					                ret_data[key] = getattr(self, key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for key in relationships:
 | 
				
			||||||
 | 
					            check = '%s.%s' % (path, key)
 | 
				
			||||||
 | 
					            if check in hide or key in hidden:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            if show_all or check in show or key in default:
 | 
				
			||||||
 | 
					                hide.append(check)
 | 
				
			||||||
 | 
					                is_list = self.__mapper__.relationships[key].uselist
 | 
				
			||||||
 | 
					                if is_list:
 | 
				
			||||||
 | 
					                    ret_data[key] = []
 | 
				
			||||||
 | 
					                    for item in getattr(self, key):
 | 
				
			||||||
 | 
					                        ret_data[key].append(item.to_dict(
 | 
				
			||||||
 | 
					                            show=show,
 | 
				
			||||||
 | 
					                            hide=hide,
 | 
				
			||||||
 | 
					                            path=('%s.%s' % (path, key.lower())),
 | 
				
			||||||
 | 
					                            show_all=show_all,
 | 
				
			||||||
 | 
					                        ))
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    if self.__mapper__.relationships[key].query_class is not None:
 | 
				
			||||||
 | 
					                        ret_data[key] = getattr(self, key).to_dict(
 | 
				
			||||||
 | 
					                            show=show,
 | 
				
			||||||
 | 
					                            hide=hide,
 | 
				
			||||||
 | 
					                            path=('%s.%s' % (path, key.lower())),
 | 
				
			||||||
 | 
					                            show_all=show_all,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        ret_data[key] = getattr(self, key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for key in list(set(properties) - set(columns) - set(relationships)):
 | 
				
			||||||
 | 
					            if key.startswith('_'):
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            check = '%s.%s' % (path, key)
 | 
				
			||||||
 | 
					            if check in hide or key in hidden:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            if show_all or check in show or key in default:
 | 
				
			||||||
 | 
					                val = getattr(self, key)
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    ret_data[key] = json.loads(json.dumps(val))
 | 
				
			||||||
 | 
					                except:
 | 
				
			||||||
 | 
					                    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ret_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def insert_or_update(cls, cond, data):
 | 
				
			||||||
 | 
					        obj = cls.query.filter_by(**cond).first()
 | 
				
			||||||
 | 
					        if obj:
 | 
				
			||||||
 | 
					            obj.set_columns(**data)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            data.update(cond)
 | 
				
			||||||
 | 
					            obj = cls(**data)
 | 
				
			||||||
 | 
					            db.session.add(obj)
 | 
				
			||||||
 | 
					        db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def save(self):
 | 
				
			||||||
 | 
					        if self not in db.session:
 | 
				
			||||||
 | 
					            db.session.merge(self)
 | 
				
			||||||
 | 
					        db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def delete(self):
 | 
				
			||||||
 | 
					        if self not in db.session:
 | 
				
			||||||
 | 
					            db.session.merge(self)
 | 
				
			||||||
 | 
					        db.session.delete(self)
 | 
				
			||||||
 | 
					        db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_by_id(cls, id_):
 | 
				
			||||||
 | 
					        return cls.query.filter_by(id=id_).first()
 | 
				
			||||||
| 
						 | 
					@ -1,46 +0,0 @@
 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
from functools import update_wrapper
 | 
					 | 
				
			||||||
from flask import request, g
 | 
					 | 
				
			||||||
from services import db
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RateLimit(object):
 | 
					 | 
				
			||||||
    expiration_window = 10
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, key_prefix, limit, per, send_x_headers):
 | 
					 | 
				
			||||||
        self.reset = (int(time.time()) // per) * per + per
 | 
					 | 
				
			||||||
        self.key = key_prefix + str(self.reset)
 | 
					 | 
				
			||||||
        self.limit = limit
 | 
					 | 
				
			||||||
        self.per = per
 | 
					 | 
				
			||||||
        self.send_x_headers = send_x_headers
 | 
					 | 
				
			||||||
        p = db.pipeline()
 | 
					 | 
				
			||||||
        p.incr(self.key)
 | 
					 | 
				
			||||||
        p.expireat(self.key, self.reset + self.expiration_window)
 | 
					 | 
				
			||||||
        self.current = min(p.execute()[0], limit)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    remaining = property(lambda x: x.limit - x.current)
 | 
					 | 
				
			||||||
    over_limit = property(lambda x: x.current >= x.limit)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def get_view_rate_limit():
 | 
					 | 
				
			||||||
    return getattr(g, '_view_rate_limit', None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def on_over_limit(limit):
 | 
					 | 
				
			||||||
    return 'Slow it down', 400
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def ratelimiter(limit, per=300, send_x_headers=True,
 | 
					 | 
				
			||||||
              over_limit=on_over_limit,
 | 
					 | 
				
			||||||
              scope_func=lambda: request.remote_addr,
 | 
					 | 
				
			||||||
              key_func=lambda: request.endpoint):
 | 
					 | 
				
			||||||
    def decorator(f):
 | 
					 | 
				
			||||||
        def rate_limited(*args, **kwargs):
 | 
					 | 
				
			||||||
            key = 'rate-limit/%s/%s/' % (key_func(), scope_func())
 | 
					 | 
				
			||||||
            rlimit = RateLimit(key, limit, per, send_x_headers)
 | 
					 | 
				
			||||||
            g._view_rate_limit = rlimit
 | 
					 | 
				
			||||||
            if over_limit is not None and rlimit.over_limit:
 | 
					 | 
				
			||||||
                return over_limit(rlimit)
 | 
					 | 
				
			||||||
            return f(*args, **kwargs)
 | 
					 | 
				
			||||||
        return update_wrapper(rate_limited, f)
 | 
					 | 
				
			||||||
    return decorator
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,4 +0,0 @@
 | 
				
			||||||
import redis
 | 
					 | 
				
			||||||
from realms import config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
db = redis.StrictRedis(host=config.REDIS_HOST, port=config.REDIS_PORT, db=config.REDIS_DB)
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,64 +0,0 @@
 | 
				
			||||||
import pickle
 | 
					 | 
				
			||||||
from datetime import timedelta
 | 
					 | 
				
			||||||
from uuid import uuid4
 | 
					 | 
				
			||||||
from redis import Redis
 | 
					 | 
				
			||||||
from werkzeug.datastructures import CallbackDict
 | 
					 | 
				
			||||||
from flask.sessions import SessionInterface, SessionMixin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RedisSession(CallbackDict, SessionMixin):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, initial=None, sid=None, new=False):
 | 
					 | 
				
			||||||
        def on_update(self):
 | 
					 | 
				
			||||||
            self.modified = True
 | 
					 | 
				
			||||||
        CallbackDict.__init__(self, initial, on_update)
 | 
					 | 
				
			||||||
        self.sid = sid
 | 
					 | 
				
			||||||
        self.new = new
 | 
					 | 
				
			||||||
        self.modified = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RedisSessionInterface(SessionInterface):
 | 
					 | 
				
			||||||
    serializer = pickle
 | 
					 | 
				
			||||||
    session_class = RedisSession
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, redis=None, prefix='session:'):
 | 
					 | 
				
			||||||
        if redis is None:
 | 
					 | 
				
			||||||
            redis = Redis()
 | 
					 | 
				
			||||||
        self.redis = redis
 | 
					 | 
				
			||||||
        self.prefix = prefix
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def generate_sid(self):
 | 
					 | 
				
			||||||
        return str(uuid4())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_redis_expiration_time(self, app, session):
 | 
					 | 
				
			||||||
        if session.permanent:
 | 
					 | 
				
			||||||
            return app.permanent_session_lifetime
 | 
					 | 
				
			||||||
        return timedelta(days=1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def open_session(self, app, request):
 | 
					 | 
				
			||||||
        sid = request.cookies.get(app.session_cookie_name)
 | 
					 | 
				
			||||||
        if not sid:
 | 
					 | 
				
			||||||
            sid = self.generate_sid()
 | 
					 | 
				
			||||||
            return self.session_class(sid=sid)
 | 
					 | 
				
			||||||
        val = self.redis.get(self.prefix + sid)
 | 
					 | 
				
			||||||
        if val is not None:
 | 
					 | 
				
			||||||
            data = self.serializer.loads(val)
 | 
					 | 
				
			||||||
            return self.session_class(data, sid=sid)
 | 
					 | 
				
			||||||
        return self.session_class(sid=sid, new=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def save_session(self, app, session, response):
 | 
					 | 
				
			||||||
        domain = self.get_cookie_domain(app)
 | 
					 | 
				
			||||||
        if not session:
 | 
					 | 
				
			||||||
            self.redis.delete(self.prefix + session.sid)
 | 
					 | 
				
			||||||
            if session.modified:
 | 
					 | 
				
			||||||
                response.delete_cookie(app.session_cookie_name,
 | 
					 | 
				
			||||||
                                       domain=domain)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        redis_exp = self.get_redis_expiration_time(app, session)
 | 
					 | 
				
			||||||
        cookie_exp = self.get_expiration_time(app, session)
 | 
					 | 
				
			||||||
        val = self.serializer.dumps(dict(session))
 | 
					 | 
				
			||||||
        self.redis.setex(self.prefix + session.sid, val,
 | 
					 | 
				
			||||||
                         int(redis_exp.total_seconds()))
 | 
					 | 
				
			||||||
        response.set_cookie(app.session_cookie_name, session.sid,
 | 
					 | 
				
			||||||
                            expires=cookie_exp, httponly=True,
 | 
					 | 
				
			||||||
                            domain=domain)
 | 
					 | 
				
			||||||
| 
						 | 
					@ -3,8 +3,6 @@ import os
 | 
				
			||||||
import hashlib
 | 
					import hashlib
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from realms.lib.services import db
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AttrDict(dict):
 | 
					class AttrDict(dict):
 | 
				
			||||||
    def __init__(self, *args, **kwargs):
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
| 
						 | 
					@ -34,36 +32,6 @@ def to_dict(data):
 | 
				
			||||||
        return row2dict(data)
 | 
					        return row2dict(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cache_it(fn):
 | 
					 | 
				
			||||||
    def wrap(*args, **kw):
 | 
					 | 
				
			||||||
        key = "%s:%s" % (args[0].table, args[1])
 | 
					 | 
				
			||||||
        data = db.get(key)
 | 
					 | 
				
			||||||
        # Assume strings are JSON encoded
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            data = json.loads(data)
 | 
					 | 
				
			||||||
        except TypeError:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
        except ValueError:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if data is not None:
 | 
					 | 
				
			||||||
            return data
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            data = fn(*args)
 | 
					 | 
				
			||||||
            print data
 | 
					 | 
				
			||||||
            ret = data
 | 
					 | 
				
			||||||
            if data is None:
 | 
					 | 
				
			||||||
                data = ''
 | 
					 | 
				
			||||||
            if not isinstance(data, basestring):
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    data = json.dumps(data, separators=(',', ':'))
 | 
					 | 
				
			||||||
                except TypeError:
 | 
					 | 
				
			||||||
                    pass
 | 
					 | 
				
			||||||
            db.set(key, data)
 | 
					 | 
				
			||||||
            return ret
 | 
					 | 
				
			||||||
    return wrap
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def mkdir_safe(path):
 | 
					def mkdir_safe(path):
 | 
				
			||||||
    if path and not(os.path.exists(path)):
 | 
					    if path and not(os.path.exists(path)):
 | 
				
			||||||
        os.makedirs(path)
 | 
					        os.makedirs(path)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
from wtforms import Form, StringField, PasswordField, validators
 | 
					from flask_wtf import Form, RecaptchaField
 | 
				
			||||||
 | 
					from wtforms import StringField, PasswordField, validators
 | 
				
			||||||
 | 
					from realms import config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RegistrationForm(Form):
 | 
					class RegistrationForm(Form):
 | 
				
			||||||
    username = StringField('Username', [validators.Length(min=4, max=25)])
 | 
					    username = StringField('Username', [validators.Length(min=4, max=25)])
 | 
				
			||||||
| 
						 | 
					@ -10,9 +11,12 @@ class RegistrationForm(Form):
 | 
				
			||||||
    ])
 | 
					    ])
 | 
				
			||||||
    confirm = PasswordField('Repeat Password')
 | 
					    confirm = PasswordField('Repeat Password')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if 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()])
 | 
				
			||||||
    password = PasswordField('Password', [validators.DataRequired])
 | 
					    password = PasswordField('Password', [validators.DataRequired()])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,23 +1,15 @@
 | 
				
			||||||
from flask.ext.login import UserMixin, logout_user, login_user
 | 
					from flask.ext.login import UserMixin, logout_user, login_user, AnonymousUserMixin
 | 
				
			||||||
from realms import config, login_manager
 | 
					from realms import config, login_manager, db
 | 
				
			||||||
from realms.lib.services import db
 | 
					from realms.lib.model import Model
 | 
				
			||||||
 | 
					from realms.lib.util import gravatar_url
 | 
				
			||||||
from itsdangerous import URLSafeSerializer, BadSignature
 | 
					from itsdangerous import URLSafeSerializer, BadSignature
 | 
				
			||||||
from hashlib import sha256
 | 
					from hashlib import sha256
 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import bcrypt
 | 
					import bcrypt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FIELD_MAP = dict(
 | 
					 | 
				
			||||||
    u='username',
 | 
					 | 
				
			||||||
    e='email',
 | 
					 | 
				
			||||||
    p='password',
 | 
					 | 
				
			||||||
    nv='not_verified',
 | 
					 | 
				
			||||||
    a='admin',
 | 
					 | 
				
			||||||
    b='banned')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_manager.user_loader
 | 
					@login_manager.user_loader
 | 
				
			||||||
def load_user(user_id):
 | 
					def load_user(user_id):
 | 
				
			||||||
    return User.get(user_id)
 | 
					    return User.get_by_id(user_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_manager.token_loader
 | 
					@login_manager.token_loader
 | 
				
			||||||
| 
						 | 
					@ -29,7 +21,7 @@ def load_token(token):
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # User key *could* be stored in payload to avoid user lookup in db
 | 
					    # User key *could* be stored in payload to avoid user lookup in db
 | 
				
			||||||
    user = User.get(payload.get('id'))
 | 
					    user = User.get_by_id(payload.get('id'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not user:
 | 
					    if not user:
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
| 
						 | 
					@ -43,68 +35,78 @@ def load_token(token):
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class User(UserMixin):
 | 
					class AnonUser(AnonymousUserMixin):
 | 
				
			||||||
 | 
					    username = 'Anon'
 | 
				
			||||||
 | 
					    email = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    username = None
 | 
					 | 
				
			||||||
    email = None
 | 
					 | 
				
			||||||
    password = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, email, data=None):
 | 
					class User(Model, UserMixin):
 | 
				
			||||||
        self.id = email
 | 
					    __tablename__ = 'users'
 | 
				
			||||||
        for k, v in data.items():
 | 
					    id = db.Column(db.Integer, primary_key=True)
 | 
				
			||||||
            setattr(self, FIELD_MAP.get(k, k), v)
 | 
					    username = db.Column(db.String, unique=True)
 | 
				
			||||||
 | 
					    email = db.Column(db.String, unique=True)
 | 
				
			||||||
 | 
					    password = db.Column(db.String)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    hidden_fields = ['password']
 | 
				
			||||||
 | 
					    readonly_fields = ['email', 'password']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_auth_token(self):
 | 
					    def get_auth_token(self):
 | 
				
			||||||
        key = sha256(self.password).hexdigest()
 | 
					        key = sha256(self.password).hexdigest()
 | 
				
			||||||
        return User.signer(key).dumps(dict(id=self.username))
 | 
					        return User.signer(key).dumps(dict(id=self.id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def avatar(self):
 | 
				
			||||||
 | 
					        return gravatar_url(self.email)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def create(username, email, password):
 | 
					    def create(username, email, password):
 | 
				
			||||||
        User.set(email, dict(u=username, e=email, p=User.hash(password), nv=1))
 | 
					        u = User()
 | 
				
			||||||
 | 
					        u.username = username
 | 
				
			||||||
 | 
					        u.email = email
 | 
				
			||||||
 | 
					        u.password = User.hash_password(password)
 | 
				
			||||||
 | 
					        u.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def get_by_username(username):
 | 
				
			||||||
 | 
					        return User.query.filter_by(username=username).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def get_by_email(email):
 | 
				
			||||||
 | 
					        return User.query.filter_by(email=email).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def signer(salt):
 | 
					    def signer(salt):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        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 + salt)
 | 
					        return URLSafeSerializer(config.SECRET_KEY + salt)
 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def set(email, data):
 | 
					 | 
				
			||||||
        db.set('u:%s' % email, json.dumps(data, separators=(',', ':')))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def get(email):
 | 
					 | 
				
			||||||
        data = db.get('u:%s', email)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            data = json.loads(data)
 | 
					 | 
				
			||||||
        except ValueError:
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if data:
 | 
					 | 
				
			||||||
            return User(email, data)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def auth(email, password):
 | 
					    def auth(email, password):
 | 
				
			||||||
        user = User.get(email)
 | 
					        user = User.query.filter_by(email=email).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not user:
 | 
					        if not user:
 | 
				
			||||||
 | 
					            # User doesn't exist
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if bcrypt.checkpw(password, user.password):
 | 
					        if User.check_password(password, user.password):
 | 
				
			||||||
 | 
					            # Password is good, log in user
 | 
				
			||||||
            login_user(user, remember=True)
 | 
					            login_user(user, remember=True)
 | 
				
			||||||
            return user
 | 
					            return user
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
 | 
					            # Password check failed
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def hash(password):
 | 
					    def hash_password(password):
 | 
				
			||||||
        return bcrypt.hashpw(password, bcrypt.gensalt(log_rounds=12))
 | 
					        return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(12))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def check_password(password, hashed):
 | 
				
			||||||
 | 
					        return bcrypt.hashpw(password.encode('utf-8'), hashed.encode('utf-8')) == hashed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def logout(cls):
 | 
					    def logout(cls):
 | 
				
			||||||
        logout_user()
 | 
					        logout_user()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					login_manager.anonymous_user = AnonUser
 | 
				
			||||||
| 
						 | 
					@ -13,24 +13,55 @@ def logout_page():
 | 
				
			||||||
    return redirect(url_for(config.ROOT_ENDPOINT))
 | 
					    return redirect(url_for(config.ROOT_ENDPOINT))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/login")
 | 
					@blueprint.route("/login", methods=['GET', 'POST'])
 | 
				
			||||||
def login():
 | 
					def login():
 | 
				
			||||||
    if request.method == "POST":
 | 
					    form = LoginForm()
 | 
				
			||||||
        form = RegistrationForm()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # TODO
 | 
					    if request.method == "POST":
 | 
				
			||||||
        if not form.validate():
 | 
					        if not form.validate():
 | 
				
			||||||
            flash('Form invalid')
 | 
					            flash('Form invalid', 'warning')
 | 
				
			||||||
            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(config.ROOT_ENDPOINT))
 | 
					            return redirect(request.args.get("next") or url_for(config.ROOT_ENDPOINT))
 | 
				
			||||||
 | 
					 | 
				
			||||||
    return render_template("auth/login.html")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@blueprint.route("/register")
 | 
					 | 
				
			||||||
def register():
 | 
					 | 
				
			||||||
    if request.method == "POST":
 | 
					 | 
				
			||||||
        return redirect(request.args.get("next") or url_for(config.ROOT_ENDPOINT))
 | 
					 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
        return render_template("auth/register.html")
 | 
					            flash('Email or Password Incorrect', 'warning')
 | 
				
			||||||
 | 
					            return redirect(url_for('auth.login'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return render_template("auth/login.html", form=form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@blueprint.route("/register", methods=['GET', 'POST'])
 | 
				
			||||||
 | 
					def register():
 | 
				
			||||||
 | 
					    form = RegistrationForm()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if request.method == "POST":
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not form.validate():
 | 
				
			||||||
 | 
					            flash('Form invalid', 'warning')
 | 
				
			||||||
 | 
					            return redirect(url_for('auth.register'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if User.get_by_username(request.form['username']):
 | 
				
			||||||
 | 
					            flash('Username is taken', 'warning')
 | 
				
			||||||
 | 
					            return redirect(url_for('auth.register'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if User.get_by_email(request.form['email']):
 | 
				
			||||||
 | 
					            flash('Email is taken', 'warning')
 | 
				
			||||||
 | 
					            return redirect(url_for('auth.register'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        User.create(request.form['username'], request.form['email'], request.form['password'])
 | 
				
			||||||
 | 
					        User.auth(request.form['email'], request.form['password'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return redirect(request.args.get("next") or url_for(config.ROOT_ENDPOINT))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return render_template("auth/register.html", form=form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@blueprint.route("/settings", methods=['GET', 'POST'])
 | 
				
			||||||
 | 
					def settings():
 | 
				
			||||||
 | 
					    return render_template("auth/settings.html")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@blueprint.route("/logout")
 | 
				
			||||||
 | 
					def logout():
 | 
				
			||||||
 | 
					    User.logout()
 | 
				
			||||||
 | 
					    return redirect("/")
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
from realms.lib.assets import register
 | 
					from realms import assets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
register(
 | 
					assets.register('editor',
 | 
				
			||||||
    'editor',
 | 
					 | 
				
			||||||
                'js/ace/ace.js',
 | 
					                'js/ace/ace.js',
 | 
				
			||||||
                'js/ace/mode-markdown.js',
 | 
					                'js/ace/mode-markdown.js',
 | 
				
			||||||
                'vendor/keymaster/keymaster.js',
 | 
					                'vendor/keymaster/keymaster.js',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,8 @@ import gittle.utils
 | 
				
			||||||
from gittle import Gittle
 | 
					from gittle import Gittle
 | 
				
			||||||
from dulwich.repo import NotGitRepository
 | 
					from dulwich.repo import NotGitRepository
 | 
				
			||||||
from werkzeug.utils import escape, unescape
 | 
					from werkzeug.utils import escape, unescape
 | 
				
			||||||
from util import to_canonical
 | 
					from realms.lib.util import to_canonical
 | 
				
			||||||
 | 
					from realms import cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MyGittle(Gittle):
 | 
					class MyGittle(Gittle):
 | 
				
			||||||
| 
						 | 
					@ -94,7 +95,7 @@ class Wiki():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        content = lxml.html.tostring(tree, encoding='utf-8', method='html')
 | 
					        content = lxml.html.tostring(tree, encoding='utf-8', method='html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # post processing to fix errors
 | 
					        # remove added div tags
 | 
				
			||||||
        content = content[5:-6]
 | 
					        content = content[5:-6]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # FIXME this is for block quotes, doesn't work for double ">"
 | 
					        # FIXME this is for block quotes, doesn't work for double ">"
 | 
				
			||||||
| 
						 | 
					@ -103,7 +104,8 @@ class Wiki():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        content = re.sub(r"```(.*?)```", unescape_repl, content, flags=re.DOTALL)
 | 
					        content = re.sub(r"```(.*?)```", unescape_repl, content, flags=re.DOTALL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        filename = self.cname_to_filename(to_canonical(name))
 | 
					        cname = to_canonical(name)
 | 
				
			||||||
 | 
					        filename = self.cname_to_filename(cname)
 | 
				
			||||||
        with open(self.path + "/" + filename, 'w') as f:
 | 
					        with open(self.path + "/" + filename, 'w') as f:
 | 
				
			||||||
            f.write(content)
 | 
					            f.write(content)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -119,11 +121,15 @@ class Wiki():
 | 
				
			||||||
        if not email:
 | 
					        if not email:
 | 
				
			||||||
            email = self.default_committer_email
 | 
					            email = self.default_committer_email
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return self.repo.commit(name=username,
 | 
					        ret = self.repo.commit(name=username,
 | 
				
			||||||
                               email=email,
 | 
					                               email=email,
 | 
				
			||||||
                               message=message,
 | 
					                               message=message,
 | 
				
			||||||
                               files=[filename])
 | 
					                               files=[filename])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cache.delete_memoized(Wiki.get_page, cname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def rename_page(self, old_name, new_name):
 | 
					    def rename_page(self, old_name, new_name):
 | 
				
			||||||
        old_name, new_name = map(self.cname_to_filename, [old_name, new_name])
 | 
					        old_name, new_name = map(self.cname_to_filename, [old_name, new_name])
 | 
				
			||||||
        self.repo.mv([(old_name, new_name)])
 | 
					        self.repo.mv([(old_name, new_name)])
 | 
				
			||||||
| 
						 | 
					@ -131,7 +137,10 @@ class Wiki():
 | 
				
			||||||
                         email=self.default_committer_email,
 | 
					                         email=self.default_committer_email,
 | 
				
			||||||
                         message="Moving %s to %s" % (old_name, new_name),
 | 
					                         message="Moving %s to %s" % (old_name, new_name),
 | 
				
			||||||
                         files=[old_name])
 | 
					                         files=[old_name])
 | 
				
			||||||
 | 
					        cache.delete_memoized(Wiki.get_page, old_name)
 | 
				
			||||||
 | 
					        cache.delete_memoized(Wiki.get_page, new_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @cache.memoize()
 | 
				
			||||||
    def get_page(self, name, sha='HEAD'):
 | 
					    def get_page(self, name, sha='HEAD'):
 | 
				
			||||||
        # commit = gittle.utils.git.commit_info(self.repo[sha])
 | 
					        # commit = gittle.utils.git.commit_info(self.repo[sha])
 | 
				
			||||||
        name = self.cname_to_filename(name).encode('latin-1')
 | 
					        name = self.cname_to_filename(name).encode('latin-1')
 | 
				
			||||||
| 
						 | 
					@ -151,5 +160,6 @@ class Wiki():
 | 
				
			||||||
    def get_history(self, name):
 | 
					    def get_history(self, name):
 | 
				
			||||||
        return self.repo.file_history(self.cname_to_filename(name))
 | 
					        return self.repo.file_history(self.cname_to_filename(name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def cname_to_filename(self, cname):
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def cname_to_filename(cname):
 | 
				
			||||||
        return cname.lower() + ".md"
 | 
					        return cname.lower() + ".md"
 | 
				
			||||||
| 
						 | 
					@ -1,5 +0,0 @@
 | 
				
			||||||
import realms
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
c = realms.app.test_client()
 | 
					 | 
				
			||||||
print c.get('/wiki/_create')
 | 
					 | 
				
			||||||
print c.get('/wiki/_create/blah')
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,19 @@
 | 
				
			||||||
from flask import g, render_template, request, redirect, Blueprint, flash, url_for
 | 
					from flask import g, render_template, request, redirect, Blueprint, flash, url_for, current_app
 | 
				
			||||||
 | 
					from flask.ext.login import login_required
 | 
				
			||||||
from realms.lib.util import to_canonical, remove_ext
 | 
					from realms.lib.util import to_canonical, remove_ext
 | 
				
			||||||
from realms import config
 | 
					from realms.modules.wiki.models import Wiki
 | 
				
			||||||
 | 
					from realms import config, current_user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
blueprint = Blueprint('wiki', __name__, url_prefix=config.RELATIVE_PATH)
 | 
					blueprint = Blueprint('wiki', __name__, url_prefix=config.RELATIVE_PATH)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					wiki = Wiki(config.WIKI_PATH)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data = g.current_wiki.get_page(cname, sha=sha)
 | 
					    data = wiki.get_page(cname, sha=sha)
 | 
				
			||||||
    if data:
 | 
					    if data:
 | 
				
			||||||
        return render_template('wiki/page.html', name=name, page=data, commit=sha)
 | 
					        return render_template('wiki/page.html', name=name, page=data, commit=sha)
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
| 
						 | 
					@ -18,41 +22,42 @@ 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 = 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
 | 
				
			||||||
def revert():
 | 
					def revert():
 | 
				
			||||||
    if request.method == 'POST':
 | 
					 | 
				
			||||||
    name = request.form.get('name')
 | 
					    name = request.form.get('name')
 | 
				
			||||||
    commit = request.form.get('commit')
 | 
					    commit = request.form.get('commit')
 | 
				
			||||||
    cname = to_canonical(name)
 | 
					    cname = to_canonical(name)
 | 
				
			||||||
        g.current_wiki.revert_page(name, commit, message="Reverting %s" % cname,
 | 
					    wiki.revert_page(name, commit, message="Reverting %s" % cname,
 | 
				
			||||||
                                   username=g.current_user.get('username'))
 | 
					                     username=g.current_user.username)
 | 
				
			||||||
    flash('Page reverted', 'success')
 | 
					    flash('Page reverted', 'success')
 | 
				
			||||||
    return redirect(url_for('wiki.page', name=cname))
 | 
					    return redirect(url_for('wiki.page', name=cname))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/_history/<name>")
 | 
					@blueprint.route("/_history/<name>")
 | 
				
			||||||
def history(name):
 | 
					def history(name):
 | 
				
			||||||
    history = g.current_wiki.get_history(name)
 | 
					    history = wiki.get_history(name)
 | 
				
			||||||
    return render_template('wiki/history.html', name=name, history=history, wiki_home=url_for('wiki.page'))
 | 
					    return render_template('wiki/history.html', name=name, history=history, wiki_home=url_for('wiki.page'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/_edit/<name>", methods=['GET', 'POST'])
 | 
					@blueprint.route("/_edit/<name>", methods=['GET', 'POST'])
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
def edit(name):
 | 
					def edit(name):
 | 
				
			||||||
    data = g.current_wiki.get_page(name)
 | 
					    data = wiki.get_page(name)
 | 
				
			||||||
    cname = to_canonical(name)
 | 
					    cname = to_canonical(name)
 | 
				
			||||||
    if request.method == 'POST':
 | 
					    if request.method == 'POST':
 | 
				
			||||||
        edit_cname = to_canonical(request.form['name'])
 | 
					        edit_cname = to_canonical(request.form['name'])
 | 
				
			||||||
        if edit_cname.lower() != cname.lower():
 | 
					        if edit_cname.lower() != cname.lower():
 | 
				
			||||||
            g.current_wiki.rename_page(cname, edit_cname)
 | 
					            wiki.rename_page(cname, edit_cname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        g.current_wiki.write_page(edit_cname,
 | 
					        wiki.write_page(edit_cname,
 | 
				
			||||||
                        request.form['content'],
 | 
					                        request.form['content'],
 | 
				
			||||||
                        message=request.form['message'],
 | 
					                        message=request.form['message'],
 | 
				
			||||||
                                  username=g.current_user.get('username'))
 | 
					                        username=g.current_user.username)
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        if data:
 | 
					        if data:
 | 
				
			||||||
            name = remove_ext(data['name'])
 | 
					            name = remove_ext(data['name'])
 | 
				
			||||||
| 
						 | 
					@ -64,22 +69,24 @@ def edit(name):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/_delete/<name>", methods=['POST'])
 | 
					@blueprint.route("/_delete/<name>", methods=['POST'])
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
def delete(name):
 | 
					def delete(name):
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/_create/", defaults={'name': None}, methods=['GET', 'POST'])
 | 
					@blueprint.route("/_create/", defaults={'name': None}, methods=['GET', 'POST'])
 | 
				
			||||||
@blueprint.route("/_create/<name>", methods=['GET', 'POST'])
 | 
					@blueprint.route("/_create/<name>", methods=['GET', 'POST'])
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
def create(name):
 | 
					def create(name):
 | 
				
			||||||
    if request.method == 'POST':
 | 
					    if request.method == 'POST':
 | 
				
			||||||
        g.current_wiki.write_page(request.form['name'],
 | 
					        wiki.write_page(request.form['name'],
 | 
				
			||||||
                        request.form['content'],
 | 
					                        request.form['content'],
 | 
				
			||||||
                        message=request.form['message'],
 | 
					                        message=request.form['message'],
 | 
				
			||||||
                        create=True,
 | 
					                        create=True,
 | 
				
			||||||
                                  username=g.current_user.get('username'))
 | 
					                        username=g.current_user.username)
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        cname = to_canonical(name) if name else ""
 | 
					        cname = to_canonical(name) if name else ""
 | 
				
			||||||
        if cname and g.current_wiki.get_page(cname):
 | 
					        if cname and wiki.get_page(cname):
 | 
				
			||||||
            # Page exists, edit instead
 | 
					            # Page exists, edit instead
 | 
				
			||||||
            return redirect(url_for('wiki.edit', name=cname))
 | 
					            return redirect(url_for('wiki.edit', name=cname))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -94,7 +101,7 @@ def page(name):
 | 
				
			||||||
    if cname != name:
 | 
					    if cname != name:
 | 
				
			||||||
        return redirect(url_for('wiki.page', name=cname))
 | 
					        return redirect(url_for('wiki.page', name=cname))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data = g.current_wiki.get_page(cname)
 | 
					    data = wiki.get_page(cname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if data:
 | 
					    if data:
 | 
				
			||||||
        return render_template('wiki/page.html', name=cname, page=data)
 | 
					        return render_template('wiki/page.html', name=cname, page=data)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -145,14 +145,8 @@ body {
 | 
				
			||||||
    border-radius: 2px;
 | 
					    border-radius: 2px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.navbar-nav>li.user-avatar img {
 | 
				
			||||||
.navbar-nav .user-avatar a {
 | 
					    margin-right: 3px;
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.navbar-nav>li.user-avatar a {
 | 
					 | 
				
			||||||
    padding-top: 9px;
 | 
					 | 
				
			||||||
    padding-bottom: 9px;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.floating-header {
 | 
					.floating-header {
 | 
				
			||||||
| 
						 | 
					@ -170,3 +164,40 @@ body {
 | 
				
			||||||
    /* background-image: linear-gradient(to bottom,#fff 0%,#fff 25%,rgba(255,255,255,0.9) 100%); */
 | 
					    /* background-image: linear-gradient(to bottom,#fff 0%,#fff 25%,rgba(255,255,255,0.9) 100%); */
 | 
				
			||||||
    font-size: 10px;
 | 
					    font-size: 10px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input.parsley-success,
 | 
				
			||||||
 | 
					select.parsley-success,
 | 
				
			||||||
 | 
					textarea.parsley-success {
 | 
				
			||||||
 | 
					  color: #468847;
 | 
				
			||||||
 | 
					  background-color: #DFF0D8;
 | 
				
			||||||
 | 
					  border: 1px solid #D6E9C6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input.parsley-error,
 | 
				
			||||||
 | 
					select.parsley-error,
 | 
				
			||||||
 | 
					textarea.parsley-error {
 | 
				
			||||||
 | 
					  color: #B94A48;
 | 
				
			||||||
 | 
					  background-color: #F2DEDE;
 | 
				
			||||||
 | 
					  border: 1px solid #EED3D7;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.parsley-errors-list {
 | 
				
			||||||
 | 
					  margin: 2px 0 3px 0;
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					  list-style-type: none;
 | 
				
			||||||
 | 
					  font-size: 0.9em;
 | 
				
			||||||
 | 
					  line-height: 0.9em;
 | 
				
			||||||
 | 
					  opacity: 0;
 | 
				
			||||||
 | 
					  -moz-opacity: 0;
 | 
				
			||||||
 | 
					  -webkit-opacity: 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  transition: all .3s ease-in;
 | 
				
			||||||
 | 
					  -o-transition: all .3s ease-in;
 | 
				
			||||||
 | 
					  -ms-transition: all .3s ease-in;
 | 
				
			||||||
 | 
					  -moz-transition: all .3s ease-in;
 | 
				
			||||||
 | 
					  -webkit-transition: all .3s ease-in;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.parsley-errors-list.filled {
 | 
				
			||||||
 | 
					  opacity: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,18 +1,8 @@
 | 
				
			||||||
{% extends 'layout.html' %}
 | 
					{% extends 'layout.html' %}
 | 
				
			||||||
 | 
					{% from 'macros.html' import render_form, render_field %}
 | 
				
			||||||
{% block body %}
 | 
					{% block body %}
 | 
				
			||||||
 | 
					     {% call render_form(form, action_url=url_for('auth.login'), action_text='Login', btn_class='btn btn-primary') %}
 | 
				
			||||||
    <form role="form" method="post" action="{{ url_for('auth.login') }}" data-parsley-validate>
 | 
					        {{ render_field(form.email, placeholder='Email', type='email', required=1) }}
 | 
				
			||||||
        <div class="form-group">
 | 
					        {{ render_field(form.password, placeholder='Password', type='password', required=1) }}
 | 
				
			||||||
            <label for="email">Email</label>
 | 
					    {% endcall %}
 | 
				
			||||||
            <input id="email" type="email" class="form-control" name="email" placeholder="Email" required />
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <div class="form-group">
 | 
					 | 
				
			||||||
            <label for="password">Password</label>
 | 
					 | 
				
			||||||
            <input type="password" name="password" id="password" class="form-control" placeholder="Password" min="6" required />
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <input type="submit" class="btn btn-primary" value="Login" />
 | 
					 | 
				
			||||||
    </form>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					@ -1,29 +1,13 @@
 | 
				
			||||||
{% extends 'layout.html' %}
 | 
					{% extends 'layout.html' %}
 | 
				
			||||||
 | 
					{% from 'macros.html' import render_form, render_field %}
 | 
				
			||||||
{% block body %}
 | 
					{% block body %}
 | 
				
			||||||
 | 
					     {% call render_form(form, action_url=url_for('auth.register'), action_text='Register', btn_class='btn btn-primary') %}
 | 
				
			||||||
    <form role="form" method="post" action="{{ url_for('auth.register') }}" data-parsley-validate>
 | 
					        {{ render_field(form.username, placeholder='Username', type='username', **{"required": 1, "data-parsley-type": "alphanum"}) }}
 | 
				
			||||||
        <div class="form-group">
 | 
					        {{ render_field(form.email, placeholder='Email', type='email', required=1) }}
 | 
				
			||||||
            <label for="username">Username</label>
 | 
					        {{ render_field(form.password, placeholder='Password', type='password', **{"required": 1, "data-parsley-minlength": "6"}) }}
 | 
				
			||||||
            <input id="username" type="text" class="form-control" name="username" placeholder="Username" required data-parsley-type="alphanum" />
 | 
					        {{ render_field(form.confirm, placeholder='Confirm Password', type='password', **{"required": 1, "data-parsley-minlength": "6"}) }}
 | 
				
			||||||
        </div>
 | 
					        {% if config.RECAPTCHA_ENABLE %}
 | 
				
			||||||
 | 
					            {{ render_field(form.recaptcha) }}
 | 
				
			||||||
        <div class="form-group">
 | 
					        {% endif %}
 | 
				
			||||||
            <label for="email">Email</label>
 | 
					     {% endcall %}
 | 
				
			||||||
            <input id="email" type="email" class="form-control" name="email" placeholder="Email" required />
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <div class="form-group">
 | 
					 | 
				
			||||||
            <label for="password">Password</label>
 | 
					 | 
				
			||||||
            <input type="password" name="password" id="password" class="form-control" placeholder="Password" required min="6"/>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <div class="form-group">
 | 
					 | 
				
			||||||
            <label for="password_again">Confirm Password</label>
 | 
					 | 
				
			||||||
            <input type="password" name="password_again" id="password_again" class="form-control" placeholder="Password" required min="6"/>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <input type="submit" class="btn btn-primary" value="Register" />
 | 
					 | 
				
			||||||
    </form>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										5
									
								
								realms/templates/auth/settings.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								realms/templates/auth/settings.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					{% extends 'layout.html' %}
 | 
				
			||||||
 | 
					{% from 'macros.html' import render_form, render_field %}
 | 
				
			||||||
 | 
					{% block body %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					@ -43,16 +43,16 @@
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
          </ul>
 | 
					          </ul>
 | 
				
			||||||
          <ul class="nav navbar-nav navbar-right">
 | 
					          <ul class="nav navbar-nav navbar-right">
 | 
				
			||||||
            {% if session.get('user') %}
 | 
					            {% if g.current_user.is_authenticated() %}
 | 
				
			||||||
              <li class="dropdown user-avatar">
 | 
					              <li class="dropdown user-avatar">
 | 
				
			||||||
                <a href="#" class="dropdown-toggle" data-toggle="dropdown">
 | 
					                <a href="#" class="dropdown-toggle" data-toggle="dropdown">
 | 
				
			||||||
                <span>
 | 
					                <span>
 | 
				
			||||||
                    <img src="{{ session['user'].get('avatar') }}" class="menu-avatar">
 | 
					                    <img src="{{ g.current_user.avatar }}" class="menu-avatar">
 | 
				
			||||||
                    <span>{{ session['user'].get('username') }} <i class="icon-caret-down"></i></span>
 | 
					                    <span>{{ g.current_user.username }} <i class="icon-caret-down"></i></span>
 | 
				
			||||||
                </span>
 | 
					                </span>
 | 
				
			||||||
                </a>
 | 
					                </a>
 | 
				
			||||||
                <ul class="dropdown-menu">
 | 
					                <ul class="dropdown-menu">
 | 
				
			||||||
                    <li><a href="{{ url_for('account') }}">Account</a></li>
 | 
					                    <li><a href="{{ url_for('auth.settings') }}">Settings</a></li>
 | 
				
			||||||
                    <li><a href="{{ url_for('auth.logout') }}">Logout</a></li>
 | 
					                    <li><a href="{{ url_for('auth.logout') }}">Logout</a></li>
 | 
				
			||||||
                </ul>
 | 
					                </ul>
 | 
				
			||||||
                </li>
 | 
					                </li>
 | 
				
			||||||
| 
						 | 
					@ -68,8 +68,6 @@
 | 
				
			||||||
    <!-- Page Menu -->
 | 
					    <!-- Page Menu -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <div class="container">
 | 
					    <div class="container">
 | 
				
			||||||
      <div id="main-body">
 | 
					      <div id="main-body">
 | 
				
			||||||
     {% with messages = get_flashed_messages(with_categories=True) %}
 | 
					     {% with messages = get_flashed_messages(with_categories=True) %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										99
									
								
								realms/templates/macros.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								realms/templates/macros.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,99 @@
 | 
				
			||||||
 | 
					{# Source: https://gist.github.com/bearz/7394681 #}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{# Renders field for bootstrap 3 standards.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Params:
 | 
				
			||||||
 | 
					        field - WTForm field
 | 
				
			||||||
 | 
					        kwargs - pass any arguments you want in order to put them into the html attributes.
 | 
				
			||||||
 | 
					        There are few exceptions: for - for_, class - class_, class__ - class_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Example usage:
 | 
				
			||||||
 | 
					        {{ macros.render_field(form.email, placeholder='Input email', type='email') }}
 | 
				
			||||||
 | 
					#}
 | 
				
			||||||
 | 
					{% macro render_field(field, label_visible=true) -%}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="form-group {% if field.errors %}has-error{% endif %} {{ kwargs.pop('class_', '') }}">
 | 
				
			||||||
 | 
					        {% if (field.type != 'HiddenField' or field.type !='CSRFTokenField') and label_visible %}
 | 
				
			||||||
 | 
					            <label for="{{ field.id }}" class="control-label">{{ field.label }}</label>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					        {{ field(class_='form-control', **kwargs) }}
 | 
				
			||||||
 | 
					        {% if field.errors %}
 | 
				
			||||||
 | 
					            {% for e in field.errors %}
 | 
				
			||||||
 | 
					                <p class="help-block">{{ e }}</p>
 | 
				
			||||||
 | 
					            {% endfor %}
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					{%- endmacro %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{# Renders checkbox fields since they are represented differently in bootstrap
 | 
				
			||||||
 | 
					    Params:
 | 
				
			||||||
 | 
					        field - WTForm field (there are no check, but you should put here only BooleanField.
 | 
				
			||||||
 | 
					        kwargs - pass any arguments you want in order to put them into the html attributes.
 | 
				
			||||||
 | 
					        There are few exceptions: for - for_, class - class_, class__ - class_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Example usage:
 | 
				
			||||||
 | 
					        {{ macros.render_checkbox_field(form.remember_me) }}
 | 
				
			||||||
 | 
					 #}
 | 
				
			||||||
 | 
					{% macro render_checkbox_field(field) -%}
 | 
				
			||||||
 | 
					    <div class="checkbox">
 | 
				
			||||||
 | 
					        <label>
 | 
				
			||||||
 | 
					            {{ field(type='checkbox', **kwargs) }} {{ field.label }}
 | 
				
			||||||
 | 
					        </label>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					{%- endmacro %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{# Renders radio field
 | 
				
			||||||
 | 
					    Params:
 | 
				
			||||||
 | 
					        field - WTForm field (there are no check, but you should put here only BooleanField.
 | 
				
			||||||
 | 
					        kwargs - pass any arguments you want in order to put them into the html attributes.
 | 
				
			||||||
 | 
					        There are few exceptions: for - for_, class - class_, class__ - class_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Example usage:
 | 
				
			||||||
 | 
					        {{ macros.render_radio_field(form.answers) }}
 | 
				
			||||||
 | 
					 #}
 | 
				
			||||||
 | 
					{% macro render_radio_field(field) -%}
 | 
				
			||||||
 | 
					    {% for value, label, _ in field.iter_choices() %}
 | 
				
			||||||
 | 
					        <div class="radio">
 | 
				
			||||||
 | 
					            <label>
 | 
				
			||||||
 | 
					                <input type="radio" name="{{ field.id }}" id="{{ field.id }}" value="{{ value }}">{{ label }}
 | 
				
			||||||
 | 
					            </label>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    {% endfor %}
 | 
				
			||||||
 | 
					{%- endmacro %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{# Renders WTForm in bootstrap way. There are two ways to call function:
 | 
				
			||||||
 | 
					     - as macros: it will render all field forms using cycle to iterate over them
 | 
				
			||||||
 | 
					     - as call: it will insert form fields as you specify:
 | 
				
			||||||
 | 
					     e.g. {% call macros.render_form(form, action_url=url_for('login_view'), action_text='Login',
 | 
				
			||||||
 | 
					                                        class_='login-form') %}
 | 
				
			||||||
 | 
					                {{ macros.render_field(form.email, placeholder='Input email', type='email') }}
 | 
				
			||||||
 | 
					                {{ macros.render_field(form.password, placeholder='Input password', type='password') }}
 | 
				
			||||||
 | 
					                {{ macros.render_checkbox_field(form.remember_me, type='checkbox') }}
 | 
				
			||||||
 | 
					            {% endcall %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     Params:
 | 
				
			||||||
 | 
					        form - WTForm class
 | 
				
			||||||
 | 
					        action_url - url where to submit this form
 | 
				
			||||||
 | 
					        action_text - text of submit button
 | 
				
			||||||
 | 
					        class_ - sets a class for form
 | 
				
			||||||
 | 
					    #}
 | 
				
			||||||
 | 
					{% macro render_form(form, action_url='', action_text='Submit', class_='', btn_class='btn btn-default') -%}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <form method="POST" action="{{ action_url }}" role="form" class="{{ class_ }}" data-parsley-validate>
 | 
				
			||||||
 | 
					        {{ form.hidden_tag() if form.hidden_tag }}
 | 
				
			||||||
 | 
					        {% if caller %}
 | 
				
			||||||
 | 
					            {{ caller() }}
 | 
				
			||||||
 | 
					        {% else %}
 | 
				
			||||||
 | 
					            {% for f in form %}
 | 
				
			||||||
 | 
					                {% if f.type == 'BooleanField' %}
 | 
				
			||||||
 | 
					                    {{ render_checkbox_field(f) }}
 | 
				
			||||||
 | 
					                {% elif f.type == 'RadioField' %}
 | 
				
			||||||
 | 
					                    {{ render_radio_field(f) }}
 | 
				
			||||||
 | 
					                {% else %}
 | 
				
			||||||
 | 
					                    {{ render_field(f) }}
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					            {% endfor %}
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					        <button type="submit" class="{{ btn_class }}">{{ action_text }} </button>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					{%- endmacro %}
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,10 @@
 | 
				
			||||||
bcrypt
 | 
					bcrypt
 | 
				
			||||||
Flask
 | 
					Flask
 | 
				
			||||||
Flask-Login
 | 
					 | 
				
			||||||
Flask-Assets
 | 
					Flask-Assets
 | 
				
			||||||
 | 
					Flask-Cache
 | 
				
			||||||
 | 
					Flask-Login
 | 
				
			||||||
Flask-Script
 | 
					Flask-Script
 | 
				
			||||||
 | 
					Flask-SQLAlchemy
 | 
				
			||||||
Flask-WTF
 | 
					Flask-WTF
 | 
				
			||||||
beautifulsoup4
 | 
					beautifulsoup4
 | 
				
			||||||
closure==20121212
 | 
					closure==20121212
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue