Merge pull request #54 from andreoliw/feature/lint-with-flake8

Add a lint command with flake8 and isort
master
Steven Loria 9 years ago
commit 4d670d6c59
  1. 2
      .travis.yml
  2. 2
      {{cookiecutter.app_name}}/.isort.cfg
  3. 10
      {{cookiecutter.app_name}}/.travis.yml
  4. 2
      {{cookiecutter.app_name}}/README.rst
  5. 58
      {{cookiecutter.app_name}}/manage.py
  6. 10
      {{cookiecutter.app_name}}/requirements/dev.txt
  7. 2
      {{cookiecutter.app_name}}/setup.cfg
  8. 1
      {{cookiecutter.app_name}}/tests/__init__.py
  9. 6
      {{cookiecutter.app_name}}/tests/conftest.py
  10. 16
      {{cookiecutter.app_name}}/tests/factories.py
  11. 5
      {{cookiecutter.app_name}}/tests/test_config.py
  12. 20
      {{cookiecutter.app_name}}/tests/test_forms.py
  13. 42
      {{cookiecutter.app_name}}/tests/test_functional.py
  14. 31
      {{cookiecutter.app_name}}/tests/test_models.py
  15. 1
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/__init__.py
  16. 22
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py
  17. 21
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/assets.py
  18. 10
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/compat.py
  19. 32
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py
  20. 21
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py
  21. 3
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/__init__.py
  22. 9
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py
  23. 40
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py
  24. 12
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py
  25. 2
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/__init__.py
  26. 23
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py
  27. 24
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py
  28. 9
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py
  29. 5
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/utils.py

@ -1,3 +1,5 @@
# Config file for automatic testing at travis-ci.org
sudo: false # http://docs.travis-ci.com/user/migrating-from-legacy/
language: python
python:
- 2.7

@ -0,0 +1,2 @@
[settings]
line_length=120

@ -1,11 +1,9 @@
# Config file for automatic testing at travis-ci.org
sudo: false # http://docs.travis-ci.com/user/migrating-from-legacy/
language: python
python:
- "3.3"
- "2.7"
- 2.7
- 3.3
- 3.4
install: pip install -r requirements/dev.txt
script: py.test tests

@ -20,7 +20,7 @@ Then run the following commands to bootstrap your environment.
::
git clone https://github.com/{{cookiecutter.github_username}}/{{ cookiecutter.app_name }}
git clone https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.app_name}}
cd {{cookiecutter.app_name}}
pip install -r requirements/dev.txt
python manage.py server

@ -1,30 +1,29 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Management script."""
import os
from flask_script import Manager, Shell, Server
from flask_script.commands import Clean, ShowUrls
from glob import glob
from subprocess import call
from flask_migrate import MigrateCommand
from flask_script import Command, Manager, Option, Server, Shell
from flask_script.commands import Clean, ShowUrls
from {{cookiecutter.app_name}}.app import create_app
from {{cookiecutter.app_name}}.user.models import User
from {{cookiecutter.app_name}}.settings import DevConfig, ProdConfig
from {{cookiecutter.app_name}}.database import db
from {{cookiecutter.app_name}}.settings import DevConfig, ProdConfig
from {{cookiecutter.app_name}}.user.models import User
if os.environ.get("{{cookiecutter.app_name | upper}}_ENV") == 'prod':
app = create_app(ProdConfig)
else:
app = create_app(DevConfig)
CONFIG = ProdConfig if os.environ.get('{{cookiecutter.app_name | upper}}_ENV') == 'prod' else DevConfig
HERE = os.path.abspath(os.path.dirname(__file__))
TEST_PATH = os.path.join(HERE, 'tests')
app = create_app(CONFIG)
manager = Manager(app)
def _make_context():
"""Return context dict for a shell session so you can access
app, db, and the User model by default.
"""
"""Return context dict for a shell session so you can access app, db, and the User model by default."""
return {'app': app, 'db': db, 'User': User}
@ -36,11 +35,42 @@ def test():
return exit_code
class Lint(Command):
"""Lint and check code style with flake8 and isort."""
def get_options(self):
"""Command line options."""
return (
Option('-f', '--fix-imports', action='store_true', dest='fix_imports', default=False,
help='Fix imports using isort, before linting'),
)
def run(self, fix_imports):
"""Run command."""
skip = ['requirements']
root_files = glob('*.py')
root_directories = [name for name in next(os.walk('.'))[1] if not name.startswith('.')]
files_and_directories = [arg for arg in root_files + root_directories if arg not in skip]
def execute_tool(description, *args):
"""Execute a checking tool with its arguments."""
command_line = list(args) + files_and_directories
print('{}: {}'.format(description, ' '.join(command_line)))
rv = call(command_line)
if rv is not 0:
exit(rv)
if fix_imports:
execute_tool('Fixing import order', 'isort', '-rc')
execute_tool('Checking code style', 'flake8')
manager.add_command('server', Server())
manager.add_command('shell', Shell(make_context=_make_context))
manager.add_command('db', MigrateCommand)
manager.add_command("urls", ShowUrls())
manager.add_command("clean", Clean())
manager.add_command('urls', ShowUrls())
manager.add_command('clean', Clean())
manager.add_command('lint', Lint())
if __name__ == '__main__':
manager.run()

@ -8,3 +8,13 @@ factory-boy==2.6.0
# Management script
Flask-Script==2.0.5
# Lint and code style
flake8==2.5.0
flake8-blind-except==0.1.0
flake8-debugger==1.4.0
flake8-docstrings==0.2.1.post1
flake8-isort==0.2
flake8-quotes==0.1.1
isort==4.2.2
pep8-naming==0.3.3

@ -0,0 +1,2 @@
[flake8]
max-line-length=120

@ -1,19 +1,19 @@
# -*- coding: utf-8 -*-
"""Defines fixtures available to all tests."""
import os
import pytest
from webtest import TestApp
from {{ cookiecutter.app_name }}.settings import TestConfig
from {{cookiecutter.app_name}}.app import create_app
from {{cookiecutter.app_name}}.database import db as _db
from {{cookiecutter.app_name}}.settings import TestConfig
from .factories import UserFactory
@pytest.yield_fixture(scope='function')
def app():
"""An application for the tests."""
_app = create_app(TestConfig)
ctx = _app.test_request_context()
ctx.push()
@ -31,6 +31,7 @@ def testapp(app):
@pytest.yield_fixture(scope='function')
def db(app):
"""A database for the tests."""
_db.app = app
with app.app_context():
_db.create_all()
@ -42,6 +43,7 @@ def db(app):
@pytest.fixture
def user(db):
"""A user for the tests."""
user = UserFactory(password='myprecious')
db.session.commit()
return user

@ -1,23 +1,31 @@
# -*- coding: utf-8 -*-
from factory import Sequence, PostGenerationMethodCall
"""Factories to help in tests."""
from factory import PostGenerationMethodCall, Sequence
from factory.alchemy import SQLAlchemyModelFactory
from {{cookiecutter.app_name}}.user.models import User
from {{cookiecutter.app_name}}.database import db
from {{cookiecutter.app_name}}.user.models import User
class BaseFactory(SQLAlchemyModelFactory):
"""Base factory."""
class Meta:
"""Factory configuration."""
abstract = True
sqlalchemy_session = db.session
class UserFactory(BaseFactory):
username = Sequence(lambda n: "user{0}".format(n))
email = Sequence(lambda n: "user{0}@example.com".format(n))
"""User factory."""
username = Sequence(lambda n: 'user{0}'.format(n))
email = Sequence(lambda n: 'user{0}@example.com'.format(n))
password = PostGenerationMethodCall('set_password', 'example')
active = True
class Meta:
"""Factory configuration."""
model = User

@ -1,9 +1,11 @@
# -*- coding: utf-8 -*-
"""Test configs."""
from {{cookiecutter.app_name}}.app import create_app
from {{cookiecutter.app_name}}.settings import ProdConfig, DevConfig
from {{cookiecutter.app_name}}.settings import DevConfig, ProdConfig
def test_production_config():
"""Production config."""
app = create_app(ProdConfig)
assert app.config['ENV'] == 'prod'
assert app.config['DEBUG'] is False
@ -12,6 +14,7 @@ def test_production_config():
def test_dev_config():
"""Development config."""
app = create_app(DevConfig)
assert app.config['ENV'] == 'dev'
assert app.config['DEBUG'] is True

@ -1,38 +1,41 @@
# -*- coding: utf-8 -*-
import pytest
"""Test forms."""
from {{cookiecutter.app_name}}.public.forms import LoginForm
from {{cookiecutter.app_name}}.user.forms import RegisterForm
from .factories import UserFactory
class TestRegisterForm:
"""Register form."""
def test_validate_user_already_registered(self, user):
# Enters username that is already registered
"""Enter username that is already registered."""
form = RegisterForm(username=user.username, email='foo@bar.com',
password='example', confirm='example')
password='example', confirm='example')
assert form.validate() is False
assert 'Username already registered' in form.username.errors
def test_validate_email_already_registered(self, user):
# enters email that is already registered
"""Enter email that is already registered."""
form = RegisterForm(username='unique', email=user.email,
password='example', confirm='example')
password='example', confirm='example')
assert form.validate() is False
assert 'Email already registered' in form.email.errors
def test_validate_success(self, db):
"""Register with success."""
form = RegisterForm(username='newusername', email='new@test.test',
password='example', confirm='example')
password='example', confirm='example')
assert form.validate() is True
class TestLoginForm:
"""Login form."""
def test_validate_success(self, user):
"""Login successful."""
user.set_password('example')
user.save()
form = LoginForm(username=user.username, password='example')
@ -40,12 +43,14 @@ class TestLoginForm:
assert form.user == user
def test_validate_unknown_username(self, db):
"""Unknown username."""
form = LoginForm(username='unknown', password='example')
assert form.validate() is False
assert 'Unknown username' in form.username.errors
assert form.user is None
def test_validate_invalid_password(self, user):
"""Invalid password."""
user.set_password('example')
user.save()
form = LoginForm(username=user.username, password='wrongpassword')
@ -53,6 +58,7 @@ class TestLoginForm:
assert 'Invalid password' in form.password.errors
def test_validate_inactive_user(self, user):
"""Inactive user."""
user.active = False
user.set_password('example')
user.save()

@ -3,19 +3,20 @@
See: http://webtest.readthedocs.org/
"""
import pytest
from flask import url_for
from {{cookiecutter.app_name}}.user.models import User
from .factories import UserFactory
class TestLoggingIn:
"""Login."""
def test_can_log_in_returns_200(self, user, testapp):
"""Login successful."""
# Goes to homepage
res = testapp.get("/")
res = testapp.get('/')
# Fills out login form in navbar
form = res.forms['loginForm']
form['username'] = user.username
@ -25,7 +26,8 @@ class TestLoggingIn:
assert res.status_code == 200
def test_sees_alert_on_log_out(self, user, testapp):
res = testapp.get("/")
"""Show alert on logout."""
res = testapp.get('/')
# Fills out login form in navbar
form = res.forms['loginForm']
form['username'] = user.username
@ -37,8 +39,9 @@ class TestLoggingIn:
assert 'You are logged out.' in res
def test_sees_error_message_if_password_is_incorrect(self, user, testapp):
"""Show error if password is incorrect."""
# Goes to homepage
res = testapp.get("/")
res = testapp.get('/')
# Fills out login form, password incorrect
form = res.forms['loginForm']
form['username'] = user.username
@ -46,11 +49,12 @@ class TestLoggingIn:
# Submits
res = form.submit()
# sees error
assert "Invalid password" in res
assert 'Invalid password' in res
def test_sees_error_message_if_username_doesnt_exist(self, user, testapp):
"""Show error if username doesn't exist."""
# Goes to homepage
res = testapp.get("/")
res = testapp.get('/')
# Fills out login form, password incorrect
form = res.forms['loginForm']
form['username'] = 'unknown'
@ -58,19 +62,21 @@ class TestLoggingIn:
# Submits
res = form.submit()
# sees error
assert "Unknown user" in res
assert 'Unknown user' in res
class TestRegistering:
"""Register a user."""
def test_can_register(self, user, testapp):
"""Register a new user."""
old_count = len(User.query.all())
# Goes to homepage
res = testapp.get("/")
res = testapp.get('/')
# Clicks Create Account button
res = res.click("Create account")
res = res.click('Create account')
# Fills out the form
form = res.forms["registerForm"]
form = res.forms['registerForm']
form['username'] = 'foobar'
form['email'] = 'foo@bar.com'
form['password'] = 'secret'
@ -82,10 +88,11 @@ class TestRegistering:
assert len(User.query.all()) == old_count + 1
def test_sees_error_message_if_passwords_dont_match(self, user, testapp):
"""Show error if passwords don't match."""
# Goes to registration page
res = testapp.get(url_for("public.register"))
res = testapp.get(url_for('public.register'))
# Fills out form, but passwords don't match
form = res.forms["registerForm"]
form = res.forms['registerForm']
form['username'] = 'foobar'
form['email'] = 'foo@bar.com'
form['password'] = 'secret'
@ -93,15 +100,16 @@ class TestRegistering:
# Submits
res = form.submit()
# sees error message
assert "Passwords must match" in res
assert 'Passwords must match' in res
def test_sees_error_message_if_user_already_registered(self, user, testapp):
"""Show error if user already registered."""
user = UserFactory(active=True) # A registered user
user.save()
# Goes to registration page
res = testapp.get(url_for("public.register"))
res = testapp.get(url_for('public.register'))
# Fills out form, but username is already registered
form = res.forms["registerForm"]
form = res.forms['registerForm']
form['username'] = user.username
form['email'] = 'foo@bar.com'
form['password'] = 'secret'
@ -109,4 +117,4 @@ class TestRegistering:
# Submits
res = form.submit()
# sees error
assert "Username already registered" in res
assert 'Username already registered' in res

@ -4,14 +4,17 @@ import datetime as dt
import pytest
from {{ cookiecutter.app_name }}.user.models import User, Role
from {{cookiecutter.app_name}}.user.models import Role, User
from .factories import UserFactory
@pytest.mark.usefixtures('db')
class TestUser:
"""User tests."""
def test_get_by_id(self):
"""Get user by ID."""
user = User('foo', 'foo@bar.com')
user.save()
@ -19,18 +22,21 @@ class TestUser:
assert retrieved == user
def test_created_at_defaults_to_datetime(self):
"""Test creation date."""
user = User(username='foo', email='foo@bar.com')
user.save()
assert bool(user.created_at)
assert isinstance(user.created_at, dt.datetime)
def test_password_is_nullable(self):
"""Test null password."""
user = User(username='foo', email='foo@bar.com')
user.save()
assert user.password is None
def test_factory(self, db):
user = UserFactory(password="myprecious")
"""Test user factory."""
user = UserFactory(password='myprecious')
db.session.commit()
assert bool(user.username)
assert bool(user.email)
@ -40,19 +46,22 @@ class TestUser:
assert user.check_password('myprecious')
def test_check_password(self):
user = User.create(username="foo", email="foo@bar.com",
password="foobarbaz123")
"""Check password."""
user = User.create(username='foo', email='foo@bar.com',
password='foobarbaz123')
assert user.check_password('foobarbaz123') is True
assert user.check_password("barfoobaz") is False
assert user.check_password('barfoobaz') is False
def test_full_name(self):
user = UserFactory(first_name="Foo", last_name="Bar")
assert user.full_name == "Foo Bar"
"""User full name."""
user = UserFactory(first_name='Foo', last_name='Bar')
assert user.full_name == 'Foo Bar'
def test_roles(self):
"""Add a role to a user."""
role = Role(name='admin')
role.save()
u = UserFactory()
u.roles.append(role)
u.save()
assert role in u.roles
user = UserFactory()
user.roles.append(role)
user.save()
assert role in user.roles

@ -2,22 +2,14 @@
"""The app module, containing the app factory function."""
from flask import Flask, render_template
from {{cookiecutter.app_name}}.settings import ProdConfig
from {{cookiecutter.app_name}}.assets import assets
from {{cookiecutter.app_name}}.extensions import (
bcrypt,
cache,
db,
login_manager,
migrate,
debug_toolbar,
)
from {{cookiecutter.app_name}} import public, user
from {{cookiecutter.app_name}}.assets import assets
from {{cookiecutter.app_name}}.extensions import bcrypt, cache, db, debug_toolbar, login_manager, migrate
from {{cookiecutter.app_name}}.settings import ProdConfig
def create_app(config_object=ProdConfig):
"""An application factory, as explained here:
http://flask.pocoo.org/docs/patterns/appfactories/
"""An application factory, as explained here: http://flask.pocoo.org/docs/patterns/appfactories/.
:param config_object: The configuration object to use.
"""
@ -30,6 +22,7 @@ def create_app(config_object=ProdConfig):
def register_extensions(app):
"""Register Flask extensions."""
assets.init_app(app)
bcrypt.init_app(app)
cache.init_app(app)
@ -41,16 +34,19 @@ def register_extensions(app):
def register_blueprints(app):
"""Register Flask blueprints."""
app.register_blueprint(public.views.blueprint)
app.register_blueprint(user.views.blueprint)
return None
def register_errorhandlers(app):
"""Register error handlers."""
def render_error(error):
"""Render error template."""
# If a HTTPException, pull the `code` attribute; default to 500
error_code = getattr(error, 'code', 500)
return render_template("{0}.html".format(error_code)), error_code
return render_template('{0}.html'.format(error_code)), error_code
for errcode in [401, 404, 500]:
app.errorhandler(errcode)(render_error)
return None

@ -1,22 +1,23 @@
# -*- coding: utf-8 -*-
"""Application assets."""
from flask_assets import Bundle, Environment
css = Bundle(
"libs/bootstrap/dist/css/bootstrap.css",
"css/style.css",
filters="cssmin",
output="public/css/common.css"
'libs/bootstrap/dist/css/bootstrap.css',
'css/style.css',
filters='cssmin',
output='public/css/common.css'
)
js = Bundle(
"libs/jQuery/dist/jquery.js",
"libs/bootstrap/dist/js/bootstrap.js",
"js/plugins.js",
'libs/jQuery/dist/jquery.js',
'libs/bootstrap/dist/js/bootstrap.js',
'js/plugins.js',
filters='jsmin',
output="public/js/common.js"
output='public/js/common.js'
)
assets = Environment()
assets.register("js_all", js)
assets.register("css_all", css)
assets.register('js_all', js)
assets.register('css_all', css)

@ -1,17 +1,15 @@
# -*- coding: utf-8 -*-
"""Python 2/3 compatibility module."""
import sys
PY2 = int(sys.version[0]) == 2
if PY2:
text_type = unicode
text_type = unicode # noqa
binary_type = str
string_types = (str, unicode)
unicode = unicode
basestring = basestring
string_types = (str, unicode) # noqa
unicode = unicode # noqa
basestring = basestring # noqa
else:
text_type = str
binary_type = bytes

@ -1,11 +1,9 @@
# -*- coding: utf-8 -*-
"""Database module, including the SQLAlchemy database object and DB-related
utilities.
"""
"""Database module, including the SQLAlchemy database object and DB-related utilities."""
from sqlalchemy.orm import relationship
from .extensions import db
from .compat import basestring
from .extensions import db
# Alias common SQLAlchemy names
Column = db.Column
@ -13,9 +11,7 @@ relationship = relationship
class CRUDMixin(object):
"""Mixin that adds convenience methods for CRUD (create, read, update, delete)
operations.
"""
"""Mixin that adds convenience methods for CRUD (create, read, update, delete) operations."""
@classmethod
def create(cls, **kwargs):
@ -41,39 +37,41 @@ class CRUDMixin(object):
db.session.delete(self)
return commit and db.session.commit()
class Model(CRUDMixin, db.Model):
"""Base model class that includes CRUD convenience methods."""
__abstract__ = True
# From Mike Bayer's "Building the app" talk
# https://speakerdeck.com/zzzeek/building-the-app
class SurrogatePK(object):
"""A mixin that adds a surrogate integer 'primary key' column named
``id`` to any declarative-mapped class.
"""
"""A mixin that adds a surrogate integer 'primary key' column named ``id`` to any declarative-mapped class."""
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, primary_key=True)
@classmethod
def get_by_id(cls, id):
def get_by_id(cls, record_id):
"""Get record by ID."""
if any(
(isinstance(id, basestring) and id.isdigit(),
isinstance(id, (int, float))),
(isinstance(record_id, basestring) and record_id.isdigit(),
isinstance(record_id, (int, float))),
):
return cls.query.get(int(id))
return cls.query.get(int(record_id))
return None
def ReferenceCol(tablename, nullable=False, pk_name='id', **kwargs):
def reference_col(tablename, nullable=False, pk_name='id', **kwargs):
"""Column that adds primary key foreign key reference.
Usage: ::
category_id = ReferenceCol('category')
category_id = reference_col('category')
category = relationship('Category', backref='categories')
"""
return db.Column(
db.ForeignKey("{0}.{1}".format(tablename, pk_name)),
db.ForeignKey('{0}.{1}'.format(tablename, pk_name)),
nullable=nullable, **kwargs)

@ -1,22 +1,15 @@
# -*- coding: utf-8 -*-
"""Extensions module. Each extension is initialized in the app factory located
in app.py
"""
"""Extensions module. Each extension is initialized in the app factory located in app.py."""
from flask_bcrypt import Bcrypt
bcrypt = Bcrypt()
from flask_cache import Cache
from flask_debugtoolbar import DebugToolbarExtension
from flask_login import LoginManager
login_manager = LoginManager()
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
from flask_migrate import Migrate
bcrypt = Bcrypt()
login_manager = LoginManager()
db = SQLAlchemy()
migrate = Migrate()
from flask_cache import Cache
cache = Cache()
from flask_debugtoolbar import DebugToolbarExtension
debug_toolbar = DebugToolbarExtension()

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""The public module, including the homepage and user auth."""
from . import views
from . import views # noqa

