Flask WebForm

Date: Mar. 3rd, 2019

Lecturer: Chia

OUTLINE

  • Web Form
    • Flask-WTF
    • Bootstrap 表單模板
    • 轉址與使用者 session
    • 閃現(flash) 訊息
  • 用 Flask-Moment 來將日期與時間當地化

Web Form

  • 使用者提供資料,讓伺服器接收與處理。
  • 通常使用POST request。
  • 安裝擴充套件:Flask-WTF
$ mkdir webform
$ cd webform
$ python -m venv venv
$ venv\Scripts\activate
(venv) $ pip install flask

開啟虛擬環境

hello.py初始化

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return '<h1>Hello World!</h1>'
(venv) $ set FLASK_APP=hello.py
(venv) $ set FLASK_DEBUG=1
(venv) $ flask run

執行Flask

安裝擴充套件:Flask-WTF

(venv) $ pip install flask-wtf
  • Flask-WTF內含無關特定框架的WTForms套件。
  • Flask-WTF不需要做app層級的初始化。
  1. 在app組態設置密鑰(secret key)
  2. 在app定義表單類別
  3. 轉譯表單HTML
  4. 在app的view函式中處理表單

Basic HTML Form

在app組態設置密鑰(secret key):

  1. 防止使用者session的內容被算改。
  2. 避免受到跨網站請求造照(CSRF,cross-site request forgery)的攻擊。
from flask import Flask            

app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired

class NameForm(FlaskForm):
    name = StringField('What is your name?', validators=[DataRequired()])
    submit = SubmitField('Submit')

在app定義表單類別:

  • 每一個Web表單都是用一個繼承FlaskForm的類別來表示。
  • 類別內包含欄位、欄位物件、驗證函式。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Web Form</title>
</head>
<body>
    <form method="POST">
        {{ form.hidden_tag() }}
        {{ form.name.label }} {{ form.name() }}
        {{ form.submit() }}
    </form>
</body>
</html>

轉譯表單HTML:

在templates資料夾下,建立webform.html

  • {{ form.hidden_tag() }} 定義一個隱藏的表單欄位,可用來實作CSRF防護
  • {{ form.name(id='my-text-field') }} 可透過 id 定義CSS樣式
from flask import render_template

@app.route('/', methods=['GET', 'POST'])
def index():
    name = None
    form = NameForm()
    if form.validate_on_submit():
        name = form.name.data
        form.name.data = ''
    return render_template('webform.html', form=form, name=name)

在app的view函式中處理表單:

  • validate_on_submit()方法:

在表單被送出且所有欄位驗證函式都收到資料時回傳True。

LAB- 1

  • 請新增欄位名稱為a的BooleanField(),必須有資料驗證,並於html上顯示其<label>和<input>
  • 請新增欄位名稱為b的TextAreaField(),無資料驗證,並於html上顯示其<label>和<input>
  • 請擇一給予特定的 id,並定義其CSS樣式    

安裝擴充套件:Flask-Bootstrap

(venv) $ pip install flask-bootstrap
  • 透過匯入Bootstrap的表單模板,來轉譯Flask-WTF表單。
  • Bootstrap表單模板:bootstrap/wtf.html
  1. 在建立app實例時同時初始化
  2. 新增一個基礎模版(base_index.html)
  3. webform.html繼承基礎模板,並套用wtf.html

Bootstrap表單模板

在建立app實例時同時初始化:

from flask_bootstrap import Bootstrap

bootstrap = Bootstrap(app)

新增一個基礎模版(base_index.html):

  • 此基礎模版會繼承bootstrap/base.html
{% extends 'bootstrap/base.html' %}

{% block head %}
{{ super() }}
    <title>{% block title %}{% endblock %} - My Application</title>
{% endblock %}

{% block content %}
<div class="container">
    {% block page_content %}{% endblock %}
</div>
{% endblock %}

