Flask 基礎知識與實作 part 1

Author: 毛毛, Alicia

Outline

  • Ch1: Introduction to Flask
  • Ch2: Program Your First Flask Web Application
  • Ch3: Template
  • Ch4: Web Form

Ch1: Introduction to Flask

Flask Overview

  • Flask is a small framework that is often called "micro-framework".
  • Flask is desigend as an extensible framework.
  • Flask depends on two main packages:
    • Werkzeug: routing, debugging, and Web Server Gateway Interface (WSGI) subsystems
    • Jinjia2: template support
  • No native support for databases, web forms, authenticating users, or other high-level tasks.
    • But we can do it via extension.

How a Flask Web works

alt text

alt text

Ch2: Program Your First Flask Web Application

First flask web application

In [ ]:
# hello.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "<h1>Hello Word</h1>"

if __name__ == "__main__":
    app.run()

Run your first app

  • $ python hello.py

app.run parameter: debug

  • True: show the detail debugging information
  • False: just show "internal server error" msg (default)
In [ ]:
# hello.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "<h1>Hello Word</h1>"

@app.route("/error")
def error():
    raise RuntimeError

if __name__ == "__main__":
    app.run(debug=True)

app.run parameter: port

  • Default: 5000
  • You can modify the number as you want.
In [ ]:
# hello.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "<h1>Hello Word</h1>"

if __name__ == "__main__":
    app.run(port=5566)

Unknown URL

  • If you type in unknown URL, you'll see an error message.

Add new URL map

In [ ]:
@app.route("/users")
def get_users():
    users = ["Maomao", "Alicia"]
    resp = ["<p>{}</p>".format(user) for user in users]
    resp = "\n".join(resp)

    return resp

Dynamic route

  • By default flask takes "string", but you can also specify other types.
    • Type: int, float, path
In [ ]:
@app.route("/user/<name>")
def get_user_name(name):
    return "<h1>Hello, {}!</h1>".format(name)

@app.route("/user/<int:uid>")
def get_user_id(uid):
    if isinstance(uid, int):
        return "<h1>Your ID: {}</h1>".format(uid)
    return "<h1>ID should be int</h1>"

@app.route("/user/<path:path>")
def get_user_path(path):
    return "<h1>Path: {}</h1>".format(path)

Check your URL map

You can get the above code from github

app.run parameter: threaded

  • True: multi thread
  • False: single thread (default)
In [ ]:
# hello.py

from time import sleep

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    sleep(10)
    return "<h1>Hello Word</h1>"

@app.route("/test")
def test():
    return "<h1>Test</h1>"

if __name__ == "__main__":
    app.run(threaded=True, debug=True)

You can trigger "/" first and then "/test" with/without "threaded=True", and see what is different .

Get 'request' information

In [ ]:
# hello.py

from flask import Flask, request

app = Flask(__name__)

@app.route("/") 
def index():
    user_agent = request.headers.get("User-Agent")
    user_name = request.args.get("name")
    return "<p>Your browser is {}</p><p>Your name is {}</p>".format(user_agent, user_name)

if __name__ == "__main__":
    app.run(threaded=True, debug=True)

Request hooks

  • before_first_request: Register a function to run before the first request is handled.
  • before_request: Register a function to run before each request.
  • after_request: Register a function to run after each request, if no unhandled exceptions occurred.
  • teardown_request: Register a function to run after each request, even if unhandled exceptions occurred.

Example: use before_request to calculate the number of times you buy thiings

In [ ]:
#from flask import url_for

statistic_data = {}

@app.before_request
def statistic():
    # request.path in [url_for("index"), url_for("get_statistic")]
    if request.path in ["/", "/statistic"]:
        return

    statistic_data[request.path] = statistic_data.setdefault(request.path, 0) + 1

@app.route("/statistic") 
def get_statistic():
    return "statistic_data: {}".format(statistic_data)

@app.route("/buy/food")
def buy_food():
    return "<p>Here is your food.</p>"

