# 第六週: More about Flask
###### tags: `Flask`
### Part 1: 透過 Flask 套件, 建立python 網頁伺服器
練習1:
步驟1: 創建新的專案資料夾, flask_blog
步驟2: 在該專案資料夾中, 新建 app.py 檔案.
app.py
```python=
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello, World!'
if __name__ == '__main__' :
app.run()
```
練習2: 建立 route with decorator
步驟1: 修改 app.py, 並執行應用程式
```python=
@app.route('/home')
def home():
return '<h1>Home Page</h1>'
@app.route('/about')
def about():
return '<h1>About Page</h1>'
```
### Part 2: 新增 templates 資料夾, 並新增 html 檔案: home.html 和 about.html
步驟1: 修改 app.py 呼叫 templates 資料夾中的 html 檔案
app.py
```python=
from flask import Flask
from flask import render_template
app = Flask(__name__)
@app.route('/')
@app.route('/home')
def home():
return render_template("home.html")
@app.route('/about')
def about():
return render_template("about.html")
if __name__ == '__main__' :
app.run()
```
步驟2: 在專案資料夾中, 新增 templates 資料夾
步驟3: 在 templates 資料夾中, 新增 home.html 檔案
home.html
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
<title>Flask Blog</title>
</head>
<body>
<h1>Home Page</h1>
</body>
</html>
```
步驟4: 在 templates 資料夾中, 新增 about.html 檔案
about.html
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<h1>About Page</h1>
</body>
</html>
```
### Part 3: 執行應用程式並將資料傳送到網頁頁面
練習1: 新增測試資料( dummy data )
步驟1: 在 app.py 的主程式中, 建立測試資料
```python=
posts = [
{
'author' : 'Alice Chen',
'title' : 'Blog Post 1',
'content' : 'First post content',
'date_posted': 'October 10, 2021',
},
{
'author' : 'Bob Lin',
'title' : 'Blog Post 2',
'content' : 'Second post content',
'date_posted': 'October 12, 2021',
}
]
....
....
....
@app.route('/home')
def home():
return render_template("home.html", posts=posts)
```
步驟2: 修改 home.html 檔案, 取出主程式傳過來的資料(via posts)
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
<title>Flask Blog</title>
</head>
<body>
{% for post in posts %}
<h1> {{ post.title}} </h1>
<p> By {{ post.author}} on {{ post.date_posted }}</p>
<p> {{ post.content}}</p>
{% endfor %}
</body>
</html>
```
練習2 了解主程式和網頁間,資訊的傳遞.
步驟1: 修改 for both home.html 和 about.html 檔案
```htmlmixed=
<head>
{% if title %}
<title>Flask Blog - {{ title }}</title>
{% else %}
<title>Flask Blog</title>
{% endif %}
</head>
```
步驟2: 修改 app.py
```python=
@app.route('/about')
def about():
return render_template("about.html", title='About')
```
步驟3: 執行程式並觀察 title tab 的執行結果.
練習3 模組化 HTML 頁面
步驟1: 新增 layout.html, 並取出 home.html 和 about.html 共同的程式碼
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
{% if title %}
<title>Flask Blog - {{ title }}</title>
{% else %}
<title>Flask Blog</title>
{% endif %}
</head>
<body>
{% block content %} {% endblock %}
</body>
</html>
```
步驟2: 修改 home.html
```htmlmixed=
{% extends "layout.html" %}
{% block content %}
{% for post in posts %}
<h1> {{ post.title}} </h1>
<p> By {{ post.author}} on {{ post.date_posted }}</p>
<p> {{ post.content}}</p>
{% endfor %}
{% endblock content %}
```
步驟3: 修改 about.html
```htmlmixed=
{% extends "layout.html" %}
{% block content %}
<h1> About Page </h1>
{% endblock content %}
```
步驟4: 執行程式並觀察網頁原始碼(source)的執行結果
### Part 4: 套入 Bootstrap
練習1
步驟1: 修改 layout.html
```htmlmixed=
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
......
</head>
<body>
<div class="container">
{% block content %} {% endblock %}
</div>
<!-- Optional JavaScript; choose one of the two! -->
<!-- Option 1: Bootstrap Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
```
步驟2: 套入 Navigator Bar
(1) 修改 layout.html 檔案, 套入 navigation
```htmlmixed=
<header class="site-header">
<nav class="navbar navbar-expand-md navbar-dark bg-steel fixed-top">
<div class="container">
<a class="navbar-brand mr-4" href="/">Flask Blog</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggle" aria-controls="navbarToggle" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarToggle">
<div class="navbar-nav mr-auto">
<a class="nav-item nav-link" href="/">Home</a>
<a class="nav-item nav-link" href="/about">About</a>
</div>
<!-- Navbar Right Side -->
<div class="navbar-nav">
<a class="nav-item nav-link" href="/login">Login</a>
<a class="nav-item nav-link" href="/register">Register</a>
</div>
</div>
</div>
</nav>
</header>
```
步驟2: 套入主選單 main
```htmlmixed=
<main role="main" class="container">
<div class="row">
<div class="col-md-8">
{% block content %}{% endblock %}
</div>
<div class="col-md-4">
<div class="content-section">
<h3>Our Sidebar</h3>
<p class='text-muted'>You can put any information here you'd like.
<ul class="list-group">
<li class="list-group-item list-group-item-light">Latest Posts</li>
<li class="list-group-item list-group-item-light">Announcements</li>
<li class="list-group-item list-group-item-light">Calendars</li>
<li class="list-group-item list-group-item-light">etc</li>
</ul>
</p>
</div>
</div>
</div>
</main>
```
步驟3: 新增 static 資料夾, 並在該資料夾新增 main.css 檔案
```htmlmixed=
body {
background: #fafafa;
color: #333333;
margin-top: 5rem;
}
h1, h2, h3, h4, h5, h6 {
color: #444444;
}
.bg-steel {
background-color: #5f788a;
}
.site-header .navbar-nav .nav-link {
color: #cbd5db;
}
.site-header .navbar-nav .nav-link:hover {
color: #ffffff;
}
.site-header .navbar-nav .nav-link.active {
font-weight: 500;
}
.content-section {
background: #ffffff;
padding: 10px 20px;
border: 1px solid #dddddd;
border-radius: 3px;
margin-bottom: 20px;
}
.article-title {
color: #444444;
}
a.article-title:hover {
color: #428bca;
text-decoration: none;
}
.article-content {
white-space: pre-line;
}
.article-img {
height: 65px;
width: 65px;
margin-right: 16px;
}
.article-metadata {
padding-bottom: 1px;
margin-bottom: 4px;
border-bottom: 1px solid #e3e3e3
}
.article-metadata a:hover {
color: #333;
text-decoration: none;
}
.article-svg {
width: 25px;
height: 25px;
vertical-align: middle;
}
.account-img {
height: 125px;
width: 125px;
margin-right: 20px;
margin-bottom: 16px;
}
.account-heading {
font-size: 2.5rem;
}
```
步驟4: 修改 home.html, 套入 boostrap
```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>
```
步驟5: 套入 url_for 套件, 修改 app.py
```python=
from flask import Flask, render_template, url_for
```
步驟6: 修改 layout.html 檔案
```htmlmixed=
<!-- Bootstrap CSS -->
......
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">
```
步驟7: 執行程式並觀察網頁執行結果
### Part 5: 表單處理及使用者輸入資料驗證
練習1: 主要概念
步驟1: 安裝 flask-wtf 套件
```shell=
pip install flask-wtf
```
步驟2: 在專案資料夾中新增一個檔案, forms.py
步驟3: 表單及其驗證的基本操作方式
```python=
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired, Length
class RegistrationForm(FlaskForm):
username = StringField('Username',
validators=[DataRequired(), Length(min=2, max=20)])
```
練習2: 完成 registration form 以及 login form
步驟1: registration form.
```python=
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, Email, EqualTo
class RegistrationForm(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')
```
步驟2: login form
```python=
from wtforms import StringField, PasswordField, SubmitField, BooleanField
class LoginForm(FlaskForm):
username = StringField('Username',
validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
remember = BooleandField('Remember Me')
submit = SubmitField('Login')
```
步驟3: 產生 secret key
(1)
```python=
app.config['SECRET_KDY'] = ''
```
(2)
```shell=
$ python
>>> import secrets
>>> secrets.token_hex(16)
...............
>>> exit()
```
(3) 將上述(2)所產生的亂數, 填入程式碼的 SECRET_KEY 中.
```python=
app.config['SECRET_KDY'] = '<secrets.token_hex>'
```
步驟4: 修改 app.py, 並設定路由(route)到填寫表格的頁面.
(1) 修改 app.py,
```python=
from forms import RegistrationForm, LoginForm
@app.route('/register')
def register():
form = RegistrationForm()
return render_template("register.html", title='Register', form=form)
@app.route('/login')
def register():
form = LoginForm()
return render_template("login.html", title='Login', form=form)
```
(2) 在 templates 資料夾中, 新建 register.html 及 login.html 檔案.
register.html
```htmlmixed=
{% extends "layout.html" %}
{% block content %}
{% for post in posts %}
<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>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</fieldset>
</form>
</div>
<div class="border-top pt-3">
<small class="text-muted">
Already Hav An Account? <a class="ml-2" href="{{ url_for('login')}}">Sign In</a>
</small>
</div>
{% endfor %}
{% endblock content %}
```
login.html
```htmlmixed=
{% extends "layout.html" %}
{% block content %}
{% for post in posts %}
<h1> About Page </h1>
{% endfor %}
{% endblock content %}
```
(3) 修改 app.py
```python=
@app.route('/register', method=['GET', 'POST'])
def register():
form = RegistrationForm()
return render_template("register.html", title='Register', form=form)
```
練習3: 驗證使用者輸入的資料, 並用 flash message 告知/警告使用者
步驟1: 修改 app.py
```python=
from flask import Flask, render_template, url_for, flash, redirect
@app.route('/register')
def register():
form = RegistrationForm()
if form.validate_on_submit():
flash(f'Account created for {form.username.data}!','success')
return redirect(url_for('home'))
return render_template("register.html", title='Register', form=form)
```
步驟2: 修改 layout.html
```htmlmixed=
<main role="main" class="container">
<div class="row">
<div class="col-md-8">
{% with message = 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>
.....
.....
</div>
</main>
```
步驟3: 修改register.html網頁處理使用者輸入錯誤
```htmlmixed=
<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>
<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>
<div class="form-group">
{{ form.password.label(class="form=control-label")}}
{% if form.password.errors %}
{{ form.password(class="form-control form-control-lg is-invalid")}}
<div class="invalid=feedback">
{% for error in form.password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.password(class="form-control form-control-lg")}}
{% endif %}
</div>
<div class="form-group">
{{ form.confirm_password.label(class="form=control-label")}}
{% if form.confirm_password.errors %}
{{ form.confirm_password(class="form-control form-control-lg is-invalid")}}
<div class="invalid=feedback">
{% for error in form.confirm_password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.confirm_password(class="form-control form-control-lg")}}
{% endif %}
</div>
```
步驟4: 修改 login.html 網頁處理使用者輸入錯誤
```htmlmixed=
{% extends "layout.html" %}
{% block content %}
{% for post in posts %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Log In</legend>
<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>
<div class="form-group">
{{ form.password.label(class="form=control-label")}}
{% if form.password.errors %}
{{ form.password(class="form-control form-control-lg is-invalid")}}
<div class="invalid=feedback">
{% for error in form.password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.password(class="form-control form-control-lg")}}
{% endif %}
</div>
<div class="form-check">
{{ form.remember(class="form-check-input") }}
{{ form.remember.label(class="form-check-label") }}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
<small class="text-muted ml-2">
<a href="#">Forgot Password? </a>
</small>
</form>
</div>
<div class="border-top pt-3">
<small class="text-muted">
Need An Account? <a class="ml-2" href="{{ url_for('register')}}">Sign Up Now</a>
</small>
</div>
{% endfor %}
{% endblock content %}
```
(4) 測試 login page (使用 dummy fake data)
```python=
@app.route('/login', method=['GET', 'POST'])
def register():
form = LoginForm()
if form.validate_on_submit():
if form.email.data == 'admin@blog.com' and form.email.password == 'password':
flash('You have been logged in!', 'success')
return redirect(url_for('home'))
else:
flash('Login Unsuccessful. Please check username and password', 'danger')
return render_template("login.html", title='Login', form=form)
```
步驟5 修改 layout.html, 讓應用程式的路由(routing)統一由主程式處理.
```htmlmixed=
<div class="collapse navbar-collapse" id="navbarToggle">
<div class="navbar-nav mr-auto">
<a class="nav-item nav-link" href="/">"{{ url_for('home')}}"</a>
<a class="nav-item nav-link" href="/about">="/">"{{ url_for('about')}}"</a>
</div>
<!-- Navbar Right Side -->
<div class="navbar-nav">
<a class="nav-item nav-link" href="/login">"{{ url_for('login')}}"</a>
<a class="nav-item nav-link" href="/register">"{{ url_for('register')}}"</a>
</div>
</div>
```
================================================
```python=
from flask import render_template
@app.route('/register')
def register():
return render_template("register.html")
```
並在 template 資料夾中新增 register.html
```htmlmixed=
<!DOCTYPE html>
<html>
<body>
<h2>Text input fields</h2>
<form>
<label for="fname">First name:</label><br>
<input type="text" id="fname" name="fname"><br>
<label for="lname">Last name:</label><br>
<input type="text" id="lname" name="lname">
<input type="submit" value="submit">
</form>
<p>Note that the form itself is not visible.</p>
<p>Also note that the default width of text input fields is 20 characters.</p>
</body>
</html>
```
嘗試從 url 傳入 value
```python=
@app.route('/hello/<username>')
def hello_user(username):
return 'Hello, ' + username
```
參考資料:
1. https://www.w3schools.com/html/html_forms.asp
2. Bootstrap: https://getbootstrap.com/docs/5.1/getting-started/introduction/#starter-template
3.