diff --git a/README.rst b/README.rst index 5174f92..6e07ca1 100644 --- a/README.rst +++ b/README.rst @@ -16,6 +16,7 @@ Features - A simple ``manage.py`` script. - CSS and JS minification using Flask-Assets - Easily switch between development and production environments through the MYFLASKAPP_ENV system variable. +- Utilizes best practices: `Blueprints `_ and `Application Factory `_ patterns Screenshots ----------- @@ -42,9 +43,10 @@ Inspiration ----------- - `Building Websites in Python with Flask `_ +- `Getting Bigger with Flask `_ - `Structuring Flask Apps `_ -- `Flask-Foundation `_ -- `flask-basic-registration `_ +- `Flask-Foundation `_ by `@JackStouffer `_ +- `flask-basic-registration `_ by `@mjhea0 `_ - `Flask Official Documentation `_ diff --git a/{{cookiecutter.repo_name}}/manage.py b/{{cookiecutter.repo_name}}/manage.py index a9b19b5..4fe3a76 100644 --- a/{{cookiecutter.repo_name}}/manage.py +++ b/{{cookiecutter.repo_name}}/manage.py @@ -1,9 +1,16 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- +import os import sys import subprocess from flask.ext.script import Manager, Shell, Server -from {{ cookiecutter.repo_name }} import models -from {{ cookiecutter.repo_name }}.main import app, db +from {{cookiecutter.repo_name }} import models +from {{cookiecutter.repo_name }}.app import create_app +from {{cookiecutter.repo_name}}.models import db + +env = os.environ.get("{{cookiecutter.repo_name | upper }}_ENV", 'prod') +app = create_app("{{cookiecutter.repo_name}}.settings.{0}Config" + .format(env.capitalize()), env) manager = Manager(app) TEST_CMD = "nosetests" diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/app.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/app.py index 88947a0..f83f439 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/app.py +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/app.py @@ -6,19 +6,31 @@ from flask.ext.assets import Environment from webassets.loaders import PythonLoader from {{cookiecutter.repo_name}} import assets +from {{cookiecutter.repo_name}}.models import db -app = Flask(__name__) -# The environment variable, either 'prod' or 'dev' -env = os.environ.get("{{cookiecutter.repo_name | upper}}_ENV", "prod") -# Use the appropriate environment-specific settings -app.config.from_object('{{cookiecutter.repo_name}}.settings.{env}Config' - .format(env=env.capitalize())) -app.config['ENV'] = env -db = SQLAlchemy(app) - -# Register asset bundles assets_env = Environment() -assets_env.init_app(app) -assets_loader = PythonLoader(assets) -for name, bundle in assets_loader.load_bundles().iteritems(): - assets_env.register(name, bundle) + +def create_app(config_object, env): + '''An application factory, as explained here: + http://flask.pocoo.org/docs/patterns/appfactories/ + + :param config_object: The configuration object to use. + :param env: A string, the current environment. Either "dev" or "prod" + ''' + app = Flask(__name__) + app.config.from_object('{{cookiecutter.repo_name}}.settings.{env}Config' + .format(env=env.capitalize())) + app.config['ENV'] = env + # Initialize SQLAlchemy + db.init_app(app) + # Register asset bundles + assets_env.init_app(app) + assets_loader = PythonLoader(assets) + for name, bundle in assets_loader.load_bundles().iteritems(): + assets_env.register(name, bundle) + # Register blueprints + from {{cookiecutter.repo_name}}.modules import public, member + app.register_blueprint(public.blueprint) + app.register_blueprint(member.blueprint) + + return app diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/main.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/main.py index 153bad0..5652835 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/main.py +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/main.py @@ -4,11 +4,12 @@ Entry point for all things, to avoid circular imports. """ import os -from .app import app, db, assets_env -from .models import * -from .views import * - +from .app import create_app +from .models import User, db +import {{cookiecutter.repo_name}}.modules as modules if __name__ == '__main__': - port = int(os.environ.get('PORT', 5000)) - app.run(host='0.0.0.0', port=port) + # Get the environment setting from the system environment variable + env = os.environ.get("{{cookiecutter.repo_name | upper}}_ENV", "prod") + app = create_app("{{cookiecutter.repo_name}}.settings.{env}Config" + .format(env=env.capitalize()), env) diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/models.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/models.py index fa200d2..fe93bd5 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/models.py +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/models.py @@ -3,7 +3,9 @@ """ {{cookiecutter.project_name}} models. """ -from .app import db +from flask.ext.sqlalchemy import SQLAlchemy + +db = SQLAlchemy() class User(db.Model): @@ -22,5 +24,3 @@ class User(db.Model): def __repr__(self): return ''.format(username=self.username) - -db.create_all() diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/modules/__init__.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/modules/__init__.py new file mode 100644 index 0000000..33bdd6a --- /dev/null +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/modules/__init__.py @@ -0,0 +1 @@ +'''Blueprint modules for {{cookiecutter.repo_name}}.''' diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/modules/member.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/modules/member.py new file mode 100644 index 0000000..e732f6e --- /dev/null +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/modules/member.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +'''Members-only module, typically including the app itself. +''' +from flask import Blueprint, render_template +from {{cookiecutter.repo_name}}.utils import login_required + +blueprint = Blueprint('member', __name__, + static_folder="../static", + template_folder="../templates") + +@blueprint.route("/members/") +@login_required +def members(): + return render_template("members.html") diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/views.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/modules/public.py similarity index 54% rename from {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/views.py rename to {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/modules/public.py index 6ed5208..d69025d 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/views.py +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/modules/public.py @@ -1,29 +1,20 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals -from flask import render_template, session, request, flash, redirect, url_for -from functools import wraps -from .app import app, db -from .models import User -from .forms import RegisterForm, LoginForm +'''Public section, including homepage and signup.''' +from flask import (Blueprint, request, render_template, flash, url_for, + redirect, session) from sqlalchemy.exc import IntegrityError -def flash_errors(form): - for field, errors in form.errors.items(): - for error in errors: - flash("Error in the {0} field - {1}" - .format(getattr(form, field).label.text, error), 'error') +from {{cookiecutter.repo_name}}.models import User +from {{cookiecutter.repo_name}}.forms import RegisterForm, LoginForm +from {{cookiecutter.repo_name}}.utils import flash_errors +from {{cookiecutter.repo_name}}.models import db + +blueprint = Blueprint('public', __name__, + static_folder="../static", + template_folder="../templates") -def login_required(test): - @wraps(test) - def wrap(*args, **kwargs): - if 'logged_in' in session: - return test(*args, **kwargs) - else: - flash('You need to log in first.') - return redirect(url_for('home')) - return wrap -@app.route("/", methods=["GET", "POST"]) +@blueprint.route("/", methods=["GET", "POST"]) def home(): form = LoginForm(request.form) if request.method == 'POST': @@ -31,29 +22,22 @@ def home(): password=request.form['password']).first() if u is None: error = 'Invalid username or password.' - flash(error, 'error') + flash(error, 'warning') else: session['logged_in'] = True session['username'] = u.username flash("You are logged in.", 'success') - return redirect(url_for("members")) + return redirect(url_for("member.members")) return render_template("home.html", form=form) - -@app.route("/members/") -@login_required -def members(): - return render_template("members.html") - - -@app.route('/logout/') +@blueprint.route('/logout/') def logout(): session.pop('logged_in', None) session.pop('username', None) flash('You are logged out.', 'info') - return redirect(url_for('home')) + return redirect(url_for('public.home')) -@app.route("/register/", methods=['GET', 'POST']) +@blueprint.route("/register/", methods=['GET', 'POST']) def register(): form = RegisterForm(request.form, csrf_enabled=False) if form.validate_on_submit(): @@ -62,21 +46,19 @@ def register(): db.session.add(new_user) db.session.commit() flash("Thank you for registering. You can now log in.", 'success') - return redirect(url_for('home')) + return redirect(url_for('public.home')) except IntegrityError as err: print(err) - flash("That username and/or email already exists. Try again.", 'error') + flash("That username and/or email already exists. Try again.", 'warning') else: flash_errors(form) return render_template('register.html', form=form) - -@app.route("/about/") +@blueprint.route("/about/") def about(): form = LoginForm(request.form) return render_template("about.html", form=form) - -@app.errorhandler(404) +@blueprint.errorhandler(404) def page_not_found(e): return render_template("404.html"), 404 diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings.py index 574df75..243bda4 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings.py +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings.py @@ -18,6 +18,3 @@ class DevConfig(Config): DB_PATH = os.path.join(Config.PROJECT_ROOT, DB_NAME) SQLALCHEMY_DATABASE_URI = "sqlite:///{0}".format(DB_PATH) SQLALCHEMY_ECHO = True - - - diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/_layouts/footer.html b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/_layouts/footer.html index da563eb..5f4bf3a 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/_layouts/footer.html +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/_layouts/footer.html @@ -6,9 +6,9 @@ - \ No newline at end of file + diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/_layouts/nav.html b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/_layouts/nav.html index 479b970..7406f49 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/_layouts/nav.html +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/_layouts/nav.html @@ -8,7 +8,7 @@ - + {% endraw %} {{cookiecutter.project_name}} {% raw %} @@ -17,17 +17,17 @@