From 97d224cdc34528861b0c7e898a34ae6a66facc55 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 25 Jun 2013 10:52:57 -0700 Subject: [PATCH] initial version --- .gitignore | 3 + README | 38 +++++++++++ alembic.ini | 36 +++++++++++ alembic/env.py | 66 ++++++++++++++++++++ alembic/script.py.mako | 21 +++++++ alembic/versions/35b593d48d6a_user_models.py | 40 ++++++++++++ basic_app/__init__.py | 33 ++++++++++ basic_app/auth/__init__.py | 5 ++ basic_app/auth/models.py | 23 +++++++ basic_app/auth/views.py | 8 +++ basic_app/config.py | 4 ++ manage.py | 3 + requirements.txt | 3 + 13 files changed, 283 insertions(+) create mode 100644 .gitignore create mode 100644 README create mode 100644 alembic.ini create mode 100644 alembic/env.py create mode 100644 alembic/script.py.mako create mode 100644 alembic/versions/35b593d48d6a_user_models.py create mode 100644 basic_app/__init__.py create mode 100644 basic_app/auth/__init__.py create mode 100644 basic_app/auth/models.py create mode 100644 basic_app/auth/views.py create mode 100644 basic_app/config.py create mode 100755 manage.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..41bc898 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +app.db +.idea diff --git a/README b/README new file mode 100644 index 0000000..14dad57 --- /dev/null +++ b/README @@ -0,0 +1,38 @@ +Basic Flask +=========== + +This sample project shows how I organize a project to use Flask, +Flask-SQLAlchemy, and Alembic together. It demonstrates the application +factory pattern and is organized using blueprints. + +Demonstrating migration generation +---------------------------------- + +I used Alembic to autogenerate a migration and then tweaked the results. If you +want to see autogeneration in action, just delete +`alembic/versions/35b593d48d6a_user_models.py`, then run +`alembic revision --autogenerate -m "user models"`. + +Set up the database +------------------- + +Change `basic_app.config.SQLALCHEMY_DATABASE_URI` to what you want. By default +it points to a sqlite file called `app.db` in the project folder. + +Then run `alembic upgrade head` to set up the database through migrations. + +Or, permform the following from a Python shell. + + In [1]: from basic_app import create_app, db + In [2]: create_app().app_context().push() + In [3]: db.create_all() + +Then run `alembic stamp head` to mark migrations as current. + +Origin +------ + +Created in answer to this question on /r/flask and StackOverflow: + +* [\[AF\] Has anyone ever gotten Alembic to work with Flask Blueprints? \[SQLAlchemy\]](http://www.reddit.com/r/flask/comments/1h1k5g/af_has_anyone_ever_gotten_alembic_to_work_with/) +* [Alembic autogenerates empty Flask-SQLAlchemy migrations](http://stackoverflow.com/questions/17201800/alembic-autogenerates-empty-flask-sqlalchemy-migrations) diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000..0c4babf --- /dev/null +++ b/alembic.ini @@ -0,0 +1,36 @@ +[alembic] +script_location = alembic + +[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 diff --git a/alembic/env.py b/alembic/env.py new file mode 100644 index 0000000..a774dba --- /dev/null +++ b/alembic/env.py @@ -0,0 +1,66 @@ +from logging.config import fileConfig +import os +import sys +from alembic import context +from sqlalchemy import engine_from_config, pool + +sys.path.append(os.getcwd()) + +from basic_app import db, create_app + +config = context.config + +fileConfig(config.config_file_name) + +app = create_app() +config.set_main_option("sqlalchemy.url", app.config["SQLALCHEMY_DATABASE_URI"]) + +target_metadata = db.metadata + + +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) + + 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. + + """ + + engine = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool + ) + connection = engine.connect() + context.configure(connection=connection, target_metadata=target_metadata) + + try: + with context.begin_transaction(): + context.run_migrations() + finally: + connection.close() + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/alembic/script.py.mako b/alembic/script.py.mako new file mode 100644 index 0000000..2681882 --- /dev/null +++ b/alembic/script.py.mako @@ -0,0 +1,21 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision} +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)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/alembic/versions/35b593d48d6a_user_models.py b/alembic/versions/35b593d48d6a_user_models.py new file mode 100644 index 0000000..b9fb9c2 --- /dev/null +++ b/alembic/versions/35b593d48d6a_user_models.py @@ -0,0 +1,40 @@ +"""user models + +Revision ID: 35b593d48d6a +Revises: None +Create Date: 2013-06-25 10:10:53.866281 +""" + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql import expression + +# revision identifiers, used by Alembic. +revision = "35b593d48d6a" +down_revision = None + + +def upgrade(): + op.create_table("group", + sa.Column("id", sa.Integer, primary_key=True), + sa.Column("name", sa.String(100), nullable=False, unique=True, index=True) + ) + op.create_table("user", + sa.Column("id", sa.Integer), + sa.Column("username", sa.String(254), nullable=False, unique=True, index=True), + sa.Column("password", sa.String(60)), + sa.Column("name", sa.String(254), nullable=False, server_default=""), + sa.Column("email", sa.String(254), nullable=False, server_default=""), + sa.Column("active", sa.Boolean, nullable=False, server_default=expression.true()), + sa.PrimaryKeyConstraint("id") + ) + op.create_table("user_group", + sa.Column("user_id", sa.Integer, sa.ForeignKey("user.id"), primary_key=True), + sa.Column("group_id", sa.Integer, sa.ForeignKey("group.id"), primary_key=True), + ) + + +def downgrade(): + op.drop_table("user_group") + op.drop_table("user") + op.drop_table("group") diff --git a/basic_app/__init__.py b/basic_app/__init__.py new file mode 100644 index 0000000..424dccf --- /dev/null +++ b/basic_app/__init__.py @@ -0,0 +1,33 @@ +from flask import Flask +from flask.ext.script import Manager +from flask.ext.sqlalchemy import SQLAlchemy +from basic_app import config + +db = SQLAlchemy() + + +def load_models(): + from basic_app.auth import models + +load_models() + + +def init_extensions(app): + db.init_app(app) + + +def init_views(app): + from basic_app import auth + + app.register_blueprint(auth.bp, url_prefix="/auth") + + +def create_app(config=config): + app = Flask(__name__) + app.config.from_object(config) + + init_extensions(app) + + return app + +manager = Manager(create_app) diff --git a/basic_app/auth/__init__.py b/basic_app/auth/__init__.py new file mode 100644 index 0000000..6a8ae5f --- /dev/null +++ b/basic_app/auth/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +bp = Blueprint("auth", __name__, template_folder="templates") + +from basic_app.auth import views diff --git a/basic_app/auth/models.py b/basic_app/auth/models.py new file mode 100644 index 0000000..9f4078a --- /dev/null +++ b/basic_app/auth/models.py @@ -0,0 +1,23 @@ +from sqlalchemy.sql import expression +from basic_app import db + + +class Group(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False, unique=True, index=True) + + +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(254), nullable=False, unique=True, index=True) + password = db.Column(db.String(60)) + name = db.Column(db.String(254), nullable=False, default="", server_default="") + email = db.Column(db.String(254), nullable=False, default="", server_default="") + active = db.Column(db.Boolean, nullable=False, default=False, server_default=expression.true()) + + groups = db.relationship(Group, lambda: user_group, backref=db.backref("users")) + +user_group = db.Table("user_group", + db.Column("user_id", db.Integer, db.ForeignKey(User.id), primary_key=True), + db.Column("group_id", db.Integer, db.ForeignKey(Group.id), primary_key=True) +) diff --git a/basic_app/auth/views.py b/basic_app/auth/views.py new file mode 100644 index 0000000..c5feb9f --- /dev/null +++ b/basic_app/auth/views.py @@ -0,0 +1,8 @@ +from basic_app.auth import bp +from basic_app.auth.models import User + + +@bp.route("/") +def hello(username): + u = User.query.filter_by(username=username).first_or_404() + return "Hello, {}!".format(u.username) diff --git a/basic_app/config.py b/basic_app/config.py new file mode 100644 index 0000000..0f98174 --- /dev/null +++ b/basic_app/config.py @@ -0,0 +1,4 @@ +DEBUG = True +SECRET_KEY = "dev" + +SQLALCHEMY_DATABASE_URI = "sqlite:///app.db" diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..d72b831 --- /dev/null +++ b/manage.py @@ -0,0 +1,3 @@ +from basic_app import manager + +manager.run() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..97d38f5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Flask-Script +Flask-SQLAlchemy +alembic