Boilerplate
commit
0477400a74
|
@ -0,0 +1,2 @@
|
|||
*.pyc
|
||||
.ropeproject
|
|
@ -0,0 +1,30 @@
|
|||
FROM alpine:3.11.3@sha256:ddba4d27a7ffc3f86dd6c2f92041af252a1f23a8e742c90e6e1297bfa1bc0c45
|
||||
EXPOSE 5000
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN apk add --no-cache \
|
||||
uwsgi-python3 \
|
||||
python3 \
|
||||
libpq git
|
||||
|
||||
# psycopg2 needs some extra build tools and headers. Install them and build in a
|
||||
# single step in order not to pollute Docker layers
|
||||
RUN apk add --no-cache --virtual .build-deps gcc python3-dev musl-dev postgresql-dev libffi-dev && \
|
||||
pip3 install --no-cache-dir psycopg2==2.8.4 pycparser==2.20 cffi==1.14.0 bcrypt==3.1.7 && \
|
||||
apk del --no-cache .build-deps
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip3 install --no-cache-dir -r requirements.txt
|
||||
|
||||
ENV FLASK_APP formity
|
||||
ENV FLASK_ENV production
|
||||
|
||||
COPY . .
|
||||
|
||||
STOPSIGNAL SIGINT
|
||||
CMD flask db upgrade && exec uwsgi --http-socket 0.0.0.0:5000 \
|
||||
--processes 4 \
|
||||
--uid uwsgi \
|
||||
--plugins python3 \
|
||||
--wsgi formity.wsgi:application \
|
||||
--touch-reload formity/wsgi.py
|
|
@ -0,0 +1,19 @@
|
|||
version: "3"
|
||||
services:
|
||||
postgres:
|
||||
# postgres:9.6.17-alpine
|
||||
image: postgres@sha256:c48c87e19b1c9bdc9d1de8a0f53fa1c7f91f887ecc06d0c2efd3f3425090b6c0
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=secret
|
||||
|
||||
backend:
|
||||
build: .
|
||||
ports:
|
||||
- 5000:5000
|
||||
volumes:
|
||||
- .:/usr/src/app
|
||||
|
||||
volumes:
|
||||
pgdata:
|
|
@ -0,0 +1,24 @@
|
|||
import flask
|
||||
from formity.extensions import db, migrate, admin
|
||||
|
||||
def create_app():
|
||||
app = flask.Flask(
|
||||
__name__,
|
||||
template_folder='../templates',
|
||||
static_folder='../static',
|
||||
)
|
||||
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.config.from_object('formity.settings.%s' % app.env)
|
||||
|
||||
db.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
admin.init_app(app)
|
||||
|
||||
import formity.admin
|
||||
import formity.views
|
||||
import formity.models
|
||||
|
||||
app.register_blueprint(formity.views.bp)
|
||||
|
||||
return app
|
|
@ -0,0 +1,28 @@
|
|||
from flask import current_app
|
||||
import flask_sqlalchemy
|
||||
import flask_migrate
|
||||
import flask_admin
|
||||
|
||||
from flask_admin.contrib.sqla import ModelView as BaseModelView
|
||||
from flask_login import current_user
|
||||
|
||||
|
||||
class SecurityMixin:
|
||||
def is_accessible(self):
|
||||
return current_user.is_authenticated
|
||||
|
||||
def inaccessible_callback(self, name, **kwargs):
|
||||
# redirect to login page if user doesn't have access
|
||||
return current_app.login_manager.unauthorized()
|
||||
|
||||
class IndexView(SecurityMixin, flask_admin.AdminIndexView):
|
||||
pass
|
||||
|
||||
|
||||
class ModelView(SecurityMixin, BaseModelView):
|
||||
pass
|
||||
|
||||
|
||||
db = flask_sqlalchemy.SQLAlchemy()
|
||||
migrate = flask_migrate.Migrate()
|
||||
admin = flask_admin.Admin(template_mode='bootstrap3', index_view=IndexView())
|
|
@ -0,0 +1 @@
|
|||
from formity.extensions import db
|
|
@ -0,0 +1,8 @@
|
|||
from environs import Env
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
FLASK_ADMIN_FLUID_LAYOUT = True
|
||||
|
||||
SECRET_KEY = env.str('SECRET_KEY', default='randomstring')
|
||||
SQLALCHEMY_DATABASE_URI = env.str('DATABASE_URI', default='postgresql+psycopg2://postgres:secret@postgres')
|
|
@ -0,0 +1,49 @@
|
|||
import re
|
||||
import unicodedata
|
||||
from sqlalchemy.orm import exc
|
||||
from werkzeug.exceptions import abort
|
||||
|
||||
|
||||
def get_object_or_404(model, *criterion):
|
||||
try:
|
||||
rv = model.query.filter(*criterion).one()
|
||||
except (exc.NoResultFound, exc.MultipleResultsFound):
|
||||
abort(404)
|
||||
else:
|
||||
return rv
|
||||
|
||||
|
||||
def strip_accents(text):
|
||||
"""
|
||||
Strip accents from input String.
|
||||
|
||||
:param text: The input string.
|
||||
:type text: String.
|
||||
|
||||
:returns: The processed String.
|
||||
:rtype: String.
|
||||
"""
|
||||
try:
|
||||
text = unicode(text, 'utf-8')
|
||||
except (TypeError, NameError): # unicode is a default on python 3
|
||||
pass
|
||||
text = unicodedata.normalize('NFD', text)
|
||||
text = text.encode('ascii', 'ignore')
|
||||
text = text.decode("utf-8")
|
||||
return str(text)
|
||||
|
||||
|
||||
def text_to_id(text):
|
||||
"""
|
||||
Convert input text to id.
|
||||
|
||||
:param text: The input string.
|
||||
:type text: String.
|
||||
|
||||
:returns: The processed String.
|
||||
:rtype: String.
|
||||
"""
|
||||
text = strip_accents(text.lower())
|
||||
text = re.sub('[ ]+', '_', text)
|
||||
text = re.sub('[^0-9a-zA-Z_-]', '', text)
|
||||
return text
|
|
@ -0,0 +1,3 @@
|
|||
from flask import Blueprint
|
||||
|
||||
bp = Blueprint('main', __name__)
|
|
@ -0,0 +1,3 @@
|
|||
import formity
|
||||
|
||||
application = formity.create_app()
|
|
@ -0,0 +1 @@
|
|||
Generic single-database configuration.
|
|
@ -0,0 +1,45 @@
|
|||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
|
@ -0,0 +1,96 @@
|
|||
from __future__ import with_statement
|
||||
|
||||
import logging
|
||||
from logging.config import fileConfig
|
||||
|
||||
from sqlalchemy import engine_from_config
|
||||
from sqlalchemy import pool
|
||||
|
||||
from alembic import context
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
fileConfig(config.config_file_name)
|
||||
logger = logging.getLogger('alembic.env')
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
from flask import current_app
|
||||
config.set_main_option(
|
||||
'sqlalchemy.url',
|
||||
str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%'))
|
||||
target_metadata = current_app.extensions['migrate'].db.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():
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url, target_metadata=target_metadata, literal_binds=True
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
|
||||
# this callback is used to prevent an auto-migration from being generated
|
||||
# when there are no changes to the schema
|
||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||
def process_revision_directives(context, revision, directives):
|
||||
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||
script = directives[0]
|
||||
if script.upgrade_ops.is_empty():
|
||||
directives[:] = []
|
||||
logger.info('No changes in schema detected.')
|
||||
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section),
|
||||
prefix='sqlalchemy.',
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
process_revision_directives=process_revision_directives,
|
||||
**current_app.extensions['migrate'].configure_args
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
|
@ -0,0 +1,24 @@
|
|||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else "pass"}
|
|
@ -0,0 +1,30 @@
|
|||
alembic==1.4.2
|
||||
blinker==1.4
|
||||
certifi==2019.11.28
|
||||
chardet==3.0.4
|
||||
click==7.1.1
|
||||
environs==7.3.1
|
||||
Flask==1.1.1
|
||||
Flask-Admin==1.5.5
|
||||
Flask-Login==0.5.0
|
||||
Flask-Migrate==2.5.3
|
||||
Flask-OAuthlib==0.9.5
|
||||
git+https://code.hackerspace.pl/informatic/flask-spaceauth#egg=Flask-SpaceAuth
|
||||
Flask-SQLAlchemy==2.4.1
|
||||
idna==2.9
|
||||
itsdangerous==1.1.0
|
||||
Jinja2==2.11.1
|
||||
Mako==1.1.2
|
||||
MarkupSafe==1.1.1
|
||||
marshmallow==3.5.1
|
||||
oauthlib==2.1.0
|
||||
python-dateutil==2.8.1
|
||||
python-dotenv==0.12.0
|
||||
python-editor==1.0.4
|
||||
requests==2.23.0
|
||||
requests-oauthlib==1.3.0
|
||||
six==1.14.0
|
||||
SQLAlchemy==1.3.15
|
||||
urllib3==1.25.8
|
||||
Werkzeug==1.0.0
|
||||
WTForms==2.2.1
|
Loading…
Reference in New Issue