webform.html 繼承基礎模板,並套用wtf.html

  • wtf.quick_form() 接收Flask-WTF表單物件。
{% extends "base_index.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}webform{% endblock %}
{% block head %}
    {{ super() }}
{% endblock %}

{% block page_content %}
<div class="page_header">
    <h1>Hello, 
    {% if name %}
        {{ name }}
    {% else %}
        Stranger
    {% endif %}!</h1>
</div>
    {{ wtf.quick_form(form) }}
{% endblock %}

轉址與使用者session

  • 原因:重新整理網頁時,瀏覽器會重複送出上一個送出的request,亦即重複送出表單資料。
  • 解決方法:利用轉址(redirect) - Post/Redirect/Get 模式

轉址(redirect)

  • 為一種特殊的回應,常用於處理web表單。

 

  • 常見的四種回應:
    1. HTTP狀態碼
    2. 回應物件
    3. 轉址(redirect)
    4. 錯誤處理
# 轉址(redirect)

from flask import redirect
@app.route('/')
def index():
    return redirect('http://www.example.com')

轉址與使用者session

然而,當Post request一結束,表單資料即消失。

因此,需要透過session(私用的存放區)在兩次request之間"記得"它。

  • 使用轉址(Redirect)來回應POST請求,轉址的內容為URL。
  • 當瀏覽器收到轉址回應時,會用GET發出轉址URL。
from flask import session, redirect, url_for

@app.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        session['name'] = form.name.data
        return redirect(url_for('index'))
    return render_template('webform.html', form=form, name=session.get('name'))

url_for(endpoint)​

redirect(url_for('index')) 亦可寫成 redirect('/')
  • 第一個且唯一的引數是endpoint(端點)。
  • 預設下,路由的endpoint為view函式名稱
    • 呼叫 url_for('index', _external=True)
    • 收到絕對URL,在本範例為 http://127.0.0.1:5000/

Request

用戶端發出request,伺服器會接收並呼叫view函式來處理。

URL map存有URL與view函式之間的對應關係。

(venv) $ python
>>> from hello import app
>>> app.url_map

LAB - 2

  • 請在app中,定義名稱為web的view函式,路由為/web,將之轉址至任一網站。

閃現(flash)訊息

  • 顯示更新訊息,可能用於確認、警告或錯誤。
from flask import flash

@app.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        old_name = session.get('name')
        if old_name is not None and old_name != form.name.data:
            flash('Looks like tou have changed your name!')
        session['name'] = form.name.data

        return redirect(url_for('index'))
    return render_template('webform.html', form=form, name=session.get('name'))

修改base_index.html基礎模板:

  • 以轉譯app所定義的訊息。
  • 基礎模板為轉譯閃現訊息的最佳地點。
{% block content %}
<div class="container">

{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
    <button type="button" class="close" data-dismiss="alert">&times;</button>
    {{ message }}
</div>
{% endfor %}

{% block page_content %}{% endblock %}

</div>
{% endblock %}

get_flashed_messages()

得到的訊息在下次呼叫此函數時就無法取得,故只會閃現一次。

將日期與時間當地化

安裝擴充套件:Flask-Moment

(venv) $ pip install flask-moment
  • 讓瀏覽器讀取電腦的時區與地區,並用JavaScript轉換並轉譯成當地的時間。
  • Moment.js:Flask擴充套件,需要jQuery.js。

初始化Flask-Moment

from flask_moment import Moment
moment = Moment(app)

base_index.html中,匯入Moment.js程式庫

{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}
  • 若使用Flask-Bootstrap,則只需加入Moment.js即可。

添加datetime變數

from datetime import datetime

@app.route('/')
def index():
    return render_template('webform.html', current_time=datetime.utcnow())

使用時戳 (timestamp)

webform.html中,用Flask-Moment來轉譯時戳

{% block content %}
    <p>The local date and time is {{ moment(current_time).format('LLL') }}.</p>
    <p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>
{{ super() }}
{% endblock %}

使用時戳 (timestamp)

  • format('LLL') 根據電腦的時區與地區來轉譯日期與時間。
  • 從 'L' 到 'LLLL' 代表四個等級的詳細程度。
  • fromNow() 轉譯相對時戳。如:“a few seconds ago”

base_index.html中,更改時戳轉譯的語言

{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{{ moment.locale('es')}}
{% endblock %}

更改時戳轉譯的語言

LAB-3

  • 請將時戳轉譯的語言改為德文

  • 請將時戳的格式改為最精簡

Thanks for your listening

Made with Slides.com