0%

[Python]Flask串接Firebase

Flask

首先用pip安裝Flask,接者創建一個app.py

app.py

1
2
3
4
5
6
7
8
9
import flask
from flask import render_template, url_for, redirect, request, jsonify, Response #之後會用到的

app = flask.Flask(__name__)
app.config["DEBUG"] = True

@app.route('/', methods=['GET'])
def index():
return render_template('index.html')

接著下python app.py就可以執行了,但是這邊還沒創建Html檔,所以會報錯,因為使用了render_template,需要再創建一個templates資料夾,並在資料夾內創建index.html

回到網頁,就會看到全白的網站了。

未來可能會用到許多頁面,而這些頁面通常會共用一個header,所以我這邊將它獨立為一個header.html

header.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>
<title>Document</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="/">中職球員數據</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup"
aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-item nav-link" href="/">首頁 <span class="sr-only">(current)</span></a>
</div>
</div>
</nav>
</body>
</html>

這邊我使用了bootstrap來進行排版

回到index.html,因為Flask支援jinja 2,讓我能直接引入header。 官方文件

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>數據可視化</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<script src="https://cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>
<script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous">
</script>
</head>
<body>
<div class="container">
{% include 'header.html' %}
</div>
<body>
<html>

上面引入的script都是之後會用的

從Firebase取得資料

前面有介紹到如何連接Firebase,而連接的程式碼就放在app.py裡面

1
2
3
4
5
6
7
8
9
10
11
12
13
import flask
from flask import render_template, url_for, redirect, request, jsonify, Response

app = flask.Flask(__name__)
app.config["DEBUG"] = True

cred = credentials.Certificate('path/to/serviceAccount.json')
firebase_admin.initialize_app(cred)

...

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

為了不讓app.py太過雜亂,所以我把取資料的程式碼獨立為db_connect.py,並且連同之前儲存資料的程式碼一起放進來。

取得資料的方法非常簡單,指定好collection和document後,使用get()就能取得,取出來的資料可以用to_dict()轉成dict,讓Python好處理。

db_connect.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore

def get_hitter_stat(player_name):
db = firestore.client()
doc_ref = db.collection(u'打者').document(str(player_name))
doc = doc_ref.get()
if doc.exists:
return doc.to_dict()
else:
return False

def store_month(data):
db = firestore.client()
batch = db.batch()
for player in data:
doc_ref = db.collection(u'打者').document(player['Name'])
del player['Name']
batch.update(doc_ref, {u'月份':player})
batch.commit()

接著把db_connect.pyimport進app.py

前端設置

接著要先把index.html切出我要顯示出來的位置,首先選擇球員需要用兩個下拉式選單<select>,並用bootstrap稍微美化一下表單。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<form>
<div class="row">
<div class="form-group col-6">
<select name="team" id="team" class="form-control">
<option value="中信兄弟">中信兄弟</option>
<option value="統一獅">統一獅</option>
<option value="富邦悍將">富邦悍將</option>
<option value="樂天桃猿">樂天桃猿</option>
</select>
</div>
<div class="form-group col-6">
<select name="player" id="player" class="form-control">
</select>
</div>
</div>
</form>

並且我想要將這兩個下拉式選單連動,也就是當我選擇中信兄弟時,player的選單會顯示出中信兄弟的球員,這邊我需要用到select的onchange觸發ajax讓Flask傳球員名單過來,先寫一個js function。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function select_team(){
let team = $('#team').val();
$.ajax({
type: 'POST',
url: "/select_team",
data: {"team":team},
success: function(result){
$('#player').empty();
$('#player').append($("<option/>", {
value: '選擇球員',
text: '選擇球員'
}));
for(let i = 0; i < result.length ;i++){
$('#player').append($("<option/>",{
value: result[i],
text: result[i]
}));
}
}
});
}

這邊我用POST的方式,而data是前端必須傳給後端的資料,讓後端能判斷,empty很重要(de很久才想到),而for迴圈則是加入球員,前端的程式碼寫好了,接著回到app.py,我需要設置一個/select_team才能接起來。

app.py

1
2
3
4
5
6
from select_player import getplayer
@app.route('/select_team', methods=['POST'])
def select_team():
team = request.form.getlist('team')
player_list = getplayer(team[0])
return jsonify(player_list)

getlist進來會是list

select_player.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# coding: utf-8
import csv

def getplayer(team):
with open('player_ID.csv', 'r',encoding='utf8') as csvfile:
rows = csv.DictReader(csvfile)
player_list = []
if team == '中信兄弟':
for row in rows:
if row['Team ID'] == 'E02':
player_list.append(row['Name'])
elif team == '統一獅':
for row in rows:
if row['Team ID'] == 'L01':
player_list.append(row['Name'])
elif team == '富邦悍將':
for row in rows:
if row['Team ID'] == 'B04':
player_list.append(row['Name'])
elif team == '樂天桃猿':
for row in rows:
if row['Team ID'] == 'AJL011':
player_list.append(row['Name'])

return player_list

但是現在在第一次進網頁時,第二欄會是空的,我希望在載入好時能先執行一遍,這邊我用了ready來達成

1
$(document).ready(select_team());