如何使用Flask和Sqlalchemy實作一個基本的CRUD網頁
本篇文章將一步一步跟著影片實作出一個有CRUD功能的網頁,使用Flask以及Sqlalchemy package
前置作業
Windows10
環境- 安裝
Python
- 安裝
env
,相關指令如下:
# 安裝env
pip3 install virtualenv
# 建置env
virtualenv env
# 開啟env
env\Scripts\activate.bat
# 看到(env)在命令行最前面的時候就是成功了
- 在啟動env後安裝Flask和sqlalchemy
建立Flask
新建app.py
from flask import Flask, render_template, url_for
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello world'
if __name__ == '__main__':
app.run(debug=True)
以上是flask最基本的樣子,這時還尚未建置html,執行後開啟localhost:5000
網頁可以看到Hello world
字樣
建置基本網頁
新建base.html
,在templates
資料夾底下,這是為了讓jinja2
引擎可以reference
這個頁面是當作其他頁面的根基,讓其他頁面extends
這個base
,就可以不用一直寫重複的html
語句
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
{% block head %}{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
這裡的{% %}
就是jinja2
用來傳遞訊息的地方,這裡可能還不是很清楚,所以接下來建立index.html
來繼承這個網頁就會比較清楚了
但在那之前要先建置簡單的CSS以便清楚看到結果
建立CSS
這是css檔案放置的路徑,以供jinja2
調用
body{
margin: 0;
font-family: sans-serif;
}
而在base.html
之中要加入這個css,格式很特殊,而且我的vscode
沒有自動提示🤣錯了一個字就GG了,這裡使用到了jinja2的url_for,以防錯誤,還是在app.py
頂部加入url_for,見以上
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
{% block head %}{% endblock %}
</head>
建立index首頁
在templates資料夾下建立index.html
這裡extends
了base.html
的內容,並在body block
之中插入了h1
標題,這裡就完成了兩個頁面的結合
{% extends 'base.html' %}
{% block head %}
{% endblock %}
{% block body %}
<h1>Template</h1>
{% endblock %}
建立資料庫
接下來就進入到建置資料庫的環節,使用Sqlalchemy
來使用python
操作sqlite
SQLAlchemy
是為Python
提供的開源SQL
工具包及物件關聯對映器(ORM)
在app.py
中
from flask import Flask, render_template, url_for
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
# Config. database
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
# Init. database
db = SQLAlchemy(app)
- sqlite後三個反斜線是相對位置的意思,資料庫取名為test
- 記得
import
需要的packages
建立Model
# Create a model for database
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(200), nullable=False)
date_created = db.Column(db.DateTime, default=datetime.utcnow)
# Return a string when create a new element
def __repr__(self):
return '<Task %r>' % self.id
primary_key=True
就是作為主鍵,值不重複nullable=False
就可以防止用戶不輸入值- 最後當這個
db
新增項目的時候,返回一個string
-<Task %r>
把資料庫造出來
這裡要直接在terminal
使用python
但在那之前要先開起env
,如一開始所述,接著就輸入以下三行
>>> from app import app,db
>>> app.app_context().push()
>>> db.create_all()
>>> exit()
順利執行後就可以看到project
資料夾的instance
資料夾底下有一個test.db
了
這裡出現了一個小插曲,我不小心把資料庫date_created,寫成data_created,導致error瘋狂出現,這時候只要把code修改好之後,再重複上面步驟重新生成資料庫就可以了!
實作Create功能
- 修改app.py,讓其讀取index中的form裡的資料,也就是POST過來的資料,所以app.route那行也要加上POST
- 收到資料後放入資料庫並commit
# 要POST,用於接收html post過來的資料
@app.route('/', methods=['POST', 'GET'])
def index():
if request.method == 'POST':
# 收到資料
task_content = request.form['content']
# 資料轉換為Todo class
new_task = Todo(content=task_content)
try:
# 資料加入
db.session.add(new_task)
db.session.commit()
return redirect('/')
except:
return 'There was an issue adding your task'
else:
# 獲取所有資料
tasks = Todo.query.order_by(Todo.date_created).all()
# 傳資料到index.html
return render_template('index.html', tasks=tasks)
至於index.html
方面也要修改,附上完整程式碼以免遺漏
{% extends 'base.html' %}
{% block head %}
<title>Task Master</title>
{% endblock %}
{% block body %}
<div class="content">
<h1 style="text-align: center;">Task Master</h1>
<table>
<tr>
<th>Task</th>
<th>Added</th>
<th>Actions</th>
</tr>
{% for task in tasks %}
<tr>
<td>{{ task.content }}</td>
<td>{{ task.date_created.date() }}</td>
<td>
<a href="">Delete</a>
<br>
<a href="">Update</a>
</td>
</tr>
{% endfor %}
</table>
<form action="/" method="POST">
<input type="text" name="content" id="content">
<input type="submit" value="Add Task">
</form>
</div>
{% endblock %}
稍微渲染一下頁面
body, html {
margin: 0;
font-family: sans-serif;
background-color: lightblue;
}
.content {
margin: 0 auto;
width: 400px;
}
table, td, th {
border: 1px solid #aaa;
}
table {
border-collapse: collapse;
width: 100%;
}
th {
height: 30px;
}
td {
text-align: center;
padding: 5px;
}
.form {
margin-top: 20px;
}
#content {
width: 70%;
}
實作Delete功能
很簡單就可以實作了,一樣先在app.py
加上下面的method
@app.route('/delete/<int:id>')
def delete(id):
# 從資料庫取得該task
task_to_delete = Todo.query.get_or_404(id)
try:
# 刪除該task
db.session.delete(task_to_delete)
db.session.commit()
return redirect('/')
except:
return 'There was an issue deleting that task'
然後在index.html
加入jinja2,把delete按鈕附近修改成這樣
<tr>
<td>{{ task.content }}</td>
<td>{{ task.date_created.date() }}</td>
<td>
<a href="/delete/{{task.id}}">Delete</a>
<br>
<a href="">Update</a>
</td>
</tr>
這裡讓我覺得比較神奇的是/delete/{{task.id}}
,因為我們是用flask
繪製整個頁面,是直接把資料庫拉過來,然後利用jinja
讓資料都顯示出來,所以想當然爾task
的id
也一併被拉過來了,因此就可以直接使用task.id
附上該task
的id
這裡我想整理一下delete
的流程
- 按下
delete
按鈕後,會觸發/delete/{{task.id}}
這個連結 - 然後就會觸發
delete method
,在app.py
裡面,這時id
也會傳到該method
裡 - 待資料庫操作完後,指定的
task
被刪除,這時會redirect
回主頁面,會觸發index()
,在app.py
裡 - 因為是
GET
所以會到else
的程式碼塊裡面,也就是取得所有tasks
,然後傳給html
顯示,如此一來就可以完成頁面的更新
實作Update功能
- 新增
update function
在app.py
- 新增
update
頁面 - 修改
index.html
@app.route('/update/<int:id>', methods=['POST', 'GET'])
def update(id):
# 從資料庫獲取
task = Todo.query.get_or_404(id)
if request.method == 'POST':
# 修改content為用戶輸入的值
task.content = request.form['content']
try:
# 直接commit就好
db.session.commit()
# 返回首頁
return redirect('/')
except:
return 'There was an issue updating the task'
else:
# 當用戶從首頁點update按鈕時
# 顯示task以供修改
return render_template('update.html', task=task)
接著是給使用者的update
頁面
{% extends 'base.html' %}
{% block head %}
<title>Task Master</title>
{% endblock %}
{% block body %}
<div class="content">
<h1 style="text-align: center;">Update Task</h1>
<div class="form">
<form action="/update/{{task.id}}" method="POST">
<input type="text" name="content" id="content" value="{{task.content}}">
<input type="submit" value="Update">
</form>
</div>
</div>
{% endblock %}
接著在index.html
做修改,以免遺漏所以把block body
的部分都貼上來了
{% block body %}
<div class="content">
<h1 style="text-align: center;">Task Master</h1>
{% if tasks|length < 1 %}
<h4 style="text-align: center;">There are no tasks so far~</h4>
{% else %}
<table>
<tr>
<th>Task</th>
<th>Added</th>
<th>Actions</th>
</tr>
{% for task in tasks %}
<tr>
<td>{{ task.content }}</td>
<td>{{ task.date_created.date() }}</td>
<td>
<a href="/delete/{{task.id}}">Delete</a>
<br>
<a href="/update/{{task.id}}">Update</a>
</td>
</tr>
{% endfor %}
</table>
{% endif %}
<div class="form">
<form action="/" method="POST">
<input type="text" name="content" id="content">
<input type="submit" value="Add Task">
</form>
</div>
</div>
{% endblock %}
到這裡就完成了一個網頁的CRUD功能了!