第13章 Web开发¶
前言¶
本章将带领读者深入学习Python Web开发的各个方面,从基础的Web开发概念到主流框架的实际应用。我们将学习Flask轻量级框架、Django全栈框架和FastAPI现代框架,掌握数据库操作、前端集成、API设计等核心技能。通过大量的实际代码示例和项目实践,读者将能够独立开发完整的Web应用程序。
13.1 Web开发基础¶
Web应用架构概述¶
Web应用程序采用客户端-服务器架构模式,客户端(通常是浏览器)通过HTTP协议向服务器发送请求,服务器处理请求后返回响应。现代Web应用通常分为三层架构:
- 表示层(Presentation Layer):负责用户界面和用户交互
- 业务逻辑层(Business Logic Layer):处理业务规则和数据处理
- 数据访问层(Data Access Layer):负责数据存储和检索
HTTP协议基础¶
HTTP(HyperText Transfer Protocol)是Web通信的基础协议。让我们通过一个简单的Python脚本来理解HTTP请求和响应:
# http_demo.py
import requests
# 发送GET请求
response = requests.get('https://httpbin.org/get')
print(f"状态码: {response.status_code}")
print(f"响应头: {response.headers}")
print(f"响应内容: {response.json()}")
# 发送POST请求
data = {'name': 'Python学习者', 'skill': 'Web开发'}
response = requests.post('https://httpbin.org/post', json=data)
print(f"POST响应: {response.json()}")
运行上述代码,输出类似如下:
状态码: 200
响应头: {'Date': 'Mon, 15 Jan 2024 10:30:00 GMT', 'Content-Type': 'application/json', 'Content-Length': '312'}
响应内容: {'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.28.1'}, 'origin': '192.168.1.100', 'url': 'https://httpbin.org/get'}
POST响应: {'args': {}, 'data': '{"name": "Python学习者", "skill": "Web开发"}', 'files': {}, 'form': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Content-Length': '52', 'Content-Type': 'application/json', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.28.1'}, 'json': {'name': 'Python学习者', 'skill': 'Web开发'}, 'origin': '192.168.1.100', 'url': 'https://httpbin.org/post'}
Python Web开发生态¶
Python拥有丰富的Web开发生态系统,主要框架对比如下:
| 框架 | 特点 | 适用场景 | 学习难度 |
|---|---|---|---|
| Flask | 轻量级、灵活 | 小型项目、API开发 | 低 |
| Django | 全栈、功能完整 | 大型项目、快速开发 | 中 |
| FastAPI | 现代、高性能 | API开发、微服务 | 中 |
| Tornado | 异步、高并发 | 实时应用 | 高 |
13.2 Flask轻量级框架¶
Flask安装和环境配置¶
首先创建虚拟环境并安装Flask:
# 创建虚拟环境
python -m venv flask_env
# 激活虚拟环境(Windows)
flask_env\Scripts\activate
# 安装Flask
pip install Flask
安装成功后,输出类似如下:
Collecting Flask
Downloading Flask-2.3.3-py3-none-any.whl (96 kB)
Collecting Werkzeug>=2.3.7
Downloading Werkzeug-2.3.7-py3-none-any.whl (242 kB)
Collecting Jinja2>=3.1.2
Downloading Jinja2-3.1.2-py3-none-any.whl (133 kB)
Collecting itsdangerous>=2.1.2
Downloading itsdangerous-2.1.2-py3-none-any.whl (15 kB)
Collecting click>=8.1.3
Downloading click-8.1.7-py3-none-any.whl (97 kB)
Installing collected packages: Werkzeug, Jinja2, itsdangerous, click, Flask
Successfully installed Flask-2.3.3 Jinja2-3.1.2 Werkzeug-2.3.7 click-8.1.7 itsdangerous-2.1.2
第一个Flask应用¶
创建一个简单的Flask应用:
# app.py
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)
@app.route('/')
def home():
return '<h1>欢迎来到Python Web开发世界!</h1>'
@app.route('/user/<name>')
def user_profile(name):
return f'<h2>用户:{name}</h2><p>欢迎访问您的个人主页!</p>'
@app.route('/api/data', methods=['GET', 'POST'])
def api_data():
if request.method == 'POST':
data = request.get_json()
return jsonify({
'message': '数据接收成功',
'received_data': data,
'status': 'success'
})
else:
return jsonify({
'message': 'Hello from Flask API',
'version': '1.0',
'endpoints': ['/api/data']
})
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
运行Flask应用:
python app.py
输出类似如下:
* Serving Flask app 'app'
* Debug mode: on
* Running on http://0.0.0.0:5000 (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 345-678-901
Flask模板系统¶
Flask使用Jinja2模板引擎。首先创建模板目录结构:
flask_project/
├── app.py
├── templates/
│ ├── base.html
│ ├── index.html
│ └── user.html
└── static/
├── css/
│ └── style.css
└── js/
└── main.js
创建基础模板:
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Flask Web应用{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<nav>
<a href="{{ url_for('home') }}">首页</a>
<a href="{{ url_for('user_profile', name='guest') }}">用户</a>
</nav>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2024 Python Web开发教程</p>
</footer>
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
</body>
</html>
创建首页模板:
<!-- templates/index.html -->
{% extends "base.html" %}
{% block title %}首页 - Flask Web应用{% endblock %}
{% block content %}
<div class="hero">
<h1>欢迎来到Flask Web开发</h1>
<p>这是一个完整的Flask应用示例</p>
</div>
{% endblock %}
Flask数据库集成¶
安装Flask-SQLAlchemy:
pip install Flask-SQLAlchemy
创建数据库模型:
# models.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'your-secret-key-here'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
posts = db.relationship('Post', backref='author', lazy=True)
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(200), nullable=False)
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f'<Post {self.title}>'
# 路由和视图函数
@app.route('/')
def index():
posts = Post.query.order_by(Post.created_at.desc()).all()
return render_template('index.html', posts=posts)
@app.route('/user/<username>')
def user_posts(username):
user = User.query.filter_by(username=username).first_or_404()
posts = Post.query.filter_by(author=user).order_by(Post.created_at.desc()).all()
return render_template('user_posts.html', user=user, posts=posts)
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
13.3 Django全栈框架¶
Django安装和项目创建¶
安装Django:
pip install Django
创建Django项目:
django-admin startproject myblog
cd myblog
python manage.py startapp blog
Django模型定义¶
在blog/models.py中定义数据模型:
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
description = models.TextField(blank=True)
class Meta:
verbose_name_plural = "Categories"
def __str__(self):
return self.name
class Post(models.Model):
STATUS_CHOICES = [
('draft', '草稿'),
('published', '已发布'),
]
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog:post_detail', kwargs={'slug': self.slug})
Django视图和URL配置¶
创建视图函数:
# blog/views.py
from django.shortcuts import render, get_object_or_404
from django.core.paginator import Paginator
from .models import Post, Category
def post_list(request):
posts = Post.objects.filter(status='published')
category_id = request.GET.get('category')
if category_id:
posts = posts.filter(category_id=category_id)
paginator = Paginator(posts, 5) # 每页显示5篇文章
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
categories = Category.objects.all()
context = {
'page_obj': page_obj,
'categories': categories,
'current_category': category_id
}
return render(request, 'blog/post_list.html', context)
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug, status='published')
return render(request, 'blog/post_detail.html', {'post': post})
配置URL路由:
# blog/urls.py
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.post_list, name='post_list'),
path('post/<slug:slug>/', views.post_detail, name='post_detail'),
]
数据库迁移¶
执行数据库迁移:
python manage.py makemigrations
python manage.py migrate
创建超级用户:
python manage.py createsuperuser
13.4 FastAPI现代框架¶
FastAPI安装和基础应用¶
安装FastAPI和相关依赖:
pip install fastapi uvicorn python-multipart
创建第一个FastAPI应用:
# main.py
from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
app = FastAPI(
title="Python Web开发API",
description="这是一个完整的FastAPI应用示例",
version="1.0.0"
)
security = HTTPBearer()
# 数据模型
class UserBase(BaseModel):
username: str
email: str
full_name: Optional[str] = None
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
created_at: datetime
class Config:
orm_mode = True
class PostBase(BaseModel):
title: str
content: str
category: Optional[str] = None
class PostCreate(PostBase):
pass
class Post(PostBase):
id: int
author_id: int
created_at: datetime
updated_at: datetime
class Config:
orm_mode = True
# 模拟数据库
fake_users_db = []
fake_posts_db = []
user_id_counter = 1
post_id_counter = 1
# 依赖注入
def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
# 这里应该验证JWT token,简化处理
if credentials.credentials != "valid-token":
raise HTTPException(
status_code=401,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return {"user_id": 1, "username": "testuser"}
# API路由
@app.get("/")
async def root():
return {
"message": "欢迎使用FastAPI",
"version": "1.0.0",
"docs_url": "/docs"
}
@app.post("/users/", response_model=User, status_code=201)
async def create_user(user: UserCreate):
global user_id_counter
new_user = {
"id": user_id_counter,
"username": user.username,
"email": user.email,
"full_name": user.full_name,
"is_active": True,
"created_at": datetime.utcnow()
}
fake_users_db.append(new_user)
user_id_counter += 1
return new_user
@app.get("/users/", response_model=List[User])
async def get_users(skip: int = 0, limit: int = 10):
return fake_users_db[skip : skip + limit]
@app.post("/posts/", response_model=Post, status_code=201)
async def create_post(post: PostCreate, current_user: dict = Depends(get_current_user)):
global post_id_counter
new_post = {
"id": post_id_counter,
"title": post.title,
"content": post.content,
"category": post.category,
"author_id": current_user["user_id"],
"created_at": datetime.utcnow(),
"updated_at": datetime.utcnow()
}
fake_posts_db.append(new_post)
post_id_counter += 1
return new_post
运行FastAPI应用:
uvicorn main:app --reload
13.5 数据库操作¶
SQLite基础操作¶
创建一个完整的数据库操作示例:
```python
sqlite_demo.py¶
import sqlite3
from datetime import datetime
class BlogDatabase:
def init(self, db_name=”blog.db”):
self.db_name = db_name
self.init_database()
def init_database(self):
"""初始化数据库表"""
conn = sqlite3.connect(self.db_name)
cursor = conn.cursor()
# 创建用户表