update deployment
add a bunch of boilerplate from fbone based on MIT licensed code at: https://github.com/imwilsonxu/fbone
This commit is contained in:
parent
2df2b47b4e
commit
1800193802
@ -1,7 +0,0 @@
|
|||||||
RefStack
|
|
||||||
========
|
|
||||||
|
|
||||||
Vendor-facing API for registration of interop-compliance endpoints and credentials for on-demand testing.
|
|
||||||
|
|
||||||
Running at http://refstack.org
|
|
||||||
See (living) documentation at https://etherpad.openstack.org/RefStackBlueprint
|
|
35
README.rst
Executable file
35
README.rst
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
RefStack
|
||||||
|
========
|
||||||
|
|
||||||
|
Vendor-facing API for registration of interop-compliance endpoints and credentials for on-demand testing.
|
||||||
|
|
||||||
|
Running at http://refstack.org
|
||||||
|
See (living) documentation at https://etherpad.openstack.org/RefStackBlueprint
|
||||||
|
|
||||||
|
|
||||||
|
Okay, I'm Sold, How Do I Run This Myself?
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
This is our documentation for how we get this set up::
|
||||||
|
|
||||||
|
# Git you clonin'
|
||||||
|
git clone http://github.com/openstack-ops/refstack
|
||||||
|
|
||||||
|
cd refstack
|
||||||
|
|
||||||
|
# Setup or update the database
|
||||||
|
# NOTE: you are going to have to modify the db connection string in
|
||||||
|
# `alembic.ini` to get this working
|
||||||
|
# PROTIP: if you just want to test this out, use `-n alembic_sqlite` to
|
||||||
|
# make a local sqlite db
|
||||||
|
# $ alembic -n alembic_sqlite update head
|
||||||
|
alembic update head
|
||||||
|
|
||||||
|
# Plug this bad boy into your server infrastructure.
|
||||||
|
# We use nginx and gunicorn, you may use something else if you are smarter
|
||||||
|
# than we are.
|
||||||
|
# For the most basic setup that you can try right now, just kick off
|
||||||
|
# gunicorn:
|
||||||
|
gunicorn refstack.web:app
|
||||||
|
|
||||||
|
# Now browse to http://localhost:8000
|
@ -14,6 +14,13 @@ script_location = alembic
|
|||||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||||
|
|
||||||
|
|
||||||
|
[alembic_sqlite]
|
||||||
|
# path to migration scripts
|
||||||
|
script_location = alembic
|
||||||
|
|
||||||
|
sqlalchemy.url = sqlite:///refstack.db
|
||||||
|
|
||||||
|
|
||||||
# Logging configuration
|
# Logging configuration
|
||||||
[loggers]
|
[loggers]
|
||||||
keys = root,sqlalchemy,alembic
|
keys = root,sqlalchemy,alembic
|
||||||
|
@ -24,14 +24,10 @@ sys.path.append("./")
|
|||||||
from alembic import context
|
from alembic import context
|
||||||
from sqlalchemy import engine_from_config, pool
|
from sqlalchemy import engine_from_config, pool
|
||||||
from logging.config import fileConfig
|
from logging.config import fileConfig
|
||||||
from refstack import app
|
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
# this is the Alembic Config object, which provides
|
||||||
# access to the values within the .ini file in use.
|
# access to the values within the .ini file in use.
|
||||||
config = context.config
|
config = context.config
|
||||||
cur_db_uri = config.get_section_option('alembic', 'sqlalchemy.url')
|
|
||||||
my_db_uri = 'sqlite:////tmp/refstack.db' # app.config.get('SQLALCHEMY_DATABASE_URI', cur_db_uri)
|
|
||||||
config.set_section_option('alembic', 'sqlalchemy.url', my_db_uri)
|
|
||||||
|
|
||||||
# Interpret the config file for Python logging.
|
# Interpret the config file for Python logging.
|
||||||
# This line sets up loggers basically.
|
# This line sets up loggers basically.
|
||||||
@ -45,6 +41,10 @@ fileConfig(config.config_file_name)
|
|||||||
from refstack.models import *
|
from refstack.models import *
|
||||||
target_metadata = Base.metadata
|
target_metadata = Base.metadata
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
def run_migrations_offline():
|
def run_migrations_offline():
|
||||||
"""Run migrations in 'offline' mode.
|
"""Run migrations in 'offline' mode.
|
||||||
@ -92,4 +92,3 @@ if context.is_offline_mode():
|
|||||||
run_migrations_offline()
|
run_migrations_offline()
|
||||||
else:
|
else:
|
||||||
run_migrations_online()
|
run_migrations_online()
|
||||||
|
|
||||||
|
209
refstack/app.py
Executable file → Normal file
209
refstack/app.py
Executable file → Normal file
@ -1,50 +1,181 @@
|
|||||||
#!/usr/bin/env python
|
# -*- coding: utf-8 -*-
|
||||||
#
|
|
||||||
# Copyright (c) 2013 Piston Cloud Computing, Inc.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""Common Flask app config."""
|
# This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import flask
|
from flask import Flask, request, render_template
|
||||||
|
#from flask.ext.babel import Babel
|
||||||
|
|
||||||
|
from .config import DefaultConfig
|
||||||
|
#from .user import User, user
|
||||||
|
#from .settings import settings
|
||||||
|
#from .frontend import frontend
|
||||||
|
#from .api import api
|
||||||
|
#from .admin import admin
|
||||||
|
#from .extensions import db, mail, cache, login_manager, oid
|
||||||
|
from .extensions import db, mail, login_manager, oid
|
||||||
|
from .utils import INSTANCE_FOLDER_PATH
|
||||||
|
|
||||||
|
|
||||||
#db_path = os.path.abspath(
|
# For import *
|
||||||
# os.path.join(os.path.basename(__file__), "../"))
|
__all__ = ['create_app']
|
||||||
|
|
||||||
db_path = 'tmp'
|
DEFAULT_BLUEPRINTS = tuple()
|
||||||
|
# frontend,
|
||||||
|
# user,
|
||||||
|
# settings,
|
||||||
|
# api,
|
||||||
|
# admin,
|
||||||
|
#)
|
||||||
|
|
||||||
|
|
||||||
app = flask.Flask(__name__)
|
def create_app(config=None, app_name=None, blueprints=None):
|
||||||
|
"""Create a Flask app."""
|
||||||
|
|
||||||
|
if app_name is None:
|
||||||
|
app_name = DefaultConfig.PROJECT
|
||||||
|
if blueprints is None:
|
||||||
|
blueprints = DEFAULT_BLUEPRINTS
|
||||||
|
|
||||||
|
app = Flask(app_name,
|
||||||
|
instance_path=INSTANCE_FOLDER_PATH,
|
||||||
|
instance_relative_config=True)
|
||||||
|
configure_app(app, config)
|
||||||
|
configure_hook(app)
|
||||||
|
configure_blueprints(app, blueprints)
|
||||||
|
# NOTE(termie): commented out until we switch the web config to this
|
||||||
|
#configure_extensions(app)
|
||||||
|
configure_logging(app)
|
||||||
|
configure_template_filters(app)
|
||||||
|
configure_error_handlers(app)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
app.config['MAILGUN_KEY'] = '#@#@#@#@'
|
def configure_app(app, config=None):
|
||||||
app.config['MAILGUN_DOMAIN'] = 'refstack.org'
|
"""Different ways of configurations."""
|
||||||
app.config['SECRET_KEY'] = '#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@'
|
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get(
|
# http://flask.pocoo.org/docs/api/#configuration
|
||||||
'DATABASE_URL', 'sqlite:///%s/refstack.db' % (db_path))
|
app.config.from_object(DefaultConfig)
|
||||||
app.config['DEBUG'] = True
|
|
||||||
app.config['SECURITY_PASSWORD_HASH'] = 'sha512_crypt'
|
# http://flask.pocoo.org/docs/config/#instance-folders
|
||||||
app.config['SECURITY_PASSWORD_SALT'] = app.config['SECRET_KEY']
|
app.config.from_pyfile('production.cfg', silent=True)
|
||||||
app.config['SECURITY_POST_LOGIN_VIEW'] = 'dashboard'
|
|
||||||
app.config['SECURITY_RECOVERABLE'] = True
|
if config:
|
||||||
app.config['SECURITY_REGISTERABLE'] = True
|
app.config.from_object(config)
|
||||||
app.config['SECURITY_EMAIL_SENDER'] = "refstack.org"
|
|
||||||
app.config['MAIL_SERVER'] = 'smtp.refstack.org'
|
# Use instance folder instead of env variables to make deployment easier.
|
||||||
app.config['MAIL_PORT'] = 465
|
#app.config.from_envvar('%s_APP_CONFIG' % DefaultConfig.PROJECT.upper(), silent=True)
|
||||||
app.config['MAIL_USE_SSL'] = True
|
|
||||||
app.config['MAIL_USERNAME'] = 'postmaster@refstack.org'
|
|
||||||
app.config['MAIL_PASSWORD'] = '1234'
|
def configure_extensions(app):
|
||||||
|
# flask-sqlalchemy
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
# flask-mail
|
||||||
|
mail.init_app(app)
|
||||||
|
|
||||||
|
# flask-cache
|
||||||
|
cache.init_app(app)
|
||||||
|
|
||||||
|
# flask-babel
|
||||||
|
babel = Babel(app)
|
||||||
|
|
||||||
|
@babel.localeselector
|
||||||
|
def get_locale():
|
||||||
|
accept_languages = app.config.get('ACCEPT_LANGUAGES')
|
||||||
|
return request.accept_languages.best_match(accept_languages)
|
||||||
|
|
||||||
|
# flask-login
|
||||||
|
login_manager.login_view = 'frontend.login'
|
||||||
|
login_manager.refresh_view = 'frontend.reauth'
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(id):
|
||||||
|
return User.query.get(id)
|
||||||
|
login_manager.setup_app(app)
|
||||||
|
|
||||||
|
# flask-openid
|
||||||
|
oid.init_app(app)
|
||||||
|
|
||||||
|
|
||||||
|
def configure_blueprints(app, blueprints):
|
||||||
|
"""Configure blueprints in views."""
|
||||||
|
|
||||||
|
for blueprint in blueprints:
|
||||||
|
app.register_blueprint(blueprint)
|
||||||
|
|
||||||
|
|
||||||
|
def configure_template_filters(app):
|
||||||
|
|
||||||
|
@app.template_filter()
|
||||||
|
def pretty_date(value):
|
||||||
|
return pretty_date(value)
|
||||||
|
|
||||||
|
@app.template_filter()
|
||||||
|
def format_date(value, format='%Y-%m-%d'):
|
||||||
|
return value.strftime(format)
|
||||||
|
|
||||||
|
|
||||||
|
def configure_logging(app):
|
||||||
|
"""Configure file(info) and email(error) logging."""
|
||||||
|
|
||||||
|
if app.debug or app.testing:
|
||||||
|
# Skip debug and test mode. Just check standard output.
|
||||||
|
return
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from logging.handlers import SMTPHandler
|
||||||
|
|
||||||
|
# Set info level on logger, which might be overwritten by handers.
|
||||||
|
# Suppress DEBUG messages.
|
||||||
|
app.logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
info_log = os.path.join(app.config['LOG_FOLDER'], 'info.log')
|
||||||
|
info_file_handler = logging.handlers.RotatingFileHandler(info_log, maxBytes=100000, backupCount=10)
|
||||||
|
info_file_handler.setLevel(logging.INFO)
|
||||||
|
info_file_handler.setFormatter(logging.Formatter(
|
||||||
|
'%(asctime)s %(levelname)s: %(message)s '
|
||||||
|
'[in %(pathname)s:%(lineno)d]')
|
||||||
|
)
|
||||||
|
app.logger.addHandler(info_file_handler)
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
#app.logger.info("testing info.")
|
||||||
|
#app.logger.warn("testing warn.")
|
||||||
|
#app.logger.error("testing error.")
|
||||||
|
|
||||||
|
mail_handler = SMTPHandler(app.config['MAIL_SERVER'],
|
||||||
|
app.config['MAIL_USERNAME'],
|
||||||
|
app.config['ADMINS'],
|
||||||
|
'O_ops... %s failed!' % app.config['PROJECT'],
|
||||||
|
(app.config['MAIL_USERNAME'],
|
||||||
|
app.config['MAIL_PASSWORD']))
|
||||||
|
mail_handler.setLevel(logging.ERROR)
|
||||||
|
mail_handler.setFormatter(logging.Formatter(
|
||||||
|
'%(asctime)s %(levelname)s: %(message)s '
|
||||||
|
'[in %(pathname)s:%(lineno)d]')
|
||||||
|
)
|
||||||
|
app.logger.addHandler(mail_handler)
|
||||||
|
|
||||||
|
|
||||||
|
def configure_hook(app):
|
||||||
|
@app.before_request
|
||||||
|
def before_request():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def configure_error_handlers(app):
|
||||||
|
|
||||||
|
@app.errorhandler(403)
|
||||||
|
def forbidden_page(error):
|
||||||
|
return render_template("errors/forbidden_page.html"), 403
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def page_not_found(error):
|
||||||
|
return render_template("errors/page_not_found.html"), 404
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def server_error_page(error):
|
||||||
|
return render_template("errors/server_error.html"), 500
|
||||||
|
78
refstack/config.py
Normal file
78
refstack/config.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from utils import make_dir, INSTANCE_FOLDER_PATH
|
||||||
|
|
||||||
|
|
||||||
|
class BaseConfig(object):
|
||||||
|
|
||||||
|
PROJECT = "fbone"
|
||||||
|
|
||||||
|
# Get app root path, also can use flask.root_path.
|
||||||
|
# ../../config.py
|
||||||
|
PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
DEBUG = False
|
||||||
|
TESTING = False
|
||||||
|
|
||||||
|
ADMINS = ['youremail@yourdomain.com']
|
||||||
|
|
||||||
|
# http://flask.pocoo.org/docs/quickstart/#sessions
|
||||||
|
SECRET_KEY = 'secret key'
|
||||||
|
|
||||||
|
LOG_FOLDER = os.path.join(INSTANCE_FOLDER_PATH, 'logs')
|
||||||
|
make_dir(LOG_FOLDER)
|
||||||
|
|
||||||
|
# Fild upload, should override in production.
|
||||||
|
# Limited the maximum allowed payload to 16 megabytes.
|
||||||
|
# http://flask.pocoo.org/docs/patterns/fileuploads/#improving-uploads
|
||||||
|
MAX_CONTENT_LENGTH = 16 * 1024 * 1024
|
||||||
|
UPLOAD_FOLDER = os.path.join(INSTANCE_FOLDER_PATH, 'uploads')
|
||||||
|
make_dir(UPLOAD_FOLDER)
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultConfig(BaseConfig):
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
# Flask-Sqlalchemy: http://packages.python.org/Flask-SQLAlchemy/config.html
|
||||||
|
SQLALCHEMY_ECHO = True
|
||||||
|
# SQLITE for prototyping.
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + INSTANCE_FOLDER_PATH + '/db.sqlite'
|
||||||
|
# MYSQL for production.
|
||||||
|
#SQLALCHEMY_DATABASE_URI = 'mysql://username:password@server/db?charset=utf8'
|
||||||
|
|
||||||
|
# Flask-babel: http://pythonhosted.org/Flask-Babel/
|
||||||
|
ACCEPT_LANGUAGES = ['zh']
|
||||||
|
BABEL_DEFAULT_LOCALE = 'en'
|
||||||
|
|
||||||
|
# Flask-cache: http://pythonhosted.org/Flask-Cache/
|
||||||
|
CACHE_TYPE = 'simple'
|
||||||
|
CACHE_DEFAULT_TIMEOUT = 60
|
||||||
|
|
||||||
|
# Flask-mail: http://pythonhosted.org/flask-mail/
|
||||||
|
# https://bitbucket.org/danjac/flask-mail/issue/3/problem-with-gmails-smtp-server
|
||||||
|
MAIL_DEBUG = DEBUG
|
||||||
|
MAIL_SERVER = 'smtp.gmail.com'
|
||||||
|
MAIL_PORT = 587
|
||||||
|
MAIL_USE_TLS = True
|
||||||
|
MAIL_USE_SSL = False
|
||||||
|
# Should put MAIL_USERNAME and MAIL_PASSWORD in production under instance folder.
|
||||||
|
MAIL_USERNAME = 'yourmail@gmail.com'
|
||||||
|
MAIL_PASSWORD = 'yourpass'
|
||||||
|
MAIL_DEFAULT_SENDER = MAIL_USERNAME
|
||||||
|
|
||||||
|
# Flask-openid: http://pythonhosted.org/Flask-OpenID/
|
||||||
|
OPENID_FS_STORE_PATH = os.path.join(INSTANCE_FOLDER_PATH, 'openid')
|
||||||
|
make_dir(OPENID_FS_STORE_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
class TestConfig(BaseConfig):
|
||||||
|
TESTING = True
|
||||||
|
WTF_CSRF_ENABLED = False
|
||||||
|
|
||||||
|
SQLALCHEMY_ECHO = False
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'sqlite://'
|
17
refstack/decorators.py
Normal file
17
refstack/decorators.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#-*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from flask import abort
|
||||||
|
from flask.ext.login import current_user
|
||||||
|
|
||||||
|
|
||||||
|
def admin_required(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if not current_user.is_admin():
|
||||||
|
abort(403)
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return decorated_function
|
47
refstack/default_settings.py
Normal file
47
refstack/default_settings.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2013 Piston Cloud Computing, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
import os
|
||||||
|
|
||||||
|
###############################################################
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# THIS FILE IS NOT USED
|
||||||
|
# (leaveing it around briefly for reference)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
###############################################################
|
||||||
|
|
||||||
|
db_path = '/tmp'
|
||||||
|
|
||||||
|
|
||||||
|
class Default(object):
|
||||||
|
MAILGUN_KEY = '#@#@#@#@'
|
||||||
|
MAILGUN_DOMAIN = 'refstack.org'
|
||||||
|
SECRET_KEY = '#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@'
|
||||||
|
SQLALCHEMY_DATABASE_URI = os.environ.get(
|
||||||
|
'DATABASE_URL', 'sqlite:///%s/refstack.db' % (db_path))
|
||||||
|
DEBUG = True
|
||||||
|
SECURITY_PASSWORD_HASH = 'sha512_crypt'
|
||||||
|
SECURITY_PASSWORD_SALT = SECRET_KEY
|
||||||
|
SECURITY_POST_LOGIN_VIEW = 'dashboard'
|
||||||
|
SECURITY_RECOVERABLE = True
|
||||||
|
SECURITY_REGISTERABLE = True
|
||||||
|
SECURITY_EMAIL_SENDER = "refstack.org"
|
||||||
|
MAIL_SERVER = 'smtp.refstack.org'
|
||||||
|
MAIL_PORT = 465
|
||||||
|
MAIL_USE_SSL = True
|
||||||
|
MAIL_USERNAME = 'postmaster@refstack.org'
|
||||||
|
MAIL_PASSWORD = '1234'
|
19
refstack/extensions.py
Normal file
19
refstack/extensions.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone
|
||||||
|
|
||||||
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
from flask.ext.mail import Mail
|
||||||
|
mail = Mail()
|
||||||
|
|
||||||
|
# TODO(termie): not used yet
|
||||||
|
#from flask.ext.cache import Cache
|
||||||
|
#cache = Cache()
|
||||||
|
|
||||||
|
from flask.ext.login import LoginManager
|
||||||
|
login_manager = LoginManager()
|
||||||
|
|
||||||
|
from flask.ext.openid import OpenID
|
||||||
|
oid = OpenID()
|
106
refstack/utils.py
Normal file
106
refstack/utils.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone
|
||||||
|
|
||||||
|
"""
|
||||||
|
Utils has nothing to do with models and views.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
import os
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
# Instance folder path, make it independent.
|
||||||
|
INSTANCE_FOLDER_PATH = os.path.join('/tmp', 'instance')
|
||||||
|
|
||||||
|
ALLOWED_AVATAR_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])
|
||||||
|
|
||||||
|
# Form validation
|
||||||
|
|
||||||
|
USERNAME_LEN_MIN = 4
|
||||||
|
USERNAME_LEN_MAX = 25
|
||||||
|
|
||||||
|
REALNAME_LEN_MIN = 4
|
||||||
|
REALNAME_LEN_MAX = 25
|
||||||
|
|
||||||
|
PASSWORD_LEN_MIN = 6
|
||||||
|
PASSWORD_LEN_MAX = 16
|
||||||
|
|
||||||
|
AGE_MIN = 1
|
||||||
|
AGE_MAX = 300
|
||||||
|
|
||||||
|
DEPOSIT_MIN = 0.00
|
||||||
|
DEPOSIT_MAX = 9999999999.99
|
||||||
|
|
||||||
|
# Sex type.
|
||||||
|
MALE = 1
|
||||||
|
FEMALE = 2
|
||||||
|
OTHER = 9
|
||||||
|
SEX_TYPE = {
|
||||||
|
MALE: u'Male',
|
||||||
|
FEMALE: u'Female',
|
||||||
|
OTHER: u'Other',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Model
|
||||||
|
STRING_LEN = 64
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_time():
|
||||||
|
return datetime.utcnow()
|
||||||
|
|
||||||
|
|
||||||
|
def pretty_date(dt, default=None):
|
||||||
|
"""
|
||||||
|
Returns string representing "time since" e.g.
|
||||||
|
3 days ago, 5 hours ago etc.
|
||||||
|
Ref: https://bitbucket.org/danjac/newsmeme/src/a281babb9ca3/newsmeme/
|
||||||
|
"""
|
||||||
|
|
||||||
|
if default is None:
|
||||||
|
default = 'just now'
|
||||||
|
|
||||||
|
now = datetime.utcnow()
|
||||||
|
diff = now - dt
|
||||||
|
|
||||||
|
periods = (
|
||||||
|
(diff.days / 365, 'year', 'years'),
|
||||||
|
(diff.days / 30, 'month', 'months'),
|
||||||
|
(diff.days / 7, 'week', 'weeks'),
|
||||||
|
(diff.days, 'day', 'days'),
|
||||||
|
(diff.seconds / 3600, 'hour', 'hours'),
|
||||||
|
(diff.seconds / 60, 'minute', 'minutes'),
|
||||||
|
(diff.seconds, 'second', 'seconds'),
|
||||||
|
)
|
||||||
|
|
||||||
|
for period, singular, plural in periods:
|
||||||
|
|
||||||
|
if not period:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if period == 1:
|
||||||
|
return u'%d %s ago' % (period, singular)
|
||||||
|
else:
|
||||||
|
return u'%d %s ago' % (period, plural)
|
||||||
|
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def allowed_file(filename):
|
||||||
|
return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_AVATAR_EXTENSIONS
|
||||||
|
|
||||||
|
|
||||||
|
def id_generator(size=10, chars=string.ascii_letters + string.digits):
|
||||||
|
#return base64.urlsafe_b64encode(os.urandom(size))
|
||||||
|
return ''.join(random.choice(chars) for x in range(size))
|
||||||
|
|
||||||
|
|
||||||
|
def make_dir(dir_path):
|
||||||
|
try:
|
||||||
|
if not os.path.exists(dir_path):
|
||||||
|
os.mkdir(dir_path)
|
||||||
|
except Exception, e:
|
||||||
|
raise e
|
@ -13,6 +13,10 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import flask
|
||||||
from flask import Flask, abort, flash, request, redirect, url_for, \
|
from flask import Flask, abort, flash, request, redirect, url_for, \
|
||||||
render_template, g, session
|
render_template, g, session
|
||||||
from flask_openid import OpenID
|
from flask_openid import OpenID
|
||||||
@ -24,11 +28,20 @@ from wtforms import Form, BooleanField, TextField, \
|
|||||||
PasswordField, validators
|
PasswordField, validators
|
||||||
from flask_mail import Mail
|
from flask_mail import Mail
|
||||||
|
|
||||||
from refstack.app import app
|
from refstack import app as base_app
|
||||||
|
from refstack import utils
|
||||||
from refstack.models import *
|
from refstack.models import *
|
||||||
|
|
||||||
mail = Mail(app)
|
|
||||||
|
|
||||||
|
# TODO(termie): temporary hack for first-run experience
|
||||||
|
utils.make_dir(utils.INSTANCE_FOLDER_PATH)
|
||||||
|
|
||||||
|
# TODO(termie): transition all the routes below to blueprints
|
||||||
|
# TODO(termie): use extensions setup from the create_app() call
|
||||||
|
|
||||||
|
app = base_app.create_app()
|
||||||
|
|
||||||
|
mail = Mail(app)
|
||||||
# setup flask-openid
|
# setup flask-openid
|
||||||
oid = OpenID(app)
|
oid = OpenID(app)
|
||||||
admin = Admin(app, base_template='admin/master.html')
|
admin = Admin(app, base_template='admin/master.html')
|
||||||
@ -270,6 +283,3 @@ def logout():
|
|||||||
session.pop('openid', None)
|
session.pop('openid', None)
|
||||||
flash(u'You have been signed out')
|
flash(u'You have been signed out')
|
||||||
return redirect(oid.get_next_url())
|
return redirect(oid.get_next_url())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user