Initial commit

This commit is contained in:
2018-03-31 18:26:40 +02:00
commit 94f0009144
54 changed files with 9069 additions and 0 deletions

76
flaskbase/__init__.py Normal file
View File

@@ -0,0 +1,76 @@
import flask
import logging
import sys
import os
from flaskbase.extensions import db
from flaskbase.middleware import MethodRewriteMiddleware
_initializers = set()
def import_submodules(app, submodule):
ret = list()
for mod_name in app.config['ENABLED_MODULES']:
try:
# FIXME support for mod_name directing to submodule
mod = __import__('%s.%s' % (mod_name, submodule))
ret.append((mod_name, getattr(mod, submodule)))
except ImportError:
# Check if error occured right in that try clause
if sys.exc_info()[2].tb_next:
logging.warning('Import failed for %s.%s', mod_name, submodule,
exc_info=True)
return ret
def create_app(settings_object=None, settings={}):
app = flask.Flask(
__name__,
template_folder='../templates',
static_folder='../static'
)
if 'SETTINGS_MODULE' in os.environ:
app.config.from_envvar('SETTINGS_MODULE')
else:
if not settings_object:
settings_object = 'flaskbase.settings.{}'.format(
os.environ.get('ENV', 'production'))
app.config.from_object(settings_object)
app.config.update(settings)
# FIXME https://github.com/mattupstate/flask-social/issues/34
app.wsgi_app = MethodRewriteMiddleware(app.wsgi_app)
for m_name, m in import_submodules(app, 'extensions'):
m.init_app(app)
import_submodules(app, 'models')
for m_name, m in import_submodules(app, 'views'):
app.register_blueprint(
m.blueprint,
url_prefix=app.config['URLS'].get(m_name, '/' + m.blueprint.name),
)
import_submodules(app, 'admin')
for fun in _initializers:
fun(app)
logging.info('Starting...')
return app
def initializer(f):
_initializers.add(f)
return f
def init_db(app):
db.create_all()

26
flaskbase/admin.py Normal file
View File

@@ -0,0 +1,26 @@
from flask_admin.contrib.sqla import ModelView as _ModelView
from flask_admin import BaseView as _BaseView
from flask_security import current_user
from flask_principal import RoleNeed, Permission
# FIXME admin homepage is always accessible
class AdminProtect(object):
roles = []
def is_accessible(self):
return current_user.is_authenticated() and \
(current_user.is_superuser or
(self.roles and
Permission(*[RoleNeed(r) for r in self.roles]).can()))
class ModelView(AdminProtect, _ModelView):
def __init__(self, model, *args, **kwargs):
kwargs.setdefault('endpoint', model.__name__.lower() + '_model')
return super(ModelView, self).__init__(model, *args, **kwargs)
named_filter_urls = True
class BaseView(AdminProtect, _BaseView):
pass

52
flaskbase/extensions.py Normal file
View File

@@ -0,0 +1,52 @@
import logging
import logging.config
logging.basicConfig(level=logging.INFO)
from flask import url_for, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_security import Security, SQLAlchemyUserDatastore
from flask_social import Social
from flask_social.datastore import SQLAlchemyConnectionDatastore
from flask_mail import Mail
from flask_migrate import Migrate
from flask_admin import Admin
from flask_wtf.csrf import CsrfProtect
from flask.ext.markdown import Markdown
from flask_pagedown import PageDown
db = SQLAlchemy()
security = Security()
social = Social()
mail = Mail()
migrate = Migrate()
admin = Admin(template_mode='bootstrap3')
def init_app(app):
db.init_app(app)
mail.init_app(app)
admin.init_app(app)
migrate.init_app(app, db)
@app.context_processor
def utility_processor():
return {
'static': lambda fn: url_for('static', filename=fn)
}
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
if app.config.get('LOGGING'):
logging.config.dictConfig(app.config['LOGGING'])
from auth.models import User, Role, Connection
security.init_app(app, SQLAlchemyUserDatastore(db, User, Role))
social._state = social.init_app(
app, SQLAlchemyConnectionDatastore(db, Connection))
CsrfProtect(app)
Markdown(app)
PageDown(app)

8
flaskbase/factories.py Normal file
View File

@@ -0,0 +1,8 @@
from factory.alchemy import SQLAlchemyModelFactory
from flaskbase.extensions import db
class BaseFactory(SQLAlchemyModelFactory):
class Meta:
abstract = True
sqlalchemy_session = db.session

13
flaskbase/forms.py Normal file
View File

