Build A MVC Web App by using Flask
根據上次提到的 MVC Architectural Pattern,我們直接用之前展示的restful-flask來改寫。
我們先將原本做的RESTful API View移到app/views/api/v1的目錄內,並將原本的blueprint名字改成api_v1_users避免衝突。
# app/views/api/v1/users.py
from flask import Blueprint, request, jsonifyapi_v1_users = Blueprint('api_v1_users', __name__, template_folder='templates')@api_v1_users.errorhandler(404)
def page_not_found():
msg = {
'url': request.url,
'status': 404
}
return jsonify(msg), 404user_list = {
1: { "name": "Robin", "email": "robin@gmail.com" },
2: { "name": "george", "email": "george@gmail.com"},
}@api_v1_users.route('/', methods=['GET'])
def index():
return jsonify(user_list), 201@api_v1_users.route('/', methods=['POST'])
def create():
if len(user_list) > 0:
last_key = user_list.keys()[-1]
key = last_key + 1
else:
key = 1
name = request.form["name"]
email = request.form["email"]
user_list[key] = {"name": name, "email": email}
return jsonify(user_list), 201@api_v1_users.route('/<int:id>', methods=['GET'])
def get(id):
if id in user_list:
user = user_list[id]
return jsonify(user), 201
else:
return page_not_found()@api_v1_users.route('/<int:id>', methods=['PUT'])
def put(id):
if id in user_list:
if "name" in request.form:
user_list[id]["name"] = request.form["name"]
if "email" in request.form:
user_list[id]["email"] = request.form["email"]
return jsonify(user_list[id]), 201
else:
return page_not_found()@api_v1_users.route('/<int:id>', methods=['DELETE'])
def destroy(id):
if id in user_list:
user_list.pop(id, None)
return jsonify(user_list), 201
else:
return page_not_found()
接著在app/models建立user.py,會看到我們建立了一個User Model,包含id, username與email欄位。
# app/models/user.py
from .. import dbclass User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)def __init__(self, username = None, email = None):
self.username = username
self.email = emaildef __repr__(self):
return '<User %r>' % self.username
在此範例中我是用MySQL,使用pymysql連接,並使用SQLalchemy ORM。
安裝pymysql & Flask-SQLalchemy
pip install pymysql
pip install flask_sqlalchemy建立資料庫連線設定檔config.py
# ./config.py
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://username:password@host:port/dbname'讓flask app載入設定檔,接著建立SQLAlchemy物件
# app/__init__.py
app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)進入console
$ python manage.py shell
>>> from app import db
>>> from app.models.user import User
>>> db.create_all()
>>> george = User('george', 'george@myserver.com')
>>> robin = User('robin', 'robin@myserver.com')
>>> db.session.add(george)
>>> db.session.add(robin)
>>> db.session.commit()嘗試查詢資料
>>> users = User.query.all()
>>> george = User.query.filter_by(username='george').first()
>>> george
<User 'george'>Model的部分算是完成了,接著我們來實作User的Controller
建立app/views/users.py
# app/views/users.py
from ..models.user import User
import flask
from flask import Blueprint, request, jsonify, render_template, abort, redirect, url_for
from jinja2 import TemplateNotFound
from .. import dbusers = Blueprint('users', __name__, template_folder='templates')@users.errorhandler(404)
def page_not_found():
return abort(404)@users.route('/', methods=['GET'])
def index():
users = User.query.all()
return render_template('users/index.html', users = users)@users.route('/new', methods=['GET'])
def new():
user = User()
return render_template('users/new.html', users = user)@users.route('/create', methods=['POST'])
def create():
username = request.form['username']
email = request.form['email']
user = User(username, email)
db.session.add(user)
db.session.commit()
return redirect(url_for('users.index'))@users.route('/<int:id>', methods=['GET'])
def show(id):
try:
user = User.query.filter_by(id=id).first()
return render_template('users/show.html', user = user)
except TemplateNotFound:
abort(404)@users.route('/<int:id>/edit', methods=['GET'])
def edit(id):
try:
user = User.query.filter_by(id=id).first()
return render_template('users/edit.html', user = user)
except TemplateNotFound:
abort(404)@users.route('/<int:id>', methods=['POST'])
def update(id):
user = User.query.filter_by(id=id).first()
email = request.form['email']
user.email = email
return redirect(url_for('users.index'))@users.route('/<int:id>/delete', methods=['POST'])
def destroy(id):
user = User.query.filter_by(id=id).first()
db.session.delete(user)
db.session.commit()
return redirect(url_for('users.index'))
可以看到我們共有7個route,
index為查詢使用者列表,show為查詢特定使用者的資料,new為新增使用者的表單,create為建立使用者,edit為修改使用者的表單,update為更新使用者的資料,destroy為刪除特定使用者。
跟先前建立RESTful API不同的是,我將PUT與DELETE改成POST method。
因為目前HTTP form只支援GET & POST method,若要達到目的需要用其他方式達成,所以就先以這種方式表示。
接著要建立對應的View。
首先建立app/templates/users目錄。
$ mkdir app/templates/users接著在在目錄內加入:
layout.html作為模版
<!doctype html>
<html>
<head>
<title>Flaskr</title>
</head>
<body>
<h1>Flask</h1>
{% block body %}{% endblock %}
</body>
</html>index.html
{% extends "users/layout.html" %}
{% block body %}
<ul>
{% for user in users %}
<li><h2><a href="{{ url_for('users.show', id = user.id) }}">{{ user.username }}</a></h2>{{ user.email }}
<form action="{{ url_for('users.destroy', id=user.id) }}" method="POST">
<input type="hidden" name="_method" value="DELETE" />
<input type="submit" value="Delete user" />
</form>
</li>
{% endfor %}
</ul>
<a href="{{url_for('users.new') }}">new user</a>
{% endblock %}show.html
{% extends "users/layout.html" %}
{% block body %}
<p>{{ user.username }}</p>
<p>{{ user.email }}</p>
<a href="{{url_for('users.edit', id = user.id) }}">edit user</a>
{% endblock %}new.html
{% extends "users/layout.html" %}
{% block body %}
<form action="{{ url_for('users.create') }}" method=post>
<label for="username">username</label>
<input type="text" name="username"><br>
<label for="email">email</label>
<input type="text" name="email"><br>
<input type="submit" value="submit">
</form>
{% endblock %}edit.html
{% extends "users/layout.html" %}
{% block body %}
<p>{{ user.username }}</p>
<form action="{{ url_for('users.update', id = user.id) }}" method="POST">
<label for="email">email</label>
<input type="text" name="email" value="{{ user.email }}"><br>
<input type="submit" value="submit">
</form>
{% endblock %}執行網站
$ python manage.py runserver以上便完成了一個基本MVC架構的Web app,你也會發現在命名上略有不同。
在Flask的世界裡,我們用Views代表Controller,用Templates代表View。
會這樣命名也只是跟隨著開發文件的慣例,以Flask的靈活性,當然也可以將名稱改為app/models, app/controllers, app/views。
最後附上github連結

