Flask + Jinja2 學習紀錄

Steven Wang
32 min readApr 24, 2019

--

比Django較簡單

pip install flask
pip install jinja2

起手式

app.py

from flask import Flask, render_template

app = Flask(__name__)

@app.route(‘/’) #新增路徑
def start():
return ‘Hello World!’

@app.route(‘/create-restaurant’, methods=[‘GET’, ‘POST’])
#methods預設是GET
#GET from server 取得網頁頁面資料
#POST from webpage input data to server
def create_restaurant():
return render_template(‘create_restaurant.html’)
#render_template 到templates資料夾的html檔案

if __name__ == ‘__main__’:
#是主程式name == main
#不是主程式而是被import name==filename
app.jinja_env.auto_reload = True #不會因為cache暫存而不更改網頁
app.run(debug=True) #debug=True開啟除錯模式_正式上線要關掉

資料夾規劃

static
放css檔案

templates
放html檔案

models
資料庫的模組

網頁規劃_引用

基底網頁 base.html給其他網頁引用,置換內容的部分用
{% block name%} {% endblock%}

引用基底的網頁
{% extends ‘base.html’ %}

{% block name%}
content
{% endblock %}

網頁內取得系統檔案路徑及指定檔案

<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}

{{ url_for(‘folder_name,filename =’file_name’’)}}

取得表單的資料(如何在後端接收使用者輸入的資料)

app.py

from flask import request
name = request.form.get(‘name’)
#取得這個表單的name
#對應到html元件標籤屬性name=”name”

@app.route('/create-restaurant', methods=['GET', 'POST'])
def create_restaurant():
if request.method == 'POST':
name = request.form.get('name')
description = request.form.get('description')
site_url = request.form.get('site_url')

return '{} {} {}'.format(name, description, site_url)

return render_template('create_restaurant.html')

如果request.method方法是POST 資料來自表單,透過request.form.get(‘name’)取得輸入的值

如果request.method方法是GET
到這個頁面render_template(‘create_restaurant.html’)

request.form.get(‘name’)對應到html檔案 html元件標籤 屬性name=”name”

<input name="name" type="text">

使用者輸入的資料存到資料庫

資料沒能儲存起來,之後很難做利用
pip install SQLAlchemy

database.py

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import os

current_dir = os.path.dirname(__file__)

engine = create_engine('sqlite:////{}/luckydraw.db'.format(current_dir), convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()


def init_db():
Base.metadata.create_all(bind=engine)
print('We are connected to database successfully')

{}指到目前的路徑位置
修改luckydraw.db成為自己資料庫的名稱
新增print(‘We are connected to database successfully’)

定義目前的路徑位置

import os

current_dir = os.path.dirname(__file__)

app.py

from database import db_session, init_db

app = Flask(__name__)


@app.before_first_request
def init():
init_db()


@app.teardown_appcontext
def shutdown_session(exception=None):
db_session.remove()

第一次接收request會執行的def
init_db()#初始化資料庫

@app.before_first_request
def init():
init_db()

每次request結束 及 server shut down 能正確關閉資料庫session

@app.teardown_appcontext
def shutdown_session(exception=None):
db_session.remove()

「新增」新增資料庫的模組 讓資料可以儲存

models/restaurants.py

起手式 from SQLAlchemy webpage

from sqlalchemy import Column, Integer, String
from yourapplication.database import Base

class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True)
email = Column(String(120), unique=True)

def __init__(self, name=None, email=None):
self.name = name
self.email = email

def __repr__(self):
return '<User %r>' % (self.name)

範例說明

from sqlalchemy import Column, Integer, String, DateTime 
#import 資料庫型態 from SQLAlchemy
from database import Base
import uuid #協助產生獨一無二的ID
import datetime

#建立類別
class Restaurants(Base): #Restaurant為Class name
__tablename__ = 'restaurants' #restaurant為Table name
#以下是table的欄位
id = Column(String(50), primary_key=True) #primary_key 主鍵
name = Column(String(50), unique=True) #unique 唯一
description = Column(String(100), nullable=True) #可空白
site_url = Column(String(200), nullable=True)
draw = Column(Integer(), default=0) #default預設值
created_time = Column(DateTime(), nullable=False)
modified_time = Column(DateTime(), nullable=False)
#建立物件
def __init__(self, name, description, site_url):
#產生物件的時候只需要丟name,description,site_url就會替我們加入id,created_time,modified_time
self.id = str(uuid.uuid4())
self.name = name
self.description = description
self.site_url = site_url
self.created_time = datetime.datetime.now()
self.modified_time = datetime.datetime.now()



def __repr__(self):
return '<Restaurant %r>' % (self.name)

