简体中文 繁體中文 English Deutsch 한국 사람 بالعربية TÜRKÇE português คนไทย Français Japanese

站内搜索

搜索
AI 风月

活动公告

03-01 22:34
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

精选Flask项目实例教程从入门到精通全面掌握Python Web开发技能的实用指南

3万

主题

602

科技点

3万

积分

白金月票

碾压王

积分
32704

立华奏

发表于 2025-9-20 12:30:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1. Flask简介与入门

Flask是一个用Python编写的轻量级Web应用框架。它被称为微框架,因为它不需要特定的工具或库,没有数据库抽象层、表单验证或其他第三方库提供的常见功能。然而,Flask支持扩展,可以添加这些功能到应用程序中,就像它们是在Flask本身中实现的一样。

1.1 为什么选择Flask?

• 轻量级:核心简单但易于扩展
• 灵活性:没有强制性的项目结构或依赖关系
• 易于学习:API简单直观,文档完善
• 强大的社区:拥有丰富的扩展和活跃的社区支持

1.2 安装Flask

在开始之前,确保你已经安装了Python。然后通过pip安装Flask:
  1. pip install flask
复制代码

1.3 第一个Flask应用

让我们创建一个简单的”Hello World”应用:
  1. from flask import Flask
  2. app = Flask(__name__)
  3. @app.route('/')
  4. def hello_world():
  5.     return 'Hello, World!'
  6. if __name__ == '__main__':
  7.     app.run(debug=True)
复制代码

将这段代码保存为app.py,然后在命令行中运行:
  1. python app.py
复制代码

现在,打开浏览器并访问http://127.0.0.1:5000/,你将看到”Hello, World!“的输出。

2. Flask基础概念

2.1 路由

路由是URL与Python函数之间的映射关系。在Flask中,使用@app.route()装饰器来定义路由:
  1. @app.route('/user/<username>')
  2. def show_user_profile(username):
  3.     # 显示用户的个人资料
  4.     return f'User: {username}'
  5. @app.route('/post/<int:post_id>')
  6. def show_post(post_id):
  7.     # 显示给定id的帖子,id是整数
  8.     return f'Post: {post_id}'
复制代码

在上面的例子中,<username>是一个字符串类型的变量部分,而<int:post_id>是一个整数类型的变量部分。

2.2 模板渲染

Flask使用Jinja2模板引擎。创建一个名为templates的文件夹,并在其中创建一个HTML文件:
  1. <!-- templates/hello.html -->
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5.     <title>Hello from Flask</title>
  6. </head>
  7. <body>
  8.     <h1>Hello, {{ name }}!</h1>
  9. </body>
  10. </html>
复制代码

然后在Flask应用中使用render_template函数渲染这个模板:
  1. from flask import render_template
  2. @app.route('/hello/<name>')
  3. def hello(name):
  4.     return render_template('hello.html', name=name)
复制代码

2.3 静态文件

静态文件如CSS、JavaScript和图像文件通常放在名为static的文件夹中。在模板中,可以使用url_for('static', filename='style.css')来生成静态文件的URL。
  1. <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
复制代码

2.4 请求处理

Flask提供了全局的request对象来访问传入的请求数据:
  1. from flask import request
  2. @app.route('/login', methods=['GET', 'POST'])
  3. def login():
  4.     if request.method == 'POST':
  5.         username = request.form['username']
  6.         password = request.form['password']
  7.         # 验证用户名和密码
  8.         return f'Logged in as {username}'
  9.     else:
  10.         return '''
  11.             <form method="post">
  12.                 <p><input type=text name=username>
  13.                 <p><input type=password name=password>
  14.                 <p><input type=submit value=Login>
  15.             </form>
  16.         '''
复制代码

3. Flask进阶功能

3.1 数据库集成

Flask本身不包含数据库抽象层,但有许多扩展可以帮助你与数据库交互。最常用的是Flask-SQLAlchemy,它为SQLAlchemy提供了Flask集成。

首先安装Flask-SQLAlchemy:
  1. pip install flask-sqlalchemy
复制代码

然后配置数据库:
  1. from flask import Flask
  2. from flask_sqlalchemy import SQLAlchemy
  3. app = Flask(__name__)
  4. # 配置SQLite数据库
  5. app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mydatabase.db'
  6. app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
  7. db = SQLAlchemy(app)
  8. # 定义模型
  9. class User(db.Model):
  10.     id = db.Column(db.Integer, primary_key=True)
  11.     username = db.Column(db.String(80), unique=True, nullable=False)
  12.     email = db.Column(db.String(120), unique=True, nullable=False)
  13.     def __repr__(self):
  14.         return f'<User {self.username}>'
  15. # 创建数据库表
  16. @app.before_first_request
  17. def create_tables():
  18.     db.create_all()
  19. @app.route('/add_user')
  20. def add_user():
  21.     user = User(username='john', email='john@example.com')
  22.     db.session.add(user)
  23.     db.session.commit()
  24.     return 'User added!'
  25. @app.route('/users')
  26. def get_users():
  27.     users = User.query.all()
  28.     return '<br>'.join([f'{user.username}: {user.email}' for user in users])
复制代码

3.2 表单处理

Flask-WTF扩展简化了Web表单的处理。首先安装必要的包:
  1. pip install flask-wtf
  2. pip install email-validator
复制代码

然后创建一个表单类:
  1. from flask_wtf import FlaskForm
  2. from wtforms import StringField, PasswordField, SubmitField
  3. from wtforms.validators import DataRequired, Email, Length
  4. class RegistrationForm(FlaskForm):
  5.     username = StringField('Username',
  6.                            validators=[DataRequired(), Length(min=4, max=20)])
  7.     email = StringField('Email',
  8.                        validators=[DataRequired(), Email()])
  9.     password = PasswordField('Password',
  10.                             validators=[DataRequired(), Length(min=8)])
  11.     submit = SubmitField('Register')
复制代码

在视图函数中使用表单:
  1. @app.route('/register', methods=['GET', 'POST'])
  2. def register():
  3.     form = RegistrationForm()
  4.     if form.validate_on_submit():
  5.         # 处理表单数据
  6.         user = User(username=form.username.data,
  7.                    email=form.email.data)
  8.         user.set_password(form.password.data)  # 假设有这个方法
  9.         db.session.add(user)
  10.         db.session.commit()
  11.         return 'Registration successful!'
  12.     return render_template('register.html', form=form)
复制代码

对应的模板register.html:
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <title>Register</title>
  5. </head>
  6. <body>
  7.     <h1>Register</h1>
  8.     <form method="POST" action="">
  9.         {{ form.hidden_tag() }}
  10.         <p>
  11.             {{ form.username.label }}<br>
  12.             {{ form.username(size=32) }}
  13.         </p>
  14.         <p>
  15.             {{ form.email.label }}<br>
  16.             {{ form.email(size=32) }}
  17.         </p>
  18.         <p>
  19.             {{ form.password.label }}<br>
  20.             {{ form.password(size=32) }}
  21.         </p>
  22.         <p>{{ form.submit() }}</p>
  23.     </form>
  24. </body>
  25. </html>
复制代码

3.3 用户认证

Flask-Login扩展提供了用户会话管理功能。首先安装:
  1. pip install flask-login
复制代码

然后配置Flask-Login:
  1. from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
  2. login_manager = LoginManager()
  3. login_manager.init_app(app)
  4. login_manager.login_view = 'login'
  5. # User模型需要继承UserMixin
  6. class User(UserMixin, db.Model):
  7.     # ...之前的字段...
  8.     password_hash = db.Column(db.String(128))
  9.    
  10.     def set_password(self, password):
  11.         self.password_hash = generate_password_hash(password)
  12.    
  13.     def check_password(self, password):
  14.         return check_password_hash(self.password_hash, password)
  15. @login_manager.user_loader
  16. def load_user(user_id):
  17.     return User.query.get(int(user_id))
  18. @app.route('/login', methods=['GET', 'POST'])
  19. def login():
  20.     if current_user.is_authenticated:
  21.         return redirect(url_for('index'))
  22.    
  23.     form = LoginForm()
  24.     if form.validate_on_submit():
  25.         user = User.query.filter_by(username=form.username.data).first()
  26.         if user is None or not user.check_password(form.password.data):
  27.             flash('Invalid username or password')
  28.             return redirect(url_for('login'))
  29.         login_user(user, remember=form.remember_me.data)
  30.         return redirect(url_for('index'))
  31.    
  32.     return render_template('login.html', title='Sign In', form=form)
  33. @app.route('/logout')
  34. def logout():
  35.     logout_user()
  36.     return redirect(url_for('index'))
  37. @app.route('/protected')
  38. @login_required
  39. def protected():
  40.     return 'This is a protected page. Only logged in users can see this.'
复制代码

3.4 RESTful API

Flask可以轻松创建RESTful API。以下是一个简单的示例:
  1. from flask import jsonify
  2. @app.route('/api/users', methods=['GET'])
  3. def get_users():
  4.     users = User.query.all()
  5.     return jsonify([{'username': user.username, 'email': user.email} for user in users])
  6. @app.route('/api/users/<int:user_id>', methods=['GET'])
  7. def get_user(user_id):
  8.     user = User.query.get_or_404(user_id)
  9.     return jsonify({'username': user.username, 'email': user.email})
  10. @app.route('/api/users', methods=['POST'])
  11. def create_user():
  12.     data = request.get_json()
  13.     if not data or not 'username' in data or not 'email' in data:
  14.         return jsonify({'error': 'Bad request'}), 400
  15.    
  16.     user = User(username=data['username'], email=data['email'])
  17.     db.session.add(user)
  18.     db.session.commit()
  19.     return jsonify({'message': 'User created successfully'}), 201
  20. @app.route('/api/users/<int:user_id>', methods=['PUT'])
  21. def update_user(user_id):
  22.     user = User.query.get_or_404(user_id)
  23.     data = request.get_json()
  24.    
  25.     if 'username' in data:
  26.         user.username = data['username']
  27.     if 'email' in data:
  28.         user.email = data['email']
  29.    
  30.     db.session.commit()
  31.     return jsonify({'message': 'User updated successfully'})
  32. @app.route('/api/users/<int:user_id>', methods=['DELETE'])
  33. def delete_user(user_id):
  34.     user = User.query.get_or_404(user_id)
  35.     db.session.delete(user)
  36.     db.session.commit()
  37.     return jsonify({'message': 'User deleted successfully'})
