Flask + Jinja2 學習紀錄
比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>