一個(gè)簡單的 CRUD 操作基本可以看出某個(gè)開發(fā)框架和平臺的特點(diǎn)。Flask 作為一個(gè)微框架,在開發(fā)一些小型應(yīng)用的時(shí)候非常合適。本文試圖從開發(fā)一個(gè)簡單的 Notebook 應(yīng)用,說明 Flask 開發(fā)的基本模式。除 Flask 模塊外,本次用到以下插件:
- Flask-SQLAlchemy (基于 SQLAlchemy 的擴(kuò)展,操作數(shù)據(jù)庫)
- Flask-WTF (一個(gè)表單插件,讓 HTML 表單編寫更加簡單)
Flask 工程文件的結(jié)構(gòu)
Flask 開發(fā)并沒有業(yè)界權(quán)威的工程文件結(jié)構(gòu),但還是有主流的做法。參考網(wǎng)上的文章和代碼,我準(zhǔn)備采用如下的結(jié)構(gòu)
project-folder/
app /
templates / <---- 模版文件
static / <---- 靜態(tài)文件,比如 css 文件
__init__.py <--- 把 app 文件夾作為一個(gè) package,在此文件中創(chuàng)建 flask app
controllers.py <--- 數(shù)據(jù)庫操作
models.py <--- 數(shù)據(jù)庫表與 model 的映射
views.py <--- 視圖函數(shù)和路由
configs.py <--- app 配置
db_scripts.py <--- 數(shù)據(jù)庫腳本
server.py <--- 服務(wù)器端啟動(dòng)入口
配置
我們先從配置開始,因?yàn)橹饕獮榱苏f明 Flask CRUD 的要素,所以配置只有最基本的兩項(xiàng),連 SECRET_KEY 都省了。從 Windows 環(huán)境變量 獲取數(shù)據(jù)連接的 URI。
# configs.py
import os
SQLALCHEMY_DATABASE_URI = os.getenv('DB_URI')
SQLALCHEMY_TRACK_MODIFICATIONS = False
數(shù)據(jù)庫 CRUD 操作
使用 Flask-SQLAlchemy 進(jìn)行數(shù)據(jù)庫的 CRUD 操作。首先在 app/models.py 中定義 Model 的結(jié)構(gòu),映射到數(shù)據(jù)表和字段:
# models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Notes(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
body = db.Column(db.Text)
def __repr__(self):
return 'Note body: {}'.format(self.body)
然后在 app/controllers.py 文件中,定義數(shù)據(jù)庫的 CRUD 操作的五個(gè)方法:
# controllers.py
from app.models import db, Notes
from sqlalchemy import desc
class NotesDao():
def create_note(self, note_body):
new_note = Notes(body=note_body)
db.session.add(new_note)
db.session.commit()
return new_note
def update_note(self, note):
modified_note = Notes.query.get(note.id)
db.session.commit()
return modified_note
def delete_note(self, note):
note_to_delete = Notes.query.get(note.id)
db.session.delete(note_to_delete )
db.session.commit()
return True
def list_all(self):
return Notes.query.order_by(desc(Notes.id)).all()
def get_note(self, id):
return Notes.query.get(id)
定義視圖函數(shù)
在 app/views.py 文件中,定義藍(lán)圖 (blueprint) 和視圖函數(shù)
from flask import request, render_template, Blueprint, flash, redirect, url_for
from app.controllers import NotesDao
from app.forms import *
# 定義藍(lán)圖
notesbp = Blueprint('notesbp', __name__, template_folder='templates')
@notesbp.route('/')
def index():
noteservice = NotesDao()
# return book list to front end
notes = noteservice.list_all()
return render_template('index.html', notes=notes)
@notesbp.route('/new', methods=['GET', 'POST'])
def new_note():
form = NewNoteForm()
if request.method == 'POST':
body = request.form['body']
noteservice = NotesDao()
noteservice.create_note(body)
return redirect(url_for('notesbp.index'))
return render_template('new_note.html', form=form)
@notesbp.route('/edit/<int:note_id>', methods=['GET', 'POST'])
def edit_note(note_id):
form = EditNoteForm()
note = NotesDao().get_note(note_id)
if request.method == 'POST':
body = request.form['body']
note.body = body
NotesDao().update_note(note)
return redirect(url_for('notesbp.index'))
form.body.data = note.body
return render_template('edit_note.html', form=form)
@notesbp.route('/delete/<int:note_id>', methods=['GET'])
def delete_note(note_id):
notesdao = NotesDao()
note = notesdao.get_note(note_id)
notesdao.delete_note(note)
return redirect(url_for('notesbp.index'))
說明:blueprint 是 Flask 代碼模塊化的一種工具,本例中將當(dāng)前模塊命名為 notesbp,視圖函數(shù)利用 blueprint 映射路由, url_for() 函數(shù)利用 blueprint 查找視圖函數(shù) 。
HTML 文件
視圖函數(shù)中關(guān)聯(lián)的 html 文件,放在 templates 文件夾中。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flask basic CRUD</title>
<link rel="stylesheet" type="text/css" href="../static/styles.css">
</head>
<body>
<a href="{{ url_for('notesbp.new_note') }}">New note</a>
<h4> {{ notes|length }} Notes: </h4>
<table>
<tr>
<th>ID</th>
<th>Body</th>
<th>Action</th>
</tr>
{% for note in notes %}
<tr>
<td> {{note.id}} </td>
<td> {{note.body}} </td>
<td>
<a href="{{ url_for('notesbp.edit_note', note_id=note.id) }}">Edit</a>
<a href="{{ url_for('notesbp.delete_note', note_id=note.id) }}">Delete</a>
</td>
</tr>
{% endfor %}
</table>
</body>
</html>
edit_note.html 與 new_note.html 內(nèi)容相同
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>New note</title>
</head>
<body>
<h2>New note</h2>
<form method="POST">
{{ form.body(rows='10',cols='100') }}<br/>
{{ form.submit }}
</form>
</body>
</html>
為簡化 html 文件的編寫,使用了 flask-wtf 表單,表單的代碼放在 app/forms.py 文件中:
from wtforms import Form, TextAreaField, SubmitField
class NewNoteForm(Form):
body = TextAreaField('body')
submit = SubmitField('Save')
class EditNoteForm(Form):
body = TextAreaField('body')
submit = SubmitField('Update')
使用工廠函數(shù)創(chuàng)建 app
代碼放在 app/__init__.py 文件中:
from flask import Flask
import configs
from app.models import db
from app.views import notesbp
def create_app():
app = Flask(__name__)
# 加載配置
app.config.from_object(configs)
# 初始化db
db.app = app
db.init_app(app)
# 注冊藍(lán)圖
app.register_blueprint(notesbp)
return app
生成數(shù)據(jù)庫表
先用 SQL 語句 create database xxx charset utf8; 創(chuàng)建數(shù)據(jù)庫,然后運(yùn)行 db_scripts.py 代碼創(chuàng)建表:
from app.models import db
from app import create_app
app = create_app()
db.app = app
db.init_app(app)
if __name__ == '__main__':
db.drop_all()
db.create_all()
print ('Done')
啟動(dòng)文件
后端程序通過 server.py 啟動(dòng),代碼如下:
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True)
本文源代碼: https://github.com/stonewm/python-practice-projects/tree/master/Flask/Notebook-v1.0
參考
- 《Flask Web 開發(fā)實(shí)戰(zhàn)》(李輝)
- Building a CRUD application with Flask and SQLAlchemy
- Build a CRUD Web App With Python and Flask - Part One