Flask 项目实战 2: 后端实现
上一节介绍了待做清单项目的功能、程序的总体结构,程序的总体结构分为前端和后端两个部分,本节讲解后端的实现。
1. 数据库设计
1.1 表的设计
表 todos 用于记录待做事项,包含有如下字段:
SET character_set_database=utf8;
SET character_set_server=utf8;
DROP DATABASE IF EXISTS todoDB;
CREATE DATABASE todoDB;
USE todoDB;
2. 创建表 users
CREATE TABLE users(
userId INT NOT NULL AUTO_INCREMENT,
name VARCHAR(),
password VARCHAR(),
PRIMARY KEY(userId)
);
创建表 users,表 users 包含 userId、name、password 等字段。userId 是主键,设置为从 1 自动增长。
3. 创建表 todos
CREATE TABLE todos(
todoId INT NOT NULL AUTO_INCREMENT,
userId INT,
status VARCHAR(),
title VARCHAR(),
PRIMARY KEY(todoId)
);
4. 创建测试数据
INSERT INTO users(name, password) VALUES ("guest", "123");
INSERT INTO todos(userId, status, title) VALUES (, "todo", "吃饭");
INSERT INTO todos(userId, status, title) VALUES (, "todo", "睡觉");
INSERT INTO todos(userId, status, title) VALUES (, "done", "作业");
为了方便测试,向数据库中插入一些预定义的数据。
在第 2 行到第 3 行,向表 todos 中增加 3 个 userId 为 1 的记录,相当于为 guest 用户增加 3 个记录;在第 2 行,插入待做事项 “吃饭”;在第 3 行,插入待做事项 “睡觉”;在第 4 行,插入已完成事项 “作业”。
2. Flask 实例 app.py
from flask import Flask
from datetime import timedelta
app = Flask(__name__)
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = timedelta(seconds=)
app.config['SECRET_KEY'] = 'hard to guess string'
在程序 app.py 中创建 Flask 实例 app,并进行两项配置:
3. 入口 main.py
3.1 导入相关模块
#!/usr/bin/python3
from app import app
from flask import render_template, session
import db
import users
import todos
app.register_blueprint(users.blueprint)
app.register_blueprint(todos.blueprint)
@app.route('/')
def index():
hasLogin = session.get('hasLogin')
if hasLogin:
userId = session.get('userId')
items = db.getTodos(userId)
todos = [item for item in items if item.status == 'todo']
dones = [item for item in items if item.status == 'done']
else:
items = []
todos = []
dones = []
return render_template('index.html', hasLogin = hasLogin, todos = todos, dones = dones)
app.run()
设置网站的首页面 / 的处理函数为 index,该函数首先查询 Session 中的变量 hasLogin,如果为真,表示用户已经登录,显示用户已经输入的待做事项和完成事项;如果为假,表示用户没有登录,显示待做事项和完成事项为空。
在第 7 行,获取待做事项中 status 等于 ‘todo’ 的待做事项,保存在列表 todos 中;在第 8 行,获取待做事项中 status 等于 ‘done’ 的待做事项,保存在列表 dones 中。
在第 13 行,渲染首页模板 index.html,传递 3 个参数:
4.1 引入相关模块并配置
from app import app
from flask_sqlalchemy import sqlAlchemy
user = 'root'
password = '123456'
database = 'todoDB'
uri = 'MysqL+pyMysqL://%s:%s@localhost:3306/%s' % (user, password, database)
app.config['sqlALCHEMY_DATABASE_URI'] = uri
app.config['sqlALCHEMY_TRACK_MODIFICATIONS'] = False
orm = sqlAlchemy(app)
变量 user 是数据库的用户名,变量 password 是数据库的密码,变量 database 是数据库的名称。在这个例子中,用户是 root,密码是 123456,请调整你的 MysqL 设置。设置完这 3 个变量后,数据库访问的 URI 为:
4.2 映射表 users 和表 todos
class User(orm.Model):
__tablename__ = 'users'
userId = orm.Column(orm.Integer, primary_key=True)
name = orm.Column(orm.String())
password = orm.Column(orm.String())
class Todo(orm.Model):
__tablename__ = 'todos'
todoId = orm.Column(orm.Integer, primary_key=True)
userId = orm.Column(orm.Integer)
status = orm.Column(orm.String())
title = orm.Column(orm.String())
4.3 对表 users 进行操作
def login(name, password):
users = User.query.filter_by(name = name, password = password)
user = users.first()
return user
def register(name, password):
user = User(name = name, password = password)
orm.session.add(user)
orm.session.commit()
return True
4.4 对表 todos 进行操作
def getTodos(userId):
todos = Todo.query.filter_by(userId = userId)
return todos
def addTodo(userId, status, title):
todo = Todo(userId = userId, status = status, title = title)
orm.session.add(todo)
orm.session.commit()
return True
def updatetodo(todoId, status):
todos = Todo.query.filter_by(todoId = todoId)
todos.update({'status': status})
orm.session.commit()
return True
def deletetodo(todoId):
todos = Todo.query.filter_by(todoId = todoId)
todos.delete()
orm.session.commit()
return True
5. 蓝图 users.py
5.1 导入相关模块
from flask import Flask, render_template, request, redirect, session
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import Datarequired, Length
from flask import Blueprint
import db
blueprint = Blueprint('users', __name__, url_prefix='/users')
蓝图 users 包含有 3 个页面 /users/login、/users/register、/users/logout,设置 url_prefix 为 /users 后,使用 @app.route 注册页面的处理函数时,使用 /login、/register、/logout 作为 URL 即可,省略了前缀 /users。
5.2 登录表单
class LoginForm(FlaskForm):
name = StringField(
label = '姓名',
validators = [
Datarequired(message = '姓名不能为空')
]
)
password = PasswordField(
label = '密码',
validators =[
Datarequired(message = '密码不能为空'),
Length(min = , message = '密码至少包括 3 个字符')
]
)
submit = SubmitField('登录')
使用 WTForms 表单实现登录表单,LoginForm 继承于 FlaskForm,它包含 2 个字段 name 和 password。
5.3 请求 /users/login 页面
@blueprint.route('/login', methods = ['GET', 'POST'])
def login():
if request.method == 'GET':
form = LoginForm()
return render_template('login.html', form = form)
else:
form = LoginForm()
if form.validate_on_submit():
name = form.name.data
password = form.password.data
user = db.login(name, password)
if user:
session['hasLogin'] = True
session['userId'] = user.userId
return redirect('/')
return render_template('login.html', form = form)
使用 POST 方法请求页面 /users/login 时,用于向服务器提交登陆请求。在第 7 行,创建一个 LoginForm 实例,然后调用 form.validate_on_submit() 验证表单中的字段是否合法;在第 11 行,调用 db.login(name, password) 在数据库验证用户身份,如果登录成功,则返回登录的用户 user。
在第 12 行,如果登录成功,在 Session 中设置 hasLogin 为 Ture,设置 userId 为登录用户的 userId;在第 15 行,调用 redirect(’/’),用户登录成功后,浏览器重定向到网站根页面。
5.4 注册表单
class RegisterForm(FlaskForm):
name = StringField(
label = '姓名',
validators = [
Datarequired(message = '姓名不能为空')
]
)
password = PasswordField(
label = '密码',
validators =[
Datarequired(message = '密码不能为空'),
Length(min = , message = '密码至少包括 3 个字符')
]
)
submit = SubmitField('注册')
使用 WTForms 表单实现注册表单,RegisterForm 继承于 FlaskForm,它包含 2 个字段 name 和 password。
5.5 请求 /users/register 页面
@blueprint.route('/register', methods = ['GET', 'POST'])
def register():
if request.method == 'GET':
form = RegisterForm()
return render_template('register.html', form = form)
else:
form = RegisterForm()
if form.validate_on_submit():
name = form.name.data
password = form.password.data
if db.register(name, password):
return redirect('/')
return render_template('register.html', form = form)
使用 POST 方法请求页面 /users/register 时,用于向服务器提交登陆请求。在第 7 行,创建一个 RegisterForm 实例,然后调用 form.validate_on_submit() 验证表单中的字段是否合法;在第 11 行,调用 db.register(name, password) 在数据库注册一个新用户,如果注册成功,则返回 True。
6. 蓝图 todos.py
6.1 导入相关模块
from flask import Flask, render_template, request, redirect, session, jsonify
from flask import Blueprint
import db
blueprint = Blueprint('todos', __name__, url_prefix='/todos')
蓝图 todos 包含有 3 个页面 /todos/add、/todos/update、/todos/delete,设置 url_prefix 为 /todos 后,使用 @app.route 注册页面的处理函数时,使用 /add、/update、/delete 作为 URL 即可,省略了前缀 /todos。
@blueprint.route('/add', methods = ['POST'])
def addTodo():
userId = session.get('userId')
status = 'todo'
title = request.json['title']
db.addTodo(userId, status, title)
return jsonify({'error': None});
@blueprint.route('/update', methods = ['POST'])
def updatetodo():
todoId = request.json['todoId']
status = 'done'
db.updatetodo(todoId, status)
return jsonify({'error': None});
当用户完成一个待做事项后,将待做事项移入到完成事项中,需要使用 POST 方法请求 /todos/update 页面用于更新待做事项的 status,在第 5 行调用 db.updatetodo(todoId, status) 个更新待做事项的 status。
@blueprint.route('/delete', methods = ['POST'])
def deletetodo():
todoId = request.json['todoId']
db.deletetodo(todoId)
return jsonify({'error': None});
7. 小结
本节讲解了后端的实现,使用思维导图概括如下: