From 64b644a490444d1e4fe13ddeaacba2d069631705 Mon Sep 17 00:00:00 2001 From: James Curtin Date: Sat, 8 Jun 2019 12:48:10 -0400 Subject: [PATCH 1/2] Add black formatting --- .travis.yml | 1 - cookiecutter.json | 2 +- tasks.py | 11 ++++++----- {{cookiecutter.app_name}}/.isort.cfg | 2 -- {{cookiecutter.app_name}}/.travis.yml | 6 ++---- {{cookiecutter.app_name}}/Pipfile | 3 +-- {{cookiecutter.app_name}}/README.rst | 9 +++++++-- {{cookiecutter.app_name}}/requirements/dev.txt | 4 ++-- {{cookiecutter.app_name}}/setup.cfg | 10 +++++++++- .../{{cookiecutter.app_name}}/commands.py | 17 ++++++++++++----- 10 files changed, 40 insertions(+), 25 deletions(-) delete mode 100644 {{cookiecutter.app_name}}/.isort.cfg diff --git a/.travis.yml b/.travis.yml index f4e9fa4..96b9cef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ dist: xenial language: python python: - - 3.5 - 3.6 - 3.7 diff --git a/cookiecutter.json b/cookiecutter.json index a0da127..f43940c 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -6,6 +6,6 @@ "app_name": "{{cookiecutter.project_name.lower().replace('-', '_').replace(' ', '_')}}", "project_short_description": "A flasky app.", "use_pipenv": ["no", "yes"], - "python_version": ["3.7", "3.6", "3.5"], + "python_version": ["3.7", "3.6"], "node_version": ["12", "10", "8"] } diff --git a/tasks.py b/tasks.py index ec347b6..b43973d 100644 --- a/tasks.py +++ b/tasks.py @@ -41,9 +41,12 @@ def clean(ctx): print('App directory does not exist. Skipping.') -def _run_flask_command(ctx, command): +def _run_flask_command(ctx, command, *args): os.chdir(COOKIE) - ctx.run('flask {0}'.format(command), echo=True) + flask_command = 'flask {0}'.format(command) + if args: + flask_command = '{0} {1}'.format(flask_command, ' '.join(args)) + ctx.run(flask_command, echo=True) @task(pre=[clean, build]) @@ -55,9 +58,7 @@ def test(ctx): os.chdir(COOKIE) shutil.copyfile(os.path.join(COOKIE, '.env.example'), os.path.join(COOKIE, '.env')) - os.environ["FLASK_ENV"] = "production" - os.environ["FLASK_DEBUG"] = "0" - _run_flask_command(ctx, 'lint') + _run_flask_command(ctx, 'lint', '--check') _run_flask_command(ctx, 'test') diff --git a/{{cookiecutter.app_name}}/.isort.cfg b/{{cookiecutter.app_name}}/.isort.cfg deleted file mode 100644 index 8913d96..0000000 --- a/{{cookiecutter.app_name}}/.isort.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[settings] -line_length=120 diff --git a/{{cookiecutter.app_name}}/.travis.yml b/{{cookiecutter.app_name}}/.travis.yml index 6e25e70..b345de7 100644 --- a/{{cookiecutter.app_name}}/.travis.yml +++ b/{{cookiecutter.app_name}}/.travis.yml @@ -1,11 +1,9 @@ # Config file for automatic testing at travis-ci.org +dist: xenial language: python env: - FLASK_APP=autoapp.py FLASK_DEBUG=1 python: - - 2.7 - - 3.4 - - 3.5 - 3.6 - 3.7 install: @@ -16,5 +14,5 @@ install: before_script: - npm run lint - npm run build - - flask lint + - flask lint --check script: flask test diff --git a/{{cookiecutter.app_name}}/Pipfile b/{{cookiecutter.app_name}}/Pipfile index 24e1b57..6946eca 100644 --- a/{{cookiecutter.app_name}}/Pipfile +++ b/{{cookiecutter.app_name}}/Pipfile @@ -53,12 +53,11 @@ factory-boy = "==2.12.*" pdbpp = "==0.10.0" # Lint and code style +black = "==19.3b0" flake8 = "==3.7.7" flake8-blind-except = "==0.1.1" flake8-debugger = "==3.1.0" flake8-docstrings = "==1.3.0" flake8-isort = "==2.7.0" -flake8-quotes = "==2.0.1" isort = "==4.3.20" pep8-naming = "==0.8.2" - diff --git a/{{cookiecutter.app_name}}/README.rst b/{{cookiecutter.app_name}}/README.rst index ce9b0e4..dc15fac 100644 --- a/{{cookiecutter.app_name}}/README.rst +++ b/{{cookiecutter.app_name}}/README.rst @@ -57,13 +57,18 @@ To open the interactive shell, run :: By default, you will have access to the flask ``app``. -Running Tests -------------- +Running Tests/Linter +-------------------- To run all tests, run :: flask test +To run the linter, run :: + + flask lint + +The ``lint`` command will attempt to fix any linting/style errors in the code. If you only want to know if the code will pass CI and do not wish for the linter to make changes, add the ``--check`` argument. Migrations ---------- diff --git a/{{cookiecutter.app_name}}/requirements/dev.txt b/{{cookiecutter.app_name}}/requirements/dev.txt index 3417eb9..5054d96 100644 --- a/{{cookiecutter.app_name}}/requirements/dev.txt +++ b/{{cookiecutter.app_name}}/requirements/dev.txt @@ -8,11 +8,11 @@ factory-boy==2.11.1 pdbpp==0.10.0 # Lint and code style -flake8==3.5.0 +black==19.3b0 +flake8==3.7.7 flake8-blind-except==0.1.1 flake8-debugger==3.1.0 flake8-docstrings==1.3.0 flake8-isort==2.5 -flake8-quotes==1.0.0 isort==4.3.4 pep8-naming==0.7.0 diff --git a/{{cookiecutter.app_name}}/setup.cfg b/{{cookiecutter.app_name}}/setup.cfg index f5261ad..97ac5d6 100644 --- a/{{cookiecutter.app_name}}/setup.cfg +++ b/{{cookiecutter.app_name}}/setup.cfg @@ -1,3 +1,11 @@ [flake8] -ignore = D401 +ignore = D401,D202,E226,E302,E41 max-line-length=120 +exclude = migrations/* +max-complexity = 10 + +[isort] +line_length=88 +multi_line_output=3 +skip=migrations/* +include_trailing_comma=true diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/commands.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/commands.py index a6679d6..eac7ccd 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/commands.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/commands.py @@ -23,11 +23,12 @@ def test(): @click.command() -@click.option('-f', '--fix-imports', default=False, is_flag=True, +@click.option('-f', '--fix-imports', default=True, is_flag=True, help='Fix imports using isort, before linting') -def lint(fix_imports): - """Lint and check code style with flake8 and isort.""" - skip = ['node_modules', 'requirements'] +@click.option('-c', '--check', default=False, is_flag=True, help="Don't make any changes to files, just confirm they are formatted correctly") +def lint(fix_imports, check): + """Lint and check code style with black, flake8 and isort.""" + skip = ['node_modules', 'requirements', 'migrations'] root_files = glob('*.py') root_directories = [ name for name in next(os.walk('.'))[1] if not name.startswith('.')] @@ -42,8 +43,14 @@ def lint(fix_imports): if rv != 0: exit(rv) + isort_args = ["-rc"] + black_args = [] + if check: + isort_args.append('-c') + black_args.append('--check') if fix_imports: - execute_tool('Fixing import order', 'isort', '-rc') + execute_tool('Fixing import order', 'isort', *isort_args) + execute_tool('Formatting style', 'black', *black_args) execute_tool('Checking code style', 'flake8') From d2f37093e15635a04aca4e097ecce2388eeb62c8 Mon Sep 17 00:00:00 2001 From: James Curtin Date: Sat, 8 Jun 2019 13:20:56 -0400 Subject: [PATCH 2/2] One-time black refactor of template --- hooks/post_gen_project.py | 16 ++-- hooks/pre_gen_project.py | 1 - tasks.py | 44 ++++----- {{cookiecutter.app_name}}/package.json | 4 +- {{cookiecutter.app_name}}/tests/conftest.py | 12 +-- {{cookiecutter.app_name}}/tests/factories.py | 6 +- {{cookiecutter.app_name}}/tests/settings.py | 14 +-- {{cookiecutter.app_name}}/tests/test_forms.py | 45 ++++++---- .../tests/test_functional.py | 82 ++++++++--------- .../tests/test_models.py | 25 +++--- {{cookiecutter.app_name}}/webpack.config.js | 6 +- .../{{cookiecutter.app_name}}/app.py | 28 ++++-- .../{{cookiecutter.app_name}}/commands.py | 89 +++++++++++-------- .../{{cookiecutter.app_name}}/database.py | 18 ++-- .../{{cookiecutter.app_name}}/public/forms.py | 10 +-- .../{{cookiecutter.app_name}}/public/views.py | 49 ++++++---- .../{{cookiecutter.app_name}}/settings.py | 14 +-- .../{{cookiecutter.app_name}}/user/forms.py | 25 +++--- .../{{cookiecutter.app_name}}/user/models.py | 23 +++-- .../{{cookiecutter.app_name}}/user/views.py | 6 +- .../{{cookiecutter.app_name}}/utils.py | 4 +- 21 files changed, 292 insertions(+), 229 deletions(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index e9645f4..1bd084e 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -9,13 +9,13 @@ import sys def clean_extra_package_management_files(): """Removes either requirements files and folder or the Pipfile.""" - use_pipenv = '{{cookiecutter.use_pipenv}}' + use_pipenv = "{{cookiecutter.use_pipenv}}" to_delete = [] - if use_pipenv == 'yes': - to_delete = to_delete + ['requirements.txt', 'requirements'] + if use_pipenv == "yes": + to_delete = to_delete + ["requirements.txt", "requirements"] else: - to_delete.append('Pipfile') + to_delete.append("Pipfile") try: for file_or_dir in to_delete: @@ -25,11 +25,9 @@ def clean_extra_package_management_files(): shutil.rmtree(file_or_dir) sys.exit(0) except OSError as e: - sys.stdout.write( - 'While attempting to remove file(s) an error occurred' - ) - sys.stdout.write('Error: {}'.format(e)) + sys.stdout.write("While attempting to remove file(s) an error occurred") + sys.stdout.write("Error: {}".format(e)) -if __name__ == '__main__': +if __name__ == "__main__": clean_extra_package_management_files() diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 374a852..cb782f2 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -1,7 +1,6 @@ import re import sys - MODULE_REGEX = r"^[_a-zA-Z][_a-zA-Z0-9]+$" diff --git a/tasks.py b/tasks.py index b43973d..5503af6 100644 --- a/tasks.py +++ b/tasks.py @@ -1,34 +1,34 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """Invoke tasks.""" -import os import json +import os import shutil import webbrowser from invoke import task HERE = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(HERE, 'cookiecutter.json'), 'r') as fp: +with open(os.path.join(HERE, "cookiecutter.json"), "r") as fp: COOKIECUTTER_SETTINGS = json.load(fp) # Match default value of app_name from cookiecutter.json -COOKIECUTTER_SETTINGS["app_name"] = 'my_flask_app' -COOKIE = os.path.join(HERE, COOKIECUTTER_SETTINGS['app_name']) -REQUIREMENTS = os.path.join(COOKIE, 'requirements', 'dev.txt') +COOKIECUTTER_SETTINGS["app_name"] = "my_flask_app" +COOKIE = os.path.join(HERE, COOKIECUTTER_SETTINGS["app_name"]) +REQUIREMENTS = os.path.join(COOKIE, "requirements", "dev.txt") def _run_npm_command(ctx, command): os.chdir(COOKIE) - ctx.run('npm {0}'.format(command), echo=True) + ctx.run("npm {0}".format(command), echo=True) os.chdir(HERE) @task def build(ctx): """Build the cookiecutter.""" - ctx.run('cookiecutter {0} --no-input'.format(HERE)) - _run_npm_command(ctx, 'install') - _run_npm_command(ctx, 'run build') + ctx.run("cookiecutter {0} --no-input".format(HERE)) + _run_npm_command(ctx, "install") + _run_npm_command(ctx, "run build") @task @@ -36,34 +36,34 @@ def clean(ctx): """Clean out generated cookiecutter.""" if os.path.exists(COOKIE): shutil.rmtree(COOKIE) - print('Removed {0}'.format(COOKIE)) + print("Removed {0}".format(COOKIE)) else: - print('App directory does not exist. Skipping.') + print("App directory does not exist. Skipping.") def _run_flask_command(ctx, command, *args): os.chdir(COOKIE) - flask_command = 'flask {0}'.format(command) + flask_command = "flask {0}".format(command) if args: - flask_command = '{0} {1}'.format(flask_command, ' '.join(args)) + flask_command = "{0} {1}".format(flask_command, " ".join(args)) ctx.run(flask_command, echo=True) @task(pre=[clean, build]) def test(ctx): """Run lint commands and tests.""" - ctx.run('pip install -r {0} --ignore-installed'.format(REQUIREMENTS), - echo=True) - _run_npm_command(ctx, 'run lint') + ctx.run("pip install -r {0} --ignore-installed".format(REQUIREMENTS), echo=True) + _run_npm_command(ctx, "run lint") os.chdir(COOKIE) - shutil.copyfile(os.path.join(COOKIE, '.env.example'), - os.path.join(COOKIE, '.env')) - _run_flask_command(ctx, 'lint', '--check') - _run_flask_command(ctx, 'test') + shutil.copyfile(os.path.join(COOKIE, ".env.example"), os.path.join(COOKIE, ".env")) + os.environ["FLASK_ENV"] = "production" + os.environ["FLASK_DEBUG"] = "0" + _run_flask_command(ctx, "lint", "--check") + _run_flask_command(ctx, "test") @task def readme(ctx, browse=False): - ctx.run('rst2html.py README.rst > README.html') + ctx.run("rst2html.py README.rst > README.html") if browse: - webbrowser.open_new_tab('README.html') + webbrowser.open_new_tab("README.html") diff --git a/{{cookiecutter.app_name}}/package.json b/{{cookiecutter.app_name}}/package.json index 59ac40e..85a483d 100644 --- a/{{cookiecutter.app_name}}/package.json +++ b/{{cookiecutter.app_name}}/package.json @@ -15,7 +15,9 @@ }, "author": "{{cookiecutter.full_name}}", "license": "MIT", - "engines": { "node" : ">={{cookiecutter.node_version}}" }, + "engines": { + "node": ">={{cookiecutter.node_version}}" + }, "bugs": { "url": "https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.app_name}}/issues" }, diff --git a/{{cookiecutter.app_name}}/tests/conftest.py b/{{cookiecutter.app_name}}/tests/conftest.py index 5bb60c0..2cbe45f 100644 --- a/{{cookiecutter.app_name}}/tests/conftest.py +++ b/{{cookiecutter.app_name}}/tests/conftest.py @@ -12,8 +12,8 @@ from .factories import UserFactory @pytest.fixture def app(): - """An application for the tests.""" - _app = create_app('tests.settings') + """Create application for the tests.""" + _app = create_app("tests.settings") ctx = _app.test_request_context() ctx.push() @@ -24,13 +24,13 @@ def app(): @pytest.fixture def testapp(app): - """A Webtest app.""" + """Create Webtest app.""" return TestApp(app) @pytest.fixture def db(app): - """A database for the tests.""" + """Create database for the tests.""" _db.app = app with app.app_context(): _db.create_all() @@ -44,7 +44,7 @@ def db(app): @pytest.fixture def user(db): - """A user for the tests.""" - user = UserFactory(password='myprecious') + """Create user for the tests.""" + user = UserFactory(password="myprecious") db.session.commit() return user diff --git a/{{cookiecutter.app_name}}/tests/factories.py b/{{cookiecutter.app_name}}/tests/factories.py index 33efe7a..17d0203 100644 --- a/{{cookiecutter.app_name}}/tests/factories.py +++ b/{{cookiecutter.app_name}}/tests/factories.py @@ -20,9 +20,9 @@ class BaseFactory(SQLAlchemyModelFactory): class UserFactory(BaseFactory): """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') + 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: diff --git a/{{cookiecutter.app_name}}/tests/settings.py b/{{cookiecutter.app_name}}/tests/settings.py index a13e159..650229b 100644 --- a/{{cookiecutter.app_name}}/tests/settings.py +++ b/{{cookiecutter.app_name}}/tests/settings.py @@ -1,11 +1,13 @@ """Settings module for test app.""" -ENV = 'development' +ENV = "development" TESTING = True -SQLALCHEMY_DATABASE_URI = 'sqlite://' -SECRET_KEY = 'not-so-secret-in-tests' -BCRYPT_LOG_ROUNDS = 4 # For faster tests; needs at least 4 to avoid "ValueError: Invalid rounds" +SQLALCHEMY_DATABASE_URI = "sqlite://" +SECRET_KEY = "not-so-secret-in-tests" +BCRYPT_LOG_ROUNDS = ( + 4 +) # For faster tests; needs at least 4 to avoid "ValueError: Invalid rounds" DEBUG_TB_ENABLED = False -CACHE_TYPE = 'simple' # Can be "memcached", "redis", etc. +CACHE_TYPE = "simple" # Can be "memcached", "redis", etc. SQLALCHEMY_TRACK_MODIFICATIONS = False -WEBPACK_MANIFEST_PATH = 'webpack/manifest.json' +WEBPACK_MANIFEST_PATH = "webpack/manifest.json" WTF_CSRF_ENABLED = False # Allows form testing diff --git a/{{cookiecutter.app_name}}/tests/test_forms.py b/{{cookiecutter.app_name}}/tests/test_forms.py index 590d07f..af44410 100644 --- a/{{cookiecutter.app_name}}/tests/test_forms.py +++ b/{{cookiecutter.app_name}}/tests/test_forms.py @@ -10,24 +10,33 @@ class TestRegisterForm: def test_validate_user_already_registered(self, user): """Enter username that is already registered.""" - form = RegisterForm(username=user.username, email='foo@bar.com', - password='example', confirm='example') + form = RegisterForm( + username=user.username, + email="foo@bar.com", + password="example", + confirm="example", + ) assert form.validate() is False - assert 'Username already registered' in form.username.errors + assert "Username already registered" in form.username.errors def test_validate_email_already_registered(self, user): """Enter email that is already registered.""" - form = RegisterForm(username='unique', email=user.email, - password='example', confirm='example') + form = RegisterForm( + username="unique", email=user.email, password="example", confirm="example" + ) assert form.validate() is False - assert 'Email already registered' in form.email.errors + 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') + form = RegisterForm( + username="newusername", + email="new@test.test", + password="example", + confirm="example", + ) assert form.validate() is True @@ -36,33 +45,33 @@ class TestLoginForm: def test_validate_success(self, user): """Login successful.""" - user.set_password('example') + user.set_password("example") user.save() - form = LoginForm(username=user.username, password='example') + form = LoginForm(username=user.username, password="example") assert form.validate() is True assert form.user == user def test_validate_unknown_username(self, db): """Unknown username.""" - form = LoginForm(username='unknown', password='example') + form = LoginForm(username="unknown", password="example") assert form.validate() is False - assert 'Unknown username' in form.username.errors + 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.set_password("example") user.save() - form = LoginForm(username=user.username, password='wrongpassword') + form = LoginForm(username=user.username, password="wrongpassword") assert form.validate() is False - assert 'Invalid password' in form.password.errors + assert "Invalid password" in form.password.errors def test_validate_inactive_user(self, user): """Inactive user.""" user.active = False - user.set_password('example') + user.set_password("example") user.save() # Correct username and password, but user is not activated - form = LoginForm(username=user.username, password='example') + form = LoginForm(username=user.username, password="example") assert form.validate() is False - assert 'User not activated' in form.username.errors + assert "User not activated" in form.username.errors diff --git a/{{cookiecutter.app_name}}/tests/test_functional.py b/{{cookiecutter.app_name}}/tests/test_functional.py index 6b9151f..804be63 100644 --- a/{{cookiecutter.app_name}}/tests/test_functional.py +++ b/{{cookiecutter.app_name}}/tests/test_functional.py @@ -16,53 +16,53 @@ class TestLoggingIn: 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 - form['password'] = 'myprecious' + form = res.forms["loginForm"] + form["username"] = user.username + form["password"] = "myprecious" # Submits res = form.submit().follow() assert res.status_code == 200 def test_sees_alert_on_log_out(self, user, testapp): """Show alert on logout.""" - res = testapp.get('/') + res = testapp.get("/") # Fills out login form in navbar - form = res.forms['loginForm'] - form['username'] = user.username - form['password'] = 'myprecious' + form = res.forms["loginForm"] + form["username"] = user.username + form["password"] = "myprecious" # Submits res = form.submit().follow() - res = testapp.get(url_for('public.logout')).follow() + res = testapp.get(url_for("public.logout")).follow() # sees alert - assert 'You are logged out.' in res + 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 - form['password'] = 'wrong' + form = res.forms["loginForm"] + form["username"] = user.username + form["password"] = "wrong" # 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' - form['password'] = 'myprecious' + form = res.forms["loginForm"] + form["username"] = "unknown" + form["password"] = "myprecious" # Submits res = form.submit() # sees error - assert 'Unknown user' in res + assert "Unknown user" in res class TestRegistering: @@ -72,15 +72,15 @@ class TestRegistering: """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['username'] = 'foobar' - form['email'] = 'foo@bar.com' - form['password'] = 'secret' - form['confirm'] = 'secret' + form = res.forms["registerForm"] + form["username"] = "foobar" + form["email"] = "foo@bar.com" + form["password"] = "secret" + form["confirm"] = "secret" # Submits res = form.submit().follow() assert res.status_code == 200 @@ -90,31 +90,31 @@ class TestRegistering: 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['username'] = 'foobar' - form['email'] = 'foo@bar.com' - form['password'] = 'secret' - form['confirm'] = 'secrets' + form = res.forms["registerForm"] + form["username"] = "foobar" + form["email"] = "foo@bar.com" + form["password"] = "secret" + form["confirm"] = "secrets" # 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['username'] = user.username - form['email'] = 'foo@bar.com' - form['password'] = 'secret' - form['confirm'] = 'secret' + form = res.forms["registerForm"] + form["username"] = user.username + form["email"] = "foo@bar.com" + form["password"] = "secret" + form["confirm"] = "secret" # Submits res = form.submit() # sees error - assert 'Username already registered' in res + assert "Username already registered" in res diff --git a/{{cookiecutter.app_name}}/tests/test_models.py b/{{cookiecutter.app_name}}/tests/test_models.py index 4fe897e..813a82e 100644 --- a/{{cookiecutter.app_name}}/tests/test_models.py +++ b/{{cookiecutter.app_name}}/tests/test_models.py @@ -9,13 +9,13 @@ from {{cookiecutter.app_name}}.user.models import Role, User from .factories import UserFactory -@pytest.mark.usefixtures('db') +@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 = User("foo", "foo@bar.com") user.save() retrieved = User.get_by_id(user.id) @@ -23,43 +23,42 @@ class TestUser: def test_created_at_defaults_to_datetime(self): """Test creation date.""" - user = User(username='foo', email='foo@bar.com') + 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 = User(username="foo", email="foo@bar.com") user.save() assert user.password is None def test_factory(self, db): """Test user factory.""" - user = UserFactory(password='myprecious') + user = UserFactory(password="myprecious") db.session.commit() assert bool(user.username) assert bool(user.email) assert bool(user.created_at) assert user.is_admin is False assert user.active is True - assert user.check_password('myprecious') + assert user.check_password("myprecious") def test_check_password(self): """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 + 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 def test_full_name(self): """User full name.""" - user = UserFactory(first_name='Foo', last_name='Bar') - assert user.full_name == 'Foo Bar' + 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 = Role(name="admin") role.save() user = UserFactory() user.roles.append(role) diff --git a/{{cookiecutter.app_name}}/webpack.config.js b/{{cookiecutter.app_name}}/webpack.config.js index 10cd55f..0491cd2 100644 --- a/{{cookiecutter.app_name}}/webpack.config.js +++ b/{{cookiecutter.app_name}}/webpack.config.js @@ -67,8 +67,10 @@ module.exports = { }, { test: /\.html$/, loader: 'raw-loader' }, { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url-loader', options: { limit: 10000, mimetype: 'application/font-woff' } }, - { test: /\.(ttf|eot|svg|png|jpe?g|gif|ico)(\?.*)?$/i, - loader: `file-loader?context=${rootAssetPath}&name=[path][name].[hash].[ext]` }, + { + test: /\.(ttf|eot|svg|png|jpe?g|gif|ico)(\?.*)?$/i, + loader: `file-loader?context=${rootAssetPath}&name=[path][name].[hash].[ext]` + }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', query: { presets: ['env'], cacheDirectory: true } }, ], }, diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py index efa9fb7..3c7ba64 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py @@ -6,15 +6,24 @@ import sys from flask import Flask, render_template from {{cookiecutter.app_name}} import commands, public, user -from {{cookiecutter.app_name}}.extensions import bcrypt, cache, csrf_protect, db, debug_toolbar, login_manager, migrate, webpack +from {{cookiecutter.app_name}}.extensions import ( + bcrypt, + cache, + csrf_protect, + db, + debug_toolbar, + login_manager, + migrate, + webpack, +) -def create_app(config_object='{{cookiecutter.app_name}}.settings'): - """An application factory, as explained here: http://flask.pocoo.org/docs/patterns/appfactories/. +def create_app(config_object="{{cookiecutter.app_name}}.settings"): + """Create application factory, as explained here: http://flask.pocoo.org/docs/patterns/appfactories/. :param config_object: The configuration object to use. """ - app = Flask(__name__.split('.')[0]) + app = Flask(__name__.split(".")[0]) app.config.from_object(config_object) register_extensions(app) register_blueprints(app) @@ -47,11 +56,13 @@ def register_blueprints(app): 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 + error_code = getattr(error, "code", 500) + return render_template("{0}.html".format(error_code)), error_code + for errcode in [401, 404, 500]: app.errorhandler(errcode)(render_error) return None @@ -59,11 +70,10 @@ def register_errorhandlers(app): def register_shellcontext(app): """Register shell context objects.""" + def shell_context(): """Shell context objects.""" - return { - 'db': db, - 'User': user.models.User} + return {"db": db, "User": user.models.User} app.shell_context_processor(shell_context) diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/commands.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/commands.py index eac7ccd..d0bcd92 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/commands.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/commands.py @@ -11,34 +11,48 @@ from werkzeug.exceptions import MethodNotAllowed, NotFound HERE = os.path.abspath(os.path.dirname(__file__)) PROJECT_ROOT = os.path.join(HERE, os.pardir) -TEST_PATH = os.path.join(PROJECT_ROOT, 'tests') +TEST_PATH = os.path.join(PROJECT_ROOT, "tests") @click.command() def test(): """Run the tests.""" import pytest - rv = pytest.main([TEST_PATH, '--verbose']) + + rv = pytest.main([TEST_PATH, "--verbose"]) exit(rv) @click.command() -@click.option('-f', '--fix-imports', default=True, is_flag=True, - help='Fix imports using isort, before linting') -@click.option('-c', '--check', default=False, is_flag=True, help="Don't make any changes to files, just confirm they are formatted correctly") +@click.option( + "-f", + "--fix-imports", + default=True, + is_flag=True, + help="Fix imports using isort, before linting", +) +@click.option( + "-c", + "--check", + default=False, + is_flag=True, + help="Don't make any changes to files, just confirm they are formatted correctly", +) def lint(fix_imports, check): """Lint and check code style with black, flake8 and isort.""" - skip = ['node_modules', 'requirements', 'migrations'] - root_files = glob('*.py') + skip = ["node_modules", "requirements", "migrations"] + root_files = glob("*.py") root_directories = [ - name for name in next(os.walk('.'))[1] if not name.startswith('.')] + 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] + 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 - click.echo('{}: {}'.format(description, ' '.join(command_line))) + click.echo("{}: {}".format(description, " ".join(command_line))) rv = call(command_line) if rv != 0: exit(rv) @@ -46,12 +60,12 @@ def lint(fix_imports, check): isort_args = ["-rc"] black_args = [] if check: - isort_args.append('-c') - black_args.append('--check') + isort_args.append("-c") + black_args.append("--check") if fix_imports: - execute_tool('Fixing import order', 'isort', *isort_args) - execute_tool('Formatting style', 'black', *black_args) - execute_tool('Checking code style', 'flake8') + execute_tool("Fixing import order", "isort", *isort_args) + execute_tool("Formatting style", "black", *black_args) + execute_tool("Checking code style", "flake8") @click.command() @@ -60,19 +74,19 @@ def clean(): Borrowed from Flask-Script, converted to use Click. """ - for dirpath, dirnames, filenames in os.walk('.'): + for dirpath, dirnames, filenames in os.walk("."): for filename in filenames: - if filename.endswith('.pyc') or filename.endswith('.pyo'): + if filename.endswith(".pyc") or filename.endswith(".pyo"): full_pathname = os.path.join(dirpath, filename) - click.echo('Removing {}'.format(full_pathname)) + click.echo("Removing {}".format(full_pathname)) os.remove(full_pathname) @click.command() -@click.option('--url', default=None, - help='Url to test (ex. /static/image.png)') -@click.option('--order', default='rule', - help='Property on Rule to order by (default: rule)') +@click.option("--url", default=None, help="Url to test (ex. /static/image.png)") +@click.option( + "--order", default="rule", help="Property on Rule to order by (default: rule)" +) @with_appcontext def urls(url, order): """Display all of the url matching routes for the project. @@ -81,53 +95,50 @@ def urls(url, order): """ rows = [] column_length = 0 - column_headers = ('Rule', 'Endpoint', 'Arguments') + column_headers = ("Rule", "Endpoint", "Arguments") if url: try: - rule, arguments = ( - current_app.url_map - .bind('localhost') - .match(url, return_rule=True)) + rule, arguments = current_app.url_map.bind("localhost").match( + url, return_rule=True + ) rows.append((rule.rule, rule.endpoint, arguments)) column_length = 3 except (NotFound, MethodNotAllowed) as e: - rows.append(('<{}>'.format(e), None, None)) + rows.append(("<{}>".format(e), None, None)) column_length = 1 else: rules = sorted( - current_app.url_map.iter_rules(), - key=lambda rule: getattr(rule, order)) + current_app.url_map.iter_rules(), key=lambda rule: getattr(rule, order) + ) for rule in rules: rows.append((rule.rule, rule.endpoint, None)) column_length = 2 - str_template = '' + str_template = "" table_width = 0 if column_length >= 1: max_rule_length = max(len(r[0]) for r in rows) max_rule_length = max_rule_length if max_rule_length > 4 else 4 - str_template += '{:' + str(max_rule_length) + '}' + str_template += "{:" + str(max_rule_length) + "}" table_width += max_rule_length if column_length >= 2: max_endpoint_length = max(len(str(r[1])) for r in rows) # max_endpoint_length = max(rows, key=len) - max_endpoint_length = ( - max_endpoint_length if max_endpoint_length > 8 else 8) - str_template += ' {:' + str(max_endpoint_length) + '}' + max_endpoint_length = max_endpoint_length if max_endpoint_length > 8 else 8 + str_template += " {:" + str(max_endpoint_length) + "}" table_width += 2 + max_endpoint_length if column_length >= 3: max_arguments_length = max(len(str(r[2])) for r in rows) - max_arguments_length = ( - max_arguments_length if max_arguments_length > 9 else 9) - str_template += ' {:' + str(max_arguments_length) + '}' + max_arguments_length = max_arguments_length if max_arguments_length > 9 else 9 + str_template += " {:" + str(max_arguments_length) + "}" table_width += 2 + max_arguments_length click.echo(str_template.format(*column_headers[:column_length])) - click.echo('-' * table_width) + click.echo("-" * table_width) for row in rows: click.echo(str_template.format(*row[:column_length])) diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py index e183940..17c612d 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py @@ -47,7 +47,7 @@ class Model(CRUDMixin, db.Model): class SurrogatePK(object): """A mixin that adds a surrogate integer 'primary key' column named ``id`` to any declarative-mapped class.""" - __table_args__ = {'extend_existing': True} + __table_args__ = {"extend_existing": True} id = Column(db.Integer, primary_key=True) @@ -55,14 +55,18 @@ class SurrogatePK(object): def get_by_id(cls, record_id): """Get record by ID.""" if any( - (isinstance(record_id, basestring) and record_id.isdigit(), - isinstance(record_id, (int, float))), + ( + isinstance(record_id, basestring) and record_id.isdigit(), + isinstance(record_id, (int, float)), + ) ): return cls.query.get(int(record_id)) return None -def reference_col(tablename, nullable=False, pk_name='id', foreign_key_kwargs=None, column_kwargs=None): +def reference_col( + tablename, nullable=False, pk_name="id", foreign_key_kwargs=None, column_kwargs=None +): """Column that adds primary key foreign key reference. Usage: :: @@ -74,5 +78,7 @@ def reference_col(tablename, nullable=False, pk_name='id', foreign_key_kwargs=No column_kwargs = column_kwargs or {} return Column( - db.ForeignKey('{0}.{1}'.format(tablename, pk_name), **foreign_key_kwargs), - nullable=nullable, **column_kwargs) + db.ForeignKey("{0}.{1}".format(tablename, pk_name), **foreign_key_kwargs), + nullable=nullable, + **column_kwargs + ) diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py index d4c873b..a5f203d 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py @@ -10,8 +10,8 @@ from {{cookiecutter.app_name}}.user.models import User class LoginForm(FlaskForm): """Login form.""" - username = StringField('Username', validators=[DataRequired()]) - password = PasswordField('Password', validators=[DataRequired()]) + username = StringField("Username", validators=[DataRequired()]) + password = PasswordField("Password", validators=[DataRequired()]) def __init__(self, *args, **kwargs): """Create instance.""" @@ -26,14 +26,14 @@ class LoginForm(FlaskForm): self.user = User.query.filter_by(username=self.username.data).first() if not self.user: - self.username.errors.append('Unknown username') + self.username.errors.append("Unknown username") return False if not self.user.check_password(self.password.data): - self.password.errors.append('Invalid password') + self.password.errors.append("Invalid password") return False if not self.user.active: - self.username.errors.append('User not activated') + self.username.errors.append("User not activated") return False return True diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py index c64810d..0eae698 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py @@ -1,6 +1,14 @@ # -*- coding: utf-8 -*- """Public section, including homepage and signup.""" -from flask import Blueprint, current_app, flash, redirect, render_template, request, url_for +from flask import ( + Blueprint, + current_app, + 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 @@ -9,7 +17,7 @@ from {{cookiecutter.app_name}}.user.forms import RegisterForm from {{cookiecutter.app_name}}.user.models import User from {{cookiecutter.app_name}}.utils import flash_errors -blueprint = Blueprint('public', __name__, static_folder='../static') +blueprint = Blueprint("public", __name__, static_folder="../static") @login_manager.user_loader @@ -18,47 +26,52 @@ def load_user(user_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) - current_app.logger.info('Hello from the home page!') + current_app.logger.info("Hello from the home page!") # Handle logging in - if request.method == 'POST': + 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/') +@blueprint.route("/logout/") @login_required def logout(): """Logout.""" logout_user() - flash('You are logged out.', 'info') - return redirect(url_for('public.home')) + 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) if form.validate_on_submit(): - 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')) + 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) + 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) diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py index 4c920b0..2420da0 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py @@ -11,13 +11,13 @@ from environs import Env env = Env() env.read_env() -ENV = env.str('FLASK_ENV', default='production') -DEBUG = ENV == 'development' -SQLALCHEMY_DATABASE_URI = env.str('DATABASE_URL') -SECRET_KEY = env.str('SECRET_KEY') -BCRYPT_LOG_ROUNDS = env.int('BCRYPT_LOG_ROUNDS', default=13) +ENV = env.str("FLASK_ENV", default="production") +DEBUG = ENV == "development" +SQLALCHEMY_DATABASE_URI = env.str("DATABASE_URL") +SECRET_KEY = env.str("SECRET_KEY") +BCRYPT_LOG_ROUNDS = env.int("BCRYPT_LOG_ROUNDS", default=13) DEBUG_TB_ENABLED = DEBUG DEBUG_TB_INTERCEPT_REDIRECTS = False -CACHE_TYPE = 'simple' # Can be "memcached", "redis", etc. +CACHE_TYPE = "simple" # Can be "memcached", "redis", etc. SQLALCHEMY_TRACK_MODIFICATIONS = False -WEBPACK_MANIFEST_PATH = 'webpack/manifest.json' +WEBPACK_MANIFEST_PATH = "webpack/manifest.json" diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py index a3a05f1..402d7ad 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py @@ -10,14 +10,19 @@ from .models import User class RegisterForm(FlaskForm): """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)]) - confirm = PasswordField('Verify password', - [DataRequired(), EqualTo('password', message='Passwords must match')]) + 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)] + ) + confirm = PasswordField( + "Verify password", + [DataRequired(), EqualTo("password", message="Passwords must match")], + ) def __init__(self, *args, **kwargs): """Create instance.""" @@ -31,10 +36,10 @@ class RegisterForm(FlaskForm): 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 diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py index 721dcf8..3914274 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py @@ -4,17 +4,24 @@ 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}}.database import ( + Column, + Model, + SurrogatePK, + db, + reference_col, + relationship, +) from {{cookiecutter.app_name}}.extensions import bcrypt class Role(SurrogatePK, Model): """A role for a user.""" - __tablename__ = 'roles' + __tablename__ = "roles" name = Column(db.String(80), unique=True, nullable=False) - user_id = reference_col('users', nullable=True) - user = relationship('User', backref='roles') + user_id = reference_col("users", nullable=True) + user = relationship("User", backref="roles") def __init__(self, name, **kwargs): """Create instance.""" @@ -22,13 +29,13 @@ class Role(SurrogatePK, Model): def __repr__(self): """Represent instance as a unique string.""" - return ''.format(name=self.name) + return "".format(name=self.name) class User(UserMixin, SurrogatePK, Model): """A user of the app.""" - __tablename__ = 'users' + __tablename__ = "users" username = Column(db.String(80), unique=True, nullable=False) email = Column(db.String(80), unique=True, nullable=False) #: The hashed password @@ -58,8 +65,8 @@ class User(UserMixin, SurrogatePK, Model): @property def full_name(self): """Full user name.""" - return '{0} {1}'.format(self.first_name, self.last_name) + return "{0} {1}".format(self.first_name, self.last_name) def __repr__(self): """Represent instance as a unique string.""" - return ''.format(username=self.username) + return "".format(username=self.username) diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py index 2dff555..d3b3358 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py @@ -3,11 +3,11 @@ 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(): """List members.""" - return render_template('users/members.html') + return render_template("users/members.html") diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/utils.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/utils.py index 1bafe98..5e93055 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/utils.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/utils.py @@ -3,8 +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)