theme | background | fonts | ||||||
---|---|---|---|---|---|---|---|---|
seriph |
|
siriuskoan
- Introduction
- Preparation
- Basic Examples
- Variables
- Functions
- Jinja
- Factory Pattern
- Flask Extensions
- Flask-Login
- Flask-WTF
- Flask-SQLAlchemy
- Flask-Migrate
Flask is a lightweight WSGI (Web Server Gateway Interface in Python) web application framework.
It is easy to get started, and it is scalable as well.
We need a requirements.txt
file with the following content
Flask
Flask-SQLAlchemy
Flask-Login
Flask-WTF
Flask-Migrate
Then $ pip install -r requirements.txt
.
from flask import Flask
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def index():
return "Index Page"
app.run(host="127.0.0.1", port=8080, debug=True)
from flask import Flask
app = Flask(__name__)
@app.route("/dynamic/<int:id>", methods=["GET"])
def dynamic_url_page(id):
return str(id)
app.run(host="127.0.0.1", port=8080, debug=True)
There are many built-in variables in Flask.
current_app
- The current application object.request
- The current request object.
::left::
app.py
from flask import Flask
from utils import get_url_map
app = Flask(__name__)
@app.route("/")
def url_map():
get_url_map()
return ""
app.run(host="127.0.0.1", port=8080)
::right::
utils.py
from flask import current_app
def get_url_map():
print(current_app.url_map)
from flask import Flask, request
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def index_page():
cookies = request.cookies
if request.method == "GET":
print(cookies["user"])
return "GET /"
if request.method == "POST":
return "POST /"
app.run(host="127.0.0.1", port=8080)
There are many useful functions in Flask.
make_response
redirect
url_for
abort
render_template
from flask import Flask, make_response
app = Flask(__name__)
@app.route("/set")
def set_cookie():
response = make_response("")
response.set_cookie("user", "cat")
return response
@app.route("/delete")
def delete_cookie():
response = make_response("")
response.delete_cookie("user")
return response
app.run(host="127.0.0.1", port=8080)
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route("/google")
def redirect_to_google():
return redirect("https://google.com")
@app.route("/google2")
def redirect_to_google_2():
return redirect(url_for("redirect_to_google"))
app.run(host="127.0.0.1", port=8080)
from flask import abort
@app.route("/dont_see")
def dont_see():
abort(403)
app.run(host="127.0.0.1", port=8080)
Jinja is a templating engine, and Flask use it.
We can view it as running program in HTML templates.
Before getting started, we have to create a folder named templates
where Flask get templates from.
Besides, we need to create index.html
where we will put HTML template and Jinja code.
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index_page():
name = "cat"
return render_template("index.html", username=name)
app.run(host="127.0.0.1", port=8080)
<body>
{{ username }}
</body>
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index_page():
return render_template("index.html", state="running")
app.run(host="127.0.0.1", port=8080)
<body>
{% if state == "running" %}
<p>running</p>
{% else %}
<p>terminated</p>
{% endif %}
</body>
::left::
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index_page():
users = [
"Mary",
"Cat",
"Meow",
"Harry"
]
return render_template(
"index.html",
users=users
)
app.run(host="127.0.0.1", port=8080)
::right::
<body>
<h1>User list</h1>
<ul>
{% for user in users %}
<li>
{{ user | upper }}
{{ loop.index }}
</li>
{% endfor %}
</ul>
</body>
from flask import Flask, render_template, flash
app = Flask(__name__)
@app.route("/")
def index_page():
flash("Wrong!", category="warning")
return render_template("index.html")
app.config["SECRET_KEY"] = "rc498mt6848"
app.run(host="127.0.0.1", port=8080)
<body>
{% for category, message in get_flashed_messages(with_categories=True) %}
<div class="{{ category }}">{{ message }}</div>
{% endfor %}
</body>
A website has many pages, like main page, login page, register page, dashboard, etc.
If we create an HTML template for every page, there will be too many same part. For example, head
tag is almost the same for every page.
Therefore, we can do template inheritance to reduce duplicate parts.
In Jinja, we can create base.html
as the base template, and all the other HTML templates can inherit from it.
::left::
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>
{% block title %}{% endblock %}
</title>
</head>
<body>
<nav>nav</nav>
<main>
{% block content %}
{% endblock %}
</main>
<footer>footer</footer>
</body>
::right::
{% extends "base.html" %}
{% block title %}Index Page{% endblock %}
{% block content %}
<p>
{{ user }}
</p>
{% endblock %}
Factory Pattern is design pattern.
By following the pattern, we can obtain a new application by calling the same function.
For example, we will create a function called create_app
later, and we can create different with different arguments.
app/configs.py
class Config:
SECRET_KEY = "cyn54g544mxng"
DEBUG = True
app/__init__.py
def create_app(env):
app = Flask(__name__, template_folder="../templates", static_folder="../static")
app.config.from_object(configs.Config)
from .main import main_bp
app.register_blueprint(main_bp)
from .user import user_bp
app.register_blueprint(user_bp)
return app
app/main/__init__.py
from flask import Blueprint
main_bp = Blueprint("main", __name__)
from . import views
app/main/views.py
from . import main_bp
@main_bp.route("/", methods=["GET"])
def index_page():
return "index"
Flask itself is a simple and lightweight framework, so there are many important functions it does not have.
However, there are many Flask extensions that add functionality to a Flask application.
For example,
- Flask-Login
- Flask-WTF
- Flask-SQLAlchemy
- Flask-Migrate
- Flask-Mail
- Flask-RESTful
- ...
Flask-Login is a Flask extension that provides user session management.
It handles the common tasks of logging in, logging out, and remembering your users’ sessions over extended periods of time.
To user this extension, we must set SECRET_KEY
.
To use this extension, we have to modify create_app
function.
app/__init__.py
from flask_login import LoginManager
login_manager = LoginManager()
def create_app(env):
app = Flask(__name__, template_folder="../templates", static_folder="../static")
app.config.from_object(configs[env])
login_manager.init_app(app)
from .main import main_bp
app.register_blueprint(main_bp)
from .user import user_bp
app.register_blueprint(user_bp)
return app
We need user loader.
app/__init__.py
from flask_login import LoginManager, UserMixin
class User(UserMixin):
pass
@login_manager.user_loader
def load(user_id):
user_id = int(user_id)
sessionUser = User()
sessionUser.id = user.id
return sessionUser
from flask import Flask
from flask_login import login_user
@app.route("/login")
def login_page():
user = User()
user.id = 1
login_user(user)
return "Success"
@app.route("/logout")
def logout_page():
logout_user()
return "Success"
Flask-WTF is a Flask extension that provides a way to create HTML forms.
You can create HTML forms using Python class.
There is no need to initialize Flask-WTF with Flask application.
We only need to do is create a file called forms.py
and put some forms into it.
SECRET_KEY
is needed as well.
To create a form.
app/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
username = StringField(
"Username", validators=[DataRequired()], render_kw={"placeholder": "Username"}
)
password = PasswordField(
"Password", validators=[DataRequired()], render_kw={"placeholder": "Password"}
)
submit = SubmitField("Login")
To use the form, we need to have a route.
@app.route("/login", methods=["GET", "POST"])
def login_page():
form = LoginForm()
if request.method == "GET":
return render_template("login.html", form=form)
else
if form.validate_on_submit():
username = form.username.data
password = form.password.data
if user := login_auth(username, password):
login_user(user)
else:
print(form.errors)
return redirect(url_for("login_page"))
Then in HTML template.
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<form action="/login" method="post">
{{ form.csrf_token }}
{{ form.username }}
{{ form.password }}
{{ form.submit }}
</form>
{% endblock %}
Flask-SQLAlchemy is a Flask extension that gives developers full power and flexibility of SQL.
It uses ORM framework.
Flask-SQLAlchemy can support many database systems, like MySQL, SQLite, PostgreSQL, etc.
ORM, standing for Object Relational Mapper, is a technique that maps database to object in programming language.
Pros
- Preventing SQL injection
- Easy to manipulate SQL
- Better readability
Cons
- Slow
- Some functionalities of SQL cannot be used
Before getting started, we have to initialize it.
However, we don't create db
object in app/__init__.py
, instead, we put the instance in app/database/models.py
.
-
Create
app/database/models.py
and add the following lines.from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy()
-
And then we can import
db
inapp/__init__.py
.from .models import db
-
Add
SQLALCHEMY_DATABASE_URI = "sqlite:///data.db"
toConfig
object. -
Finally, we initialize Flask-SQLAlchemy with
app
by addingdb.init_app(app)
increate_app
function.
We first define a table model. The model is put in app/database/models.py
.
class Users(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String, unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
email = db.Column(db.String, unique=True, nullable=False)
register_time = db.Column(
db.DateTime, default=datetime.datetime.now, nullable=False
)
def __init__(self, username, password, email):
self.username = username
self.password = generate_password_hash(password)
self.email = email
Let's see CRUD in Flask-SQLAlchemy.
C
def create_user(username, password, email):
user = Users(username, password, email)
db.session.add(user)
db.session.commit()
R
def get_user_by_name(username):
user = Users.query.filter_by(username=username).first()
return user
U
def update_user_email(username, email):
filter = Users.query.filter_by(id=user_id)
filter.update({"email": email})
db.session.commit()
D
def delete_user(username):
user = Users.query.filter_by(username=username).first()
db.session.delete(user)
db.session.commit()
Recall the table we make yesterday.
CREATE TABLE users (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(255) NOT NULL UNIQUE,
level INT NOT NULL
);
In Flask-SQLAlchemy, the table is
class users(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String, unique=True, nullable=False)
level = db.Column(db.Integer, nullable=False)
Flask-Migrate is an extension that handles SQLAlchemy database migrations for Flask applications.
The database operations are made available through the Flask command-line interface.
Simply, database migration is the version control of the schema of database.
Every migration is a file with SQL statements, and they specify how to migrate to the version and how to rollback from the version.
It is useful when there are many developers developing a project. Someone can modify the schema and create a migration file, and then the others can use the migration file to migrate their local testing database to newer version.
We have to initialize Flask-Migrate first.
-
Add the following lines to
manage.py
.from flask_migrate import Migrate migrate = Migrate(app, db)
-
And then we can try to run command
flask db init
to initialize the migration.
After setting up, we can start using it.
-
Run
flask db migrate -m "description"
to generate a migration file. -
Run
flask db upgrade
to migrate to current Flask-SQLAlchemy version. -
Edit the table schema.
-
Run
flask db migrate
to create a new migration file since it detects the schema has been changed. -
Run
flask db upgrade
to migrate to the newer version. -
If something goes wrong, run
flask db downgrade
to rollback.