Merge branch 'master' into bootstrap4

master
Kirill Malev 6 years ago committed by GitHub
commit d4175ad7fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      README.rst
  2. 3
      cookiecutter.json
  3. 35
      hooks/post_gen_project.py
  4. 10
      tasks.py
  5. 5
      {{cookiecutter.app_name}}/.env.example
  6. 6
      {{cookiecutter.app_name}}/.gitignore
  7. 60
      {{cookiecutter.app_name}}/Pipfile
  8. 22
      {{cookiecutter.app_name}}/README.rst
  9. 7
      {{cookiecutter.app_name}}/autoapp.py
  10. 22
      {{cookiecutter.app_name}}/package.json
  11. 4
      {{cookiecutter.app_name}}/requirements/dev.txt
  12. 7
      {{cookiecutter.app_name}}/requirements/prod.txt
  13. 3
      {{cookiecutter.app_name}}/tests/conftest.py
  14. 11
      {{cookiecutter.app_name}}/tests/settings.py
  15. 19
      {{cookiecutter.app_name}}/tests/test_config.py
  16. 3
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py
  17. 56
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py

@ -25,6 +25,7 @@ Features
- Bootstrap 3 and Font Awesome 4 with starter templates - Bootstrap 3 and Font Awesome 4 with starter templates
- Flask-SQLAlchemy with basic User model - Flask-SQLAlchemy with basic User model
- Easy database migrations with Flask-Migrate - Easy database migrations with Flask-Migrate
- Configuration in environment variables, as per `The Twelve-Factor App <https://12factor.net/config>`_
- Flask-WTForms with login and registration forms - Flask-WTForms with login and registration forms
- Flask-Login for authentication - Flask-Login for authentication
- Flask-Bcrypt for password hashing - Flask-Bcrypt for password hashing

@ -4,5 +4,6 @@
"github_username": "sloria", "github_username": "sloria",
"project_name": "My Flask App", "project_name": "My Flask App",
"app_name": "myflaskapp", "app_name": "myflaskapp",
"project_short_description": "A flasky app." "project_short_description": "A flasky app.",
"use_pipenv": ["no", "yes"]
} }

