Build A MVC Web App by using Flask

George Chang
Jul 20, 2017 · 13 min read

根據上次提到的 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, jsonify
api_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), 404
user_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 db
class 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 = email
def __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 db
users = 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連結

下一篇:User Authentication

origino

A Technical Organization in Taiwan

)

George Chang

Written by

Software Engineer

origino

origino

A Technical Organization in Taiwan

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade