Flask 项目实战 2: 后端实现

Flask 项目实战 2: 后端实现

上一节介绍了待做清单项目的功能、程序的总体结构,程序的总体结构分为前端和后端两个部分,本节讲解后端的实现。

1. 数据库设计

1.1 表的设计@H_502_14@

数据库中存在两张表:users 和 todos。

表 users 用于记录已经注册用户,包含有如下字段:

字段 描述
userId 用户的 ID,表的主键
name 姓名
password 密码

表 todos 用于记录待做事项,包含有如下字段:

字段 描述
todoId 待做事项的 ID,表的主键
userId 所属用户的 ID
status 待做事项的状态,“todo” 表示待做,“done” 表示已经完成
title 待做事项的标题

1.2 数据库脚本 db.sql@H_502_14@

创建文件 db.sql内容由如下部分构成:

1. 创建数据库 todoDB

SET character_set_database=utf8;
SET character_set_server=utf8;
DROP DATABASE IF EXISTS todoDB;
CREATE DATABASE todoDB;
USE todoDB;

如果数据库 todoDB 已经存在,则首先删除,然后再创建数据库 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)
);

创建表 todos,表 todos 包含 todoId、userId、status、title 等字段。todoId 是主键,设置为从 1 自动增长。

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", "作业");

为了方便测试,向数据库中插入一些预定义的数据。

在第 1 行,向表 users 中增加一个用户 guest、密码为 “123”,因为该用户是表 users 中的第 1 条数据,所以 userId 为 1。

在第 2 行到第 3 行,向表 todos 中增加 3 个 userId 为 1 的记录,相当于为 guest 用户增加 3 个记录;在第 2 行,插入待做事项 “吃饭”;在第 3 行,插入待做事项 “睡觉”;在第 4 行,插入已完成事项 “作业”。

最后,启动 MysqL 数据库,在数据库中执行 db.sql

MysqL> source db.sql

2. Flask 实例 app.py

@H_502_437@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,并进行两项配置:

  • config[‘SEND_FILE_MAX_AGE_DEFAULT’],配置缓存的有效时间;
  • config[‘SECRET_KEY’],在程序中使用到了 Session,需要使用 SECRET_KEY 进行加密。

3. 入口 main.py

创建文件 main.py,它是 Flask 程序的入口,源代码由如下部分构成:

3.1 导入相关模块@H_502_14@

@H_502_437@#!/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)

在第 2 行,从模块 app.py 中导入变量 app,他是 Flask 应用程序实例;在第 5 行,导入模块 db.py,该模块用于提供了数据库访问接口。

程序包括两个蓝图:users 蓝图和 todos 蓝图,在第 8 行和第 9 行,在 Flask 实例中注册这两个蓝图。

3.2 页面 / 的视图函数@H_502_14@

@H_502_437@@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,如果为真,表示用户已经登录显示用户已经输入的待做事项和完成事项;如果为假,表示用户没有登录显示待做事项和完成事项为空。

在第 5 行,查询 Session 中的变量 userId,该变量表示已经登录用户的 Id;在第 6 行,根据 db.getTodos(userId) 获取数据库用户记录的待做事项。

在第 7 行,获取待做事项中 status 等于 ‘todo’ 的待做事项,保存在列表 todos 中;在第 8 行,获取待做事项中 status 等于 ‘done’ 的待做事项,保存在列表 dones 中。

在第 13 行,渲染首页模板 index.html,传递 3 个参数:

4. 数据库访问 db.py

db.py 中完成数据库访问相关的函数db.py 分为如下几个部分:

4.1 引入相关模块并配置@H_502_14@

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 为:

MysqL+pyMysqL://root:123456@localhost:3306/todoDB

4.2 映射表 users 和表 todos@H_502_14@

@H_502_437@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())

使用类 User 映射数据库中的表 users,该表包含 3 个字段 userId、name、password,与类 User 中相同名称的 3 个属性一一对应。

使用类 Todo 映射数据库中的表 todos,该表包含 4 个字段 todoId、userId、status、title,与类 Todo 中相同名称的 4 个属性一一对应。

4.3 对表 users 进行操作@H_502_14@

@H_502_437@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

函数 login 在表 users 中查找与 name、password 匹配的用户,如果存在,则表示登录成功。

函数 register 根据 name、password 创建一个新的用户,然后插入到表 users 中。

4.4 对表 todos 进行操作@H_502_14@

@H_502_437@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

函数 getTodos(userId) 在表中查询属于指定用户的待做事项。

函数 addTodo(userId, status, title) 根据 userId、status、title 创建一个新的待做事项,然后插入到表 todos 中。