@ -0,0 +1,35 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Post gen hook to ensure that the generated project
hase only one package managment, either pipenv or pip."""
import os
import shutil
import sys
def clean_extra_package_managment_files():
"""Removes either requirements files and folderor the Pipfile."""
use_pipenv = '{{cookiecutter.use_pipenv}}'
to_delete = []
if use_pipenv == 'yes':
to_delete = to_delete + ['requirements.txt', 'requirements']
else:
to_delete.append('Pipfile')
try:
for file_or_dir in to_delete:
if os.path.isfile(file_or_dir):
os.remove(file_or_dir)
else:
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))
if __name__ == '__main__':
clean_extra_package_managment_files()

@ -13,7 +13,6 @@ with open(os.path.join(HERE, 'cookiecutter.json'), 'r') as fp:
COOKIECUTTER_SETTINGS = json.load(fp) COOKIECUTTER_SETTINGS = json.load(fp)
# Match default value of app_name from cookiecutter.json # Match default value of app_name from cookiecutter.json
COOKIE = os.path.join(HERE, COOKIECUTTER_SETTINGS['app_name']) COOKIE = os.path.join(HERE, COOKIECUTTER_SETTINGS['app_name'])
AUTOAPP = os.path.join(COOKIE, 'autoapp.py')
REQUIREMENTS = os.path.join(COOKIE, 'requirements', 'dev.txt') REQUIREMENTS = os.path.join(COOKIE, 'requirements', 'dev.txt')
@ -42,7 +41,8 @@ def clean(ctx):
def _run_flask_command(ctx, command): def _run_flask_command(ctx, command):
ctx.run('FLASK_APP={0} flask {1}'.format(AUTOAPP, command), echo=True) os.chdir(COOKIE)
ctx.run('flask {0}'.format(command), echo=True)
@task(pre=[clean, build]) @task(pre=[clean, build])
@ -52,12 +52,14 @@ def test(ctx):
echo=True) echo=True)
_run_npm_command(ctx, 'run lint') _run_npm_command(ctx, 'run lint')
os.chdir(COOKIE) os.chdir(COOKIE)
shutil.copyfile(os.path.join(COOKIE, '.env.example'),
os.path.join(COOKIE, '.env'))
_run_flask_command(ctx, 'lint') _run_flask_command(ctx, 'lint')
_run_flask_command(ctx, 'test') _run_flask_command(ctx, 'test')
@task @task
def readme(ctx, browse=False): def readme(ctx, browse=False):
ctx.run("rst2html.py README.rst > README.html") ctx.run('rst2html.py README.rst > README.html')
if browse: if browse:
webbrowser.open_new_tab('README.html') webbrowser.open_new_tab('README.html')

@ -0,0 +1,5 @@
# Environment variable overrides for local development
FLASK_APP=autoapp.py
FLASK_ENV=development
DATABASE_URL="sqlite:////tmp/dev.db"
SECRET_KEY="not-so-secret"

@ -51,3 +51,9 @@ env/
# webpack-built files # webpack-built files
/{{cookiecutter.app_name}}/static/build/ /{{cookiecutter.app_name}}/static/build/
/{{cookiecutter.app_name}}/webpack/manifest.json /{{cookiecutter.app_name}}/webpack/manifest.json
# Configuration
.env
# Development database
*.db

@ -0,0 +1,60 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
# Flask
Flask = "==1.0.2"
MarkupSafe = "==1.0"
Werkzeug = "==0.14.1"
Jinja2 = "==2.10"
itsdangerous = "==0.24"
click = ">=5.0"
# Database
Flask-SQLAlchemy = "==2.3.2"
psycopg2 = "==2.7.5"
SQLAlchemy = "==1.2.8"
# Migrations
Flask-Migrate = "==2.2.0"
# Forms
Flask-WTF = "==0.14.2"
WTForms = "==2.2.1"
# Deployment
gunicorn = ">=19.1.1"
# Webpack
flask-webpack = "==0.1.0"
# Auth
Flask-Login = "==0.4.1"
Flask-Bcrypt = "==0.7.1"
# Caching
Flask-Caching = ">=1.0.0"
# Debug toolbar
Flask-DebugToolbar = "==0.10.1"
# Environment variable parsing
environs = "==4.0.0"
[dev-packages]
# Testing
pytest = "==3.6.1"
WebTest = "==2.0.30"
factory-boy = "==2.11.*"
# Lint and code style
flake8 = "==3.5.0"
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"

@ -8,29 +8,21 @@
Quickstart Quickstart
---------- ----------
First, set your app's secret key as an environment variable. For example,
add the following to ``.bashrc`` or ``.bash_profile``.
.. code-block:: bash
export {{cookiecutter.app_name | upper}}_SECRET='something-really-secret'
Run the following commands to bootstrap your environment :: 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}} cd {{cookiecutter.app_name}}
{%- if cookiecutter.use_pipenv == "yes" %}
pipenv install --dev
{%- else %}
pip install -r requirements/dev.txt pip install -r requirements/dev.txt
{%- endif %}
cp .env.example .env
npm install npm install
npm start # run the webpack dev server and flask server using concurrently npm start # run the webpack dev server and flask server using concurrently
You will see a pretty welcome screen. You will see a pretty welcome screen.
In general, before running shell commands, set the ``FLASK_APP`` and
``FLASK_DEBUG`` environment variables ::
export FLASK_APP=autoapp.py
export FLASK_DEBUG=1
Once you have installed your DBMS, run the following to create your app's Once you have installed your DBMS, run the following to create your app's
database tables and perform the initial migration :: database tables and perform the initial migration ::
@ -45,12 +37,14 @@ Deployment
To deploy:: To deploy::
export FLASK_ENV=production
export FLASK_DEBUG=0 export FLASK_DEBUG=0
export DATABASE_URL="<YOUR DATABASE URL>"
npm run build # build assets with webpack npm run build # build assets with webpack
flask run # start the flask server flask run # start the flask server
In your production environment, make sure the ``FLASK_DEBUG`` environment In your production environment, make sure the ``FLASK_DEBUG`` environment
variable is unset or is set to ``0``, so that ``ProdConfig`` is used. variable is unset or is set to ``0``.
Shell Shell

@ -1,10 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Create an application instance.""" """Create an application instance."""
from flask.helpers import get_debug_flag
from {{cookiecutter.app_name}}.app import create_app from {{cookiecutter.app_name}}.app import create_app
from {{cookiecutter.app_name}}.settings import DevConfig, ProdConfig
CONFIG = DevConfig if get_debug_flag() else ProdConfig
app = create_app(CONFIG) app = create_app()

