Skip to content

Latest commit

 

History

History
1218 lines (769 loc) · 22.8 KB

flask.md

File metadata and controls

1218 lines (769 loc) · 22.8 KB
theme background fonts
seriph
sans serif mono
Roboto
Roboto
JetBrains Mono

Flask

siriuskoan


Outline

  • Introduction
  • Preparation
  • Basic Examples
  • Variables
  • Functions
  • Jinja
  • Factory Pattern
  • Flask Extensions
    • Flask-Login
    • Flask-WTF
    • Flask-SQLAlchemy
    • Flask-Migrate

Introduction

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.

ithelp 鐵人賽


Preparation

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.


Basic Examples

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)

Basic Examples

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)

Variables

There are many built-in variables in Flask.

  • current_app - The current application object.
  • request - The current request object.

layout: two-cols

Variables

::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)

Variables

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)

Functions

There are many useful functions in Flask.

  • make_response
  • redirect
  • url_for
  • abort
  • render_template

Functions

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)

Functions

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)

Functions

from flask import abort

@app.route("/dont_see")
def dont_see():
    abort(403)

app.run(host="127.0.0.1", port=8080)

Jinja

Jinja is a templating engine, and Flask use it.

We can view it as running program in HTML templates.


Jinja

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.


layout: two-cols

Jinja

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>

Jinja

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>

layout: two-cols

Jinja

::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>

Jinja

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>

Jinja

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.


layout: two-cols

Jinja

::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

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.


Factory Pattern


Factory Pattern

app/configs.py

class Config:
    SECRET_KEY = "cyn54g544mxng"
    DEBUG = True

Factory Pattern

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

Factory Pattern

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 Extensions

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 Extensions - Flask-Login

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.


Flask Extensions - Flask-Login

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

Flask Extensions - Flask-Login

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

Flask Extensions - Flask-Login

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 Extensions - Flask-WTF

Flask-WTF is a Flask extension that provides a way to create HTML forms.

You can create HTML forms using Python class.


Flask Extensions - Flask-WTF

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.


Flask Extensions - Flask-WTF

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")

Flask Extensions - Flask-WTF

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"))

Flask Extensions - Flask-WTF

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 Extensions - Flask-SQLAlchemy

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.


Flask Extensions - Flask-SQLAlchemy

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

Flask Extensions - Flask-SQLAlchemy


Flask Extensions - Flask-SQLAlchemy

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.

  1. Create app/database/models.py and add the following lines.

    from flask_sqlalchemy import SQLAlchemy
    db = SQLAlchemy()
  2. And then we can import db in app/__init__.py.

    from .models import db
  3. Add SQLALCHEMY_DATABASE_URI = "sqlite:///data.db" to Config object.

  4. Finally, we initialize Flask-SQLAlchemy with app by adding db.init_app(app) in create_app function.


Flask Extensions - Flask-SQLAlchemy

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

Flask Extensions - Flask-SQLAlchemy

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

Flask Extensions - Flask-SQLAlchemy

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()

Flask Extensions - Flask-SQLAlchemy

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 Extensions - Flask-Migrate

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.


Flask Extensions - Flask-Migrate

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.


Flask Extensions - Flask-Migrate

We have to initialize Flask-Migrate first.

  1. Add the following lines to manage.py.

    from flask_migrate import Migrate
    migrate = Migrate(app, db)
  2. And then we can try to run command flask db init to initialize the migration.


Flask Extensions - Flask-Migrate

After setting up, we can start using it.

  1. Run flask db migrate -m "description" to generate a migration file.

  2. Run flask db upgrade to migrate to current Flask-SQLAlchemy version.

  3. Edit the table schema.

  4. Run flask db migrate to create a new migration file since it detects the schema has been changed.

  5. Run flask db upgrade to migrate to the newer version.

  6. If something goes wrong, run flask db downgrade to rollback.