《Python星球日记》第30天:Flask数据库集成
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》
创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊)
专栏:《Python星球日记》,限时特价订阅中ing
目录
- 一、数据库基础
- 1. SQLite 数据库简介
- 2. 使用 SQLAlchemy 管理数据库
- 二、模型定义
- 1. 创建表:`db.Model`
- 2. 定义字段:`Column()`、`String()`、`Integer()` 等
- 3. 定义关系
- 4. 创建数据库表
- 三、CRUD 操作
- 1. 创建记录(Create)
- 2. 读取记录(Read)
- 3. 更新记录(Update)
- 4. 删除记录(Delete)
- 四、实战项目:为博客系统添加用户系统
- 1. 项目结构
- 2. 安装依赖
- 3. 定义数据模型
- 4. 创建应用入口
- 5. 创建模板
- 6. 修改基础模板
- 7. 更新CSS样式
- 8. 更新首页模板
- 9. 更新文章详情模板
- 10. 运行与测试
- 五、总结与进阶方向
- 进阶方向
👋 专栏介绍: Python星球日记专栏介绍(持续更新ing)
✅ 上一篇: 《Python星球日记》第29天:Flask进阶
欢迎来到Python星球日记第30天🪐!
一、数据库基础
在构建Web应用时,数据持久化存储是一个核心需求。
前一天我们使用简单的JSON文件存储博客文章,但这种方式在实际应用中存在诸多限制:不支持复杂查询、并发访问控制薄弱、缺乏数据完整性保障等。数据库系统解决了这些问题,为我们提供更强大的数据管理能力。
1. SQLite 数据库简介
SQLite 是一个轻量级的、基于文件的关系型数据库,具有以下特点:
- 零配置:不需要安装服务器或进行复杂设置
- 自包含:整个数据库就是一个单独的文件
- 跨平台:可在各种操作系统上使用
- 标准兼容:支持大部分标准SQL语法
- Python内置支持:Python标准库中包含
sqlite3
模块
SQLite非常适合小型到中型应用,特别是在以下场景:
- 需要嵌入式数据库的应用
- 开发和测试环境
- 数据量不是特别大的网站
- 移动应用的本地存储
# 基本的sqlite3使用示例
import sqlite3# 连接到数据库(如果不存在会自动创建)
conn = sqlite3.connect('example.db')
cursor = conn.cursor()# 创建表
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT NOT NULL UNIQUE,email TEXT NOT NULL
)
''')# 提交事务
conn.commit()
conn.close()
2. 使用 SQLAlchemy 管理数据库
图片:SQLAlchemyORM概念图
直接使用sqlite3
模块需要编写原始SQL语句,而SQLAlchemy作为Python最著名的ORM(对象关系映射)库,提供了更高级的抽象:
- ORM模式:将数据库表映射为Python类,将记录映射为对象
- 多数据库支持:除SQLite外,还支持MySQL、PostgreSQL等
- 会话管理:提供事务管理和连接池
- 查询构建器:使用Python语法构建SQL查询
图片:Flask-SQLAlchemy架构图
在Flask中,通常使用Flask-SQLAlchemy扩展,它是SQLAlchemy的Flask集成版本,提供了额外的集成特性。
# 安装Flask-SQLAlchemy
# pip install flask-sqlalchemyfrom flask import Flask
from flask_sqlalchemy import SQLAlchemyapp = Flask(__name__)
# 配置数据库URI
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
# 禁用追踪修改(减少开销)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False# 创建数据库对象
db = SQLAlchemy(app)
图片:Flask、SQLAlchemy和数据库的关系
二、模型定义
模型(Model)是SQLAlchemy ORM中的核心概念,它定义了数据库表的结构和行为。在Flask-SQLAlchemy中,我们通过继承db.Model
类来创建模型。
1. 创建表:db.Model
每个继承自db.Model
的类对应数据库中的一个表:
class User(db.Model):# 定义表名(可选,默认为类名小写)__tablename__ = 'users'# 定义列id = db.Column(db.Integer, primary_key=True)username = db.Column(db.String(80), unique=True, nullable=False)email = db.Column(db.String(120), unique=True, nullable=False)# 表示对象的方法def __repr__(self):return f'<User {self.username}>'
2. 定义字段:Column()
、String()
、Integer()
等
SQLAlchemy提供了多种列类型,对应不同的数据类型:
db.Integer
:整数db.String(length)
:定长字符串db.Text
:不定长文本db.Float
:浮点数db.Boolean
:布尔值db.DateTime
:日期时间db.Date
:日期db.Time
:时间db.LargeBinary
:二进制数据
每个列还可以配置多种约束和选项:
primary_key=True
:设置为主键unique=True
:值必须唯一nullable=False
:不允许为NULLindex=True
:在此列创建索引default=value
:设置默认值server_default=value
:设置数据库级别的默认值
3. 定义关系
SQLAlchemy强大之处在于可以轻松定义表间关系:
class Post(db.Model):__tablename__ = 'posts'id = db.Column(db.Integer, primary_key=True)title = db.Column(db.String(100), nullable=False)content = db.Column(db.Text, nullable=False)created_at = db.Column(db.DateTime, default=datetime.utcnow)# 外键 - 引用users表的id列user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)# 关系 - 定义与User的关系# backref会在User模型中创建posts属性,实现反向引用author = db.relationship('User', backref=db.backref('posts', lazy=True))
常见的关系类型:
- 一对多:上例中的User和Post(一个用户可以有多篇文章)
- 多对一:Post到User(多篇文章可以对应到一个用户)
- 一对一:通过
uselist=False
参数实现 - 多对多:需要定义关联表
4. 创建数据库表
模型定义好后,需要创建实际的数据库表:
# 在应用上下文中创建所有表
with app.app_context():db.create_all()# 或在初始化时创建
if __name__ == '__main__':with app.app_context():db.create_all()app.run(debug=True)
图片:博客系统数据库模型关系图
三、CRUD 操作
CRUD是数据库操作的基本模式,代表创建(Create)、读取(Read)、更新(Update) 和 删除(Delete)四种基本操作。使用SQLAlchemy,我们可以通过Python对象来执行这些操作。
1. 创建记录(Create)
创建新记录的步骤:
- 创建模型实例
- 将实例添加到会话(Session)
- 提交会话
# 创建新用户
@app.route('/register', methods=['POST'])
def register():# 获取表单数据username = request.form.get('username')email = request.form.get('email')password = request.form.get('password')# 创建用户对象new_user = User(username=username,email=email,password_hash=generate_password_hash(password) # 假设使用werkzeug的密码哈希)# 添加到会话并提交try:db.session.add(new_user)db.session.commit()return jsonify({'message': '用户创建成功'}), 201except Exception as e:db.session.rollback() # 出错时回滚return jsonify({'error': str(e)}), 400
2. 读取记录(Read)
SQLAlchemy提供多种查询方法:
# 查询单个用户
user = User.query.get(1) # 通过主键查询
user = User.query.filter_by(username='python_fan').first() # 通过条件查询第一个匹配# 查询所有用户
all_users = User.query.all()# 条件查询
users = User.query.filter(User.email.endswith('@example.com')).all()# 排序
recent_users = User.query.order_by(User.created_at.desc()).limit(5).all()# 分页
page = User.query.paginate(page=2, per_page=10)
users_on_page = page.items
3. 更新记录(Update)
更新记录的步骤:
- 查询要更新的记录
- 修改属性
- 提交会话
@app.route('/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):user = User.query.get_or_404(user_id) # 如果不存在返回404错误# 更新属性if 'email' in request.json:user.email = request.json['email']if 'username' in request.json:user.username = request.json['username']# 提交更改try:db.session.commit()return jsonify({'message': '用户更新成功'})except Exception as e:db.session.rollback()return jsonify({'error': str(e)}), 400
4. 删除记录(Delete)
删除记录的步骤:
- 查询要删除的记录
- 从会话中删除
- 提交会话
@app.route('/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):user = User.query.get_or_404(user_id)try:db.session.delete(user)db.session.commit()return jsonify({'message': '用户删除成功'})except Exception as e:db.session.rollback()return jsonify({'error': str(e)}), 400
四、实战项目:为博客系统添加用户系统
现在,让我们将前面学到的知识应用到实践中,为昨天构建的博客系统添加用户注册和登录功能。我们将使用Flask-SQLAlchemy来管理数据库,使用Flask-Login来处理用户会话。
1. 项目结构
首先,确定新的项目结构:
flask_blog/
├── app.py # 应用入口
├── models.py # 数据模型
├── forms.py # 表单定义(可选)
├── static/ # 静态文件
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── script.js
│ └── images/
├── templates/ # 模板文件
│ ├── base.html # 基础模板
│ ├── index.html # 首页
│ ├── post.html # 文章详情页
│ ├── about.html # 关于页面
│ ├── login.html # 登录页面
│ ├── create.html # 创建新文章页面
│ └── register.html # 注册页面
└── instance/ # 实例文件夹(存放数据库文件等)└── blog.db # SQLite数据库文件
2. 安装依赖
首先,安装所需的扩展:
pip install flask-sqlalchemy flask-login werkzeug
3. 定义数据模型
创建models.py
文件,定义用户和文章模型:
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash# 创建数据库对象
db = SQLAlchemy()class User(db.Model, UserMixin):"""用户模型"""id = db.Column(db.Integer, primary_key=True)username = db.Column(db.String(80), unique=True, nullable=False)email = db.Column(db.String(120), unique=True, nullable=False)password_hash = db.Column(db.String(128), nullable=False)created_at = db.Column(db.DateTime, default=datetime.utcnow)# 关系 - 一个用户可以有多篇文章posts = db.relationship('Post', backref='author', lazy=True, cascade='all, delete-orphan')@propertydef password(self):"""密码属性不可读"""raise AttributeError('密码不是可读属性')@password.setterdef password(self, password):"""设置密码时进行哈希"""self.password_hash = generate_password_hash(password)def verify_password(self, password):"""验证密码"""return check_password_hash(self.password_hash, password)def __repr__(self):return f'<User {self.username}>'class Post(db.Model):"""文章模型"""id = db.Column(db.Integer, primary_key=True)title = db.Column(db.String(100), nullable=False)content = db.Column(db.Text, nullable=False)created_at = db.Column(db.DateTime, default=datetime.utcnow)# 外键 - 关联到用户user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)def __repr__(self):return f'<Post {self.title}>'
4. 创建应用入口
修改app.py
文件,集成数据库和用户认证:
from flask import Flask, render_template, redirect, url_for, flash, request
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash
import os
from datetime import datetime# 创建应用实例
app = Flask(__name__)# 配置密钥和数据库
app.config['SECRET_KEY'] = 'your-secret-key' # 用于会话安全# 获取应用的绝对路径
basedir = os.path.abspath(os.path.dirname(__file__))# 确保instance文件夹存在
instance_path = os.path.join(basedir, 'instance')
os.makedirs(instance_path, exist_ok=True)# 配置数据库URI使用绝对路径
db_path = os.path.join(instance_path, 'blog.db')
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False# 初始化数据库
from models import db, User, Post# 在应用上下文中初始化数据库
db.init_app(app)# 设置Flask-Login
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login' # 未登录时重定向到登录页面# 用户加载函数
@login_manager.user_loader
def load_user(user_id):return User.query.get(int(user_id))# 创建所有表
with app.app_context():db.create_all()# 首页路由
@app.route('/')
def index():posts = Post.query.order_by(Post.created_at.desc()).all()return render_template('index.html', posts=posts)# 文章详情页
@app.route('/post/<int:post_id>')
def post(post_id):post = Post.query.get_or_404(post_id)return render_template('post.html', post=post)# 注册路由
@app.route('/register', methods=['GET', 'POST'])
def register():if current_user.is_authenticated:return redirect(url_for('index'))if request.method == 'POST':username = request.form.get('username')email = request.form.get('email')password = request.form.get('password')# 验证用户名和邮箱是否已存在if User.query.filter_by(username=username).first():flash('用户名已被使用')return redirect(url_for('register'))if User.query.filter_by(email=email).first():flash('邮箱已被注册')return redirect(url_for('register'))# 创建新用户new_user = User(username=username, email=email)new_user.password = password # 使用setter方法哈希密码# 添加到数据库db.session.add(new_user)db.session.commit()flash('注册成功!请登录')return redirect(url_for('login'))return render_template('register.html')# 登录路由
@app.route('/login', methods=['GET', 'POST'])
def login():if current_user.is_authenticated:return redirect(url_for('index'))if request.method == 'POST':email = request.form.get('email')password = request.form.get('password')remember = True if request.form.get('remember') else False# 查找用户user = User.query.filter_by(email=email).first()# 检查用户是否存在和密码是否正确if not user or not user.verify_password(password):flash('请检查邮箱和密码')return redirect(url_for('login'))# 登录用户login_user(user, remember=remember)return redirect(url_for('index'))return render_template('login.html')# 登出路由
@app.route('/logout')
@login_required
def logout():logout_user()return redirect(url_for('index'))# 创建文章路由
@app.route('/create', methods=['GET', 'POST'])
@login_required
def create_post():if request.method == 'POST':title = request.form.get('title')content = request.form.get('content')# 创建新文章new_post = Post(title=title,content=content,user_id=current_user.id)# 添加到数据库db.session.add(new_post)db.session.commit()return redirect(url_for('post', post_id=new_post.id))return render_template('create.html')# 编辑文章路由
@app.route('/post/<int:post_id>/edit', methods=['GET', 'POST'])
@login_required
def edit_post(post_id):post = Post.query.get_or_404(post_id)# 确保只有作者才能编辑if post.user_id != current_user.id:flash('您无权编辑此文章')return redirect(url_for('post', post_id=post_id))if request.method == 'POST':post.title = request.form.get('title')post.content = request.form.get('content')db.session.commit()return redirect(url_for('post', post_id=post_id))return render_template('edit.html', post=post)# 删除文章路由
@app.route('/post/<int:post_id>/delete', methods=['POST'])
@login_required
def delete_post(post_id):post = Post.query.get_or_404(post_id)# 确保只有作者才能删除if post.user_id != current_user.id:flash('您无权删除此文章')return redirect(url_for('post', post_id=post_id))db.session.delete(post)db.session.commit()flash('文章已删除')return redirect(url_for('index'))# 关于页面
@app.route('/about')
def about():return render_template('about.html')if __name__ == '__main__':app.run(debug=True)
5. 创建模板
接下来,创建新的登录和注册模板。
templates/register.html:
{% extends "base.html" %}{% block title %}注册 - Python星球博客{% endblock %}{% block content %}
<section class="auth-form"><h2>注册新账号</h2>{% with messages = get_flashed_messages() %}{% if messages %}<div class="flashes">{% for message in messages %}<div class="flash-message">{{ message }}</div>{% endfor %}</div>{% endif %}{% endwith %}<form method="post"><div class="form-group"><label for="username">用户名</label><input type="text" id="username" name="username" required></div><div class="form-group"><label for="email">邮箱</label><input type="email" id="email" name="email" required></div><div class="form-group"><label for="password">密码</label><input type="password" id="password" name="password" required></div><button type="submit" class="submit-btn">注册</button></form><div class="auth-links">已有账号?<a href="{{ url_for('login') }}">登录</a></div>
</section>
{% endblock %}
templates/login.html:
{% extends "base.html" %}{% block title %}登录 - Python星球博客{% endblock %}{% block content %}
<section class="auth-form"><h2>登录账号</h2>{% with messages = get_flashed_messages() %}{% if messages %}<div class="flashes">{% for message in messages %}<div class="flash-message">{{ message }}</div>{% endfor %}</div>{% endif %}{% endwith %}<form method="post"><div class="form-group"><label for="email">邮箱</label><input type="email" id="email" name="email" required></div><div class="form-group"><label for="password">密码</label><input type="password" id="password" name="password" required></div><div class="form-group checkbox"><input type="checkbox" id="remember" name="remember"><label for="remember">记住我</label></div><button type="submit" class="submit-btn">登录</button></form><div class="auth-links">还没有账号?<a href="{{ url_for('register') }}">注册</a></div>
</section>
{% endblock %}
6. 修改基础模板
修改base.html
以支持用户登录状态:
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{% block title %}Python星球博客{% endblock %}</title><link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">{% block styles %}{% endblock %}
</head>
<body><header class="main-header"><div class="container"><h1 class="site-title">Python星球博客</h1><nav class="main-nav"><ul><li><a href="{{ url_for('index') }}">首页</a></li>{% if current_user.is_authenticated %}<li><a href="{{ url_for('create_post') }}">写文章</a></li><li><a href="{{ url_for('logout') }}">登出 ({{ current_user.username }})</a></li>{% else %}<li><a href="{{ url_for('login') }}">登录</a></li><li><a href="{{ url_for('register') }}">注册</a></li>{% endif %}<li><a href="{{ url_for('about') }}">关于</a></li></ul></nav></div></header><main class="container">{% with messages = get_flashed_messages() %}{% if messages %}<div class="flashes">{% for message in messages %}<div class="flash-message">{{ message }}</div>{% endfor %}</div>{% endif %}{% endwith %}{% block content %}{% endblock %}</main><footer class="main-footer"><div class="container"><p>© 2025 Python星球日记 | Flask数据库集成教程</p></div></footer>{% block scripts %}{% endblock %}
</body>
</html>
7. 更新CSS样式
在static/css/style.css
中添加认证表单的样式:
/* 认证表单样式 */
.auth-form {max-width: 500px;margin: 2rem auto;padding: 2rem;background: white;border-radius: 5px;box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}.auth-form h2 {margin-top: 0;margin-bottom: 1.5rem;text-align: center;
}.flashes {margin-bottom: 1.5rem;
}.flash-message {padding: 0.75rem;margin-bottom: 0.5rem;border-radius: 3px;background-color: #f8d7da;color: #721c24;border: 1px solid #f5c6cb;
}.auth-links {margin-top: 1.5rem;text-align: center;font-size: 0.9rem;
}.checkbox {display: flex;align-items: center;
}.checkbox input {width: auto;margin-right: 0.5rem;
}.checkbox label {margin-bottom: 0;font-weight: normal;
}
8. 更新首页模板
修改index.html
以显示文章作者:
{% extends "base.html" %}{% block title %}首页 - Python星球博客{% endblock %}{% block content %}
<section class="blog-list"><h2>最新文章</h2>{% if posts %}<div class="posts">{% for post in posts %}<article class="post-card"><h3><a href="{{ url_for('post', post_id=post.id) }}">{{ post.title }}</a></h3><div class="post-meta"><span class="author">作者: {{ post.author.username }}</span><span class="date">{{ post.created_at.strftime('%Y-%m-%d %H:%M') }}</span></div><div class="post-excerpt">{{ post.content[:100] }}{% if post.content|length > 100 %}...{% endif %}</div><a href="{{ url_for('post', post_id=post.id) }}" class="read-more">阅读全文</a></article>{% endfor %}</div>{% else %}<p class="no-posts">暂无文章,来创建第一篇吧!</p>{% endif %}
</section>
{% endblock %}
9. 更新文章详情模板
修改post.html
以显示文章作者:
{% extends "base.html" %}{% block title %}{{ post.title }} - Python星球博客{% endblock %}{% block content %}
<article class="post-detail"><header class="post-header"><h2 class="post-title">{{ post.title }}</h2><div class="post-meta"><span class="author">作者: {{ post.author.username }}</span><span class="date">{{ post.created_at.strftime('%Y-%m-%d %H:%M') }}</span></div></header><div class="post-content">{{ post.content|safe }}</div><footer class="post-footer"><a href="{{ url_for('index') }}" class="back-link">返回首页</a>{% if current_user.is_authenticated and current_user.id == post.user_id %}<div class="post-actions"><a href="{{ url_for('edit_post', post_id=post.id) }}" class="edit-link">编辑</a><form method="post" action="{{ url_for('delete_post', post_id=post.id) }}" class="inline-form"><button type="submit" class="delete-btn" onclick="return confirm('确定要删除这篇文章吗?')">删除</button></form></div>{% endif %}</footer>
</article>
{% endblock %}
10. 运行与测试
完成所有修改后,按照以下步骤运行和测试项目:
1. 确保安装了所有依赖:
pip install flask flask-sqlalchemy flask-login werkzeug
2. 运行应用:
python app.py
3. 访问 http://127.0.0.1:5000/ 查看博客系统
图片:首页
图片:注册界面
图片:登录界面
图片:登录后的首页界面
图片:写文章界面
写好的文章可以编辑,可以删除
我们写了一篇文章,回到首页可以看到最新文章
4. 主要功能汇总:
- 注册新用户
- 登录系统
- 创建新文章
- 查看文章详情
- 登出系统
五、总结与进阶方向
在本章中,我们学习了Flask与数据库集成的核心概念和实践,包括:
- SQLite数据库的基础知识和优势
- 使用SQLAlchemy ORM进行数据库抽象
- 定义数据模型和表关系
- 执行基本的CRUD操作
- 实现完整的用户认证系统
相比昨天使用JSON文件存储数据,今天的博客系统在以下方面得到了显著提升:
- 数据结构化:通过关系型数据库保证了数据的结构化存储
- 查询能力:可以执行复杂的数据查询
- 数据完整性:通过外键和约束保证数据一致性
- 用户认证:添加了安全的用户注册和登录功能
- 功能扩展:为后续添加更多功能奠定了基础
进阶方向
想要继续提升Flask和数据库集成技能,可以探索以下方向:
- 数据库迁移:使用Flask-Migrate管理数据库架构变更
- 更复杂的数据关系:实现多对多关系、自引用关系等
- 高级查询:聚合函数、子查询、联结查询等
- 性能优化:数据库索引、查询优化、连接池配置
- 测试:数据库操作的单元测试和集成测试
- 安全性:防SQL注入、数据验证、访问控制
- 扩展功能:评论系统、标签分类、搜索功能
通过这一章的学习,我们已经掌握了Flask与数据库集成的核心技能,这为构建更复杂、功能更丰富的Web应用奠定了坚实基础。
数据库是几乎所有现代Web应用的核心组件,深入理解这一部分对于成为一名全栈Python开发者至关重要。
创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊)
如果你对今天的内容有任何问题,或者想分享你的学习心得,欢迎在评论区留言讨论!