Python Flask
Using this!
MAKE APP
- Make & cd project directory
- Make and start virtual environment
python3 -m venv v-env
source v-env/bin/activate
NOTE: You can exit from the virtualenv using exit command, or by pressing Ctrl+d.
- Install packages
pip3 install flask flask-sqlalchemy
- Make
app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello, world!"
if __name__ == "__main__":
app.run(debug=True)
- Create two folders:
static
andtemplates
5b. Add this line to app.py
from flask import Flask, render_template
- Add
index.html
to templates folder (with basic html) and updateapp.py
to returnrender_template
instead of just ‘hellow world’
def index():
# return "Hello, world!"
return render_template('index.html')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
Hello cats!
</body>
</html>
- Add
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
{% block head %}{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
7b. Update index.html
{% extends 'base.html' %}
{% block head %}
<h1>What is this? Todos for cats?!</h1>
{% endblock %}
{% block body %}
{% endblock %}
- Make a css file and folder within
static
–static/css/main.css
body {
margin: 0;
font-family: sans-serif;
}
- Link the stylesheet
9a. Update app.py
from flask import Flask, render_template, url_for
9b. Add link to base.html
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
ADD DATABASE
- Database time!
app.py
from flask import Flask, render_template, url_for
from flask_sqlalchemy import SQLAlchemy
# NEW LINE!! ^^^
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy(app)
# NEW LINES!! ^^^
# Telling our app where our DB is located
@app.route('/')
def index():
# return "Hello, world!"
return render_template('index.html')
if __name__ == "__main__":
app.run(debug=True)
NOTE: sqlite:///
////
= absolute path
///
= relative path, what we want
- Add models
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy(app)
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(200), nullable=False)
date_created = db.Column(db.DateTime, default=datetime.utcnow)
def __repr__(self):
return '<Task %r>' % self.id
and add this to the top
from datetime import datetime
- Activate environment
12a. Make sure you are still in virtual environment
python3
>> from app import db
>> db.create_all()
- Add methods to routes
app.py
# @app.route('/')
@app.route('/', methods=['POST', 'GET'])
def index():
# return "Hello, world!"
return render_template('index.html')
if __name__ == "__main__":
app.run(debug=True)
- Add actions to
index.html
<form actions="/" method="POST">
<input type="text" name="content" id="content">
<input type="submit" value="Add Task">
</form>
- Add requests to
app.py
from flask import Flask, render_template, url_for, request
.
.
.
@app.route('/', methods=['POST', 'GET'])
def index():
if request.method == 'POST':
pass
else:
return render_template('index.html')
15b. Try hitting submit – we get error because we’re not passing anything (try return 'hello'
instead of pass
)
- Add ability to add todo (and add redirect to the top) and…
from flask import Flask, render_template, url_for, request, redirect
.
.
.
@app.route('/', methods=['POST', 'GET'])
def index():
if request.method == 'POST':
# get `content` from form
task_content = request.form['content']
# create new instance of model
new_task = Todo(content=task_content)
# add to db
try:
db.session.add(new_task)
db.session.commit()
return redirect('/')
except:
return 'There was an issue adding your task'
else:
return render_template('index.html')
- …add query for non-post requests (just arriving at the page)
def index():
if request.method == 'POST':
# get `content` from form
task_content = request.form['content']
# create new instance of model
new_task = Todo(content=task_content)
# add to db
try:
db.session.add(new_task)
db.session.commit()
return redirect('/')
except:
return 'There was an issue adding your task'
else:
# querying our database and ordering them by date created
tasks = Todo.query.order_by(Todo.date_created).all()
# pass this variable to our template
return render_template('index.html', tasks=tasks)
- Add more
Jinja
to index.html so we can view all of our tasks with this fun for-loop!
<h1>Task Master</h1>
<table>
<tr>
<th>Task</th>
<th>Added</th>
<th>Actions</th>
</tr>
{% for task in tasks%}
<tr>
<td>{{ task.content }}</td>
<td>{{ task.date_created.date() }}</td>
<td>
<a href="">Delete</a>
<br>
<a href="">Update</a>
</td>
</tr>
{% endfor %}
</table>
- Add delete route (& function!) to
app.py
@app.route('/delete/<int:id>')
def delete(id):
task_to_delete = Todo.query.get_or_404(id)
# delete from database
try:
db.session.delete(task_to_delete)
db.session.commit()
return redirect('/')
except:
return 'There was a problem deleting that task'
- Add route to html, and while we’re there, add the update route!
{% for task in tasks%}
<tr>
<td>{{ task.content }}</td>
<td>{{ task.date_created.date() }}</td>
<td>
<a href="/delete/{{ task.id }}">Delete</a>
<br>
<a href="/update/{{ task.id }}">Update</a>
</td>
</tr>
{% endfor %}
- Add update template
update.html
(pretty much copied fromindex.html
with the table removed)
{% extends 'base.html' %}
{% block head %}
<title>Task Master</title>
{% endblock %}
{% block body %}
<div class="content">
<h1>Update Task</h1>
<form actions="/update/{{ task.id }}" method="POST">
<input type="text" name="content" id="content">
<input type="submit" value="Add Task">
</form>
</div>
{% endblock %}
- Add update route (& function!) to
app.py
@app.route('/update/<int:id>', methods=['GET', 'POST'])
def update(id):
task = Todo.query.get_or_404(id)
if request.method == 'POST':
task.content = request.form['content']
try:
# db.session.update(task_to_update)
# commit() does the update for us don't need ^^
db.session.commit()
return redirect('/')
except:
return 'There was a problem updating that task'
else:
return render_template('update.html', task=task)
- Update the update form
<form actions="/update/{{ task.id }}" method="POST">
<input type="text" name="content" id="content" value="{{ task.content }}">
<input type="submit" value="Update">
</form>
- Add
if
statement toindex.html
to account for no tasks
<h1>Task Master</h1>
{% if tasks|length < 1 %}
<h4>There are no tasks. Create one below!</h4>
{% else %}
<table>
<tr>
<th>Task</th>
<th>Added</th>
<th>Actions</th>
</tr>
{% for task in tasks%}
<tr>
<td>{{ task.content }}</td>
<td>{{ task.date_created.date() }}</td>
<td>
<a href="/delete/{{ task.id }}">Delete</a>
<br>
<a href="/update/{{ task.id }}">Update</a>
</td>
</tr>
{% endfor %}
</table>
{% endif %}
PUSH IT LIVE!!
-
Sign up for / sign in to Heroku
-
Install Heroku cli
- Login via cli & create heroku app
heroku login
- Freeze Requirements
pip3 install gunicorn pip3 freeze > requirements.txt
- OH WAIT ADD PROCFILE
This starts a webserver and tells webserver what to run
touch Procfile
Procfile
web: gunicorn app:app
- Create Heroku app and GIT IT
heroku create git add . git commit -m "WHEE FLASK!" git push heroku master heroku open