PythonFlask实战项目开发
Python Flask 实战项目开发:构建一个完整的 Web 应用
本文将深入探讨如何使用 Python Flask 框架进行实战项目开发,从项目规划、环境搭建到核心功能实现、部署上线,带你一步步构建一个完整的 Web 应用。我们将以一个简单的博客系统为例,涵盖 Flask 的核心概念和常用技术。
一、项目规划与需求分析
在开始编码之前,清晰的项目规划至关重要。我们需要明确项目的目标、功能需求、用户群体以及技术选型。
1. 项目目标:
构建一个简单的博客系统,允许用户注册、登录、发布文章、浏览文章、评论文章以及管理个人信息。
2. 功能需求:
- 用户认证:
- 用户注册(用户名、邮箱、密码)
- 用户登录(用户名/邮箱、密码)
- 用户注销
- 密码重置(可选)
- 文章管理:
- 发布文章(标题、内容、标签)
- 编辑文章
- 删除文章
- 浏览文章列表(分页)
- 查看文章详情
- 评论系统:
- 发表评论
- 回复评论(可选)
- 删除评论(管理员权限)
- 用户管理:
- 查看个人资料
- 修改个人资料(头像、昵称等)
- 管理后台(可选)
- 文章管理 (审核、置顶等)
- 评论管理
- 用户管理
3. 技术选型:
- 后端框架: Python Flask
- 数据库: SQLite (开发阶段), PostgreSQL 或 MySQL (生产环境)
- ORM: SQLAlchemy
- 模板引擎: Jinja2 (Flask 自带)
- 表单处理: WTForms
- 用户认证: Flask-Login
- 前端框架: Bootstrap (或其他前端框架,可选)
- 部署: Gunicorn + Nginx (或其他 WSGI 服务器和反向代理服务器)
二、项目环境搭建
-
安装 Python: 确保已安装 Python 3.6 或更高版本。
-
创建虚拟环境:
bash
python3 -m venv venv
source venv/bin/activate # Linux/macOS
venv\Scripts\activate # Windows -
安装 Flask 及相关依赖:
bash
pip install flask flask-sqlalchemy flask-wtf flask-login
如果需要其他依赖,例如邮件发送(Flask-Mail),根据需求安装。 -
创建项目目录结构:
my_blog/
├── app/ # 应用主目录
│ ├── __init__.py # 初始化文件
│ ├── models.py # 数据库模型
│ ├── views.py # 视图函数
│ ├── forms.py # 表单定义
│ ├── templates/ # HTML 模板
│ │ ├── base.html # 基础模板
│ │ ├── index.html # 首页
│ │ ├── ...
│ └── static/ # 静态文件 (CSS, JavaScript, 图片)
│ ├── css/
│ ├── js/
│ └── img/
├── migrations/ # 数据库迁移 (使用 Flask-Migrate)
├── config.py # 配置文件
├── run.py # 应用启动文件
└── requirements.txt # 项目依赖列表
三、核心功能实现
1. config.py
配置文件:
```python
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'your-secret-key'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(os.path.abspath(os.path.dirname(file)), 'app.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 其他配置...
class DevelopmentConfig(Config):
DEBUG = True
class ProductionConfig(Config):
DEBUG = False
# 生产环境特定配置...
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
```
2. app/__init__.py
初始化文件:
```python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from config import config
db = SQLAlchemy()
login_manager = LoginManager()
login_manager.login_view = 'auth.login' # 设置登录视图
def create_app(config_name):
app = Flask(name)
app.config.from_object(config[config_name])
db.init_app(app)
login_manager.init_app(app)
# 注册蓝图 (后面会创建)
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/auth') # 设置 URL 前缀
# from .admin import admin as admin_blueprint # 如果有管理后台
# app.register_blueprint(admin_blueprint, url_prefix='/admin')
return app
```
3. app/models.py
数据库模型:
```python
from datetime import datetime
from . import db, login_manager
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
email = db.Column(db.String(120), unique=True, index=True)
password_hash = db.Column(db.String(128))
posts = db.relationship('Post', backref='author', lazy='dynamic')
comments = db.relationship('Comment', backref='author', lazy='dynamic')
# 其他字段 (例如头像、个人简介等)
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)
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))
body = db.Column(db.Text)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
comments = db.relationship('Comment', backref='post', lazy='dynamic')
# 其他字段 (例如标签)
def __repr__(self):
return f'<Post {self.title}>'
class Comment(db.Model):
id = db.Column(db.Integer, primary_key = True)
body = db.Column(db.Text)
timestamp = db.Column(db.DateTime, index=True, default = datetime.utcnow)
disabled = db.Column(db.Boolean)
author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
```
4. app/forms.py
表单定义:
```python
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, TextAreaField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from .models import User
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Remember Me')
submit = SubmitField('Log In')
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=4, max=20)])
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
password2 = PasswordField(
'Repeat Password', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Register')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user is not None:
raise ValidationError('Please use a different username.')
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user is not None:
raise ValidationError('Please use a different email address.')
class PostForm(FlaskForm):
title = StringField('Title', validators=[DataRequired()])
body = TextAreaField('Body', validators=[DataRequired()])
submit = SubmitField('Submit')
class CommentForm(FlaskForm):
body = TextAreaField('Enter your comment', validators=[DataRequired()])
submit = SubmitField('Submit')
```
5. app/views.py
视图函数 (以 main
蓝图为例):
```python
from flask import render_template, redirect, url_for, flash, request, Blueprint
from flask_login import login_required, current_user
from . import db
from .models import Post, Comment
from .forms import PostForm, CommentForm
main = Blueprint('main', name)
@main.route('/')
@main.route('/index')
def index():
page = request.args.get('page', 1, type=int) # 分页
posts = Post.query.order_by(Post.timestamp.desc()).paginate(
page=page, per_page=10, error_out=False) # 每页显示 10 篇文章
return render_template('index.html', posts=posts)
@main.route('/post/
def post(post_id):
post = Post.query.get_or_404(post_id)
form = CommentForm()
if form.validate_on_submit():
comment = Comment(body=form.body.data, post=post, author=current_user)
db.session.add(comment)
db.session.commit()
flash('Your comment has been published.')
return redirect(url_for('main.post', post_id=post.id, page=-1))
# page=-1 跳转到最后一页评论
page = request.args.get('page', 1, type=int)
if page == -1:
page = (post.comments.count() -1 ) // 10 + 1 #最后一页评论页数
comments = post.comments.order_by(Comment.timestamp.asc()).paginate(
page=page, per_page=10, error_out=False)
return render_template('post.html', post=post, form=form, comments=comments)
@main.route('/new_post', methods=['GET', 'POST'])
@login_required
def new_post():
form = PostForm()
if form.validate_on_submit():
post = Post(title=form.title.data, body=form.body.data, author=current_user)
db.session.add(post)
db.session.commit()
flash('Your post has been published!')
return redirect(url_for('main.index'))
return render_template('new_post.html', form=form)
其他视图函数 (编辑文章、删除文章等)
```
6. app/auth/views.py
视图函数 (用户认证蓝图):
```python
from flask import render_template, redirect, url_for, flash, request, Blueprint
from flask_login import login_user, logout_user, login_required
from . import db
from .models import User
from .forms import LoginForm, RegistrationForm
auth = Blueprint('auth', name)
@auth.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated: #防止重复登录
return redirect(url_for('main.index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is not None and user.check_password(form.password.data):
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next') # 获取登录后要跳转的页面
if not next_page or not next_page.startswith('/'):
next_page = url_for('main.index')
return redirect(next_page)
flash('Invalid username or password.')
return render_template('auth/login.html', form=form)
@auth.route('/logout')
@login_required
def logout():
logout_user()
flash('You have been logged out.')
return redirect(url_for('main.index'))
@auth.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('Congratulations, you are now a registered user!')
return redirect(url_for('auth.login'))
return render_template('auth/register.html', form=form)
```
7. app/templates/
HTML 模板 (以 base.html
和 index.html
为例):
base.html
:
```html
{% block styles %}{% endblock %}
{% if messages %}
-
{% for message in messages %}
- {{ message }}
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
{% block scripts %}{% endblock %}
```
index.html
:
```html
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block content %}
Welcome to My Blog!
{% for post in posts.items %}
{{ post.title }}
{{ post.body|truncate(200) }}
Posted by {{ post.author.username }} on {{ post.timestamp.strftime('%Y-%m-%d %H:%M') }}
{% endfor %}