@app.route("/buy/drink")
def buy_drink():
    return "<p>Here is your dirnk.</p>"

You can get the above code from github

Status code

  • Default: 200

Status code 400

In [ ]:
# hello.py

from flask import Flask

app = Flask(__name__)

@app.route("/") 
def index():
    return "<h1>Bad Request</h1>", 400

if __name__ == "__main__":
    app.run(threaded=True, debug=True)

Status code 302

In [ ]:
@app.route("/") 
def index():
    return "<h1>Redirect</h1>", 302, {"Location": "http://www.google.com"}
In [ ]:
from flask import redirect

@app.route("/")
def index():
    return redirect("http://www.google.com")

Response

In [ ]:
from flask import make_response

@app.route("/no_cookie")
def no_cookie(): 
    response = make_response("<h1>This document doesn't carry a cookie!</h1>")
    return response

In [ ]:
@app.route("/has_cookie")
def has_cookie():
    response = make_response("<h1>This document carries a cookie!</h1>")
    response.set_cookie("answer", "42")
    return response

In [ ]:
from flask import Response

@app.route("/has_cookie")
def has_cookie():
    data = "<h1>This document carries a cookie!</h1>"
    headers = {}
    headers["Set-Cookie"] = "answer=45"
    return Response(data, headers=headers)

abort

  • Return specific HTTP status code
    • client error: 4xx
    • server error: 5xx
In [ ]:
from flask import abort

def load_user(uid):
    try:
        uid = int(uid)
        if uid == 1:
            return "Maomao"
        elif uid == 2:
            return "Alicia"
    except BaseException:
        return

@app.route("/user/<uid>")
def get_user(uid):
    user = load_user(uid)
    if not user:
        abort(400)   
    else:
        return "<h1>Hello, {}!</h1>".format(user)

You can get the above code from github

Flask extension

Command-Line options with Flask-Script

  • A command-line parser.
  • Can define custom commands by yourself.

Installation

  • pip install flask-script
In [ ]:
# hello.py

from flask import Flask
from flask_script import Manager
#from flask.ext.script import Manager

app = Flask(__name__)
manager = Manager(app)

@app.route("/")
def index():
    return "<h1>Hello Word</h1>"

if __name__ == "__main__":
    manager.run()

Command: runserver

  • $ python hello.py runserver
  • $ python hello.py runserver --port 5566
  • $ python hello.py runserver --host 0.0.0.0

Command: shell

  • Start up an interactive Python shell, setup the correct application context and the local variables.
  • $ python hello.py shell

Custom command: hello

  • $ python hello.py hello
In [ ]:
@manager.command
def hello():
    """Just say hello"""
    print("hello")

You can get the above code from github

Ch3: Template

Mixing front-end and back-end codes leads to code that is hard to understand and maintain.

We will separate front-end code and put them in a folder.

By default, flask looks for front-end code in a subdirectory called 'templates' located in the root folder.

The Jinja2 template engine

In [ ]:
<!-- templates/index.html -->

<h1>Hello World!</h1>
In [ ]:
<!-- templates/user.html -->

<h1>Hello, {{ name }}!</h1>
In [ ]:
# hello.py

from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
    return render_template("index.html")

@app.route("/user/<name>")
def user(name):
    return render_template("user.html", name=name)

if __name__ == "__main__":
    app.run(debug=True)

Jinja2 recognizes variables of any type such as lists, dictionaries and objects.

In [ ]:
<!-- templates/test.html -->