@ -6,7 +6,7 @@
"build": "NODE_ENV=production webpack --progress --colors -p", "build": "NODE_ENV=production webpack --progress --colors -p",
"start": "concurrently -n \"WEBPACK,FLASK\" -c \"bgBlue.bold,bgMagenta.bold\" \"npm run webpack-dev-server\" \"npm run flask-server\"", "start": "concurrently -n \"WEBPACK,FLASK\" -c \"bgBlue.bold,bgMagenta.bold\" \"npm run webpack-dev-server\" \"npm run flask-server\"",
"webpack-dev-server": "NODE_ENV=debug webpack-dev-server --port 2992 --hot --inline", "webpack-dev-server": "NODE_ENV=debug webpack-dev-server --port 2992 --hot --inline",
"flask-server": "FLASK_APP=$PWD/autoapp.py FLASK_DEBUG=1 flask run", "flask-server": "flask run",
"lint": "eslint \"assets/js/*.js\"" "lint": "eslint \"assets/js/*.js\""
}, },
"repository": { "repository": {
@ -28,25 +28,25 @@
}, },
"devDependencies": { "devDependencies": {
"babel-core": "^6.25.0", "babel-core": "^6.25.0",
"babel-eslint": "^7.2.3", "babel-eslint": "^9.0.0",
"babel-loader": "^7.0.0", "babel-loader": "^7.0.0",
"babel-preset-env": "^1.6.0", "babel-preset-env": "^1.6.0",
"concurrently": "^3.5.0", "concurrently": "^4.0.1",
"css-loader": "^0.28.4", "css-loader": "^1.0.0",
"eslint": "^3.19.0", "eslint": "^5.3.0",
"eslint-config-airbnb-base": "^11.2.0", "eslint-config-airbnb-base": "^13.1.0",
"eslint-plugin-import": "^2.3.0", "eslint-plugin-import": "^2.3.0",
"extract-text-webpack-plugin": "^2.1.2", "extract-text-webpack-plugin": "^2.1.2",
"file-loader": "^0.11.2", "file-loader": "^2.0.0",
"font-awesome-webpack": "0.0.5-beta.2", "font-awesome-webpack": "0.0.5-beta.2",
"less": "^2.7.2", "less": "^3.8.0",
"less-loader": "^4.0.4", "less-loader": "^4.0.4",
"manifest-revision-webpack-plugin": "^0.4.0", "manifest-revision-webpack-plugin": "^0.4.0",
"raw-loader": "^0.5.1", "raw-loader": "^0.5.1",
"style-loader": "^0.18.2", "style-loader": "^0.23.0",
"url-loader": "^0.5.9", "url-loader": "^1.0.1",
"webpack": "^2.6.1", "webpack": "^2.6.1",
"webpack-dev-server": "^2.4.5", "webpack-dev-server": "^3.1.5",
"sync-exec": "^0.6.2" "sync-exec": "^0.6.2"
} }
} }

@ -2,8 +2,8 @@
-r prod.txt -r prod.txt
# Testing # Testing
pytest==3.6.1 pytest==3.7.4
WebTest==2.0.29 WebTest==2.0.30
factory-boy==2.11.1 factory-boy==2.11.1
# Lint and code style # Lint and code style

@ -11,10 +11,10 @@ click>=5.0
# Database # Database
Flask-SQLAlchemy==2.3.2 Flask-SQLAlchemy==2.3.2
psycopg2==2.7.5 psycopg2==2.7.5
SQLAlchemy==1.2.8 SQLAlchemy==1.2.11
# Migrations # Migrations
Flask-Migrate==2.2.0 Flask-Migrate==2.2.1
# Forms # Forms
Flask-WTF==0.14.2 Flask-WTF==0.14.2
@ -35,3 +35,6 @@ Flask-Caching>=1.0.0
# Debug toolbar # Debug toolbar
Flask-DebugToolbar==0.10.1 Flask-DebugToolbar==0.10.1
# Environment variable parsing
environs==4.0.0