@@ -0,0 +1,13 @@
from flask_wtf import Form
from wtforms_alchemy import model_form_factory
# The variable db here is a SQLAlchemy object instance from
# Flask-SQLAlchemy package
from flaskbase.extensions import db
BaseModelForm = model_form_factory(Form)
class ModelForm(BaseModelForm):
@classmethod
def get_session(self):
return db.session

16
flaskbase/middleware.py Normal file
View File

@@ -0,0 +1,16 @@
from werkzeug import url_decode
class MethodRewriteMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
if 'METHOD_OVERRIDE' in environ.get('QUERY_STRING', ''):
args = url_decode(environ['QUERY_STRING'])
method = args.get('__METHOD_OVERRIDE__')
if method:
method = method.encode('ascii', 'replace')
environ['REQUEST_METHOD'] = method
return self.app(environ, start_response)

View File

View File

@@ -0,0 +1,76 @@
import os.path
PROJECT_NAME = 'flaskbase'.capitalize()
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
DEBUG = True
SECRET_KEY = 'nothing'
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, 'db.sqlite3')
SQLALCHEMY_TRACK_MODIFICATIONS = True
ENABLED_MODULES = [
'flaskbase',
'auth',
'flatpages',
]
URLS = {
'flaskbase': '',
'flatpages': '/p',
}
SECURITY_REGISTERABLE = True
SECURITY_RECOVERABLE = True
SECURITY_CHANGEABLE = True
SECURITY_CONFIRMABLE = True
SECURITY_TRACKABLE = True
SECURITY_URL_PREFIX = '/auth'
SECURITY_PASSWORD_HASH = 'bcrypt'
SECURITY_PASSWORD_SALT = SECRET_KEY
SECURITY_EMAIL_SUBJECT_REGISTER = 'Welcome to ' + PROJECT_NAME
SECURITY_INVITE_WITHIN = '60 days'
MAIL_SUPPRESS_SEND = DEBUG
# Logging setup
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
},
},
'handlers': {
'default': {
'level': 'INFO',
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': os.path.join(BASE_DIR, 'logs', 'app.log'),
'formatter': 'standard',
'when': 'midnight',
},
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'standard'
},
},
'loggers': {
'': {
'handlers': ['default', 'console'],
'level': 'INFO',
'propagate': True
},
'django.request': {
'handlers': ['default'],
'level': 'WARN',
'propagate': False
},
}
}

View File

@@ -0,0 +1,21 @@
from flaskbase.settings.base import *
#SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://postgres:postgres@postgres'
SQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/flaskbase.db'
SOCIAL_FACEBOOK = {
'consumer_key': 'APP-ID',
'consumer_secret': 'APP-SECRET',
'request_token_params': {
'scope': 'email,user_friends',
}
}
from flask_mail import email_dispatched
def log_message(message, app):
app.logger.debug('subject:%s', message.subject)
app.logger.debug('body:%s', message.body)
email_dispatched.connect(log_message)

View File

@@ -0,0 +1,4 @@
from flaskbase.settings.base import *
TESTING = True
SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://postgres:postgres@postgres/testing'

27
flaskbase/tests.py Normal file
View File

@@ -0,0 +1,27 @@
import unittest
import flaskbase
import contextlib
from flaskbase.extensions import db
class TestCase(unittest.TestCase):
"""
Base testcase class
"""
def setUp(self):
self.flask_app = flaskbase.create_app(settings={
'SQLALCHEMY_DATABASE_URI': 'sqlite://',
})
self.app = self.flask_app.test_client()
flaskbase.init_db(self.flask_app)
def tearDown(self):
db.session.remove()
with contextlib.closing(db.engine.connect()) as con:
trans = con.begin()
for table in reversed(db.Model.metadata.sorted_tables):
con.execute(table.delete())
trans.commit()

9
flaskbase/utils.py Normal file
View File

@@ -0,0 +1,9 @@
from sqlalchemy.orm import exc
from werkzeug.exceptions import abort
def get_object_or_404(model, *criterion):
try:
return model.query.filter(*criterion).one()
except exc.NoResultFound, exc.MultipleResultsFound:
abort(404)

8
flaskbase/views.py Normal file
View File

@@ -0,0 +1,8 @@
from flask import Blueprint, render_template
blueprint = Blueprint('core', __name__)
@blueprint.route('/')
def index():
return render_template('index.html')

3
flaskbase/wsgi.py Normal file
View File

@@ -0,0 +1,3 @@
from flaskbase import create_app
application = create_app()