Modular structure with blueprints

Views are all in located in the modules package, which contains blueprints
Also, use alert-warning instead of alert-error for Bootstrap3
master
Steven Loria 11 years ago
parent 0f142a65e8
commit 3a411b4abe
  1. 6
      README.rst
  2. 11
      {{cookiecutter.repo_name}}/manage.py
  3. 40
      {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/app.py
  4. 13
      {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/main.py
  5. 6
      {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/models.py
  6. 1
      {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/modules/__init__.py
  7. 14
      {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/modules/member.py
  8. 60
      {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/modules/public.py
  9. 3
      {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings.py
  10. 4
      {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/_layouts/footer.html
  11. 12
      {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/_layouts/nav.html
  12. 3
      {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/tests/unit_tests.py
  13. 22
      {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/utils.py

@ -16,6 +16,7 @@ Features
- A simple ``manage.py`` script. - A simple ``manage.py`` script.
- CSS and JS minification using Flask-Assets - CSS and JS minification using Flask-Assets
- Easily switch between development and production environments through the MYFLASKAPP_ENV system variable. - Easily switch between development and production environments through the MYFLASKAPP_ENV system variable.
- Utilizes best practices: `Blueprints <http://flask.pocoo.org/docs/blueprints/>`_ and `Application Factory <http://flask.pocoo.org/docs/patterns/appfactories/>`_ patterns
Screenshots Screenshots
----------- -----------
@ -42,9 +43,10 @@ Inspiration
----------- -----------
- `Building Websites in Python with Flask <http://maximebf.com/blog/2012/10/building-websites-in-python-with-flask/>`_ - `Building Websites in Python with Flask <http://maximebf.com/blog/2012/10/building-websites-in-python-with-flask/>`_
- `Getting Bigger with Flask <http://maximebf.com/blog/2012/11/getting-bigger-with-flask/>`_
- `Structuring Flask Apps <http://charlesleifer.com/blog/structuring-flask-apps-a-how-to-for-those-coming-from-django/>`_ - `Structuring Flask Apps <http://charlesleifer.com/blog/structuring-flask-apps-a-how-to-for-those-coming-from-django/>`_
- `Flask-Foundation <https://github.com/JackStouffer/Flask-Foundation>`_ - `Flask-Foundation <https://github.com/JackStouffer/Flask-Foundation>`_ by `@JackStouffer <https://github.com/JackStouffer>`_
- `flask-basic-registration <https://github.com/mjhea0/flask-basic-registration>`_ - `flask-basic-registration <https://github.com/mjhea0/flask-basic-registration>`_ by `@mjhea0 <https://github.com/mjhea0>`_
- `Flask Official Documentation <http://flask.pocoo.org/docs/>`_ - `Flask Official Documentation <http://flask.pocoo.org/docs/>`_

@ -1,9 +1,16 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys import sys
import subprocess import subprocess
from flask.ext.script import Manager, Shell, Server from flask.ext.script import Manager, Shell, Server
from {{ cookiecutter.repo_name }} import models from {{cookiecutter.repo_name }} import models
from {{ cookiecutter.repo_name }}.main import app, db 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) manager = Manager(app)
TEST_CMD = "nosetests" TEST_CMD = "nosetests"

@ -6,19 +6,31 @@ from flask.ext.assets import Environment
from webassets.loaders import PythonLoader from webassets.loaders import PythonLoader
from {{cookiecutter.repo_name}} import assets 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 = Environment()
assets_env.init_app(app)
assets_loader = PythonLoader(assets) def create_app(config_object, env):
for name, bundle in assets_loader.load_bundles().iteritems(): '''An application factory, as explained here:
assets_env.register(name, bundle) 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

@ -4,11 +4,12 @@
Entry point for all things, to avoid circular imports. Entry point for all things, to avoid circular imports.
""" """
import os import os
from .app import app, db, assets_env from .app import create_app
from .models import * from .models import User, db
from .views import * import {{cookiecutter.repo_name}}.modules as modules
if __name__ == '__main__': if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000)) # Get the environment setting from the system environment variable
app.run(host='0.0.0.0', port=port) env = os.environ.get("{{cookiecutter.repo_name | upper}}_ENV", "prod")
app = create_app("{{cookiecutter.repo_name}}.settings.{env}Config"
.format(env=env.capitalize()), env)

@ -3,7 +3,9 @@
""" """
{{cookiecutter.project_name}} models. {{cookiecutter.project_name}} models.
""" """
from .app import db from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class User(db.Model): class User(db.Model):
@ -22,5 +24,3 @@ class User(db.Model):
def __repr__(self): def __repr__(self):
return '<User "{username}">'.format(username=self.username) return '<User "{username}">'.format(username=self.username)
db.create_all()

@ -0,0 +1 @@
'''Blueprint modules for {{cookiecutter.repo_name}}.'''

@ -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")

@ -1,29 +1,20 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals '''Public section, including homepage and signup.'''
from flask import render_template, session, request, flash, redirect, url_for from flask import (Blueprint, request, render_template, flash, url_for,
from functools import wraps redirect, session)
from .app import app, db
from .models import User
from .forms import RegisterForm, LoginForm
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
def flash_errors(form): from {{cookiecutter.repo_name}}.models import User
for field, errors in form.errors.items(): from {{cookiecutter.repo_name}}.forms import RegisterForm, LoginForm
for error in errors: from {{cookiecutter.repo_name}}.utils import flash_errors
flash("Error in the {0} field - {1}" from {{cookiecutter.repo_name}}.models import db
.format(getattr(form, field).label.text, error), 'error')
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(): def home():
form = LoginForm(request.form) form = LoginForm(request.form)
if request.method == 'POST': if request.method == 'POST':
@ -31,29 +22,22 @@ def home():
password=request.form['password']).first() password=request.form['password']).first()
if u is None: if u is None:
error = 'Invalid username or password.' error = 'Invalid username or password.'
flash(error, 'error') flash(error, 'warning')
else: else:
session['logged_in'] = True session['logged_in'] = True
session['username'] = u.username session['username'] = u.username
flash("You are logged in.", 'success') flash("You are logged in.", 'success')
return redirect(url_for("members")) return redirect(url_for("member.members"))
return render_template("home.html", form=form) return render_template("home.html", form=form)
@blueprint.route('/logout/')
@app.route("/members/")
@login_required
def members():
return render_template("members.html")
@app.route('/logout/')
def logout(): def logout():
session.pop('logged_in', None) session.pop('logged_in', None)
session.pop('username', None) session.pop('username', None)
flash('You are logged out.', 'info') 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(): def register():
form = RegisterForm(request.form, csrf_enabled=False) form = RegisterForm(request.form, csrf_enabled=False)
if form.validate_on_submit(): if form.validate_on_submit():
@ -62,21 +46,19 @@ def register():
db.session.add(new_user) db.session.add(new_user)
db.session.commit() db.session.commit()
flash("Thank you for registering. You can now log in.", 'success') 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: except IntegrityError as err:
print(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: else:
flash_errors(form) flash_errors(form)
return render_template('register.html', form=form) return render_template('register.html', form=form)
@blueprint.route("/about/")
@app.route("/about/")
def about(): def about():
form = LoginForm(request.form) form = LoginForm(request.form)
return render_template("about.html", form=form) return render_template("about.html", form=form)
@blueprint.errorhandler(404)
@app.errorhandler(404)
def page_not_found(e): def page_not_found(e):
return render_template("404.html"), 404 return render_template("404.html"), 404

@ -18,6 +18,3 @@ class DevConfig(Config):
DB_PATH = os.path.join(Config.PROJECT_ROOT, DB_NAME) DB_PATH = os.path.join(Config.PROJECT_ROOT, DB_NAME)
SQLALCHEMY_DATABASE_URI = "sqlite:///{0}".format(DB_PATH) SQLALCHEMY_DATABASE_URI = "sqlite:///{0}".format(DB_PATH)
SQLALCHEMY_ECHO = True SQLALCHEMY_ECHO = True

@ -6,9 +6,9 @@
<ul class="footer-nav"> <ul class="footer-nav">
{% raw %} {% raw %}
<li><a href="{{ url_for('about') }}">About</a></li> <li><a href="{{ url_for('public.about') }}">About</a></li>
{% endraw %} {% endraw %}
<li><a href="mailto:{{ cookiecutter.email }}">Contact</a></li> <li><a href="mailto:{{ cookiecutter.email }}">Contact</a></li>
</ul> </ul>
</small> </small>
</footer> </footer>

@ -8,7 +8,7 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="{{ url_for('home') }}"> <a class="navbar-brand" href="{{ url_for('public.home') }}">
{% endraw %} {% endraw %}
{{cookiecutter.project_name}} {{cookiecutter.project_name}}
{% raw %} {% raw %}
@ -17,17 +17,17 @@
<!-- Collect the nav links, forms, and other content for toggling --> <!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse navbar-ex1-collapse"> <div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li class="active"><a href="{{ url_for('home') }}">Home</a></li> <li class="active"><a href="{{ url_for('public.home') }}">Home</a></li>
<li><a href="{{ url_for('about') }}">About</a></li> <li><a href="{{ url_for('public.about') }}">About</a></li>
</ul> </ul>
{% if session.logged_in %} {% if session.logged_in %}
<a class="btn btn-default btn-sm navbar-btn navbar-right" href="{{ url_for('logout') }}">Log out</a> <a class="btn btn-default btn-sm navbar-btn navbar-right" href="{{ url_for('public.logout') }}">Log out</a>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li><a href="{{ url_for('members') }}">Logged in as {{ session.username }}</a></li> <li><a href="{{ url_for('member.members') }}">Logged in as {{ session.username }}</a></li>
</ul> </ul>
{% elif form %} {% elif form %}
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li><a href="{{ url_for('register') }}">Create account</a></li> <li><a href="{{ url_for('public.register') }}">Create account</a></li>
</ul> </ul>
<form method="POST" class="navbar-form form-inline navbar-right" action="" role="login"> <form method="POST" class="navbar-form form-inline navbar-right" action="" role="login">
{{ form.hidden_tag() }} {{ form.hidden_tag() }}

@ -7,11 +7,12 @@ except ImportError:
import sys import sys
print('nose required. Run "pip install nose".') print('nose required. Run "pip install nose".')
from {{cookiecutter.repo_name}}.main import app from {{cookiecutter.repo_name}}.main import create_app
class Test{{cookiecutter.repo_name | capitalize}}(unittest.TestCase): class Test{{cookiecutter.repo_name | capitalize}}(unittest.TestCase):
def setUp(self): def setUp(self):
app = create_app("{{cookiecutter.repo_name}}.settings.DevConfig", 'dev')
app.config['TESTING'] = True app.config['TESTING'] = True
self.app = app.test_client() self.app = app.test_client()

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
'''Helper utilities and decorators.'''
from flask import session, flash, redirect, url_for
from functools import wraps
def flash_errors(form):
'''Flash all errors for a 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), 'warning')
def login_required(test):
'''Decorator that makes a view require authentication.'''
@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
Loading…
Cancel
Save