函数 updatetodo(todoId,status) 更新待做事项的 status,当用户完成一个待做事项时,需要将待做事项的 status 从 “todo” 更改为 “done”。

函数 deletetodo(todoId) 删除待做事项。

5. 蓝图 users.py

蓝图 users 包含有 3 个页面:/users/login、/users/register、/users/logout,代码由如下部分构成:

5.1 导入相关模块@H_502_14@

@H_502_437@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')

导入相关模块,然后创建蓝图对象 blueprint,参数 ‘users’ 是蓝图的名称,参数 url_prefix 是页面的前缀。

蓝图 users 包含有 3 个页面 /users/login、/users/register、/users/logout,设置 url_prefix 为 /users 后,使用 @app.route 注册页面的处理函数时,使用 /login、/register、/logout 作为 URL 即可,省略了前缀 /users。

5.2 登录表单@H_502_14@

@H_502_437@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。

name 字段的验证器 Datarequired 要求字段不能为空;password 字段的验证器 Datarequired 要求字段不能为空,验证器 Length 要求密码至少包括 3 个字符。

5.3 请求 /users/login 页面@H_502_14@

@H_502_437@@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)

页面 /users/login 有两种请求方法:GET 和 POST。

使用 GET 方法请求页面 /users/login 时,用于显示登陆界面。在第 5 行,使用 render_template 渲染登陆页面模板 login.html。

使用 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 注册表单@H_502_14@

@H_502_437@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。

name 字段的验证器 Datarequired 要求字段不能为空;password 字段的验证器 Datarequired 要求字段不能为空,验证器 Length 要求密码至少包括 3 个字符。

5.5 请求 /users/register 页面@H_502_14@

@H_502_437@@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)

页面 /users/register 有两种请求方法:GET 和 POST。

使用 GET 方法请求页面 /users/register 时,用于显示注册界面。在第 5 行,使用 render_template 渲染注册页面模板 register.html。

使用 POST 方法请求页面 /users/register 时,用于向服务器提交登陆请求。在第 7 行,创建一个 RegisterForm 实例,然后调用 form.validate_on_submit() 验证表单中的字段是否合法;在第 11 行,调用 db.register(name, password) 在数据库注册一个新用户,如果注册成功,则返回 True。

在第 12 行,如果注册成功,调用 redirect(’/’),用户注册成功后,浏览器重定向到网站根页面

5.6 退出系统 /logout@H_502_14@

@H_502_437@@blueprint.route('/logout') def logout(): session['hasLogin'] = False return redirect('/')

访问 /users/logout 页面时,用户退出系统。在 Session 中设置 hasLogin 为 False,调用 redirect(’/’),用户退出系统后,浏览器重定向到网站根页面

6. 蓝图 todos.py

蓝图 todos 包含有 3 个页面:/todos/add、/todos/update、/todos/delete,代码由如下部分构成:

6.1 导入相关模块@H_502_14@

@H_502_437@from flask import Flask, render_template, request, redirect, session, jsonify from flask import Blueprint import db blueprint = Blueprint('todos', __name__, url_prefix='/todos')

导入相关模块,然后创建蓝图对象 blueprint,参数 ‘todos’ 是蓝图的名称,参数 url_prefix 是页面的前缀。

蓝图 todos 包含有 3 个页面 /todos/add、/todos/update、/todos/delete,设置 url_prefix 为 /todos 后,使用 @app.route 注册页面的处理函数时,使用 /add、/update、/delete 作为 URL 即可,省略了前缀 /todos。

6.2 请求 /todos/add 页面@H_502_14@

@H_502_437@@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});

使用 POST 方法请求 /todos/add 页面用于新增一个待做事项,在第 6 行调用 db.addTodo(userId, status, title) 向表 todos 中插入一行。

在例子中忽略了错误处理,在第 7 行,返回错误为 None。

6.3 请求 /todos/update 页面@H_502_14@

@H_502_437@@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。

在例子中忽略了错误处理,在第 6 行,返回错误为 None。

6.4 请求 /todos/delete 页面@H_502_14@

@H_502_437@@blueprint.route('/delete', methods = ['POST']) def deletetodo(): todoId = request.json['todoId'] db.deletetodo(todoId) return jsonify({'error': None});

使用 POST 方法请求 /todos/delete 页面用于删除一个待做事项,在第 4 行调用 db.deletetodo(todoId) 删除指定的待做事项。

在例子中忽略了错误处理,在第 5 行,返回错误为 None。

7. 小结

本节讲解了后端的实现,使用思维导图概括如下:

图片描述