目录
Python实例题
题目
基于 Flask 的博客系统
要求:
- 使用 Flask 框架构建一个简单的博客系统。
- 实现用户认证(注册、登录、注销)。
- 支持博客文章的创建、编辑、删除和查看。
- 使用 SQLite 数据库存储用户和文章数据。
- 添加评论功能,允许已登录用户对文章发表评论。
- 实现简单的权限控制(如只有文章作者才能编辑 / 删除文章)。
- 添加分页功能,美化界面(使用 Bootstrap)。
解题思路:
- 使用 Flask 蓝图组织代码结构。
- 通过 Flask-Login 处理用户认证。
- 使用 Flask-SQLAlchemy 管理数据库操作。
- 结合 WTForms 实现表单验证。
- 使用 Bootstrap 美化前端界面。
代码实现:
from flask import Flask, render_template, request, redirect, url_for, flash, Blueprint
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
import os
# 初始化 Flask 应用
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(app.root_path, 'blog.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# 初始化数据库
db = SQLAlchemy(app)
# 初始化 Flask-Login
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'auth.login'
# 数据模型
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(120), nullable=False)
posts = db.relationship('Post', backref='author', lazy=True)
comments = db.relationship('Comment', backref='author', lazy=True)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
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)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
comments = db.relationship('Comment', backref='post', lazy=True)
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
# 用户加载回调
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# 认证蓝图
auth = Blueprint('auth', __name__)
@auth.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if User.query.filter_by(username=username).first():
flash('用户名已存在', 'error')
return redirect(url_for('auth.register'))
user = User(username=username)
user.set_password(password)
db.session.add(user)
db.session.commit()
flash('注册成功,请登录', 'success')
return redirect(url_for('auth.login'))
return render_template('register.html')
@auth.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
login_user(user)
flash('登录成功', 'success')
return redirect(url_for('main.index'))
else:
flash('用户名或密码错误', 'error')
return render_template('login.html')
@auth.route('/logout')
@login_required
def logout():
logout_user()
flash('已注销', 'success')
return redirect(url_for('main.index'))
# 主蓝图
main = Blueprint('main', __name__)
@main.route('/')
def index():
page = request.args.get('page', 1, type=int)
posts = Post.query.order_by(Post.date_posted.desc()).paginate(page=page, per_page=5)
return render_template('index.html', posts=posts)
@main.route('/post/new', methods=['GET', 'POST'])
@login_required
def new_post():
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
post = Post(title=title, content=content, author=current_user)
db.session.add(post)
db.session.commit()
flash('文章发布成功', 'success')
return redirect(url_for('main.index'))
return render_template('create_post.html', title='新文章')
@main.route('/post/<int:post_id>')
def post(post_id):
post = Post.query.get_or_404(post_id)
return render_template('post.html', post=post)
@main.route('/post/<int:post_id>/update', methods=['GET', 'POST'])
@login_required
def update_post(post_id):
post = Post.query.get_or_404(post_id)
if post.author != current_user:
flash('你无权编辑此文章', 'error')
return redirect(url_for('main.post', post_id=post.id))
if request.method == 'POST':
post.title = request.form['title']
post.content = request.form['content']
db.session.commit()
flash('文章已更新', 'success')
return redirect(url_for('main.post', post_id=post.id))
return render_template('create_post.html', title='更新文章', post=post)
@main.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.author != current_user:
flash('你无权删除此文章', 'error')
return redirect(url_for('main.post', post_id=post.id))
db.session.delete(post)
db.session.commit()
flash('文章已删除', 'success')
return redirect(url_for('main.index'))
@main.route('/post/<int:post_id>/comment', methods=['POST'])
@login_required
def add_comment(post_id):
post = Post.query.get_or_404(post_id)
content = request.form['content']
comment = Comment(content=content, author=current_user, post=post)
db.session.add(comment)
db.session.commit()
flash('评论已提交', 'success')
return redirect(url_for('main.post', post_id=post_id))
# 注册蓝图
app.register_blueprint(main)
app.register_blueprint(auth)
# 创建数据库表
with app.app_context():
db.create_all()
if __name__ == '__main__':
app.run(debug=True)
对应的 HTML 模板文件(需要放在 templates 文件夹中):
1. base.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flask博客</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="{{ url_for('main.index') }}">Flask博客</a>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.index') }}">首页</a>
</li>
</ul>
<ul class="navbar-nav">
{% if current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.new_post') }}">新文章</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}">注销 ({{ current_user.username }})</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.login') }}">登录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.register') }}">注册</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
{% 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 %}
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
2. index.html
{% extends "base.html" %}
{% block content %}
<h1 class="mb-4">博客文章</h1>
{% for post in posts.items %}
<div class="card mb-4">
<div class="card-body">
<h2 class="card-title">{{ post.title }}</h2>
<p class="card-text">{{ post.content[:200] }}{% if post.content|length > 200 %}...{% endif %}</p>
<a href="{{ url_for('main.post', post_id=post.id) }}" class="btn btn-primary">阅读更多</a>
</div>
<div class="card-footer text-muted">
发布于 {{ post.date_posted.strftime('%Y-%m-%d') }} by {{ post.author.username }}
</div>
</div>
{% endfor %}
<!-- 分页导航 -->
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if posts.has_prev %}
<li class="page-item">
<a class="page-link" href="{{ url_for('main.index', page=posts.prev_num) }}">上一页</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">上一页</span>
</li>
{% endif %}
{% for page_num in posts.iter_pages(left_edge=1, right_edge=1, left_current=1, right_current=2) %}
{% if page_num %}
{% if posts.page == page_num %}
<li class="page-item active">
<span class="page-link">{{ page_num }}</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="{{ url_for('main.index', page=page_num) }}">{{ page_num }}</a>
</li>
{% endif %}
{% else %}
<li class="page-item disabled">
<span class="page-link">...</span>
</li>
{% endif %}
{% endfor %}
{% if posts.has_next %}
<li class="page-item">
<a class="page-link" href="{{ url_for('main.index', page=posts.next_num) }}">下一页</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">下一页</span>
</li>
{% endif %}
</ul>
</nav>
{% endblock %}
3. post.html
{% extends "base.html" %}
{% block content %}
<div class="card mb-4">
<div class="card-body">
<h2 class="card-title">{{ post.title }}</h2>
<p class="card-text">{{ post.content }}</p>
</div>
<div class="card-footer text-muted">
发布于 {{ post.date_posted.strftime('%Y-%m-%d %H:%M') }} by {{ post.author.username }}
{% if current_user.is_authenticated and current_user == post.author %}
<div class="float-right">
<a href="{{ url_for('main.update_post', post_id=post.id) }}" class="btn btn-sm btn-outline-secondary">编辑</a>
<form action="{{ url_for('main.delete_post', post_id=post.id) }}" method="POST" class="d-inline">
<button type="submit" class="btn btn-sm btn-outline-danger" onclick="return confirm('确定要删除这篇文章吗?')">删除</button>
</form>
</div>
{% endif %}
</div>
</div>
<!-- 评论区 -->
<h3 class="mb-4">评论 ({{ post.comments|length }})</h3>
{% for comment in post.comments %}
<div class="card mb-3">
<div class="card-body">
<p class="card-text">{{ comment.content }}</p>
<div class="text-muted">
<small>由 {{ comment.author.username }} 发布于 {{ comment.date_posted.strftime('%Y-%m-%d %H:%M') }}</small>
</div>
</div>
</div>
{% endfor %}
<!-- 添加评论表单 -->
{% if current_user.is_authenticated %}
<div class="card mb-4">
<div class="card-header">添加评论</div>
<div class="card-body">
<form method="POST" action="{{ url_for('main.add_comment', post_id=post.id) }}">
<div class="form-group">
<textarea class="form-control" name="content" rows="3" required></textarea>
</div>
<button type="submit" class="btn btn-primary mt-3">提交评论</button>
</form>
</div>
</div>
{% else %}
<div class="alert alert-info">
<a href="{{ url_for('auth.login') }}">登录</a> 后可发表评论
</div>
{% endif %}
{% endblock %}
4. create_post.html
{% extends "base.html" %}
{% block content %}
<h1>{{ title }}</h1>
<form method="POST">
<div class="form-group">
<label for="title">标题</label>
<input type="text" class="form-control" id="title" name="title"
value="{{ post.title if post else '' }}" required>
</div>
<div class="form-group mt-3">
<label for="content">内容</label>
<textarea class="form-control" id="content" name="content" rows="10" required>{{ post.content if post else '' }}</textarea>
</div>
<button type="submit" class="btn btn-primary mt-3">提交</button>
</form>
{% endblock %}
5. login.html
{% extends "base.html" %}
{% block content %}
<h1>登录</h1>
<form method="POST">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group mt-3">
<label for="password">密码</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary mt-3">登录</button>
</form>
<p class="mt-3">
还没有账号? <a href="{{ url_for('auth.register') }}">注册</a>
</p>
{% endblock %}
6. register.html
{% extends "base.html" %}
{% block content %}
<h1>注册</h1>
<form method="POST">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group mt-3">
<label for="password">密码</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary mt-3">注册</button>
</form>
<p class="mt-3">
已有账号? <a href="{{ url_for('auth.login') }}">登录</a>
</p>
{% endblock %}