第13章 Web开发

前言

本章将带领读者深入学习Python Web开发的各个方面,从基础的Web开发概念到主流框架的实际应用。我们将学习Flask轻量级框架、Django全栈框架和FastAPI现代框架,掌握数据库操作、前端集成、API设计等核心技能。通过大量的实际代码示例和项目实践,读者将能够独立开发完整的Web应用程序。

13.1 Web开发基础

Web应用架构概述

Web应用程序采用客户端-服务器架构模式,客户端(通常是浏览器)通过HTTP协议向服务器发送请求,服务器处理请求后返回响应。现代Web应用通常分为三层架构:

  1. 表示层(Presentation Layer):负责用户界面和用户交互
  2. 业务逻辑层(Business Logic Layer):处理业务规则和数据处理
  3. 数据访问层(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>&copy; 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()

    # 创建用户表
Xiaoye