diff --git a/README.rst b/README.rst index 87f262d..83fb8c8 100755 --- a/README.rst +++ b/README.rst @@ -32,4 +32,14 @@ This is our documentation for how we get this set up:: # gunicorn: gunicorn refstack.web:app + # To actually configure this winner, check out the config section and + # crack open refstack.cfg in vim. + # `vim refstack.cfg` + # Now browse to http://localhost:8000 + + +Configuration +------------- + +Coming soon! diff --git a/alembic.ini b/alembic.ini index d26c492..80fbbde 100755 --- a/alembic.ini +++ b/alembic.ini @@ -18,7 +18,7 @@ sqlalchemy.url = driver://user:pass@localhost/dbname # path to migration scripts script_location = alembic -sqlalchemy.url = sqlite:///refstack.db +sqlalchemy.url = sqlite:///db.sqlite # Logging configuration diff --git a/refstack/admin.py b/refstack/admin.py new file mode 100644 index 0000000..79955e8 --- /dev/null +++ b/refstack/admin.py @@ -0,0 +1,27 @@ + +import flask +from flask.ext.admin.contrib import sqla + +from refstack import models + +# Global admin object +from .extensions import admin +from .extensions import db + + +class SecureView(sqla.ModelView): + def is_accessible(self): + # let us look at the admin if we're in debug mode + if flask.current_app.debug: + return True + return flask.g.user.su is not False + + +def init_app(app): + admin.init_app(app) + + +def configure_admin(): + admin.add_view(SecureView(models.Cloud, db.session)) + admin.add_view(SecureView(models.User, db.session)) + admin.add_view(SecureView(models.Vendor, db.session)) diff --git a/refstack/app.py b/refstack/app.py index fbdcfb3..f15a761 100644 --- a/refstack/app.py +++ b/refstack/app.py @@ -14,8 +14,15 @@ #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 +from refstack import admin +from .extensions import db +from .extensions import oid + + +from refstack import utils + + +INSTANCE_FOLDER_PATH = utils.INSTANCE_FOLDER_PATH # For import * @@ -38,18 +45,28 @@ def create_app(config=None, app_name=None, blueprints=None): if blueprints is None: blueprints = DEFAULT_BLUEPRINTS + # NOTE(termie): Flask has this new instance_path stuff that allows + # you to keep config and such in different places, but I don't really + # see how that is going to be very helpful, so we're going to stick + # to using config relative to the root unless we explicitly set such + # a path in the INSTANCE_FOLDER_PATH environment variable. 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_extensions(app) configure_logging(app) configure_template_filters(app) configure_error_handlers(app) + if app.debug: + print utils.dump_config(app) + return app @@ -59,8 +76,10 @@ def configure_app(app, config=None): # http://flask.pocoo.org/docs/api/#configuration app.config.from_object(DefaultConfig) - # http://flask.pocoo.org/docs/config/#instance-folders - app.config.from_pyfile('production.cfg', silent=True) + # If we've set the INSTANCE_FOLDER_PATH environment var, this may be + # loaded from an instance folder, otherwise relative to flask.root_path. + # http://flask.pocoo.org/docs/config/#instance-folders + app.config.from_pyfile('refstack.cfg', silent=True) if config: app.config.from_object(config) @@ -73,28 +92,32 @@ def configure_extensions(app): # flask-sqlalchemy db.init_app(app) - # flask-mail - mail.init_app(app) + # flask-admin + admin.init_app(app) + admin.configure_admin() + + ## flask-mail + #mail.init_app(app) - # flask-cache - cache.init_app(app) + ## flask-cache + #cache.init_app(app) - # flask-babel - babel = Babel(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) + #@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' + ## 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) + #@login_manager.user_loader + #def load_user(id): + # return User.query.get(id) + #login_manager.setup_app(app) # flask-openid oid.init_app(app) @@ -128,7 +151,7 @@ def configure_logging(app): import logging from logging.handlers import SMTPHandler - # Set info level on logger, which might be overwritten by handers. + # Set info level on logger, which might be overwritten by handlers. # Suppress DEBUG messages. app.logger.setLevel(logging.INFO) diff --git a/refstack/config.py b/refstack/config.py index 168efb4..b4487eb 100644 --- a/refstack/config.py +++ b/refstack/config.py @@ -4,16 +4,15 @@ import os -from utils import make_dir, INSTANCE_FOLDER_PATH +from utils import make_dir, INSTANCE_FOLDER_PATH, PROJECT_ROOT class BaseConfig(object): - PROJECT = "fbone" + PROJECT = "refstack" - # Get app root path, also can use flask.root_path. - # ../../config.py - PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + # The app root path, also can use flask.root_path. + PROJECT_ROOT = PROJECT_ROOT DEBUG = False TESTING = False diff --git a/refstack/extensions.py b/refstack/extensions.py index e83656c..6b36240 100644 --- a/refstack/extensions.py +++ b/refstack/extensions.py @@ -2,6 +2,9 @@ # This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone +from flask.ext.admin import Admin +admin = Admin() + from flask.ext.sqlalchemy import SQLAlchemy db = SQLAlchemy() diff --git a/refstack/models.py b/refstack/models.py index 3b0f20b..79bf662 100755 --- a/refstack/models.py +++ b/refstack/models.py @@ -13,44 +13,25 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -"""striaght up sqlalchemy declarative_base model structure. - *I created this because i was having a problem getting - the cli to use the models that were generated for the flask - webapp. The plan is to use this for both. But I have not - started my serious efforts on the web interface. dl 10.2013 - - *For now in dev I have this database in /tmp we can talk - about someplace else for it by default. -""" from datetime import datetime -from sqlalchemy import create_engine -from sqlalchemy.orm import scoped_session, sessionmaker,relationship, backref -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Binary, Boolean -engine = create_engine('sqlite:////tmp/refstack.db', convert_unicode=True) -db = scoped_session(sessionmaker(autocommit=False, - autoflush=False, - bind=engine)) +from .extensions import db -Base = declarative_base() -Base.query = db.query_property() - -class User(Base): +class User(db.Model): __tablename__ = 'user' - id = Column(Integer, primary_key=True) - vendor_id = Column(Integer, ForeignKey('vendor.id')) - vendor = relationship('Vendor', - backref=backref('clouds', + id = db.Column(db.Integer, primary_key=True) + vendor_id = db.Column(db.Integer, db.ForeignKey('vendor.id')) + vendor = db.relationship('Vendor', + backref=db.backref('clouds', lazy='dynamic')) - name = Column(String(60)) - email = Column(String(200), unique=True) - email_verified = Column(Boolean) - openid = Column(String(200), unique=True) - authorized = Column(Boolean, default=False) - su = Column(Boolean, default=False) + name = db.Column(db.String(60)) + email = db.Column(db.String(200), unique=True) + email_verified = db.Column(db.Boolean) + openid = db.Column(db.String(200), unique=True) + authorized = db.Column(db.Boolean, default=False) + su = db.Column(db.Boolean, default=False) def __init__(self, name, email, openid): self.name = name @@ -60,64 +41,63 @@ def __init__(self, name, email, openid): def __str__(self): return self.name - """ Note: The vendor list will be pre-populated from the sponsoring company database. TODO: better define the vendor object and its relationship with user it needs the ability to facilitate a login. """ -class Vendor(Base): +class Vendor(db.Model): __tablename__ = 'vendor' - id = Column(Integer, primary_key=True) - vendor_name = Column(String(80), unique=True) - contact_email = Column(String(120), unique=True) - contact_name = Column(String(120), unique=False) + id = db.Column(db.Integer, primary_key=True) + vendor_name = db.Column(db.String(80), unique=True) + contact_email = db.Column(db.String(120), unique=True) + contact_name = db.Column(db.String(120), unique=False) def __str__(self): return self.vendor_name -class Cloud(Base): +class Cloud(db.Model): """*need to take the time to descibe this stuff in detail. """ __tablename__ = 'cloud' - id = Column(Integer, primary_key=True) + id = db.Column(db.Integer, primary_key=True) - label = Column(String(60), unique=False) - endpoint = Column(String(120), unique=True) - test_user = Column(String(80), unique=False) - test_key = Column(String(80), unique=False) - admin_endpoint = Column(String(120), unique=False) - admin_user = Column(String(80), unique=False) - admin_key = Column(String(80), unique=False) + label = db.Column(db.String(60), unique=False) + endpoint = db.Column(db.String(120), unique=True) + test_user = db.Column(db.String(80), unique=False) + test_key = db.Column(db.String(80), unique=False) + admin_endpoint = db.Column(db.String(120), unique=False) + admin_user = db.Column(db.String(80), unique=False) + admin_key = db.Column(db.String(80), unique=False) - user_id = Column(Integer, ForeignKey('user.id')) - user = relationship('User', - backref=backref('clouds',lazy='dynamic')) + user_id = db.Column(db.Integer, db.ForeignKey('user.id')) + user = db.relationship('User', + backref=db.backref('clouds',lazy='dynamic')) -class Test(Base): +class Test(db.Model): __tablename__ = 'test' - id = Column(Integer, primary_key=True) - cloud_id = Column(Integer, ForeignKey('cloud.id')) - cloud = relationship('Cloud', - backref=backref('tests',lazy='dynamic')) - config = Column(String(4096)) + id = db.Column(db.Integer, primary_key=True) + cloud_id = db.Column(db.Integer, db.ForeignKey('cloud.id')) + cloud = db.relationship('Cloud', + backref=db.backref('tests',lazy='dynamic')) + config = db.Column(db.String(4096)) def __init__(self, cloud_id): self.cloud_id = cloud_id -class TestStatus(Base): +class TestStatus(db.Model): __tablename__ = 'test_status' - id = Column(Integer, primary_key=True) - test_id = Column(Integer, ForeignKey('test.id')) - test = relationship('Test', - backref=backref('status',lazy='dynamic')) - message = Column(String(1024)) - finished = Column(Boolean, default=False) - timestamp = Column(DateTime, default=datetime.now) + id = db.Column(db.Integer, primary_key=True) + test_id = db.Column(db.Integer, db.ForeignKey('test.id')) + test = db.relationship('Test', + backref=db.backref('status',lazy='dynamic')) + message = db.Column(db.String(1024)) + finished = db.Column(db.Boolean, default=False) + timestamp = db.Column(db.DateTime, default=datetime.now) def __init__(self,test_id, message, finished=False): @@ -126,14 +106,14 @@ def __init__(self,test_id, message, finished=False): self.finished = finished -class TestResults(Base): +class TestResults(db.Model): __tablename__ = 'test_results' - id = Column(Integer, primary_key=True) - test_id = Column(Integer, ForeignKey('test.id')) - test = relationship('Test', - backref=backref('results',lazy='dynamic')) - timestamp = Column(DateTime, default=datetime.now) - subunit = Column(String(8192)) - blob = Column(Binary) + id = db.Column(db.Integer, primary_key=True) + test_id = db.Column(db.Integer, db.ForeignKey('test.id')) + test = db.relationship('Test', + backref=db.backref('results',lazy='dynamic')) + timestamp = db.Column(db.DateTime, default=datetime.now) + subunit = db.Column(db.String(8192)) + blob = db.Column(db.Binary) diff --git a/refstack/schema.sql b/refstack/schema.sql deleted file mode 100755 index 4aaa01b..0000000 --- a/refstack/schema.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE TABLE IF NOT EXISTS VENDORS - (vendor_id integer primary key asc autoincrement, - vendor_name TEXT NOT NULL); diff --git a/refstack/templates/admin/master.html b/refstack/templates/admin/master_legacy.html similarity index 100% rename from refstack/templates/admin/master.html rename to refstack/templates/admin/master_legacy.html diff --git a/refstack/utils.py b/refstack/utils.py index 5def7aa..45be994 100644 --- a/refstack/utils.py +++ b/refstack/utils.py @@ -6,15 +6,17 @@ Utils has nothing to do with models and views. """ -import string -import random -import os - from datetime import datetime +import logging +import os +import pprint +import random +import string -# Instance folder path, make it independent. -INSTANCE_FOLDER_PATH = os.path.join('/tmp', 'instance') +# Instance folder path, if set, otherwise project root. +PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) +INSTANCE_FOLDER_PATH = os.environ.get('INSTANCE_FOLDER_PATH', PROJECT_ROOT) ALLOWED_AVATAR_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif']) @@ -104,3 +106,11 @@ def make_dir(dir_path): os.mkdir(dir_path) except Exception, e: raise e + + +### Begin Non-Fbone stuff + + +def dump_config(app): + """Useful to dump app config for debug purposes.""" + return pprint.pformat(dict(app.config.iteritems())) diff --git a/refstack/web.py b/refstack/web.py index 675ab69..09dff0d 100755 --- a/refstack/web.py +++ b/refstack/web.py @@ -30,31 +30,20 @@ from refstack import app as base_app from refstack import utils -from refstack.models import * +from refstack.extensions import oid +from refstack.models import Cloud +from refstack.models import Test +from refstack.models import TestResults +from refstack.models import TestStatus +from refstack.models import Vendor -# 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 -oid = OpenID(app) -admin = Admin(app, base_template='admin/master.html') - - -class SecureView(ModelView): - def is_accessible(self): - return g.user.su is not False - - -admin.add_view(SecureView(Vendor, db)) -admin.add_view(SecureView(Cloud, db)) -admin.add_view(SecureView(User, db)) @app.before_request diff --git a/requirements.txt b/requirements.txt index 5c640a5..e7a9770 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,13 @@ -Flask==0.9 -Flask-Admin==1.0.6 +Flask==0.10.1 +Flask-Admin==1.0.7 Flask-Login==0.1.3 Flask-Mail==0.8.2 Flask-OpenID==1.1.1 Flask-Principal==0.3.5 -Flask-SQLAlchemy==0.16 +Flask-SQLAlchemy==1.0 Flask-Security==1.6.3 Flask-WTF==0.8.3 -SQLAlchemy==0.8.1 +SQLAlchemy==0.8.3 WTForms==1.0.4 Werkzeug==0.8.3 alembic==0.5.0