<p>mydict["key"]: {{ mydict["key"] }}</p>
<p>mylist[3]: {{ mylist[3] }}</p>
<p>mylist[myintvar]: {{ mylist[myintvar] }}</p>
<p>instance.method1(): {{ instance.method1() }}</p>
<p>instance.method2(): {{ instance.method2() }}</p>
<p>instance.method3(5): {{ instance.method3(5) }}</p>
<p>class.method2(): {{ class.method2() }}</p>
<p>class.method3(10): {{ class.method3(10) }}</p>
In [ ]:
@app.route("/test")
def test():
    mydict = {"key": "This is a secret"}
    mylist = [1, 2, 3, 4]
    myintvar = 0

    class Myobj():
        def method1(self):
            return "I'm a instance method."

        @staticmethod
        def method2():
            return "I'm a static method."

        @classmethod
        def method3(cls, value):
            return "I'm a class method, get value {}".format(value)

    context = {
        "mydict": mydict,
        "mylist": mylist,
        "myintvar": myintvar,
        "instance": Myobj(),
        "class": Myobj
    }

    return render_template("test.html", **context)

If you don't know the difference between instance, static and class method, you can refer to

Jinja2 does not support all python syntax

In [ ]:
<!-- templates/test.html -->

<p>mylist length: {{ len(mylist) }}</p>

Variables can be modified with filters (pipe character as separator)

In [ ]:
<!-- templates/test.html -->

<p>mylist length: {{ mylist|length }}</p>

In [ ]:
<!-- templates/user.html -->

<h1>Hello, {{ name|capitalize }}</h1>

Common filters

  • safe: Renders the value without applying escaping
  • capitalize: Converts the first character of the value to uppercase and the rest to lowercase
  • lower: Converts the value to lowercase characters
  • upper: Converts the value to uppercase characters
  • title: Capitalizes each word in the value
  • trim: Removes leading and trailing whitespace from the value
  • striptags: Removes any HTML tags from the value before rendering

Filter: safe

In [ ]:
<!-- template/test.html -->

{{ code }}
{{ code|safe }}
In [ ]:
@app.route("/test")
def test():
    code = "<h1>Hello</h1>"
    return render_template("test.html", code=code)

In [ ]:
@app.route("/vulnerable")
def vulnerable():
    code = """<script>alert("I'm a hacker.")</script>"""
    return render_template("test.html", code=code)

Multiple filters can be chained. The output of one filter is applied to the next.

In [ ]:
<!-- templates/user.html -->

<h1>Have tags: {{ name|title }}</h1>
<h1>No tags: {{ name|striptags|title }}</h1>

You can get the above code from github

Jinja2 control structure

if... else...

In [ ]:
<!-- templates/user.html -->

{% if name %}
    <h1>Hello, {{ name|title }}!</h1>
{% else %}
    <h1>Hello, Stranger!</h1>
{% endif %}
In [ ]:
# hello.py

from flask import Flask, render_template

app = Flask(__name__)
registered_users = ["maomao", "alicia"]

@app.route("/user/<name>")
def user(name):
    if name not in registered_users:
        name = None
    return render_template("user.html", name=name) 

if __name__ == "__main__":
    app.run(debug=True)

for... in ...

In [ ]:
<!-- templates/users.html -->

<h1>User List</h1>
<ul>
    {% for user in users %}
        <li>{{ user|title }}</li>
    {% endfor %}
</ul>
In [ ]:
@app.route("/users")
def users():
    return render_template("users.html", users=registered_users)

macro

  • Similar to a python function
In [ ]:
<!-- templates/users.html -->

{% macro render_user(user) %}
    <li>{{ user|title }}</li>
{% endmacro %}

<h1>User List</h1>
<ul>
    {% for user in users %}
        {{ render_user(user) }}
    {% endfor %}
</ul>

import

  • To resue the code, we can import some parts from other templates.
In [ ]:
<!-- template/macros.html -->

{% macro render_user(user) %}
    <li>{{ user|title }}</li>
{% endmacro %}
In [ ]:
<!-- templates/users.html -->

{% import "macros.html" as macros %}

<h1>User List</h1>
<ul>
    {% for user in users %}
        {{ macros.render_user(user) }}
    {% endfor %}
</ul>

include

  • To aviod repetition, we can have separate templates that stores all the contents.
  • We only have to 'include' it when we need it.
In [ ]:
<!-- templates/footer.html -->

<footer>
  <p>footer © 2017</p>
</footer>
In [ ]:
<!-- templates/users.html -->

