Merge pull request #5 from termie/api

add basic api
This commit is contained in:
David Lenwell 2013-11-26 17:26:57 -08:00
commit 377916670a
11 changed files with 198 additions and 43 deletions

View File

@ -38,8 +38,8 @@ fileConfig(config.config_file_name)
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
# target_metadata = None
from refstack.models import *
target_metadata = Base.metadata
from refstack.models import db
target_metadata = db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:

View File

@ -0,0 +1,31 @@
"""empty message
Revision ID: 449461dbc725
Revises: 59e15d864941
Create Date: 2013-11-26 16:57:16.062788
"""
# revision identifiers, used by Alembic.
revision = '449461dbc725'
down_revision = '59e15d864941'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table('apikey',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=60), nullable=True),
sa.Column('key', sa.String(length=200), nullable=True),
sa.Column('openid', sa.String(length=200), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('timestamp', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id'),
)
def downgrade():
op.drop_Table('apikey')

28
refstack/admin.py Normal file
View File

@ -0,0 +1,28 @@
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(app):
admin.add_view(SecureView(models.ApiKey, db.session))
admin.add_view(SecureView(models.Cloud, db.session))
admin.add_view(SecureView(models.User, db.session))
admin.add_view(SecureView(models.Vendor, db.session))

68
refstack/api.py Normal file
View File

@ -0,0 +1,68 @@
"""Basic API code.
This is using Flask-Restless at the moment because it is super simple,
but probably should be moved to a more versatile framework like
Flask-Restful later on.
"""
import flask
from flask.ext import restless
from refstack import models
from refstack.extensions import api
def init_app(app, *args, **kw):
api.init_app(app, *args, **kw)
def access_control(**kw):
if not flask.g.user:
raise _not_authorized()
if not flask.g.user.su:
return _not_authorized()
# That's it, we're defaulting to superuser only access
# until we flesh this out further
ALL_METHODS = {'GET_SINGLE': [access_control],
'GET_MANY': [access_control],
'PUT_SINGLE': [access_control],
'PUT_MANY': [access_control],
'POST': [access_control],
'DELETE': [access_control]}
def configure_api(app):
cloud_api = api.create_api_blueprint(models.Cloud,
preprocessors=ALL_METHODS)
cloud_api.before_request(authenticate)
app.register_blueprint(cloud_api)
def _not_authorized():
return restless.ProcessingException(message='Not Authorized',
status_code=401)
def authenticate():
# If we're already authenticated, we can ignore this
if flask.g.user:
return
# Otherwise check headers
openid = flask.request.headers.get('X-AUTH-OPENID')
if openid:
# In debug mode accept anything
if flask.current_app.debug and False:
flask.g.user = models.User.query.filter_by(openid=openid).first()
return
apikey = flask.request.headers.get('X-AUTH-APIKEY')
apikey_ref = models.ApiKey.query.filter_by(key=apikey)
if apikey_ref['openid'] == openid:
flask.g.user = apikey_ref.user

View File

@ -14,7 +14,11 @@ from .config import DefaultConfig
#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 refstack import admin
from refstack import api
from .extensions import db
from .extensions import oid
from refstack import utils
@ -89,6 +93,14 @@ def configure_extensions(app):
# flask-sqlalchemy
db.init_app(app)
# flask-admin
admin.init_app(app)
admin.configure_admin(app)
# flask-restless
api.init_app(app, flask_sqlalchemy_db=db)
api.configure_api(app)
## flask-mail
#mail.init_app(app)
@ -112,8 +124,8 @@ def configure_extensions(app):
# return User.query.get(id)
#login_manager.setup_app(app)
## flask-openid
#oid.init_app(app)
# flask-openid
oid.init_app(app)
def configure_blueprints(app, blueprints):

View File

@ -2,6 +2,12 @@
# This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone
from flask.ext.admin import Admin
admin = Admin()
from flask.ext.restless import APIManager
api = APIManager()
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy()

View File

@ -41,6 +41,20 @@ class User(db.Model):
def __str__(self):
return self.name
class ApiKey(db.Model):
__tablename__ = 'apikey'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60))
key = db.Column(db.String(200))
openid = db.Column(db.String(200))
timestamp = db.Column(db.DateTime, default=datetime.now)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship('User',
backref=db.backref('apikeys', lazy='dynamic'))
"""
Note: The vendor list will be pre-populated from the sponsoring company database.
TODO: better define the vendor object and its relationship with user

View File

@ -5,19 +5,19 @@
<script src="{{ url_for('static', filename='jquery-1.10.1.min.js') }}"></script>
<script src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"></script>
<!-- refstack specific js-->
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" />
<link rel="stylesheet" type="text/css" href="/static/toast.css">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='refstack.css') }}">
<!-- compiled and minified bootstrap JavaScript -->
{% block head_css %}{% endblock %}
</head>
<body>
<body>
<div class="container">
<div class="header">
<div class="header">
<a href="/" class="logo">
<span class="glyphicon glyphicon-check"></span>
Refstack
@ -32,7 +32,7 @@
<li class="dropdown">
<span class="glyphicon glyphicon-cog"></span>
<a data-toggle="dropdown" href="#">
Admin
<span class="caret"></span>
</a>
@ -46,7 +46,7 @@
<li >
<a href="/admin/userview/">Users</a>
</li>
</ul>
</li>
<li>
@ -96,4 +96,4 @@
{% block tail_js %}{% endblock %}
<script src="{{ url_for('static', filename='refstack.js') }}"></script>
</body>
</html>
</html>

View File

@ -29,8 +29,16 @@ from wtforms import Form, BooleanField, TextField, \
from flask_mail import Mail
from refstack import app as base_app
from refstack import utils
from refstack.models import *
from refstack.extensions import db
from refstack.extensions import oid
from refstack import api
from refstack.models import ApiKey
from refstack.models import Cloud
from refstack.models import Test
from refstack.models import TestResults
from refstack.models import TestStatus
from refstack.models import User
from refstack.models import Vendor
# TODO(termie): transition all the routes below to blueprints
@ -39,27 +47,14 @@ from refstack.models import *
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
def before_request():
"""Runs before the request itself."""
g.user = None
flask.g.user = None
if 'openid' in session:
g.user = User.query.filter_by(openid=session['openid']).first()
flask.g.user = User.query.filter_by(openid=session['openid']).first()
@app.route('/', methods=['POST', 'GET'])
@ -122,8 +117,8 @@ def create_profile():
flash(u'Error: you have to enter a valid email address')
else:
flash(u'Profile successfully created')
db.add(User(name, email, session['openid']))
db.commit()
db.session.add(User(name, email, session['openid']))
db.session.commit()
return redirect(oid.get_next_url())
return render_template(
'create_profile.html', next_url=oid.get_next_url())
@ -139,8 +134,8 @@ def delete_cloud(cloud_id):
elif not c.user_id == g.user.id:
flash(u"This isn't your cloud!")
else:
db.delete(c)
db.commit()
db.session.delete(c)
db.session.commit()
return redirect('/')
@ -180,7 +175,7 @@ def edit_cloud(cloud_id):
c.admin_user = request.form['admin_user']
c.admin_key = request.form['admin_key']
db.commit()
db.session.commit()
flash(u'Cloud Saved!')
return redirect('/')
@ -230,8 +225,8 @@ def create_cloud():
c.admin_user = request.form['admin_user']
c.admin_key = request.form['admin_key']
db.add(c)
db.commit()
db.session.add(c)
db.session.commit()
return redirect('/')
return render_template('create_cloud.html', next_url='/')
@ -245,8 +240,8 @@ def edit_profile():
form = dict(name=g.user.name, email=g.user.email)
if request.method == 'POST':
if 'delete' in request.form:
db.delete(g.user)
db.commit()
db.session.delete(g.user)
db.session.commit()
session['openid'] = None
flash(u'Profile deleted')
return redirect(url_for('index'))
@ -260,7 +255,7 @@ def edit_profile():
flash(u'Profile successfully created')
g.user.name = form['name']
g.user.email = form['email']
db.commit()
db.session.commit()
return redirect(url_for('edit_profile'))
return render_template('edit_profile.html', form=form)

View File

@ -1,13 +1,14 @@
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
Flask-Restless=0.12.0
SQLAlchemy==0.8.3
WTForms==1.0.4
Werkzeug==0.8.3
alembic==0.5.0