app.py

from models.restaurants import Restaurants@app.route('/create-restaurant', methods=['GET', 'POST'])
def create_restaurant():
if request.method == 'POST':
name = request.form.get('name')
description = request.form.get('description')
site_url = request.form.get('site_url')
#新增物件
restaurant = Restaurants(name=name, description=description, site_url=site_url)
db_session.add(restaurant) #將物件加入db_session
db_session.commit()#commit將db_session存入

return '{} {} {}'.format(name, description, site_url)

return render_template('create_restaurant.html')

透過DB Browser檢視資料庫

「讀取顯示」讀取資料庫顯示全部資料在網頁上

app.py

restaurants = Restaurants.query.all() 讀取全部資料

@app.route('/restaurants')
def restaurant_list():
restaurants = Restaurants.query.all()
#抓出所有的餐廳存入restaurants

return render_template('restaurant.html', restaurants=restaurants)
#將restaurants資料傳到restaurants.html

templates/restaurants.html

{% extends 'base.html' %} #extends 引用base.html

{% block titlte %}
Restaurants
{% endblock %}

{% block content %}
{% for restaurant in restaurants %} #迴圈跑restaurants
<div class="row justify-content-center">
<div class="col-lg-6">
<div class="card">
<div class="card-title">
<h4>{{ restaurant['name'] }}</h4>
#{{ restaurant['name'] }} 置入資料
</div>
<p class="text-muted ">
{{ restaurant['description'] }}
</p>
<div class="button-list">
<div class="btn-group">
<a href="{{ restaurant['site_url'] }}" class="btn btn-visit" target="_blank">Visit Site</a>
<a href="#" class="btn btn-primary">Edit</a>
<a href="#" onclick="return confirm('delete?')" class="btn btn-danger">Delete</a>
</div>
</div>
</div>
</div>
</div>
{% endfor %}

<div class="row justify-content-center">
<div class="col-lg-6" style="margin-bottom: 50px;">
<a href="/create-restaurant" class="btn btn-primary btn-block">New Restaurant</a>
</div>
</div>
{% endblock %}

「更新」選取的“特定”的資料

點擊 Edit 由 restaurant.html 帶出id

<a href="/edit-restaurant?id={{ restaurant['id'] }}" class="btn btn-primary">Edit</a>

templates/restaurant.html

{% for restaurant in restaurants %}
<div class="row justify-content-center">
<div class="col-lg-6">
<div class="card">
<div class="card-title">
<h4>{{ restaurant['name'] }}</h4>
</div>
<p class="text-muted ">
{{ restaurant['description'] }}
</p>
<div class="button-list">
<div class="btn-group">
<a href="{{ restaurant['site_url'] }}" class="btn btn-visit" target="_blank">Visit Site</a>
<a href="/edit-restaurant?id={{ restaurant['id'] }}" class="btn btn-primary">Edit</a> #帶出id
<a href="#" onclick="return confirm('delete?')" class="btn btn-danger">Delete</a>
</div>
</div>
</div>
</div>
</div>
{% endfor %}

templates/edit_restaurant.html

輸入input text box 顯示預設 value,顯示修改前的原值
<input name=”name” type=”text” value=”{{ restaurant[‘name’] }}” class=”form-control input-default”

按下submit帶出id傳送至/edit-restaurant做後續處理
<form method=”post” action=”/edit-restaurant?id={{ restaurant[‘id’] }}”>”

{% block content %}
<div class="row justify-content-center">
<div class="col-lg-6">

<div class="card">
<div class="card-title">
<h4>Edit Restaurant</h4>

</div>
<div class="card-body">
<div class="basic-form">
<form method="post" action="/edit-restaurant?id={{ restaurant['id'] }}"> #
<div class="form-group">
<p class="text-muted m-b-15 f-s-12">Restaurant Name</p>
<input name="name" type="text" value="{{ restaurant['name'] }}" class="form-control input-default" required="required">
</div>
<div class="form-group">
<p class="text-muted m-b-15 f-s-12">Description</p>
<input name="description" type="text" value="{{ restaurant['description'] }}" class="form-control input-default"
required="required">
</div>
<div class="form-group">
<p class="text-muted m-b-15 f-s-12">Site Url</p>
<input name="site_url" type="text" value="{{ restaurant['site_url'] }}" class="form-control input-default" required="required">
</div>
<button type="submit" class="btn btn-primary">Edit</button> #Edit才符合主題
</form>
</div>
</div>
</div>
</div>
</div>

app.py

request.args.get取出?id的值

選取id=id資料庫資料
Restaurants.query.filter(Restaurants.id == id).first()
first()選取第一個

request.method =’GET’

@app.route('/edit-restaurant', methods=['GET', 'POST'])
def edit_restaurant():
id = request.args.get('id') #request.args.get取出?id的值

restaurant = Restaurants.query.filter(Restaurants.id == id).first() #first()選取第一個

if request.method == 'POST':
name = request.form.get('name')
description = request.form.get('description')
site_url = request.form.get('site_url')

restaurant.name = name
restaurant.description = description
restaurant.site_url = site_url
restaurant.modified_time = datetime.datetime.now()

db_session.commit()

return redirect('/restaurants')

return render_template('edit_restaurant.html', restaurant=restaurant)

「刪除」選取的“特定”的資料

templates/restaurant.html

{% for restaurant in restaurants %}
<div class="row justify-content-center">
<div class="col-lg-6">
<div class="card">
<div class="card-title">
<h4>{{ restaurant['name'] }}</h4>
</div>
<p class="text-muted ">
{{ restaurant['description'] }}
</p>
<div class="button-list">
<div class="btn-group">
<a href="{{ restaurant['site_url'] }}" class="btn btn-visit" target="_blank">Visit Site</a>
<a href="/edit-restaurant?id={{ restaurant['id'] }}" class="btn btn-primary">Edit</a>
#<a href="/edit-restaurant?id={{ restaurant['id'] }}
<a href="/delete-restaurant?id={{ restaurant['id'] }}" onclick="return confirm('delete?')" class="btn btn-danger">Delete</a>
#<a href="/delete-restaurant?id={{ restaurant['id'] }}
</div>
</div>
</div>
</div>
</div>
{% endfor %}

app.py

db_session.delete(restaurant) 刪除

@app.route('/delete-restaurant')
def delete_restaurant():
id = request.args.get('id')

restaurant = Restaurants.query.filter(Restaurants.id == id).first()

if restaurant: #資料庫有的話
db_session.delete(restaurant) #delete
db_session.commit()

return redirect('/restaurants')

jinja2過濾器

app.py

meal 由app.jinja_env.filters[‘meal’]引導至def mealformat(value)
meal是一個值

def mealformat(value):
if value.hour in [4, 5, 6, 7, 8, 9]:
return 'Breakfast'
elif value.hour in [10, 11, 12, 13, 14, 15]:
return 'Lunch'
elif value.hour in [16, 17, 18, 19, 20, 21]:
return 'Dinner'
else:
return 'Supper'


app.jinja_env.filters['meal'] = mealformat

start.html

{{ now|meal }}
now是一個時間值 由meal引導至對應的def

{% block content %}
<div class="px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center" style="max-width: 700px;">
<h1 class="display-4">What to eat for {{ now|meal }}?</h1>
<p class="lead">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor.
Aenean
massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
<a href="/draw" class="btn btn-lg btn-primary">Get started</a>

</div>
{% endblock%}

歷史紀錄

model/histories.py

每一次draw會紀錄哪一個餐廳被抽到

from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
#import Foreign
from database import Base
import uuid
import datetime


class Histories(Base):#物件
__tablename__ = 'histories'
id = Column(String(50), primary_key=True)
created_time = Column(DateTime(), nullable=False)
restaurant_id = Column(String(50), ForeignKey('restaurants.id'))
#Foreign使用外來鍵 參考restaurant table的主鍵

def __init__(self, restaurant_id):#方法
self.id = str(uuid.uuid4())
self.created_time = datetime.datetime.now()
self.restaurant_id = restaurant_id

def __repr__(self):
return '<History %r>' % (self.name)

model/restaurant.py

from sqlalchemy import Column, Integer, String, DateTime
from database import Base
from sqlalchemy.orm import relationship #import relationship
import uuid
import datetime


class Restaurants(Base):
__tablename__ = 'restaurants'
id = Column(String(50), primary_key=True)
name = Column(String(50), unique=True)
description = Column(String(100), nullable=True)
site_url = Column(String(200), nullable=True)
draw = Column(Integer(), default=0)
created_time = Column(DateTime(), nullable=False)
modified_time = Column(DateTime(), nullable=False)
histories = relationship( #histories關聯到Histories Class的histories table
'Histories', #指Class Histories
backref='restaurant' #指使用History物件的時候,可以透過restaurants的欄位來取得餐廳的資料
)

app.py

from models.histories import Histories import model@app.route('/draw')
def draw():
restaurants = Restaurants.query.all()

if not restaurants:
return redirect('/create-restaurant')

random_restaurant = choice(restaurants)

restaurant = Restaurants.query.get(random_restaurant.id)
restaurant.draw = restaurant.draw + 1

