├── .gitignore ├── .python-version ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── Procfile ├── README.md ├── app.json ├── app.py ├── static └── styles │ └── main.css └── templates ├── index.html └── layout.html /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | build 3 | *~ 4 | *.pyc 5 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.6.6 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Geoffrey Yefim Vedernikoff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | url = "https://pypi.python.org/simple" 4 | verify_ssl = true 5 | name = "pypi" 6 | 7 | 8 | [dev-packages] 9 | 10 | 11 | 12 | [packages] 13 | 14 | flask = "*" 15 | flask-sqlalchemy = "*" 16 | "psycopg2" = "*" 17 | gunicorn = "*" 18 | 19 | 20 | [requires] 21 | 22 | python_version = "3.6" 23 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "a317669bf8175d15cf72c2eec32b30c4f103f787b4eb4ec3232cf071140384d0" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.python.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "click": { 20 | "hashes": [ 21 | "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", 22 | "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" 23 | ], 24 | "version": "==7.0" 25 | }, 26 | "flask": { 27 | "hashes": [ 28 | "sha256:ad7c6d841e64296b962296c2c2dabc6543752985727af86a975072dea984b6f3", 29 | "sha256:e7d32475d1de5facaa55e3958bc4ec66d3762076b074296aa50ef8fdc5b9df61" 30 | ], 31 | "index": "pypi", 32 | "version": "==1.0.3" 33 | }, 34 | "flask-sqlalchemy": { 35 | "hashes": [ 36 | "sha256:0c9609b0d72871c540a7945ea559c8fdf5455192d2db67219509aed680a3d45a", 37 | "sha256:8631bbea987bc3eb0f72b1f691d47bd37ceb795e73b59ab48586d76d75a7c605" 38 | ], 39 | "index": "pypi", 40 | "version": "==2.4.0" 41 | }, 42 | "gunicorn": { 43 | "hashes": [ 44 | "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", 45 | "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" 46 | ], 47 | "index": "pypi", 48 | "version": "==19.9.0" 49 | }, 50 | "itsdangerous": { 51 | "hashes": [ 52 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", 53 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" 54 | ], 55 | "version": "==1.1.0" 56 | }, 57 | "jinja2": { 58 | "hashes": [ 59 | "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", 60 | "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" 61 | ], 62 | "version": "==2.10.1" 63 | }, 64 | "markupsafe": { 65 | "hashes": [ 66 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 67 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 68 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 69 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 70 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 71 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 72 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 73 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 74 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 75 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 76 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 77 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 78 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 79 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 80 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 81 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 82 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 83 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 84 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 85 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 86 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 87 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 88 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 89 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 90 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 91 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 92 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 93 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" 94 | ], 95 | "version": "==1.1.1" 96 | }, 97 | "psycopg2": { 98 | "hashes": [ 99 | "sha256:128d0fa910ada0157bba1cb74a9c5f92bb8a1dca77cf91a31eb274d1f889e001", 100 | "sha256:227fd46cf9b7255f07687e5bde454d7d67ae39ca77e170097cdef8ebfc30c323", 101 | "sha256:2315e7f104681d498ccf6fd70b0dba5bce65d60ac92171492bfe228e21dcc242", 102 | "sha256:4b5417dcd2999db0f5a891d54717cfaee33acc64f4772c4bc574d4ff95ed9d80", 103 | "sha256:640113ddc943522aaf71294e3f2d24013b0edd659b7820621492c9ebd3a2fb0b", 104 | "sha256:897a6e838319b4bf648a574afb6cabcb17d0488f8c7195100d48d872419f4457", 105 | "sha256:8dceca81409898c870e011c71179454962dec152a1a6b86a347f4be74b16d864", 106 | "sha256:b1b8e41da09a0c3ef0b3d4bb72da0dde2abebe583c1e8462973233fd5ad0235f", 107 | "sha256:cb407fccc12fc29dc331f2b934913405fa49b9b75af4f3a72d0f50f57ad2ca23", 108 | "sha256:d3a27550a8185e53b244ad7e79e307594b92fede8617d80200a8cce1fba2c60f", 109 | "sha256:f0e6b697a975d9d3ccd04135316c947dd82d841067c7800ccf622a8717e98df1" 110 | ], 111 | "index": "pypi", 112 | "version": "==2.8.3" 113 | }, 114 | "sqlalchemy": { 115 | "hashes": [ 116 | "sha256:c30925d60af95443458ebd7525daf791f55762b106049ae71e18f8dd58084c2f" 117 | ], 118 | "version": "==1.3.5" 119 | }, 120 | "werkzeug": { 121 | "hashes": [ 122 | "sha256:865856ebb55c4dcd0630cdd8f3331a1847a819dda7e8c750d3db6f2aa6c0209c", 123 | "sha256:a0b915f0815982fb2a09161cb8f31708052d0951c3ba433ccc5e1aa276507ca6" 124 | ], 125 | "version": "==0.15.4" 126 | } 127 | }, 128 | "develop": {} 129 | } 130 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn -b 0.0.0.0:$PORT app:app 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Flask Heroku Sample 2 | ==================== 3 | 4 | A simple Python Flask example application that's ready to run on Heroku. 5 | 6 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 7 | 8 | ## Development Setup 9 | 10 | * `pipenv install` 11 | 12 | * `pipenv shell` 13 | 14 | * `python app.py` 15 | 16 | ## Screenshot 17 | 18 | ![screenshot](https://i.imgur.com/wf74fxY.png) 19 | 20 | ## Deploy 21 | 22 | * `heroku create` 23 | 24 | * `heroku addons:create heroku-postgresql:hobby-dev` 25 | 26 | * `git push heroku master` 27 | 28 | * Note: make sure you run `db.create_all()` to create the tables: 29 | ```bash 30 | $ heroku run python 31 | Python 3.6.8 (default, Jan 29 2019, 19:35:16) 32 | >>> from app import db 33 | >>> db.create_all() 34 | >>> exit() 35 | ``` 36 | 37 | ## Contributors 38 | 39 | * [Yefim](https://twitter.com/yefim) 40 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Flask Heroku Sample", 3 | "description": "A sample Flask app on Heroku", 4 | "keywords": [ 5 | "flask", 6 | "python", 7 | "sample" 8 | ], 9 | "repository": "https://github.com/yefim/flask-heroku-sample", 10 | "scripts": { 11 | "postdeploy": "python -c 'from app import db; db.create_all()'" 12 | }, 13 | "addons": [ 14 | "heroku-postgresql" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask import Flask, render_template, request, redirect, url_for 4 | from flask_sqlalchemy import SQLAlchemy 5 | 6 | app = Flask(__name__) 7 | 8 | DATABASE_URL = os.environ.get('DATABASE_URL', 'sqlite:////tmp/flask_app.db') 9 | 10 | app.config['SQLALCHEMY_DATABASE_URI'] = DATABASE_URL 11 | db = SQLAlchemy(app) 12 | 13 | 14 | class User(db.Model): 15 | id = db.Column(db.Integer, primary_key=True) 16 | name = db.Column(db.String(100)) 17 | email = db.Column(db.String(100)) 18 | 19 | def __init__(self, name, email): 20 | self.name = name 21 | self.email = email 22 | 23 | 24 | @app.route('/', methods=['GET']) 25 | def index(): 26 | return render_template('index.html', users=User.query.all()) 27 | 28 | 29 | @app.route('/user', methods=['POST']) 30 | def user(): 31 | u = User(request.form['name'], request.form['email']) 32 | db.session.add(u) 33 | db.session.commit() 34 | return redirect(url_for('index')) 35 | 36 | if __name__ == '__main__': 37 | db.create_all() 38 | port = int(os.environ.get('PORT', 5000)) 39 | app.run(host='0.0.0.0', port=port, debug=True) 40 | -------------------------------------------------------------------------------- /static/styles/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | } 4 | 5 | *, 6 | *:before, 7 | *:after { 8 | box-sizing: inherit; 9 | } 10 | 11 | html, 12 | body { 13 | margin: 0; 14 | padding: 0; 15 | } 16 | 17 | body { 18 | font-family: sans-serif; 19 | } 20 | 21 | main { 22 | margin: 0 auto; 23 | max-width: 100%; 24 | padding: 20px; 25 | width: 1200px; 26 | } 27 | 28 | label, 29 | input, 30 | button { 31 | display: block; 32 | font-size: 16px; 33 | margin: 0 0 8px 0; 34 | width: 100%; 35 | } 36 | 37 | form { 38 | border: 1px solid #444; 39 | max-width: 100%; 40 | padding: 20px; 41 | width: 400px; 42 | } 43 | 44 | button { 45 | background: transparent; 46 | border: 1px solid #444; 47 | border-radius: 4px; 48 | cursor: pointer; 49 | margin: 0; 50 | padding: 4px 0; 51 | } 52 | 53 | button:hover { 54 | background: #444; 55 | color: #fff; 56 | } 57 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block body %} 4 |

Add a user

5 |
6 | 7 | 8 | 9 | 10 | 11 |
12 |

List of users

13 | {% for u in users %} 14 |

{{ u.name }} -- {{ u.email }}

15 | {% else %} 16 |

No users found in the db. You can make some above.

17 | {% endfor %} 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | {% block body %}{% endblock %} 9 |
10 | 11 | 12 | --------------------------------------------------------------------------------