复制代码

4. 实战项目示例

4.1 博客系统

让我们创建一个简单的博客系统,包括文章发布、显示和评论功能。
  1. blog/
  2.     app.py
  3.     config.py
  4.     models.py
  5.     forms.py
  6.     templates/
  7.         base.html
  8.         index.html
  9.         post.html
  10.         create_post.html
  11.         register.html
  12.         login.html
  13.     static/
  14.         css/
  15.             style.css
复制代码
  1. # config.py
  2. import os
  3. class Config:
  4.     SECRET_KEY = os.environ.get('SECRET_KEY') or 'your-secret-key'
  5.     SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///blog.db'
  6.     SQLALCHEMY_TRACK_MODIFICATIONS = False
复制代码
  1. # models.py
  2. from datetime import datetime
  3. from flask_sqlalchemy import SQLAlchemy
  4. from flask_login import UserMixin
  5. from werkzeug.security import generate_password_hash, check_password_hash
  6. db = SQLAlchemy()
  7. class User(UserMixin, db.Model):
  8.     id = db.Column(db.Integer, primary_key=True)
  9.     username = db.Column(db.String(64), index=True, unique=True)
  10.     email = db.Column(db.String(120), index=True, unique=True)
  11.     password_hash = db.Column(db.String(128))
  12.     posts = db.relationship('Post', backref='author', lazy='dynamic')
  13.     def set_password(self, password):
  14.         self.password_hash = generate_password_hash(password)
  15.     def check_password(self, password):
  16.         return check_password_hash(self.password_hash, password)
  17.     def __repr__(self):
  18.         return f'<User {self.username}>'
  19. class Post(db.Model):
  20.     id = db.Column(db.Integer, primary_key=True)
  21.     title = db.Column(db.String(140))
  22.     body = db.Column(db.Text)
  23.     timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
  24.     user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
  25.     comments = db.relationship('Comment', backref='post', lazy='dynamic')
  26.     def __repr__(self):
  27.         return f'<Post {self.title}>'
  28. class Comment(db.Model):
  29.     id = db.Column(db.Integer, primary_key=True)
  30.     body = db.Column(db.Text)
  31.     timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
  32.     user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
  33.     post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
  34.     def __repr__(self):
  35.         return f'<Comment {self.body[:20]}...>'
复制代码
  1. # forms.py
  2. from flask_wtf import FlaskForm
  3. from wtforms import StringField, PasswordField, BooleanField, SubmitField, TextAreaField
  4. from wtforms.validators import DataRequired, Email, EqualTo, ValidationError, Length
  5. from models import User
  6. class LoginForm(FlaskForm):
  7.     username = StringField('Username', validators=[DataRequired()])
  8.     password = PasswordField('Password', validators=[DataRequired()])
  9.     remember_me = BooleanField('Remember Me')
  10.     submit = SubmitField('Sign In')
  11. class RegistrationForm(FlaskForm):
  12.     username = StringField('Username', validators=[DataRequired(), Length(min=4, max=20)])
  13.     email = StringField('Email', validators=[DataRequired(), Email()])
  14.     password = PasswordField('Password', validators=[DataRequired(), Length(min=8)])
  15.     password2 = PasswordField('Repeat Password', validators=[DataRequired(), EqualTo('password')])
  16.     submit = SubmitField('Register')
  17.     def validate_username(self, username):
  18.         user = User.query.filter_by(username=username.data).first()
  19.         if user is not None:
  20.             raise ValidationError('Please use a different username.')
  21.     def validate_email(self, email):
  22.         user = User.query.filter_by(email=email.data).first()
  23.         if user is not None:
  24.             raise ValidationError('Please use a different email address.')
  25. class PostForm(FlaskForm):
  26.     title = StringField('Title', validators=[DataRequired(), Length(max=140)])
  27.     body = TextAreaField('Content', validators=[DataRequired(), Length(min=1, max=2000)])
  28.     submit = SubmitField('Submit')
  29. class CommentForm(FlaskForm):
  30.     body = TextAreaField('Comment', validators=[DataRequired(), Length(min=1, max=500)])
  31.     submit = SubmitField('Submit')
复制代码
  1. # app.py
  2. from flask import Flask, render_template, flash, redirect, url_for, request
  3. from flask_login import LoginManager, current_user, login_user, logout_user, login_required
  4. from werkzeug.urls import url_parse
  5. from datetime import datetime
  6. from config import Config
  7. from models import db, User, Post, Comment
  8. from forms import LoginForm, RegistrationForm, PostForm, CommentForm
  9. app = Flask(__name__)
  10. app.config.from_object(Config)
  11. db.init_app(app)
  12. login_manager = LoginManager()
  13. login_manager.init_app(app)
  14. login_manager.login_view = 'login'
  15. @login_manager.user_loader
  16. def load_user(user_id):
  17.     return User.query.get(int(user_id))
  18. @app.before_request
  19. def before_request():
  20.     if current_user.is_authenticated:
  21.         current_user.last_seen = datetime.utcnow()
  22.         db.session.commit()
  23. @app.route('/', methods=['GET', 'POST'])
  24. @app.route('/index', methods=['GET', 'POST'])
  25. def index():
  26.     page = request.args.get('page', 1, type=int)
  27.     posts = Post.query.order_by(Post.timestamp.desc()).paginate(
  28.         page, app.config['POSTS_PER_PAGE'], False)
  29.     next_url = url_for('index', page=posts.next_num) if posts.has_next else None
  30.     prev_url = url_for('index', page=posts.prev_num) if posts.has_prev else None
  31.     return render_template('index.html', title='Home', posts=posts.items,
  32.                           next_url=next_url, prev_url=prev_url)
  33. @app.route('/login', methods=['GET', 'POST'])
  34. def login():
  35.     if current_user.is_authenticated:
  36.         return redirect(url_for('index'))
  37.     form = LoginForm()
  38.     if form.validate_on_submit():
  39.         user = User.query.filter_by(username=form.username.data).first()
  40.         if user is None or not user.check_password(form.password.data):
  41.             flash('Invalid username or password')
  42.             return redirect(url_for('login'))
  43.         login_user(user, remember=form.remember_me.data)
  44.         next_page = request.args.get('next')
  45.         if not next_page or url_parse(next_page).netloc != '':
  46.             next_page = url_for('index')
  47.         return redirect(next_page)
  48.     return render_template('login.html', title='Sign In', form=form)
  49. @app.route('/logout')
  50. def logout():
  51.     logout_user()
  52.     return redirect(url_for('index'))
  53. @app.route('/register', methods=['GET', 'POST'])
  54. def register():
  55.     if current_user.is_authenticated:
  56.         return redirect(url_for('index'))
  57.     form = RegistrationForm()
  58.     if form.validate_on_submit():
  59.         user = User(username=form.username.data, email=form.email.data)
  60.         user.set_password(form.password.data)
  61.         db.session.add(user)
  62.         db.session.commit()
  63.         flash('Congratulations, you are now a registered user!')
  64.         return redirect(url_for('login'))
  65.     return render_template('register.html', title='Register', form=form)
  66. @app.route('/create_post', methods=['GET', 'POST'])
  67. @login_required
  68. def create_post():
  69.     form = PostForm()
  70.     if form.validate_on_submit():
  71.         post = Post(title=form.title.data, body=form.body.data, author=current_user)
  72.         db.session.add(post)
  73.         db.session.commit()
  74.         flash('Your post is now live!')
  75.         return redirect(url_for('index'))
  76.     return render_template('create_post.html', title='Create Post', form=form)
  77. @app.route('/post/<int:id>', methods=['GET', 'POST'])
  78. def post(id):
  79.     post = Post.query.get_or_404(id)
  80.     form = CommentForm()
  81.     if form.validate_on_submit():
  82.         comment = Comment(body=form.body.data, post=post, author=current_user)
  83.         db.session.add(comment)
  84.         db.session.commit()
  85.         flash('Your comment has been published.')
  86.         return redirect(url_for('post', id=post.id))
  87.     comments = Comment.query.filter_by(post_id=post.id).order_by(Comment.timestamp.desc()).all()
  88.     return render_template('post.html', post=post, comments=comments, form=form)
  89. @app.route('/user/<username>')
  90. @login_required
  91. def user(username):
  92.     user = User.query.filter_by(username=username).first_or_404()
  93.     page = request.args.get('page', 1, type=int)
  94.     posts = user.posts.order_by(Post.timestamp.desc()).paginate(
  95.         page, app.config['POSTS_PER_PAGE'], False)
  96.     next_url = url_for('user', username=user.username, page=posts.next_num) if posts.has_next else None
  97.     prev_url = url_for('user', username=user.username, page=posts.prev_num) if posts.has_prev else None
  98.     return render_template('user.html', user=user, posts=posts.items,
  99.                           next_url=next_url, prev_url=prev_url)
  100. if __name__ == '__main__':
  101.     app.run(debug=True)
复制代码