@ -6,7 +6,6 @@ from webtest import TestApp
from {{cookiecutter.app_name}}.app import create_app from {{cookiecutter.app_name}}.app import create_app
from {{cookiecutter.app_name}}.database import db as _db from {{cookiecutter.app_name}}.database import db as _db
from {{cookiecutter.app_name}}.settings import TestConfig
from .factories import UserFactory from .factories import UserFactory
@ -14,7 +13,7 @@ from .factories import UserFactory
@pytest.fixture @pytest.fixture
def app(): def app():
"""An application for the tests.""" """An application for the tests."""
_app = create_app(TestConfig) _app = create_app('tests.settings')
ctx = _app.test_request_context() ctx = _app.test_request_context()
ctx.push() ctx.push()

@ -0,0 +1,11 @@
"""Settings module for test app."""
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"
DEBUG_TB_ENABLED = False
CACHE_TYPE = 'simple' # Can be "memcached", "redis", etc.
SQLALCHEMY_TRACK_MODIFICATIONS = False
WEBPACK_MANIFEST_PATH = 'webpack/manifest.json'
WTF_CSRF_ENABLED = False # Allows form testing

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
"""Test configs."""
from {{cookiecutter.app_name}}.app import create_app
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
assert app.config['DEBUG_TB_ENABLED'] is False
def test_dev_config():
"""Development config."""
app = create_app(DevConfig)
assert app.config['ENV'] == 'dev'
assert app.config['DEBUG'] is True

@ -4,10 +4,9 @@ from flask import Flask, render_template
from {{cookiecutter.app_name}} import commands, public, user 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
from {{cookiecutter.app_name}}.settings import ProdConfig
def create_app(config_object=ProdConfig): def create_app(config_object='{{cookiecutter.app_name}}.settings'):
"""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. :param config_object: The configuration object to use.

@ -1,49 +1,23 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Application configuration.""" """Application configuration.
import os
Most configuration is set via environment variables.
class Config(object): For local development, use a .env file to set
"""Base configuration.""" environment variables.
"""
from environs import Env
SECRET_KEY = os.environ.get('{{cookiecutter.app_name | upper}}_SECRET', 'secret-key') # TODO: Change me env = Env()
APP_DIR = os.path.abspath(os.path.dirname(__file__)) # This directory env.read_env()
PROJECT_ROOT = os.path.abspath(os.path.join(APP_DIR, os.pardir))
BCRYPT_LOG_ROUNDS = 13 ENV = env.str('FLASK_ENV', default='production')
DEBUG_TB_ENABLED = False # Disable Debug toolbar 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 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 SQLALCHEMY_TRACK_MODIFICATIONS = False
WEBPACK_MANIFEST_PATH = 'webpack/manifest.json' WEBPACK_MANIFEST_PATH = 'webpack/manifest.json'
class ProdConfig(Config):
"""Production configuration."""
ENV = 'prod'
DEBUG = False
SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/example' # TODO: Change me
DEBUG_TB_ENABLED = False # Disable Debug toolbar
class DevConfig(Config):
"""Development configuration."""
ENV = 'dev'
DEBUG = True
DB_NAME = 'dev.db'
# Put the db file in project root
DB_PATH = os.path.join(Config.PROJECT_ROOT, DB_NAME)
SQLALCHEMY_DATABASE_URI = 'sqlite:///{0}'.format(DB_PATH)
DEBUG_TB_ENABLED = True
CACHE_TYPE = 'simple' # Can be "memcached", "redis", etc.
class TestConfig(Config):
"""Test configuration."""
TESTING = True
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite://'
BCRYPT_LOG_ROUNDS = 4 # For faster tests; needs at least 4 to avoid "ValueError: Invalid rounds"
WTF_CSRF_ENABLED = False # Allows form testing

Loading…
Cancel
Save