{% import "macros.html" as macros %}

<h1>User List</h1>
<ul>
    {% for user in users %}
        {{ macros.render_user(user) }}
    {% endfor %}
</ul>

{% include "footer.html" %}

extends

  • Similar to Python's class inheritance.
  • You can overwrite the blocks.
  • Uses super() to retain the original contents.
In [ ]:
<!-- templates/base.html -->

<html>
<head>
    {% block head %}
    <title>{% block title %}{% endblock %} - My Application </title>
    {% endblock %}
</head>
<body>
    {% block body %}
    {% endblock %}
</body>
</html>
In [ ]:
<!-- templates/index.html -->

{% extends "base.html" %}

{% block title %}Index{% endblock %}
{% block head %}
    {{ super() }}
    <style>
    </style>
{% endblock %}
{% block body %}
<h1>Hello World!</h1>
{% endblock %}
In [ ]:
@app.route("/")
def index():
    return render_template("index.html")

You can get the above code from github

How to change the template folder?

  • template_folder: Default is 'templates'
In [5]:
from flask import Flask

app = Flask(__name__, template_folder="my_templates")

Flask extension

Twitter Bootstrap Integration with Flask-Bootstrap

  • Bootstrap is an open source framework from Twitter.
  • bootstrap/base.html
    • Includes all the CSS and javascript files from Bootstrap.
    • Base template provides numerous blocks that you can overwrite.
      • EX: head, title, body

Installation

  • pip install flask-bootstrap
In [ ]:
<!-- templates/user.html -->

{% extends "bootstrap/base.html" %}

{% block title %}Flasky{% endblock %}

{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle"
            data-toggle="collapse" data-target=".navbar-collapse">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">Flasky</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="/">Home</a></li>
            </ul>
        </div>
    </div>
</div>
{% endblock %}

{% block content %}
<div class="container">
    <div class="page-header">
        <h1>Hello, {{ name }}!</h1>
    </div>
</div>
{% endblock %}
In [ ]:
# hello.py

from flask import Flask, render_template
from flask_bootstrap import Bootstrap
#from flask.ext.bootstrap import Bootstrap

app = Flask(__name__)
bootstrap = Bootstrap(app)

@app.route("/user/<name>")
def user(name):
    return render_template("user.html", name=name)

if __name__ == "__main__":
    app.run(debug=True)

Many of the blocks are used by Flask-Bootstrap itself, so overriding them directly would cause problems. (e.g. scripts, styles)

  • Jinja2's super() function must be used if you need to overwrite the content.
In [ ]:
{% block scripts %}
{{ super }}
<script type="text/javascript" src="my-script.js"></script>
{% endblock %}

Custom error pages

In [ ]:
<!-- templates/base.html -->

{% extends "bootstrap/base.html" %}

{% block title %}Flasky{% endblock %}

{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle"
            data-toggle="collapse" data-target=".navbar-collapse">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">Flasky</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="/">Home</a></li>
            </ul>
        </div>
    </div>
</div>
{% endblock %}

{% block content %}
<div class="container">
    {% block page_content %}{% endblock %}
</div>
{% endblock %}
In [ ]:
<!-- templates/404.html -->

{% extends "base.html" %}

{% block title %}Flasky - Page Not Found{% endblock %}

{% block page_content %}
<div class="page-header">
    <h1>Not Found</h1>
</div>
{% endblock %}
In [ ]:
<!-- templates/500.html -->

{% extends "base.html" %}

{% block title %}Flasky - Error{% endblock %}

{% block page_content %}
<div class="page-header">
    <h1>Internal Server Error</h1>
</div>
{% endblock %}
In [ ]:
from flask import abort

@app.errorhandler(404)
def page_not_found(e):
    return render_template("404.html"), 404

@app.errorhandler(500)
def internal_server_error(e):
    return render_template("500.html"), 500

@app.route("/test")
def test():
    abort(500)

Coding Time

  • Complete the route to "/" and its template (templates/index.html)