base.html:
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <title>{{ title }} - Blog</title>
  6.     <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
  7. </head>
  8. <body>
  9.     <header>
  10.         <nav>
  11.             <a href="{{ url_for('index') }}">Home</a>
  12.             {% if current_user.is_anonymous %}
  13.             <a href="{{ url_for('login') }}">Login</a>
  14.             {% else %}
  15.             <a href="{{ url_for('user', username=current_user.username) }}">Profile</a>
  16.             <a href="{{ url_for('create_post') }}">New Post</a>
  17.             <a href="{{ url_for('logout') }}">Logout</a>
  18.             {% endif %}
  19.         </nav>
  20.     </header>
  21.     <main>
  22.         {% with messages = get_flashed_messages() %}
  23.         {% if messages %}
  24.         <div class="flash-messages">
  25.             {% for message in messages %}
  26.             <div class="flash">{{ message }}</div>
  27.             {% endfor %}
  28.         </div>
  29.         {% endif %}
  30.         {% endwith %}
  31.         
  32.         {% block content %}{% endblock %}
  33.     </main>
  34.     <footer>
  35.         <p>Blog &copy; 2023</p>
  36.     </footer>
  37. </body>
  38. </html>
复制代码

index.html:
  1. {% extends "base.html" %}
  2. {% block content %}
  3.     <h1>Blog Posts</h1>
  4.    
  5.     {% if current_user.is_authenticated %}
  6.     <p><a href="{{ url_for('create_post') }}">Create a new post</a></p>
  7.     {% endif %}
  8.    
  9.     {% for post in posts %}
  10.     <article class="post">
  11.         <h2><a href="{{ url_for('post', id=post.id) }}">{{ post.title }}</a></h2>
  12.         <div class="post-meta">
  13.             Posted by <a href="{{ url_for('user', username=post.author.username) }}">{{ post.author.username }}</a>
  14.             on {{ post.timestamp.strftime('%Y-%m-%d') }}
  15.         </div>
  16.         <div class="post-body">
  17.             {{ post.body[:200] }}{% if post.body|length > 200 %}...{% endif %}
  18.         </div>
  19.     </article>
  20.     {% endfor %}
  21.    
  22.     {% if prev_url %}
  23.     <a href="{{ prev_url }}">Newer posts</a>
  24.     {% endif %}
  25.     {% if next_url %}
  26.     <a href="{{ next_url }}">Older posts</a>
  27.     {% endif %}
  28. {% endblock %}
复制代码

post.html:
  1. {% extends "base.html" %}
  2. {% block content %}
  3.     <article class="post">
  4.         <h1>{{ post.title }}</h1>
  5.         <div class="post-meta">
  6.             Posted by <a href="{{ url_for('user', username=post.author.username) }}">{{ post.author.username }}</a>
  7.             on {{ post.timestamp.strftime('%Y-%m-%d %H:%M') }}
  8.         </div>
  9.         <div class="post-body">
  10.             {{ post.body }}
  11.         </div>
  12.     </article>
  13.    
  14.     <section class="comments">
  15.         <h2>Comments</h2>
  16.         
  17.         {% if current_user.is_authenticated %}
  18.         <form action="" method="post" class="comment-form">
  19.             {{ form.hidden_tag() }}
  20.             <div>
  21.                 {{ form.body.label }}<br>
  22.                 {{ form.body(cols=50, rows=4) }}
  23.             </div>
  24.             <div>{{ form.submit() }}</div>
  25.         </form>
  26.         {% endif %}
  27.         
  28.         {% for comment in comments %}
  29.         <div class="comment">
  30.             <div class="comment-meta">
  31.                 <a href="{{ url_for('user', username=comment.author.username) }}">{{ comment.author.username }}</a>
  32.                 on {{ comment.timestamp.strftime('%Y-%m-%d %H:%M') }}
  33.             </div>
  34.             <div class="comment-body">
  35.                 {{ comment.body }}
  36.             </div>
  37.         </div>
  38.         {% endfor %}
  39.     </section>
  40. {% endblock %}
复制代码

create_post.html:
  1. {% extends "base.html" %}
  2. {% block content %}
  3.     <h1>Create Post</h1>
  4.     <form action="" method="post">
  5.         {{ form.hidden_tag() }}
  6.         <div>
  7.             {{ form.title.label }}<br>
  8.             {{ form.title(size=50) }}
  9.         </div>
  10.         <div>
  11.             {{ form.body.label }}<br>
  12.             {{ form.body(cols=50, rows=10) }}
  13.         </div>
  14.         <div>{{ form.submit() }}</div>
  15.     </form>
  16. {% endblock %}
复制代码

login.html:
  1. {% extends "base.html" %}
  2. {% block content %}
  3.     <h1>Sign In</h1>
  4.     <form action="" method="post">
  5.         {{ form.hidden_tag() }}
  6.         <div>
  7.             {{ form.username.label }}<br>
  8.             {{ form.username(size=32) }}
  9.         </div>
  10.         <div>
  11.             {{ form.password.label }}<br>
  12.             {{ form.password(size=32) }}
  13.         </div>
  14.         <div>
  15.             {{ form.remember_me() }} {{ form.remember_me.label }}
  16.         </div>
  17.         <div>{{ form.submit() }}</div>
  18.     </form>
  19.     <p>New User? <a href="{{ url_for('register') }}">Click to Register!</a></p>
  20. {% endblock %}
复制代码

register.html:
  1. {% extends "base.html" %}
  2. {% block content %}
  3.     <h1>Register</h1>
  4.     <form action="" method="post">
  5.         {{ form.hidden_tag() }}
  6.         <div>
  7.             {{ form.username.label }}<br>
  8.             {{ form.username(size=32) }}
  9.         </div>
  10.         <div>
  11.             {{ form.email.label }}<br>
  12.             {{ form.email(size=32) }}
  13.         </div>
  14.         <div>
  15.             {{ form.password.label }}<br>
  16.             {{ form.password(size=32) }}
  17.         </div>
  18.         <div>
  19.             {{ form.password2.label }}<br>
  20.             {{ form.password2(size=32) }}
  21.         </div>
  22.         <div>{{ form.submit() }}</div>
  23.     </form>
  24. {% endblock %}
复制代码

user.html:
  1. {% extends "base.html" %}
  2. {% block content %}
  3.     <h1>User: {{ user.username }}</h1>
  4.    
  5.     {% for post in posts %}
  6.     <article class="post">
  7.         <h2><a href="{{ url_for('post', id=post.id) }}">{{ post.title }}</a></h2>
  8.         <div class="post-meta">
  9.             Posted on {{ post.timestamp.strftime('%Y-%m-%d') }}
  10.         </div>
  11.         <div class="post-body">
  12.             {{ post.body[:200] }}{% if post.body|length > 200 %}...{% endif %}
  13.         </div>
  14.     </article>
  15.     {% endfor %}
  16.    
  17.     {% if prev_url %}
  18.     <a href="{{ prev_url }}">Newer posts</a>
  19.     {% endif %}
  20.     {% if next_url %}
  21.     <a href="{{ next_url }}">Older posts</a>
  22.     {% endif %}
  23. {% endblock %}
复制代码
  1. /* static/css/style.css */
  2. body {
  3.     font-family: Arial, sans-serif;
  4.     line-height: 1.6;
  5.     margin: 0;
  6.     padding: 0;
  7.     background-color: #f4f4f4;
  8.     color: #333;
  9. }
  10. header {
  11.     background: #333;
  12.     color: #fff;
  13.     padding: 1rem;
  14.     text-align: center;
  15. }
  16. nav a {
  17.     color: #fff;
  18.     text-decoration: none;
  19.     margin: 0 15px;
  20. }
  21. nav a:hover {
  22.     text-decoration: underline;
  23. }
  24. main {
  25.     max-width: 800px;
  26.     margin: 20px auto;
  27.     padding: 0 20px;
  28. }
  29. footer {
  30.     text-align: center;
  31.     padding: 20px;
  32.     margin-top: 20px;
  33.     background: #333;
  34.     color: #fff;
  35. }
  36. .post {
  37.     background: #fff;
  38.     padding: 20px;
  39.     margin-bottom: 20px;
  40.     border-radius: 5px;
  41.     box-shadow: 0 2px 5px rgba(0,0,0,0.1);
  42. }
  43. .post h1, .post h2 {
  44.     margin-top: 0;
  45. }
  46. .post-meta {
  47.     color: #666;
  48.     font-size: 0.9em;
  49.     margin-bottom: 15px;
  50. }
  51. .post-body {
  52.     margin-bottom: 15px;
  53. }
  54. .flash-messages {
  55.     margin-bottom: 20px;
  56. }
  57. .flash {
  58.     padding: 10px;
  59.     margin-bottom: 10px;
  60.     background: #d4edda;
  61.     color: #155724;
  62.     border: 1px solid #c3e6cb;
  63.     border-radius: 4px;
  64. }
  65. .comment {
  66.     background: #f9f9f9;
  67.     padding: 15px;
  68.     margin-bottom: 15px;
  69.     border-radius: 5px;
  70. }
  71. .comment-meta {
  72.     color: #666;
  73.     font-size: 0.9em;
  74.     margin-bottom: 10px;
  75. }
  76. .comment-form {
  77.     margin-bottom: 30px;
  78. }
  79. form div {
  80.     margin-bottom: 15px;
  81. }
  82. input[type="text"], input[type="password"], input[type="email"], textarea {
  83.     width: 100%;
  84.     padding: 8px;
  85.     border: 1px solid #ddd;
  86.     border-radius: 4px;
  87. }
  88. input[type="submit"] {
  89.     background: #333;
  90.     color: #fff;
  91.     border: none;
  92.     padding: 10px 15px;
  93.     border-radius: 4px;
  94.     cursor: pointer;
  95. }
  96. input[type="submit"]:hover {
  97.     background: #444;
  98. }
复制代码

4.2 电子商务网站

让我们创建一个简单的电子商务网站,包括产品展示、购物车和订单功能。
  1. ecommerce/
  2.     app.py
  3.     config.py
  4.     models.py
  5.     forms.py
  6.     templates/
  7.         base.html
  8.         index.html
  9.         product.html
  10.         cart.html
  11.         checkout.html
  12.         order_confirmation.html
  13.     static/
  14.         css/
  15.             style.css
复制代码
  1. # models.py
  2. from datetime import datetime
  3. from flask_sqlalchemy import SQLAlchemy
  4. from flask_login import UserMixin
  5. from werkzeug.security import generate_password_hash, check_password_hash
  6. db = SQLAlchemy()
  7. class User(UserMixin, db.Model):
  8.     id = db.Column(db.Integer, primary_key=True)
  9.     username = db.Column(db.String(64), index=True, unique=True)
  10.     email = db.Column(db.String(120), index=True, unique=True)
  11.     password_hash = db.Column(db.String(128))
  12.     orders = db.relationship('Order', backref='user', lazy='dynamic')
  13.     def set_password(self, password):
  14.         self.password_hash = generate_password_hash(password)
  15.     def check_password(self, password):
  16.         return check_password_hash(self.password_hash, password)
  17.     def __repr__(self):
  18.         return f'<User {self.username}>'
  19. class Product(db.Model):
  20.     id = db.Column(db.Integer, primary_key=True)
  21.     name = db.Column(db.String(100), index=True)
  22.     description = db.Column(db.Text)
  23.     price = db.Column(db.Float)
  24.     image_url = db.Column(db.String(200))
  25.     stock = db.Column(db.Integer, default=0)
  26.     order_items = db.relationship('OrderItem', backref='product', lazy='dynamic')
  27.     def __repr__(self):
  28.         return f'<Product {self.name}>'
  29. class Order(db.Model):
  30.     id = db.Column(db.Integer, primary_key=True)
  31.     user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
  32.     created_at = db.Column(db.DateTime, index=True, default=datetime.utcnow)
  33.     status = db.Column(db.String(20), default='pending')  # pending, paid, shipped, delivered, cancelled
  34.     total_amount = db.Column(db.Float)
  35.     shipping_address = db.Column(db.Text)
  36.     payment_method = db.Column(db.String(50))
  37.     items = db.relationship('OrderItem', backref='order', lazy='dynamic')
  38.     def __repr__(self):
  39.         return f'<Order {self.id}>'
  40. class OrderItem(db.Model):
  41.     id = db.Column(db.Integer, primary_key=True)
  42.     order_id = db.Column(db.Integer, db.ForeignKey('order.id'))
  43.     product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
  44.     quantity = db.Column(db.Integer)
  45.     price = db.Column(db.Float)  # Price at the time of purchase
  46.     def __repr__(self):
  47.         return f'<OrderItem {self.id}>'
复制代码

由于购物车是临时性的,我们可以使用Flask的session来存储购物车数据:
  1. # cart.py
  2. from flask import session
  3. from models import Product
  4. def get_cart():
  5.     cart = session.get('cart', {})
  6.     return cart
  7. def add_to_cart(product_id, quantity=1):
  8.     cart = get_cart()
  9.     product_id = str(product_id)
  10.    
  11.     if product_id in cart:
  12.         cart[product_id] += quantity
  13.     else:
  14.         cart[product_id] = quantity
  15.    
  16.     session['cart'] = cart
  17. def remove_from_cart(product_id):
  18.     cart = get_cart()
  19.     product_id = str(product_id)
  20.    
  21.     if product_id in cart:
  22.         del cart[product_id]
  23.         session['cart'] = cart
  24. def update_cart(product_id, quantity):
  25.     cart = get_cart()
  26.     product_id = str(product_id)
  27.    
  28.     if quantity > 0:
  29.         cart[product_id] = quantity
  30.     else:
  31.         if product_id in cart:
  32.             del cart[product_id]
  33.    
  34.     session['cart'] = cart
  35. def get_cart_items():
  36.     cart = get_cart()
  37.     items = []
  38.    
  39.     for product_id, quantity in cart.items():
  40.         product = Product.query.get(int(product_id))
  41.         if product:
  42.             items.append({
  43.                 'product': product,
  44.                 'quantity': quantity,
  45.                 'total': product.price * quantity
  46.             })
  47.    
  48.     return items
  49. def get_cart_total():
  50.     items = get_cart_items()
  51.     return sum(item['total'] for item in items)
  52. def clear_cart():
  53.     session['cart'] = {}
复制代码
  1. # forms.py
  2. from flask_wtf import FlaskForm
  3. from wtforms import StringField, PasswordField, BooleanField, SubmitField, TextAreaField, IntegerField, SelectField
  4. from wtforms.validators import DataRequired, Email, EqualTo, ValidationError, NumberRange
  5. from models import User
  6. class LoginForm(FlaskForm):
  7.     username = StringField('Username', validators=[DataRequired()])
  8.     password = PasswordField('Password', validators=[DataRequired()])
  9.     remember_me = BooleanField('Remember Me')
  10.     submit = SubmitField('Sign In')
  11. class RegistrationForm(FlaskForm):
  12.     username = StringField('Username', validators=[DataRequired()])
  13.     email = StringField('Email', validators=[DataRequired(), Email()])
  14.     password = PasswordField('Password', validators=[DataRequired()])
  15.     password2 = PasswordField('Repeat Password', validators=[DataRequired(), EqualTo('password')])
  16.     submit = SubmitField('Register')
  17.     def validate_username(self, username):
  18.         user = User.query.filter_by(username=username.data).first()
  19.         if user is not None:
  20.             raise ValidationError('Please use a different username.')
  21.     def validate_email(self, email):
  22.         user = User.query.filter_by(email=email.data).first()
  23.         if user is not None:
  24.             raise ValidationError('Please use a different email address.')
  25. class CheckoutForm(FlaskForm):
  26.     full_name = StringField('Full Name', validators=[DataRequired()])
  27.     address = TextAreaField('Address', validators=[DataRequired()])
  28.     city = StringField('City', validators=[DataRequired()])
  29.     zip_code = StringField('ZIP Code', validators=[DataRequired()])
  30.     country = StringField('Country', validators=[DataRequired()])
  31.     payment_method = SelectField('Payment Method', choices=[
  32.         ('credit_card', 'Credit Card'),
  33.         ('paypal', 'PayPal'),
  34.         ('bank_transfer', 'Bank Transfer')
  35.     ], validators=[DataRequired()])
  36.     submit = SubmitField('Place Order')
复制代码
  1. # app.py
  2. from flask import Flask, render_template, flash, redirect, url_for, request, session
  3. from flask_login import LoginManager, current_user, login_user, logout_user, login_required
  4. from werkzeug.urls import url_parse
  5. from config import Config
  6. from models import db, User, Product, Order, OrderItem
  7. from forms import LoginForm, RegistrationForm, CheckoutForm
  8. from cart import get_cart, get_cart_items, get_cart_total, clear_cart
  9. app = Flask(__name__)
  10. app.config.from_object(Config)
  11. db.init_app(app)
  12. login_manager = LoginManager()
  13. login_manager.init_app(app)
  14. login_manager.login_view = 'login'
  15. @login_manager.user_loader
  16. def load_user(user_id):
  17.     return User.query.get(int(user_id))
  18. @app.route('/')
  19. def index():
  20.     products = Product.query.all()
  21.     return render_template('index.html', products=products)
  22. @app.route('/product/<int:id>')
  23. def product(id):
  24.     product = Product.query.get_or_404(id)
  25.     return render_template('product.html', product=product)
  26. @app.route('/add_to_cart/<int:product_id>')
  27. def add_to_cart(product_id):
  28.     product = Product.query.get_or_404(product_id)
  29.     quantity = int(request.args.get('quantity', 1))
  30.    
  31.     if product.stock < quantity:
  32.         flash('Not enough stock available!')
  33.         return redirect(url_for('product', id=product_id))
  34.    
  35.     from cart import add_to_cart as cart_add
  36.     cart_add(product_id, quantity)
  37.     flash(f'{product.name} added to cart!')
  38.     return redirect(url_for('cart'))
  39. @app.route('/cart')
  40. def cart():
  41.     items = get_cart_items()
  42.     total = get_cart_total()
  43.     return render_template('cart.html', items=items, total=total)
  44. @app.route('/update_cart/<int:product_id>')
  45. def update_cart(product_id):
  46.     quantity = int(request.args.get('quantity', 1))
  47.     from cart import update_cart as cart_update
  48.     cart_update(product_id, quantity)
  49.     return redirect(url_for('cart'))
  50. @app.route('/remove_from_cart/<int:product_id>')
  51. def remove_from_cart(product_id):
  52.     from cart import remove_from_cart as cart_remove
  53.     cart_remove(product_id)
  54.     return redirect(url_for('cart'))
  55. @app.route('/checkout', methods=['GET', 'POST'])
  56. @login_required
  57. def checkout():
  58.     form = CheckoutForm()
  59.     items = get_cart_items()
  60.     total = get_cart_total()
  61.    
  62.     if not items:
  63.         flash('Your cart is empty!')
  64.         return redirect(url_for('index'))
  65.    
  66.     if form.validate_on_submit():
  67.         # Create order
  68.         order = Order(
  69.             user_id=current_user.id,
  70.             total_amount=total,
  71.             shipping_address=f"{form.full_name.data}\n{form.address.data}\n{form.city.data}, {form.zip_code.data}\n{form.country.data}",
  72.             payment_method=form.payment_method.data
  73.         )
  74.         db.session.add(order)
  75.         
  76.         # Add order items
  77.         for item in items:
  78.             order_item = OrderItem(
  79.                 order_id=order.id,
  80.                 product_id=item['product'].id,
  81.                 quantity=item['quantity'],
  82.                 price=item['product'].price
  83.             )
  84.             db.session.add(order_item)
  85.             
  86.             # Update product stock
  87.             product = item['product']
  88.             product.stock -= item['quantity']
  89.         
  90.         db.session.commit()
  91.         
  92.         # Clear cart
  93.         clear_cart()
  94.         
  95.         flash('Order placed successfully!')
  96.         return redirect(url_for('order_confirmation', order_id=order.id))
  97.    
  98.     return render_template('checkout.html', form=form, items=items, total=total)
  99. @app.route('/order_confirmation/<int:order_id>')
  100. @login_required
  101. def order_confirmation(order_id):
  102.     order = Order.query.get_or_404(order_id)
  103.    
  104.     # Check if the order belongs to the current user
  105.     if order.user_id != current_user.id:
  106.         flash('You do not have permission to view this order.')
  107.         return redirect(url_for('index'))
  108.    
  109.     return render_template('order_confirmation.html', order=order)
  110. @app.route('/login', methods=['GET', 'POST'])
  111. def login():
  112.     if current_user.is_authenticated:
  113.         return redirect(url_for('index'))
  114.     form = LoginForm()
  115.     if form.validate_on_submit():
  116.         user = User.query.filter_by(username=form.username.data).first()
  117.         if user is None or not user.check_password(form.password.data):
  118.             flash('Invalid username or password')
  119.             return redirect(url_for('login'))
  120.         login_user(user, remember=form.remember_me.data)
  121.         next_page = request.args.get('next')
  122.         if not next_page or url_parse(next_page).netloc != '':
  123.             next_page = url_for('index')
  124.         return redirect(next_page)
  125.     return render_template('login.html', title='Sign In', form=form)
  126. @app.route('/logout')
  127. def logout():
  128.     logout_user()
  129.     return redirect(url_for('index'))
  130. @app.route('/register', methods=['GET', 'POST'])
  131. def register():
  132.     if current_user.is_authenticated:
  133.         return redirect(url_for('index'))
  134.     form = RegistrationForm()
  135.     if form.validate_on_submit():
  136.         user = User(username=form.username.data, email=form.email.data)
  137.         user.set_password(form.password.data)
  138.         db.session.add(user)
  139.         db.session.commit()
  140.         flash('Congratulations, you are now a registered user!')
  141.         return redirect(url_for('login'))
  142.     return render_template('register.html', title='Register', form=form)
  143. if __name__ == '__main__':
  144.     app.run(debug=True)
复制代码

base.html:
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <title>{{ title }} - E-commerce Store</title>
  6.     <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
  7. </head>
  8. <body>
  9.     <header>
  10.         <div class="container">
  11.             <div class="logo">
  12.                 <a href="{{ url_for('index') }}">E-commerce Store</a>
  13.             </div>
  14.             <nav>
  15.                 <a href="{{ url_for('index') }}">Home</a>
  16.                 {% if current_user.is_anonymous %}
  17.                 <a href="{{ url_for('login') }}">Login</a>
  18.                 <a href="{{ url_for('register') }}">Register</a>
  19.                 {% else %}
  20.                 <a href="{{ url_for('logout') }}">Logout</a>
  21.                 {% endif %}
  22.                 <a href="{{ url_for('cart') }}">Cart ({{ get_cart()|length }})</a>
  23.             </nav>
  24.         </div>
  25.     </header>
  26.     <main class="container">
  27.         {% with messages = get_flashed_messages() %}
  28.         {% if messages %}
  29.         <div class="flash-messages">
  30.             {% for message in messages %}
  31.             <div class="flash">{{ message }}</div>
  32.             {% endfor %}
  33.         </div>
  34.         {% endif %}
  35.         {% endwith %}
  36.         
  37.         {% block content %}{% endblock %}
  38.     </main>
  39.     <footer>
  40.         <div class="container">
  41.             <p>&copy; 2023 E-commerce Store. All rights reserved.</p>
  42.         </div>
  43.     </footer>
  44. </body>
  45. </html>
复制代码

index.html:
  1. {% extends "base.html" %}
  2. {% block content %}
  3.     <h1>Our Products</h1>
  4.    
  5.     <div class="products-grid">
  6.         {% for product in products %}
  7.         <div class="product-card">
  8.             {% if product.image_url %}
  9.             <img src="{{ product.image_url }}" alt="{{ product.name }}">
  10.             {% else %}
  11.             <div class="product-image-placeholder">No Image</div>
  12.             {% endif %}
  13.             <h2>{{ product.name }}</h2>
  14.             <p class="price">${{ "%.2f"|format(product.price) }}</p>
  15.             <p class="stock">In stock: {{ product.stock }}</p>
  16.             <a href="{{ url_for('product', id=product.id) }}" class="btn">View Details</a>
  17.         </div>
  18.         {% endfor %}
  19.     </div>
  20. {% endblock %}
复制代码

product.html:
  1. {% extends "base.html" %}
  2. {% block content %}
  3.     <div class="product-detail">
  4.         <div class="product-image">
  5.             {% if product.image_url %}
  6.             <img src="{{ product.image_url }}" alt="{{ product.name }}">
  7.             {% else %}
  8.             <div class="product-image-placeholder">No Image</div>
  9.             {% endif %}
  10.         </div>
  11.         <div class="product-info">
  12.             <h1>{{ product.name }}</h1>
  13.             <p class="price">${{ "%.2f"|format(product.price) }}</p>
  14.             <p class="stock">In stock: {{ product.stock }}</p>
  15.             <div class="description">
  16.                 <h3>Description</h3>
  17.                 <p>{{ product.description }}</p>
  18.             </div>
  19.             
  20.             <form action="{{ url_for('add_to_cart', product_id=product.id) }}" method="get">
  21.                 <div class="quantity-selector">
  22.                     <label for="quantity">Quantity:</label>
  23.                     <input type="number" id="quantity" name="quantity" min="1" max="{{ product.stock }}" value="1">
  24.                 </div>
  25.                 <button type="submit" class="btn btn-primary">Add to Cart</button>
  26.             </form>
  27.         </div>
  28.     </div>
  29. {% endblock %}
复制代码

cart.html:
  1. {% extends "base.html" %}
  2. {% block content %}
  3.     <h1>Shopping Cart</h1>
  4.    
  5.     {% if items %}
  6.     <table class="cart-table">
  7.         <thead>
  8.             <tr>
  9.                 <th>Product</th>
  10.                 <th>Price</th>
  11.                 <th>Quantity</th>
  12.                 <th>Total</th>
  13.                 <th>Actions</th>
  14.             </tr>
  15.         </thead>
  16.         <tbody>
  17.             {% for item in items %}
  18.             <tr>
  19.                 <td>
  20.                     <div class="cart-item-info">
  21.                         {% if item.product.image_url %}
  22.                         <img src="{{ item.product.image_url }}" alt="{{ item.product.name }}" class="cart-item-image">
  23.                         {% endif %}
  24.                         <a href="{{ url_for('product', id=item.product.id) }}">{{ item.product.name }}</a>
  25.                     </div>
  26.                 </td>
  27.                 <td>${{ "%.2f"|format(item.product.price) }}</td>
  28.                 <td>
  29.                     <form action="{{ url_for('update_cart', product_id=item.product.id) }}" method="get" class="quantity-form">
  30.                         <input type="number" name="quantity" min="1" max="{{ item.product.stock }}" value="{{ item.quantity }}">
  31.                         <button type="submit">Update</button>
  32.                     </form>
  33.                 </td>
  34.                 <td>${{ "%.2f"|format(item.total) }}</td>
  35.                 <td>
  36.                     <a href="{{ url_for('remove_from_cart', product_id=item.product.id) }}" class="btn btn-danger">Remove</a>
  37.                 </td>
  38.             </tr>
  39.             {% endfor %}
  40.         </tbody>
  41.         <tfoot>
  42.             <tr>
  43.                 <td colspan="3" class="cart-total-label">Total:</td>
  44.                 <td colspan="2" class="cart-total-amount">${{ "%.2f"|format(total) }}</td>
  45.             </tr>
  46.         </tfoot>
  47.     </table>
  48.    
  49.     <div class="cart-actions">
  50.         <a href="{{ url_for('index') }}" class="btn">Continue Shopping</a>
  51.         {% if current_user.is_authenticated %}
  52.         <a href="{{ url_for('checkout') }}" class="btn btn-primary">Proceed to Checkout</a>
  53.         {% else %}
  54.         <a href="{{ url_for('login') }}" class="btn btn-primary">Login to Checkout</a>
  55.         {% endif %}
  56.     </div>
  57.     {% else %}
  58.     <div class="empty-cart">
  59.         <p>Your cart is empty.</p>
  60.         <a href="{{ url_for('index') }}" class="btn">Continue Shopping</a>
  61.     </div>
  62.     {% endif %}
  63. {% endblock %}
复制代码

checkout.html:
  1. {% extends "base.html" %}
  2. {% block content %}
  3.     <h1>Checkout</h1>
  4.    
  5.     <div class="checkout-container">
  6.         <div class="checkout-form">
  7.             <h2>Shipping Information</h2>
  8.             <form action="" method="post">
  9.                 {{ form.hidden_tag() }}
  10.                 <div class="form-group">
  11.                     {{ form.full_name.label }}
  12.                     {{ form.full_name(class="form-control") }}
  13.                 </div>
  14.                 <div class="form-group">
  15.                     {{ form.address.label }}
  16.                     {{ form.address(class="form-control") }}
  17.                 </div>
  18.                 <div class="form-row">
  19.                     <div class="form-group">
  20.                         {{ form.city.label }}
  21.                         {{ form.city(class="form-control") }}
  22.                     </div>
  23.                     <div class="form-group">
  24.                         {{ form.zip_code.label }}
  25.                         {{ form.zip_code(class="form-control") }}
  26.                     </div>
  27.                 </div>
  28.                 <div class="form-group">
  29.                     {{ form.country.label }}
  30.                     {{ form.country(class="form-control") }}
  31.                 </div>
  32.                 <div class="form-group">
  33.                     {{ form.payment_method.label }}
  34.                     {{ form.payment_method(class="form-control") }}
  35.                 </div>
  36.                 <div class="form-group">
  37.                     {{ form.submit(class="btn btn-primary") }}
  38.                 </div>
  39.             </form>
  40.         </div>
  41.         
  42.         <div class="order-summary">
  43.             <h2>Order Summary</h2>
  44.             <table class="order-summary-table">
  45.                 <thead>
  46.                     <tr>
  47.                         <th>Product</th>
  48.                         <th>Quantity</th>
  49.                         <th>Price</th>
  50.                     </tr>
  51.                 </thead>
  52.                 <tbody>
  53.                     {% for item in items %}
  54.                     <tr>
  55.                         <td>{{ item.product.name }}</td>
  56.                         <td>{{ item.quantity }}</td>
  57.                         <td>${{ "%.2f"|format(item.total) }}</td>
  58.                     </tr>
  59.                     {% endfor %}
  60.                 </tbody>
  61.                 <tfoot>
  62.                     <tr>
  63.                         <td colspan="2">Total:</td>
  64.                         <td>${{ "%.2f"|format(total) }}</td>
  65.                     </tr>
  66.                 </tfoot>
  67.             </table>
  68.         </div>
  69.     </div>
  70. {% endblock %}
复制代码

order_confirmation.html:
  1. {% extends "base.html" %}
  2. {% block content %}
  3.     <div class="order-confirmation">
  4.         <h1>Order Confirmation</h1>
  5.         <div class="confirmation-message">
  6.             <p>Thank you for your order!</p>
  7.             <p>Your order number is: <strong>#{{ order.id }}</strong></p>
  8.             <p>We've received your order and will begin processing it soon.</p>
  9.         </div>
  10.         
  11.         <div class="order-details">
  12.             <h2>Order Details</h2>
  13.             <div class="order-info">
  14.                 <p><strong>Order Date:</strong> {{ order.created_at.strftime('%Y-%m-%d %H:%M') }}</p>
  15.                 <p><strong>Status:</strong> {{ order.status.title() }}</p>
  16.                 <p><strong>Payment Method:</strong> {{ order.payment_method.replace('_', ' ').title() }}</p>
  17.                 <p><strong>Total Amount:</strong> ${{ "%.2f"|format(order.total_amount) }}</p>
  18.             </div>
  19.             
  20.             <div class="shipping-info">
  21.                 <h3>Shipping Address</h3>
  22.                 <p>{{ order.shipping_address|replace('\n', '<br>')|safe }}</p>
  23.             </div>
  24.             
  25.             <div class="order-items">
  26.                 <h3>Items Ordered</h3>
  27.                 <table class="order-items-table">
  28.                     <thead>
  29.                         <tr>
  30.                             <th>Product</th>
  31.                             <th>Quantity</th>
  32.                             <th>Price</th>
  33.                         </tr>
  34.                     </thead>
  35.                     <tbody>
  36.                         {% for item in order.items %}
  37.                         <tr>
  38.                             <td>{{ item.product.name }}</td>
  39.                             <td>{{ item.quantity }}</td>
  40.                             <td>${{ "%.2f"|format(item.price * item.quantity) }}</td>
  41.                         </tr>
  42.                         {% endfor %}
  43.                     </tbody>
  44.                 </table>
  45.             </div>
  46.         </div>
  47.         
  48.         <div class="confirmation-actions">
  49.             <a href="{{ url_for('index') }}" class="btn">Continue Shopping</a>
  50.         </div>
  51.     </div>
  52. {% endblock %}
复制代码
  1. /* static/css/style.css */
  2. * {
  3.     box-sizing: border-box;
  4.     margin: 0;
  5.     padding: 0;
  6. }
  7. body {
  8.     font-family: Arial, sans-serif;
  9.     line-height: 1.6;
  10.     color: #333;
  11.     background-color: #f8f9fa;
  12. }
  13. .container {
  14.     max-width: 1200px;
  15.     margin: 0 auto;
  16.     padding: 0 15px;
  17. }
  18. /* Header */
  19. header {
  20.     background-color: #fff;
  21.     box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  22.     position: sticky;
  23.     top: 0;
  24.     z-index: 100;
  25. }
  26. header .container {
  27.     display: flex;
  28.     justify-content: space-between;
  29.     align-items: center;
  30.     padding: 15px;
  31. }
  32. .logo a {
  33.     font-size: 24px;
  34.     font-weight: bold;
  35.     color: #333;
  36.     text-decoration: none;
  37. }
  38. nav a {
  39.     margin-left: 20px;
  40.     color: #333;
  41.     text-decoration: none;
  42.     font-weight: 500;
  43. }
  44. nav a:hover {
  45.     color: #007bff;
  46. }
  47. /* Main Content */
  48. main {
  49.     padding: 30px 0;
  50.     min-height: calc(100vh - 160px);
  51. }
  52. /* Buttons */
  53. .btn {
  54.     display: inline-block;
  55.     background-color: #f8f9fa;
  56.     color: #333;
  57.     border: 1px solid #ddd;
  58.     padding: 8px 16px;
  59.     border-radius: 4px;
  60.     text-decoration: none;
  61.     font-weight: 500;
  62.     cursor: pointer;
  63.     transition: all 0.3s ease;
  64. }
  65. .btn:hover {
  66.     background-color: #e9ecef;
  67.     border-color: #adb5bd;
  68. }
  69. .btn-primary {
  70.     background-color: #007bff;
  71.     color: #fff;
  72.     border-color: #007bff;
  73. }
  74. .btn-primary:hover {
  75.     background-color: #0069d9;
  76.     border-color: #0062cc;
  77. }
  78. .btn-danger {
  79.     background-color: #dc3545;
  80.     color: #fff;
  81.     border-color: #dc3545;
  82. }
  83. .btn-danger:hover {
  84.     background-color: #c82333;
  85.     border-color: #bd2130;
  86. }
  87. /* Forms */
  88. .form-group {
  89.     margin-bottom: 15px;
  90. }
  91. .form-row {
  92.     display: flex;
  93.     gap: 15px;
  94. }
  95. .form-row .form-group {
  96.     flex: 1;
  97. }
  98. .form-control {
  99.     display: block;
  100.     width: 100%;
  101.     padding: 8px 12px;
  102.     font-size: 16px;
  103.     line-height: 1.5;
  104.     color: #495057;
  105.     background-color: #fff;
  106.     background-clip: padding-box;
  107.     border: 1px solid #ced4da;
  108.     border-radius: 4px;
  109.     transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
  110. }
  111. .form-control:focus {
  112.     color: #495057;
  113.     background-color: #fff;
  114.     border-color: #80bdff;
  115.     outline: 0;
  116.     box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
  117. }
  118. /* Flash Messages */
  119. .flash-messages {
  120.     margin-bottom: 20px;
  121. }
  122. .flash {
  123.     padding: 15px;
  124.     margin-bottom: 10px;
  125.     border: 1px solid transparent;
  126.     border-radius: 4px;
  127.     background-color: #d4edda;
  128.     border-color: #c3e6cb;
  129.     color: #155724;
  130. }
  131. /* Products Grid */
  132. .products-grid {
  133.     display: grid;
  134.     grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  135.     gap: 20px;
  136. }
  137. .product-card {
  138.     background-color: #fff;
  139.     border-radius: 8px;
  140.     overflow: hidden;
  141.     box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  142.     transition: transform 0.3s ease, box-shadow 0.3s ease;
  143. }
  144. .product-card:hover {
  145.     transform: translateY(-5px);
  146.     box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  147. }
  148. .product-card img {
  149.     width: 100%;
  150.     height: 200px;
  151.     object-fit: cover;
  152. }
  153. .product-image-placeholder {
  154.     width: 100%;
  155.     height: 200px;
  156.     background-color: #e9ecef;
  157.     display: flex;
  158.     align-items: center;
  159.     justify-content: center;
  160.     color: #6c757d;
  161. }
  162. .product-card h2 {
  163.     font-size: 18px;
  164.     margin: 10px;
  165. }
  166. .product-card .price {
  167.     font-size: 18px;
  168.     font-weight: bold;
  169.     color: #007bff;
  170.     margin: 0 10px;
  171. }
  172. .product-card .stock {
  173.     font-size: 14px;
  174.     color: #6c757d;
  175.     margin: 0 10px 10px;
  176. }
  177. .product-card .btn {
  178.     margin: 0 10px 15px;
  179. }
  180. /* Product Detail */
  181. .product-detail {
  182.     display: flex;
  183.     gap: 30px;
  184.     background-color: #fff;
  185.     padding: 20px;
  186.     border-radius: 8px;
  187.     box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  188. }
  189. .product-image {
  190.     flex: 1;
  191. }
  192. .product-image img {
  193.     width: 100%;
  194.     border-radius: 8px;
  195. }
  196. .product-info {
  197.     flex: 1;
  198. }
  199. .product-info h1 {
  200.     margin-bottom: 10px;
  201. }
  202. .product-info .price {
  203.     font-size: 24px;
  204.     font-weight: bold;
  205.     color: #007bff;
  206.     margin-bottom: 10px;
  207. }
  208. .product-info .stock {
  209.     font-size: 16px;
  210.     color: #6c757d;
  211.     margin-bottom: 20px;
  212. }
  213. .description {
  214.     margin-bottom: 20px;
  215. }
  216. .description h3 {
  217.     margin-bottom: 10px;
  218. }
  219. .quantity-selector {
  220.     display: flex;
  221.     align-items: center;
  222.     margin-bottom: 15px;
  223. }
  224. .quantity-selector label {
  225.     margin-right: 10px;
  226. }
  227. .quantity-selector input {
  228.     width: 60px;
  229.     padding: 5px;
  230.     border: 1px solid #ced4da;
  231.     border-radius: 4px;
  232. }
  233. /* Cart */
  234. .cart-table {
  235.     width: 100%;
  236.     border-collapse: collapse;
  237.     margin-bottom: 20px;
  238.     background-color: #fff;
  239.     border-radius: 8px;
  240.     overflow: hidden;
  241.     box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  242. }
  243. .cart-table th, .cart-table td {
  244.     padding: 12px 15px;
  245.     text-align: left;
  246.     border-bottom: 1px solid #dee2e6;
  247. }
  248. .cart-table th {
  249.     background-color: #f8f9fa;
  250.     font-weight: 600;
  251. }
  252. .cart-item-info {
  253.     display: flex;
  254.     align-items: center;
  255. }
  256. .cart-item-image {
  257.     width: 50px;
  258.     height: 50px;
  259.     object-fit: cover;
  260.     margin-right: 10px;
  261.     border-radius: 4px;
  262. }
  263. .quantity-form {
  264.     display: flex;
  265.     align-items: center;
  266. }
  267. .quantity-form input {
  268.     width: 50px;
  269.     padding: 5px;
  270.     border: 1px solid #ced4da;
  271.     border-radius: 4px;
  272.     margin-right: 5px;
  273. }
  274. .cart-total-label {
  275.     text-align: right;
  276.     font-weight: 600;
  277. }
  278. .cart-total-amount {
  279.     font-weight: bold;
  280.     font-size: 18px;
  281.     color: #007bff;
  282. }
  283. .cart-actions {
  284.     display: flex;
  285.     justify-content: space-between;
  286.     margin-top: 20px;
  287. }
  288. .empty-cart {
  289.     text-align: center;
  290.     padding: 40px;
  291.     background-color: #fff;
  292.     border-radius: 8px;
  293.     box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  294. }
  295. .empty-cart p {
  296.     margin-bottom: 20px;
  297.     font-size: 18px;
  298. }
  299. /* Checkout */
  300. .checkout-container {
  301.     display: flex;
  302.     gap: 30px;
  303. }
  304. .checkout-form {
  305.     flex: 3;
  306.     background-color: #fff;
  307.     padding: 20px;
  308.     border-radius: 8px;
  309.     box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  310. }
  311. .order-summary {
  312.     flex: 2;
  313.     background-color: #fff;
  314.     padding: 20px;
  315.     border-radius: 8px;
  316.     box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  317.     height: fit-content;
  318. }
  319. .order-summary-table {
  320.     width: 100%;
  321.     border-collapse: collapse;
  322. }
  323. .order-summary-table th, .order-summary-table td {
  324.     padding: 10px;
  325.     text-align: left;
  326.     border-bottom: 1px solid #dee2e6;
  327. }
  328. .order-summary-table th {
  329.     background-color: #f8f9fa;
  330.     font-weight: 600;
  331. }
  332. /* Order Confirmation */
  333. .order-confirmation {
  334.     background-color: #fff;
  335.     padding: 30px;
  336.     border-radius: 8px;
  337.     box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  338. }
  339. .confirmation-message {
  340.     text-align: center;
  341.     margin-bottom: 30px;
  342.     padding: 20px;
  343.     background-color: #d4edda;
  344.     border-radius: 8px;
  345.     color: #155724;
  346. }
  347. .confirmation-message p {
  348.     margin-bottom: 10px;
  349.     font-size: 18px;
  350. }
  351. .order-details {
  352.     margin-bottom: 30px;
  353. }
  354. .order-details h2 {
  355.     margin-bottom: 20px;
  356.     padding-bottom: 10px;
  357.     border-bottom: 1px solid #dee2e6;
  358. }
  359. .order-info {
  360.     margin-bottom: 20px;
  361. }
  362. .order-info p {
  363.     margin-bottom: 8px;
  364. }
  365. .shipping-info {
  366.     margin-bottom: 20px;
  367. }
  368. .shipping-info h3 {
  369.     margin-bottom: 10px;
  370. }
  371. .order-items-table {
  372.     width: 100%;
  373.     border-collapse: collapse;
  374. }
  375. .order-items-table th, .order-items-table td {
  376.     padding: 10px;
  377.     text-align: left;
  378.     border-bottom: 1px solid #dee2e6;
  379. }
  380. .order-items-table th {
  381.     background-color: #f8f9fa;
  382.     font-weight: 600;
  383. }
  384. .confirmation-actions {
  385.     text-align: center;
  386. }
  387. /* Footer */
  388. footer {
  389.     background-color: #333;
  390.     color: #fff;
  391.     padding: 20px 0;
  392.     text-align: center;
  393. }
复制代码

5. Flask高级主题

5.1 蓝图(Blueprints)

蓝图是Flask中组织应用程序的一种方式,允许你将应用程序分割成更小、更可管理的组件。这对于大型应用程序特别有用。

让我们创建一个简单的示例,展示如何使用蓝图:
  1. # app.py
  2. from flask import Flask, render_template
  3. from admin import admin_bp
  4. from blog import blog_bp
  5. app = Flask(__name__)
  6. # 注册蓝图
  7. app.register_blueprint(admin_bp, url_prefix='/admin')
  8. app.register_blueprint(blog_bp)
  9. @app.route('/')
  10. def index():
  11.     return render_template('index.html')
  12. if __name__ == '__main__':
  13.     app.run(debug=True)
复制代码
  1. # admin.py
  2. from flask import Blueprint, render_template
  3. admin_bp = Blueprint('admin', __name__,
  4.                     template_folder='templates',
  5.                     static_folder='static')
  6. @admin_bp.route('/')
  7. def admin_home():
  8.     return render_template('admin/index.html')
  9. @admin_bp.route('/users')
  10. def admin_users():
  11.     return render_template('admin/users.html')
复制代码
  1. # blog.py
  2. from flask import Blueprint, render_template
  3. blog_bp = Blueprint('blog', __name__,
  4.                    template_folder='templates',
  5.                    static_folder='static')
  6. @blog_bp.route('/')
  7. def blog_home():
  8.     return render_template('blog/index.html')
  9. @blog_bp.route('/post/<int:post_id>')
  10. def blog_post(post_id):
  11.     return render_template('blog/post.html', post_id=post_id)
复制代码

5.2 应用工厂模式

应用工厂模式是一种创建Flask应用实例的方法,它允许你在不同的环境中配置应用,并且使测试更容易。
  1. # app/__init__.py
  2. from flask import Flask
  3. from flask_sqlalchemy import SQLAlchemy
  4. from flask_login import LoginManager
  5. from config import Config
  6. db = SQLAlchemy()
  7. login_manager = LoginManager()
  8. def create_app(config_class=Config):
  9.     app = Flask(__name__)
  10.     app.config.from_object(config_class)
  11.    
  12.     # 初始化扩展
  13.     db.init_app(app)
  14.     login_manager.init_app(app)
  15.    
  16.     # 注册蓝图
  17.     from app.main import bp as main_bp
  18.     app.register_blueprint(main_bp)
  19.    
  20.     from app.admin import bp as admin_bp
  21.     app.register_blueprint(admin_bp, url_prefix='/admin')
  22.    
  23.     from app.auth import bp as auth_bp
  24.     app.register_blueprint(auth_bp, url_prefix='/auth')
  25.    
  26.     return app
复制代码
  1. # config.py
  2. import os
  3. basedir = os.path.abspath(os.path.dirname(__file__))
  4. class Config:
  5.     SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
  6.     SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
  7.         'sqlite:///' + os.path.join(basedir, 'app.db')
  8.     SQLALCHEMY_TRACK_MODIFICATIONS = False
复制代码
  1. # run.py
  2. from app import create_app
  3. app = create_app()
  4. if __name__ == '__main__':
  5.     app.run(debug=True)
复制代码

5.3 RESTful API与Flask-RESTful

Flask-RESTful是一个Flask扩展,用于快速构建REST API。首先安装:
  1. pip install flask-restful
复制代码

然后创建一个简单的API:
  1. from flask import Flask
  2. from flask_restful import reqparse, Api, Resource
  3. app = Flask(__name__)
  4. api = Api(app)
  5. TODOS = {
  6.     'todo1': {'task': 'build an API'},
  7.     'todo2': {'task': '?????'},
  8.     'todo3': {'task': 'profit!'},
  9. }
  10. def abort_if_todo_doesnt_exist(todo_id):
  11.     if todo_id not in TODOS:
  12.         api.abort(404, message="Todo {} doesn't exist".format(todo_id))
  13. parser = reqparse.RequestParser()
  14. parser.add_argument('task', type=str)
  15. # Todo
  16. # shows a single todo item and lets you delete a todo item
  17. class Todo(Resource):
  18.     def get(self, todo_id):
  19.         abort_if_todo_doesnt_exist(todo_id)
  20.         return TODOS[todo_id]
  21.     def delete(self, todo_id):
  22.         abort_if_todo_doesnt_exist(todo_id)
  23.         del TODOS[todo_id]
  24.         return '', 204
  25.     def put(self, todo_id):
  26.         abort_if_todo_doesnt_exist(todo_id)
  27.         args = parser.parse_args()
  28.         task = {'task': args['task']}
  29.         TODOS[todo_id] = task
  30.         return task, 201
  31. # TodoList
  32. # shows a list of all todos, and lets you POST to add new tasks
  33. class TodoList(Resource):
  34.     def get(self):
  35.         return TODOS
  36.     def post(self):
  37.         args = parser.parse_args()
  38.         todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
  39.         todo_id = 'todo%i' % todo_id
  40.         TODOS[todo_id] = {'task': args['task']}
  41.         return TODOS[todo_id], 201
  42. ##
  43. ## Actually setup the Api resource routing here
  44. ##
  45. api.add_resource(TodoList, '/todos')
  46. api.add_resource(Todo, '/todos/<todo_id>')
  47. if __name__ == '__main__':
  48.     app.run(debug=True)
复制代码

5.4 WebSocket与Flask-SocketIO

Flask-SocketIO为Flask应用程序添加了对WebSocket的支持。首先安装:
  1. pip install flask-socketio
复制代码

然后创建一个简单的聊天应用:
  1. from flask import Flask, render_template
  2. from flask_socketio import SocketIO, emit
  3. app = Flask(__name__)
  4. app.config['SECRET_KEY'] = 'your-secret-key'
  5. socketio = SocketIO(app)
  6. @app.route('/')
  7. def index():
  8.     return render_template('chat.html')
  9. @socketio.on('connect')
  10. def test_connect():
  11.     emit('my response', {'data': 'Connected'})
  12. @socketio.on('disconnect')
  13. def test_disconnect():
  14.     print('Client disconnected')
  15. @socketio.on('message')
  16. def handle_message(message):
  17.     print('received message: ' + message)
  18.     emit('message', message, broadcast=True)
  19. if __name__ == '__main__':
  20.     socketio.run(app, debug=True)
复制代码

对应的模板templates/chat.html:
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <title>Flask-SocketIO Chat</title>
  5.     <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
  6.     <style>
  7.         body {
  8.             font-family: Arial, sans-serif;
  9.             margin: 0;
  10.             padding: 0;
  11.         }
  12.         #messages {
  13.             list-style-type: none;
  14.             margin: 0;
  15.             padding: 0;
  16.             height: 300px;
  17.             overflow-y: auto;
  18.             border: 1px solid #ddd;
  19.             padding: 10px;
  20.         }
  21.         #messages li {
  22.             padding: 5px 10px;
  23.             margin-bottom: 5px;
  24.             border-radius: 5px;
  25.         }
  26.         #messages li:nth-child(odd) {
  27.             background-color: #f9f9f9;
  28.         }
  29.         #message-form {
  30.             display: flex;
  31.             margin-top: 10px;
  32.         }
  33.         #message-input {
  34.             flex: 1;
  35.             padding: 10px;
  36.             border: 1px solid #ddd;
  37.             border-radius: 5px 0 0 5px;
  38.         }
  39.         #send-button {
  40.             padding: 10px 20px;
  41.             background-color: #007bff;
  42.             color: white;
  43.             border: none;
  44.             border-radius: 0 5px 5px 0;
  45.             cursor: pointer;
  46.         }
  47.         #send-button:hover {
  48.             background-color: #0069d9;
  49.         }
  50.     </style>
  51. </head>
  52. <body>
  53.     <h1>Flask-SocketIO Chat</h1>
  54.     <ul id="messages"></ul>
  55.     <form id="message-form">
  56.         <input id="message-input" autocomplete="off" />
  57.         <button id="send-button">Send</button>
  58.     </form>
  59.     <script>
  60.         document.addEventListener('DOMContentLoaded', () => {
  61.             const socket = io();
  62.             const messages = document.getElementById('messages');
  63.             const messageForm = document.getElementById('message-form');
  64.             const messageInput = document.getElementById('message-input');
  65.             socket.on('connect', () => {
  66.                 console.log('Connected to server');
  67.             });
  68.             socket.on('message', (msg) => {
  69.                 const item = document.createElement('li');
  70.                 item.textContent = msg;
  71.                 messages.appendChild(item);
  72.                 messages.scrollTop = messages.scrollHeight;
  73.             });
  74.             messageForm.addEventListener('submit', (e) => {
  75.                 e.preventDefault();
  76.                 if (messageInput.value) {
  77.                     socket.emit('message', messageInput.value);
  78.                     messageInput.value = '';
  79.                 }
  80.             });
  81.         });
  82.     </script>
  83. </body>
  84. </html>
复制代码

6. Flask应用部署

6.1 使用Gunicorn部署

Gunicorn是一个WSGI HTTP服务器,用于UNIX系统上的Python应用。首先安装:
  1. pip install gunicorn
复制代码

然后运行你的Flask应用:
  1. gunicorn -w 4 -b 127.0.0.1:8000 app:app
复制代码

这会启动4个工作进程,监听在8000端口。

6.2 使用uWSGI部署

uWSGI是另一个流行的WSGI服务器。首先安装:
  1. pip install uwsgi
复制代码

然后创建一个uWSGI配置文件uwsgi.ini:
  1. [uwsgi]
  2. module = app:app
  3. master = true
  4. processes = 4
  5. socket = myapp.sock
  6. chmod-socket = 666
  7. vacuum = true
  8. die-on-term = true
复制代码

然后运行:
  1. uwsgi --ini uwsgi.ini
复制代码

6.3 使用Docker部署

Docker可以创建一个包含你的应用及其所有依赖的容器。首先创建一个Dockerfile:
  1. FROM python:3.9-slim
  2. WORKDIR /app
  3. COPY requirements.txt .
  4. RUN pip install --no-cache-dir -r requirements.txt
  5. COPY . .
  6. EXPOSE 5000
  7. CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"]
复制代码

然后构建并运行Docker容器:
  1. docker build -t my-flask-app .
  2. docker run -p 5000:5000 my-flask-app
复制代码

6.4 使用Nginx作为反向代理

Nginx可以作为反向代理,将请求转发到你的Flask应用。以下是一个基本的Nginx配置:
  1. server {
  2.     listen 80;
  3.     server_name example.com;
  4.     location / {
  5.         proxy_pass http://127.0.0.1:8000;
  6.         proxy_set_header Host $host;
  7.         proxy_set_header X-Real-IP $remote_addr;
  8.         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  9.     }
  10.     location /static {
  11.         alias /path/to/your/static/files;
  12.         expires 30d;
  13.     }
  14. }
复制代码

7. Flask最佳实践

7.1 项目结构

一个好的项目结构可以使你的应用更易于维护和扩展。以下是一个推荐的项目结构:
  1. myproject/
  2.     app/
  3.         __init__.py
  4.         config.py
  5.         extensions.py
  6.         models/
  7.             __init__.py
  8.             user.py
  9.             post.py
  10.         routes/
  11.             __init__.py
  12.             main.py
  13.             auth.py
  14.             api.py
  15.         templates/
  16.             base.html
  17.             index.html
  18.             auth/
  19.                 login.html
  20.                 register.html
  21.         static/
  22.             css/
  23.                 style.css
  24.             js/
  25.                 main.js
  26.         utils/
  27.             __init__.py
  28.             decorators.py
  29.             forms.py
  30.     migrations/
  31.     tests/
  32.         __init__.py
  33.         test_models.py
  34.         test_routes.py
  35.     requirements.txt
  36.     config.py
  37.     run.py
  38.     .env
  39.     .gitignore
复制代码

7.2 配置管理

使用环境变量和配置类来管理不同环境的配置:
  1. # config.py
  2. import os
  3. from dotenv import load_dotenv
  4. basedir = os.path.abspath(os.path.dirname(__file__))
  5. load_dotenv(os.path.join(basedir, '.env'))
  6. class Config:
  7.     SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
  8.     SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
  9.         'sqlite:///' + os.path.join(basedir, 'app.db')
  10.     SQLALCHEMY_TRACK_MODIFICATIONS = False
  11. class DevelopmentConfig(Config):
  12.     DEBUG = True
  13. class ProductionConfig(Config):
  14.     DEBUG = False
  15. class TestingConfig(Config):
  16.     TESTING = True
  17.     SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
  18. config = {
  19.     'development': DevelopmentConfig,
  20.     'production': ProductionConfig,
  21.     'testing': TestingConfig,
  22.     'default': DevelopmentConfig
  23. }
复制代码

7.3 错误处理

使用Flask的错误处理功能来提供友好的错误页面:
  1. @app.errorhandler(404)
  2. def not_found_error(error):
  3.     return render_template('errors/404.html'), 404
  4. @app.errorhandler(500)
  5. def internal_error(error):
  6.     db.session.rollback()
  7.     return render_template('errors/500.html'), 500
复制代码

7.4 日志记录

配置日志记录以便于调试和监控:
  1. import logging
  2. from logging.handlers import RotatingFileHandler
  3. import os
  4. if not app.debug:
  5.     if not os.path.exists('logs'):
  6.         os.mkdir('logs')
  7.     file_handler = RotatingFileHandler('logs/myapp.log', maxBytes=10240, backupCount=10)
  8.     file_handler.setFormatter(logging.Formatter(
  9.         '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
  10.     file_handler.setLevel(logging.INFO)
  11.     app.logger.addHandler(file_handler)
  12.     app.logger.setLevel(logging.INFO)
  13.     app.logger.info('MyApp startup')
复制代码

7.5 安全最佳实践

• 使用HTTPS保护你的应用
• 使用CSRF保护表单
• 验证和净化用户输入
• 使用安全的密码哈希
• 设置适当的CORS策略
• 定期更新依赖项
  1. from flask_wtf.csrf import CSRFProtect
  2. from flask_talisman import Talisman
  3. csrf = CSRFProtect(app)
  4. Talisman(app, force_https=True)
复制代码

8. 总结

Flask是一个强大而灵活的Python Web框架,它提供了构建Web应用所需的基本工具,同时允许你根据需要添加扩展。通过本教程,你已经学习了从基础到高级的Flask开发技能,包括:

• Flask基础概念和路由
• 模板渲染和静态文件处理
• 数据库集成和表单处理
• 用户认证和授权
• RESTful API开发
• 实战项目示例(博客系统和电子商务网站)
• 高级主题(蓝图、应用工厂、WebSocket等)
• 应用部署和最佳实践

现在你已经具备了使用Flask开发各种Web应用的能力。继续探索Flask的丰富生态系统,不断实践和学习,你将成为一名优秀的Flask开发者。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

手机版|联系我们|小黑屋|TG频道|RSS |网站地图

Powered by Pixtech

© 2025-2026 Pixtech Team.

>