@ -1,20 +1,25 @@
# -*- coding: utf-8 -*-
"""Public forms."""
from flask_wtf import Form
from wtforms import TextField, PasswordField
from wtforms import PasswordField, StringField
from wtforms.validators import DataRequired
from {{cookiecutter.app_name}}.user.models import User
class LoginForm(Form):
username = TextField('Username', validators=[DataRequired()])
"""Login form."""
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
def __init__(self, *args, **kwargs):
"""Create instance."""
super(LoginForm, self).__init__(*args, **kwargs)
self.user = None
def validate(self):
"""Validate the form."""
initial_validation = super(LoginForm, self).validate()
if not initial_validation:
return False

@ -1,63 +1,63 @@
# -*- coding: utf-8 -*-
"""Public section, including homepage and signup."""
from flask import (Blueprint, request, render_template, flash, url_for,
redirect, session)
from flask_login import login_user, login_required, logout_user
from flask import Blueprint, flash, redirect, render_template, request, url_for
from flask_login import login_required, login_user, logout_user
from {{cookiecutter.app_name}}.extensions import login_manager
from {{cookiecutter.app_name}}.user.models import User
from {{cookiecutter.app_name}}.public.forms import LoginForm
from {{cookiecutter.app_name}}.user.forms import RegisterForm
from {{cookiecutter.app_name}}.user.models import User
from {{cookiecutter.app_name}}.utils import flash_errors
from {{cookiecutter.app_name}}.database import db
blueprint = Blueprint('public', __name__, static_folder="../static")
blueprint = Blueprint('public', __name__, static_folder='../static')
@login_manager.user_loader
def load_user(id):
return User.get_by_id(int(id))
def load_user(user_id):
"""Load user by ID."""
return User.get_by_id(int(user_id))
@blueprint.route("/", methods=["GET", "POST"])
@blueprint.route('/', methods=['GET', 'POST'])
def home():
"""Home page."""
form = LoginForm(request.form)
# Handle logging in
if request.method == 'POST':
if form.validate_on_submit():
login_user(form.user)
flash("You are logged in.", 'success')
redirect_url = request.args.get("next") or url_for("user.members")
flash('You are logged in.', 'success')
redirect_url = request.args.get('next') or url_for('user.members')
return redirect(redirect_url)
else:
flash_errors(form)
return render_template("public/home.html", form=form)
return render_template('public/home.html', form=form)
@blueprint.route('/logout/')
@login_required
def logout():
"""Logout."""
logout_user()
flash('You are logged out.', 'info')
return redirect(url_for('public.home'))
@blueprint.route("/register/", methods=['GET', 'POST'])
@blueprint.route('/register/', methods=['GET', 'POST'])
def register():
"""Register new user."""
form = RegisterForm(request.form, csrf_enabled=False)
if form.validate_on_submit():
new_user = User.create(username=form.username.data,
email=form.email.data,
password=form.password.data,
active=True)
flash("Thank you for registering. You can now log in.", 'success')
User.create(username=form.username.data, email=form.email.data, password=form.password.data, active=True)
flash('Thank you for registering. You can now log in.', 'success')
return redirect(url_for('public.home'))
else:
flash_errors(form)
return render_template('public/register.html', form=form)
@blueprint.route("/about/")
@blueprint.route('/about/')
def about():
"""About page."""
form = LoginForm(request.form)
return render_template("public/about.html", form=form)
return render_template('public/about.html', form=form)

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
"""Application configuration."""
import os
os_env = os.environ
class Config(object):
SECRET_KEY = os_env.get('{{cookiecutter.app_name | upper}}_SECRET', 'secret-key') # TODO: Change me
"""Base configuration."""
SECRET_KEY = os.environ.get('{{cookiecutter.app_name | upper}}_SECRET', 'secret-key') # TODO: Change me
APP_DIR = os.path.abspath(os.path.dirname(__file__)) # This directory
PROJECT_ROOT = os.path.abspath(os.path.join(APP_DIR, os.pardir))
BCRYPT_LOG_ROUNDS = 13
@ -13,10 +14,12 @@ class Config(object):
DEBUG_TB_ENABLED = False # Disable Debug toolbar
DEBUG_TB_INTERCEPT_REDIRECTS = False
CACHE_TYPE = 'simple' # Can be "memcached", "redis", etc.
SQLALCHEMY_TRACK_MODIFICATIONS = False
class ProdConfig(Config):
"""Production configuration."""
ENV = 'prod'
DEBUG = False
SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/example' # TODO: Change me
@ -25,6 +28,7 @@ class ProdConfig(Config):
class DevConfig(Config):
"""Development configuration."""
ENV = 'dev'
DEBUG = True
DB_NAME = 'dev.db'
@ -37,6 +41,8 @@ class DevConfig(Config):
class TestConfig(Config):
"""Test configuration."""
TESTING = True
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite://'

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""The user module."""
from . import views
from . import views # noqa

@ -1,35 +1,40 @@
# -*- coding: utf-8 -*-
"""User forms."""
from flask_wtf import Form
from wtforms import TextField, PasswordField
from wtforms import PasswordField, StringField
from wtforms.validators import DataRequired, Email, EqualTo, Length
from .models import User
class RegisterForm(Form):
username = TextField('Username',
validators=[DataRequired(), Length(min=3, max=25)])
email = TextField('Email',
validators=[DataRequired(), Email(), Length(min=6, max=40)])
"""Register form."""
username = StringField('Username',
validators=[DataRequired(), Length(min=3, max=25)])
email = StringField('Email',
validators=[DataRequired(), Email(), Length(min=6, max=40)])
password = PasswordField('Password',
validators=[DataRequired(), Length(min=6, max=40)])
validators=[DataRequired(), Length(min=6, max=40)])
confirm = PasswordField('Verify password',
[DataRequired(), EqualTo('password', message='Passwords must match')])
[DataRequired(), EqualTo('password', message='Passwords must match')])
def __init__(self, *args, **kwargs):
"""Create instance."""
super(RegisterForm, self).__init__(*args, **kwargs)
self.user = None
def validate(self):
"""Validate the form."""
initial_validation = super(RegisterForm, self).validate()
if not initial_validation:
return False
user = User.query.filter_by(username=self.username.data).first()
if user:
self.username.errors.append("Username already registered")
self.username.errors.append('Username already registered')
return False
user = User.query.filter_by(email=self.email.data).first()
if user:
self.email.errors.append("Email already registered")
self.email.errors.append('Email already registered')
return False
return True

@ -1,33 +1,32 @@
# -*- coding: utf-8 -*-
"""User models."""
import datetime as dt
from flask_login import UserMixin
from {{cookiecutter.app_name}}.database import Column, Model, SurrogatePK, db, reference_col, relationship
from {{cookiecutter.app_name}}.extensions import bcrypt
from {{cookiecutter.app_name}}.database import (
Column,
db,
Model,
ReferenceCol,
relationship,
SurrogatePK,
)
class Role(SurrogatePK, Model):
"""A role for a user."""
__tablename__ = 'roles'
name = Column(db.String(80), unique=True, nullable=False)
user_id = ReferenceCol('users', nullable=True)
user_id = reference_col('users', nullable=True)
user = relationship('User', backref='roles')
def __init__(self, name, **kwargs):
"""Create instance."""
db.Model.__init__(self, name=name, **kwargs)
def __repr__(self):
"""Represent instance as a unique string."""
return '<Role({name})>'.format(name=self.name)
class User(UserMixin, SurrogatePK, Model):
"""A user of the app."""
__tablename__ = 'users'
username = Column(db.String(80), unique=True, nullable=False)
@ -41,6 +40,7 @@ class User(UserMixin, SurrogatePK, Model):
is_admin = Column(db.Boolean(), default=False)
def __init__(self, username, email, password=None, **kwargs):
"""Create instance."""
db.Model.__init__(self, username=username, email=email, **kwargs)
if password:
self.set_password(password)
@ -48,14 +48,18 @@ class User(UserMixin, SurrogatePK, Model):
self.password = None
def set_password(self, password):
"""Set password."""
self.password = bcrypt.generate_password_hash(password)
def check_password(self, value):
"""Check password."""
return bcrypt.check_password_hash(self.password, value)
@property
def full_name(self):
return "{0} {1}".format(self.first_name, self.last_name)
"""Full user name."""
return '{0} {1}'.format(self.first_name, self.last_name)
def __repr__(self):
"""Represent instance as a unique string."""
return '<User({username!r})>'.format(username=self.username)

@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
"""User views."""
from flask import Blueprint, render_template
from flask_login import login_required
blueprint = Blueprint("user", __name__, url_prefix='/users',
static_folder="../static")
blueprint = Blueprint('user', __name__, url_prefix='/users', static_folder='../static')
@blueprint.route("/")
@blueprint.route('/')
@login_required
def members():
return render_template("users/members.html")
"""List members."""
return render_template('users/members.html')

@ -3,9 +3,8 @@
from flask import flash
def flash_errors(form, category="warning"):
def flash_errors(form, category='warning'):
"""Flash all errors for a form."""
for field, errors in form.errors.items():
for error in errors:
flash("{0} - {1}"
.format(getattr(form, field).label.text, error), category)
flash('{0} - {1}'.format(getattr(form, field).label.text, error), category)

Loading…
Cancel
Save