owned this note
owned this note
Published
Linked with GitHub
---
title: 'Python Flask Tutorial: Full-Featured Web App'
disqus: lvltcode
---
Python Flask Tutorial I: Full-Featured Web App from [Corey Schafer](https://www.youtube.com/watch?v=MwZwr5Tvyxo&list=PL-osiE80TeTs4UjLw5MM6OjgkjFeUxCYH)
===
**Tell you how to work with flask to create a website**
[TOC]
---
## Github code
>The GitHub links for this tutorial are [Browse](https://github.com/CoreyMSchafer/code_snippets/tree/master/Python/Flask_Blog)
[If name equals main explanation](https://www.youtube.com/watch?v=sugvnHA7ElY)
## Part 1 - Getting Started
* [Youtube Video](https://youtu.be/MwZwr5Tvyxo)
:::warning
$ pip install flask
:::
```python=
$ python3
>>> import flask
>>> exit()
$ mkdir flask_blog
```
`Cd` to `flask_blog` then create `flaskblog.py`.
Copy this code to `flaskblog.py`
```python=
from flask import Flask
# Flask => Name of the class
app = Flask(__name__)
# __name__ => Special variables in python, just the name of the module, when run that, __name__ = __main__, will see this later. Basically flask knows where to look for your templates and static files...
@app.route("/")
# a route = what we type in the browser to go to different pages. Route decorators.
def hello():
return "Hello World!"
```
`$ export FLASK_APP=flaskblog.py`
`$ flask run`
`* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)`
Can change http://127.0.0.1:5000/ to http://localhost:5000/
`$ export FLASK_DEBUG=1`
or add this code into the main *.py file
```python=
if __name__ == '__main__':
app.run(debug=True)
```
Can run by
`$ python3 flaskblog.py`
This run the script directly with python (if you don't want to keep setting those environment variables again whenerver shut down the terminal).
Add more route
```python=
@app.route("/about")
def about():
return "<h1>About Page</h1>"
```
## Part 2 - Templates
* [Youtube Video](https://youtu.be/QnDWIZuWYW0)
Create new folder 'templates'
Create inside 'templates' -- home.html & about.html
```htmlmixed=
#home.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>Home page</title>
</head>
<body>
<h1>Home Page</h1>
</body>
</html>
```
Code inside flaskblog.py
```python=
from flask import Flask, render_template
...
@app.route("/")
@app.route("/home")
def hello():
return render_template('home.html')
```
Same code to `about.html`
Fake data for posts
```python=
# flaskblog.py
posts = [
{
'author': 'author no.1',
'title': 'title no.1',
'content': 'blog no.1 content',
'date_posted': 'May 01 2019'
},
{
'author': 'author no.2',
'title': 'title no.2',
'content': 'blog no.2 content',
'date_posted': 'May 02 2019'
}
]
# change route 'home'
return render_template('home.html', posts= posts)
```
Code inside `home.html` and `about.html`
```htmlmixed=
<!-- <head> -->
{% if title%}
<title>Flask Blog | {{ title }}</title>
{% else %}
<title>Flask Blog</title>
{% endif %}
```
More blog content to `home` page
```htmlmixed=
<!-- <body> -->
{% for post in posts %}
<h1>{{ post.title }}</h1>
<p>Posted by {{ post.author }} on {{post.date_posted }}</p>
<p>{{ post.content }}</p>
{% endfor %}
```
### Templates Inheritance
Create `layout.html` inside `templates` folder
```htmlmixed=
<!-- layout.html -->
<!DOCTYPE html>
<html lang="en">
<head>
{% if title%}
<title>Flask Blog | {{ title }}</title>
{% else %}
<title>Flask Blog</title>
{% endif %}
</head>
<body>
{% block content %} {% endblock %}
</body>
</html>
```
```htmlmixed=
<!-- home.html -->
{% extends "layout.html" %}
{% block content %}
{% for post in posts %}
<h1>{{ post.title }}</h1>
<p>Posted by {{ post.author }} on {{ post.date_posted }}</p>
<p>{{ post.content }}</p>
{% endfor %}
{% endblock content %}
<!-- should have "name" inside with endblock {% endblock content %} in case we have many block to manage, so name it like the open block tag for easier later -->
```
```htmlmixed=
<!-- about.html -->
{% extends "layout.html" %}
{% block content %}
<h1>About Page</h1>
{% endblock content %}
```
`Option + Command + U` to check source page
Now, check the powerful of this inheritance method by using Bootstrap for entire pages of the website.
```htmlmixed=
<!-- layout.html / <body> -->
<div class="container">
{% block content %} {% endblock %}
</div>
```
Reload the page hard refresh by `Command + Shift + R` (clear the cache)
Add code from this [github link](https://github.com/CoreyMSchafer/code_snippets/blob/master/Python/Flask_Blog/snippets)
* create static folder
* add main.css file
* add main.css content from main.css gihub
* add navigation.html & main.html to layout.html
* add the css link to layout
```htmlmixed=
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}"
```
Article layout in home
```htmlmixed=
<article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="#">{{ post.author }}</a>
<small class="text-muted">{{ post.date_posted }}</small>
</div>
<h2><a class="article-title" href="#">{{ post.title }}</a></h2>
<p class="article-content">{{ post.content }}</p>
</div>
</article>
```
### Result
![](https://i.imgur.com/M8aWIrD.png)
## Part 3 - Forms and User Input
* [Youtube Video](https://youtu.be/UIJKdCIEXUQ)
:::warning
$ pip install flask-wtf
:::
:::info
`forms.py`
```python=
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo
class RegisrationForm(FlaskForm):
username = StringField('Username',
validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password',
validators=[DataRequired()])
confirm_password = PasswordField('Confirm Password',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Sign Up')
class LoginForm(FlaskForm):
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password',
validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit = SubmitField('Login')
```
:::
Generate random key for SECRET_KEY
```
$ python3
>>> import secrets
>>> secrets.token_hex(16)
>>> '2833f00b7d64c20651131b5fa9f65287'
```
:::info
`flaskblog.py`
```python=
from forms import RegistrationForm, LoginForm
app.config = ['SECRET_KEY'] = '2833f00b7d64c20651131b5fa9f65287'
```
:::
:::danger
`register.html`
```htmlmixed=
{% extends "layout.html" %}
{% block content %}
<div class='content-section'>
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Join Today</legend>
<div class="form-group">
{{ form.username.label(class="form-control-label")}}
{{ form.username(class="form-control form-control-lg")}}
</div>
<div class="form-group">
{{ form.email.label(class="form-control-label")}}
{{ form.email(class="form-control form-control-lg")}}
</div>
<div class="form-group">
{{ form.password.label(class="form-control-label")}}
{{ form.password(class="form-control form-control-lg")}}
</div>
<div class="form-group">
{{ form.confirm_password.label(class="form-control-label")}}
{{ form.confirm_password(class="form-control form-control-lg")}}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info")}}
</div>
</form>
</div>
<div class="border-top pt-3">
<small class="text-muted">
Already Have An Account? <a class="ml-2" href="{{ url_for('login') }}">Sign In</a>
</small>
</div>
{% endblock content %}
```
:::
:::danger
`layout.html`
```htmlmixed=
<main role="main" class="container">
<div class="row">
<div class="col-md-8">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
...
```
:::
Validate the form's fields
:::danger
`register.html`
```htmlmixed=
...fieldset
...legend
<div class="form-group">
{{ form.username.label(class="form-control-label")}}
{% if form.username.errors %}
{{ form.username(class="form-control form-control-lg is-invalid")}}
<div class="invalid-feedback">
{% for error in form.username.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.username(class="form-control form-control-lg")}}
{% endif %}
<div class="form-group">
{{ form.email.label(class="form-control-label")}}
{% if form.email.errors %}
{{ form.email(class="form-control form-control-lg is-invalid")}}
<div class="invalid-feedback">
{% for error in form.email.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.email(class="form-control form-control-lg")}}
{% endif %}
</div>
<!-- add the same to password and confirm_password -->
```
:::
### Result 1
![](https://i.imgur.com/1BhANe9.png)
Copy to `login.html` (remove username and confirm_password)
Add methods and condition to route login
:::info
`flaskblog.py`
```python=
@app.route("/login", methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
if form.email.data == "admin@blog.com" and form.password.data == "password":
flash('You have been logged in!', 'success')
return redirect(url_for('home'))
else:
flash("Login Unsuccessful. Please check email and password", 'danger')
return render_template('login.html', title='login', form=form)
```
:::
### Result 2
![](https://i.imgur.com/JMRofxv.png)
## Part 4 - Database with Flask-SQLAlchemy
###### `May 03 2019`
:::warning
$ pip install flask-sqlalchemy
:::
:::info
`flaskblog.py`
```python=
from flask_sqlalchemy import SQLAlchemy
...
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)
...
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
password = db.Column(db.String(60), nullable=False)
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.image_file}')"
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
...
from datetime import datetime
```
:::
Author? [One-to-many Relationship](https://youtu.be/cYWiDiIUxQc?t=857) between Author(Post) & Author(User)
:::info
`flaskblog.py`
```python=
class User(db.Model):
...
posts = db.relationship('Post', backref="author", lazy=True)
class Post(db.Model):
...
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
```
:::
:::warning
```
>>> python3
>>> from flaskblog import db
>>> db.create_all()
>>> from flaskblog import User, Post
>>> user_1 = User(username="luke", email="luke@demo.com", password="password1")
>>> db.session.add(user_1)
>>> user_2 = User(username="luca", email="luca@demo.com", password="password2")
>>> db.session.add(user_2)
>>> db.session.commit()
>>> User.query.all()
[User('luke', 'luke@demo.com', 'default.jpg'), User('luca', 'luca@demo.com', 'default.jpg')]
>>> User.query.first()
User('luke', 'luke@demo.com', 'default.jpg')
>>> User.query.filter_by(username="luke").all()
[User('luke', 'luke@demo.com', 'default.jpg')]
>>> User.query.filter_by(username="luke").first()
User('luke', 'luke@demo.com', 'default.jpg')
>>> user = User.query.filter_by(username="luke").first()
>>> user
User('luke', 'luke@demo.com', 'default.jpg')
>>> user.id
1
>>> user = User.query.get(1)
>>> user
User('luke', 'luke@demo.com', 'default.jpg')
>>> user.posts
[]
```
:::
:::warning
```
>>> user.id
1
>>> post_1 = Post(title="Blog 1", content="First post content", user_id=user.id)
>>> post_2 = Post(title="Blog 2", content="Second post content", user_id=user.id)
>>> db.session.add(post_1)
>>> db.session.add(post_2)
>>> db.session.commit()
>>> user.posts
[User('Blog 1', '2019-05-03 17:16:09.993101'), User('Blog 2', '2019-05-03 17:16:09.995732')]
>>> for post in user.posts:
... print(post.title)
Blog 1
Blog 2
>>> post = Post.query.first()
>>> post
User('Blog 1', '2019-05-03 17:16:09.993101')
>>> post.user_id
1
>>> post.author
User('luke', 'luke@demo.com', 'default.jpg')
# Delete all database
>>> db.drop_all()
# Then create the new fresh one
>>> db.create_all()
# check if it ok
>>> User.query.all()
[]
```
:::
## Part 5 - Package Structure
###### `May 05 2019`
Create the `models.py`, move this code to the models
:::info
`from flaskblog.py move to models.py`
```python=
from flaskblog import db
from datetime import datetime
class User(db.Model):
...
class Post(db.Model):
...
```
:::
Create folder name `flaskblog`, inside create `__init__.py`
Move these files into "package" flaskblog
![](https://i.imgur.com/Av6T7AH.png)
:::info
`__init__.py`
```python=
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SECRET_KEY'] = '5791628bb0b13ce0c676dfde280ba245'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)
from flaskblog import routes
```
:::
Create `routes.py`
:::info
`routes.py`
```python=
from flask import render_template, url_for, flash, redirect
from flaskblog import app
from flaskblog.models import User, Post
from flaskblog.forms import RegistrationForm, LoginForm
...
```
:::
:::info
`flaskblog.py` only left
```python=
from flaskblog import app
if __name__ == '__main__':
app.run(debug=True)
```
:::
Rename `flaskblog.py` = `run.py`
## Part 6 - User Authentication
###### `May 04 2019`
:::warning
$ pip install flask-bcrypt
:::
:::warning
```
>>> from flask_bcrypt import Bcrypt
>>> bcrypt = Bcrypt()
>>> bcrypt.generate_password_hash('testing')
b'$2b$12$e9mzug5NoJCwYUlH/aNp2.Q9zIodxiKMGrLAHO0x.BO4NeHzm2HZW'
>>> bcrypt.generate_password_hash('testing').decode('utf-8')
'$2b$12$5oH0c023RIKm0QroBwKNcu6Ndh/gflgBJt6A0EzUKUpkV2xVnZnMO'
>>> hashed_pw = bcrypt.generate_password_hash('testing').decode('utf-8')
>>> bcrypt.check_password_hash(hashed_pw, 'password')
False
>>> bcrypt.check_password_hash(hashed_pw, 'testing')
True
```
`__init__.py`
```python=
...
from flask_bcrypt import Bcrypt
...
bcrypt = Bcrypt(app)
from flaskblog import routes
```
`routes.py`
```python=
...
from flaskblog import app, db, bcrypt
@app.route("/register", methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
user = User(username=form.username.data, email=form.email.data, password=hashed_password)
db.session.add(user)
db.session.commit()
flash('Your account has been created! You are now able to login', 'success')
return redirect(url_for('login'))
return render_template('register.html', title='Register', form=form)
```
:::
![](https://i.imgur.com/x9vM3R2.png)
:::warning
```
>>> from flaskblog import db
>>> from flaskblog .models import User
>>> user = User.query.first()
>>> user
User('lululu', 'lululu@gmail.com', 'default.jpg')
>>> user.password
'$2b$12$f/av35Sg02H8UopgGbt0VO6pBke9kXlTNV8rvLInyH.jO3iPOdpmy'
```
:::
### Check if the same user information has already be used
:::info
`forms.py`
```python=
def validate_field(self, field):
if True:
raise ValidationError('Validation message')
=====
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError('That username is taken, please choose another one')
from flaskblog.models import User
```
:::
#### Result for this step
![](https://i.imgur.com/zuA5Zg6.png)
### Create the login system
After creating account, user can login and log out
:::warning
$ pip install flask-login
:::
:::info
`__init__.py`
```python=
...
from flask_login import LoginManager
...
login_manager = LoginManager(app)
```
`models.py`
```python=
...
from flask_login import Usermixin
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
...
class User(db.Model, UserMixin):
...
```
`routes.py`
```python=
from flask_login import login_user
...
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and bcrypt.check_password_hash(user.password, form.password.data):
login_user(user, remember=form.remember.data)
return redirect(url_for('home'))
else:
flash('Login Unsuccessful. Please check email and password', 'danger')
return render_template('login.html', title='Login', form=form)
```
:::
#### Result for this step
![](https://i.imgur.com/6J5tNkn.png)
### Logged-in user dont need to login again, when they press on Login button (Navigation bar)
:::info
`routes.py`
```python=
from flask_login import login_user, current_user
...
def register():
if current_user.is_authenticated:
return redirect(url_for('home'))
...
def login():
if current_user.is_authenticated:
return redirect(url_for('home'))
...
```
:::
### Create Log out button to replace Log In
:::info
`routes.py`
```python=
from flask_login import login_user, current_user, logout_user
...
def register():
if current_user.is_authenticated:
return redirect(url_for('home'))
...
def login():
if current_user.is_authenticated:
return redirect(url_for('home'))
...
```
`layout.html`
```htmlmixed=
<div class="navbar-nav">
{% if current_user.is_authenticated %}
<a class="nav-item nav-link" href="{{ url_for('logout') }}">Logout</a>
{% else %}
<a class="nav-item nav-link" href="{{ url_for('login') }}">Login</a>
<a class="nav-item nav-link" href="{{ url_for('register') }}">Register</a>
{% endif %}
```
:::
### Create account routes beside log out button
:::info
`routes.py`
```python=
@app.route("/account")
def account():
return render_template('account.html', title='Account')
```
`account.html`
```htmlmixed=
{% extends "layout.html" %}
{% block content %}
<h1>{{ current_user.username }}</h1>
{% endblock content %}
```
`layout.html`
```htmlmixed=
<div class="navbar-nav">
{% if current_user.is_authenticated %}
<a class="nav-item nav-link" href="{{ url_for('account') }}">Account</a>
<a class="nav-item nav-link" href="{{ url_for('logout') }}">Logout</a>
```
:::
### Check in required not show account.html page
:::info
`routes.py`
```python=
from flask_login import ... login_required
...
@app.route("/account")
@login_required
def account():
...
```
`__init__.py`
```python=
...
login_manager.login_view ='login'
```
:::
:::success
`http://127.0.0.1:5000/account => http://127.0.0.1:5000/login?next=%2Faccount`
![](https://i.imgur.com/bLMzpzt.png)
:::
:::info
```python=
from flask import ... redirect, request
...
def login():
...
next_page = request.args.get('next')
return redirect(next_page) if next_page else redirect(url_for('home'))
```
:::
###### tags: `Python` `Corey Schafer` `Flask` `Full Web Application I`