history = Histories(restaurant_id=restaurant.id)
#history物件 紀錄歷史資料
db_session.add(history)
db_session.commit()

now = datetime.datetime.now()

return render_template('draw.html', restaurant=restaurant, now=now)

資料還原try except rollback

異常的時候還原到原來的狀態

@app.route('/draw')
def draw():
restaurants = Restaurants.query.all()

if not restaurants:
return redirect('/create-restaurant')

random_restaurant = choice(restaurants)

try: #try區塊若有問題 執行except區塊程式碼
restaurant = Restaurants.query.get(random_restaurant.id)
restaurant.draw = restaurant.draw + 1

history = Histories(restaurant_id=restaurant.id)

db_session.add(history)
db_session.commit()

except: #try有問題 執行這區塊程式碼
db_session.rollback() #復原
return redirect('/')

now = datetime.datetime.now()
return render_template('draw.html', restaurant=restaurant, now=now)

查詢資料

histories = Histories.query.order_by(desc(Histories.created_time)).limit(20)

order_by
desc
limit(20)

app.py

from sqlalchemy import desc@app.route('/history')
def history():
histories = Histories.query.order_by(desc(Histories.created_time)).limit(20)
#order_by
#desc
#limit(20)
return render_template('history.html', histories=histories)

templates/history.html

{{ history[‘restaurant’][‘description’] }} 用到model/restaurants.py的

{% for history in histories %}
<tr>
<td scope="row">{{ loop.index }}</td> #loop.id
<td>{{ history['restaurant']['name'] }}</td>
<td><span class="badge badge-primary">{{ history['restaurant']['description'] }}</span></td>
<td>{{ history['created_time']|meal }}</td>
<td>{{ history['created_time']|datetime }}</td>
</tr>
{% endfor %}

model/restaurants.py

class Restaurants(Base):#類別
__tablename__ = ‘restaurants’ #table name
id = Column(String(50), primary_key=True)
name = Column(String(50), unique=True)
description = Column(String(100), nullable=True)
site_url = Column(String(200), nullable=True)
draw = Column(Integer(), default=0)
created_time = Column(DateTime(), nullable=False)
modified_time = Column(DateTime(), nullable=False)
histories = relationship( #histories table /relationship關聯
‘Histories’, #Class Histories
backref=’restaurant’ #可以用restaurant欄位呼叫資料
)

查詢資料

app.py

restaurants = Restaurant.query.order_by('-draw').limit(5)@app.route('/top')
def top():
restaurants = Restaurant.query.order_by('-draw').limit(5)

return render_template('top.html', restaurants=restaurants)

templates/top.html

<tbody>
{% for restaurant in restaurants %}
<tr>
<td scope="row">{{ loop.index }}</td>
<td>{{ restaurant['name'] }}</td>
<td><span class="badge badge-primary">{{ restaurant['description'] }}</span></td>
<td><a href="{{ restaurant['site_url'] }}">Visit Restaurant</td>
<td>{{ restaurant['draw'] }}</td>
</tr>
{% endfor %}
</tbody>

關聯資料刪除功能修正

刪除restaurant.html在history.html會刪除不乾淨

restaurants.py

cascade=’all,delete’
在刪除restaurants table的時候,刪除(delete)跟這個restaurants有關的所有(all)histories table

class Restaurants(Base):
__tablename__ = 'restaurants'
id = Column(String(50), primary_key=True)
name = Column(String(50), unique=True)
description = Column(String(100), nullable=True)
site_url = Column(String(200), nullable=True)
draw = Column(Integer(), default=0)
created_time = Column(DateTime(), nullable=False)
modified_time = Column(DateTime(), nullable=False)
histories = relationship(
'Histories',
backref='restaurant',
cascade='all,delete'
)

Nav Bar 顯示所在位置

app.py

nav=’start’

@app.route('/')
def start():
now = datetime.datetime.now()
return render_template('start.html', nav='start', now=now)

base.html

<a class="btn {% if nav == 'start' %}btn-outline-primary{% else %} btn-outline-fix  text-dark{% endif %}" href="/">Start</a>
<a class="btn {% if nav == 'top' %}btn-outline-primary{% else %} btn-outline-fix text-dark{% endif %}" href="/top">Top 5</a>
<a class="btn {% if nav == 'restaurant' %}btn-outline-primary{% else %} btn-outline-fix text-dark{% endif %}" href="/restaurants">Restaurants</a>
<a class="btn {% if nav == 'history' %}btn-outline-primary{% else %} btn-outline-fix text-dark{% endif %}" href="/history">History</a>
</nav>

--

--

Steven Wang
Steven Wang

No responses yet