Skip to content

Flask - SQLite

SQLite 是一种轻量级、基于文件的关系型数据库。Python 通过 sqlite3 模块内置了对 SQLite 的支持,这使得它易于与 Flask 应用集成,尤其适用于较小的项目或原型。

首先,让我们创建一个简单的脚本(init_db.py)来设置我们的 SQLite 数据库文件(database.db)并创建一个名为 students 的表。

import sqlite3
# 连接到数据库文件(如果不存在则创建)
conn = sqlite3.connect('database.db')
print("Opened database successfully")
# 创建 students 表
conn.execute('''
CREATE TABLE IF NOT EXISTS students (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
addr TEXT,
city TEXT,
pin TEXT
);
''')
print("Table created successfully or already exists")
# 关闭连接
conn.close()

从你的终端运行一次此脚本:python init_db.py

现在,让我们构建一个 Flask 应用(app.py)来与此数据库交互。我们将创建路由来:

  • 显示添加新学生的表单。
  • 处理表单提交,将学生添加到数据库。
  • 列出数据库中当前所有学生。

拥有一个用于获取数据库连接的辅助函数是一个好习惯。这确保了一致性。

import sqlite3
def get_db_connection():
conn = sqlite3.connect('database.db')
# 将行返回为字典样式的对象
conn.row_factory = sqlite3.Row
return conn

设置 conn.row_factory = sqlite3.Row 允许你按名称(例如 row['name'])而不是仅按索引(row[0])访问列,这使代码更具可读性。

from flask import Flask, render_template, request, url_for, flash, redirect
import sqlite3
import sys # 用于错误打印
app = Flask(__name__)
# 消息闪现所需
app.config['SECRET_KEY'] = 'your secret key here'
def get_db_connection():
conn = sqlite3.connect('database.db')
conn.row_factory = sqlite3.Row
return conn
@app.route('/')
def index():
conn = get_db_connection()
students = conn.execute('SELECT * FROM students').fetchall()
conn.close()
return render_template('index.html', students=students)
@app.route('/new', methods=('GET', 'POST'))
def new_student():
if request.method == 'POST':
name = request.form['name']
addr = request.form['addr']
city = request.form['city']
pin = request.form['pin']
if not name:
flash('Name is required!', 'error')
else:
conn = get_db_connection()
try:
conn.execute('INSERT INTO students (name, addr, city, pin) VALUES (?, ?, ?, ?)',
(name, addr, city, pin))
conn.commit()
flash('Student added successfully!', 'success')
except sqlite3.Error as e:
print(f"Database error: {e}", file=sys.stderr)
flash(f'Error adding student: {e}', 'error')
conn.rollback() # 回滚错误时的更改
finally:
conn.close()
# 添加成功/失败后重定向到主页
return redirect(url_for('index'))
# 如果是 GET 请求,只显示表单
return render_template('new_student.html')
if __name__ == '__main__':
app.run(debug=True)

我们需要三个 HTML 模板,放在一个名为 templates 的文件夹中:

index.html(显示列表并提供添加新学生的链接)

Section titled “index.html(显示列表并提供添加新学生的链接)”
<!DOCTYPE html>
<html>
<head>
<title>Student List</title>
<style>
.alert { padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; }
.alert-success { color: #155724; background-color: #d4edda; border-color: #c3e6cb; }
.alert-error { color: #721c24; background-color: #f8d7da; border-color: #f5c6cb; }
table, th, td { border: 1px solid black; border-collapse: collapse; padding: 5px; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<h1>学生记录</h1>
<!-- Display flashed messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<p><a href="{{ url_for('new_student') }}">添加新学生</a></p>
<table>
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>地址</th>
<th>城市</th>
<th>邮编</th>
</tr>
</thead>
<tbody>
{% for student in students %}
<tr>
<td>{{ student['id'] }}</td>
<td>{{ student['name'] }}</td>
<td>{{ student['addr'] }}</td>
<td>{{ student['city'] }}</td>
<td>{{ student['pin'] }}</td>
</tr>
{% else %}
<tr>
<td colspan="5">未找到学生记录。</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>

new_student.html(添加学生的表单)

Section titled “new_student.html(添加学生的表单)”
<!DOCTYPE html>
<html>
<head>
<title>Add New Student</title>
<style>
label { display: block; margin-bottom: 5px; }
input[type=text], textarea { width: 300px; margin-bottom: 10px; }
.alert-error { color: #721c24; background-color: #f8d7da; border-color: #f5c6cb; padding: 10px; margin-bottom: 10px; border-radius: 4px; }
</style>
</head>
<body>
<h1>添加学生信息</h1>
<!-- Display flashed messages (e.g., validation errors) -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
{% if category == 'error' %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endif %}
{% endfor %}
{% endif %}
{% endwith %}
<form method="post">
<label for="name">姓名:</label>
<input type="text" name="name" id="name" required><br>
<label for="addr">地址:</label>
<textarea name="addr" id="addr"></textarea><br>
<label for="city">城市:</label>
<input type="text" name="city" id="city"><br>
<label for="pin">邮编:</label>
<input type="text" name="pin" id="pin"><br>
<input type="submit" value="添加学生">
</form>
<p><a href="{{ url_for('index') }}">返回列表</a></p>
</body>
</html>
  • Python 3: 代码使用 Python 3 语法(例如,print() 函数)。
  • 上下文管理(with): 尽管在此简单的 get_db_connection 示例中未使用,但在更复杂的场景中,建议使用 with sqlite3.connect(...) as conn:,因为它在许多情况下会自动处理 commits 和 rollbacks。在这里,我们在 try...except...finally 块中手动管理 commit() 和 rollback(),以清晰地展示插入逻辑。
  • 错误处理: 包含基本的 try...except 块用于数据库错误,并将错误打印到标准错误输出。
  • sqlite3.Row: 用于按列名访问查询结果。
  • 闪现消息: 使用 Flask 的 flash() 函数和模板中的 get_flashed_messages() 来提供用户反馈(需要设置 app.config['SECRET_KEY'])。
  • 重定向: 在表单提交后使用 redirect(url_for('index')) 以获得更好的流程(Post/Redirect/Get 模式)。
  • 基本验证: 包含一个简单的检查 if not name:。
  • 安全: 请注意,此示例未实现健壮的输入验证或 sanitization(清理),这对于实际应用至关重要,以防止 SQL 注入和其他漏洞。
  • 可扩展性: 直接使用 sqlite3 对于简单情况是没问题的。对于更大或更复杂的应用,强烈建议使用对象关系映射(ORM),如 SQLAlchemy,通常通过 Flask-SQLAlchemy 扩展。它提供了更高级别的抽象,简化了数据库交互,并有助于更有效地管理数据库连接。这将在后续章节中介绍。

运行应用(python app.py)并访问 http://localhost:5000/。你可以查看列表,点击“添加新学生”,填写表单,提交,然后查看更新后的列表。