Python Web制作完全攻略【Flask・Django・FastAPI徹底マスター】

 

Python は Web 開発において非常に人気の高いプログラミング言語です。Flask、Django、FastAPI などの優秀なフレームワークにより、シンプルなWebサイトから大規模なWebアプリケーションまで効率的に開発できます。本記事では、Python での Web制作を基本から応用まで、実用的なサンプルコードとともに徹底解説します。

Python Web開発の特徴

Python Web開発の利点

  • 学習コストが低い: 読みやすい構文
  • 豊富なライブラリ: 機械学習、データ分析との連携
  • 高い生産性: 少ないコードで機能実装
  • スケーラビリティ: 小規模から大規模まで対応
  • 活発なコミュニティ: 豊富な情報とサポート

主要なWebフレームワーク

フレームワーク特徴適用場面学習難易度
Flask軽量・シンプル小〜中規模、API
Django高機能・多機能大規模、CMS中〜高
FastAPI高速・型安全API、マイクロサービス
Tornado非同期・高性能リアルタイム通信

環境構築

基本的な環境セットアップ

# 仮想環境の作成
python -m venv web_project
source web_project/bin/activate  # Windows: web_project\Scripts\activate

# 基本パッケージのインストール
pip install flask django fastapi uvicorn requests

requirements.txtの作成

Flask==2.3.3
Django==4.2.7
FastAPI==0.104.1
uvicorn==0.24.0
requests==2.31.0

Flask入門

最小のFlaskアプリ

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return '<h1>Hello, Flask!</h1>'

@app.route('/user/<name>')
def user(name):
    return f'<h1>Hello, {name}!</h1>'

if __name__ == '__main__':
    app.run(debug=True)

HTMLテンプレート

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    users = ['Alice', 'Bob', 'Charlie']
    return render_template('index.html', users=users)

@app.route('/about')
def about():
    return render_template('about.html', title='About Us')

if __name__ == '__main__':
    app.run(debug=True)

フォーム処理

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

@app.route('/contact', methods=['GET', 'POST'])
def contact():
    if request.method == 'POST':
        name = request.form['name']
        email = request.form['email']
        message = request.form['message']
        
        # データ処理(保存、メール送信など)
        print(f"お問い合わせ: {name} ({email}) - {message}")
        return redirect(url_for('thanks'))
    
    return render_template('contact.html')

@app.route('/thanks')
def thanks():
    return '<h1>お問い合わせありがとうございました!</h1>'

if __name__ == '__main__':
    app.run(debug=True)

Django入門

Djangoプロジェクトの作成

# プロジェクト作成
django-admin startproject mysite
cd mysite

# アプリケーション作成
python manage.py startapp blog

# データベース初期化
python manage.py migrate

# 開発サーバー起動
python manage.py runserver

基本的なDjangoビュー

# blog/views.py
from django.shortcuts import render
from django.http import HttpResponse
import datetime

def index(request):
    return HttpResponse('<h1>Welcome to Django!</h1>')

def current_time(request):
    now = datetime.datetime.now()
    return HttpResponse(f'<h1>現在時刻: {now}</h1>')

def user_profile(request, user_id):
    return HttpResponse(f'<h1>ユーザーID: {user_id}</h1>')

DjangoのURL設定

# blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('time/', views.current_time, name='time'),
    path('user/<int:user_id>/', views.user_profile, name='profile'),
]

# mysite/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')),
]

Djangoモデル

# blog/models.py
from django.db import models
from django.utils import timezone

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    created_at = models.DateTimeField(default=timezone.now)
    published = models.BooleanField(default=False)
    
    def __str__(self):
        return self.title
    
    class Meta:
        ordering = ['-created_at']

# マイグレーション実行
# python manage.py makemigrations
# python manage.py migrate

FastAPI入門

最小のFastAPIアプリ

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class User(BaseModel):
    name: str
    age: int
    email: str

@app.get("/")
def read_root():
    return {"message": "Hello, FastAPI!"}

@app.get("/users/{user_id}")
def read_user(user_id: int):
    return {"user_id": user_id, "name": f"User {user_id}"}

@app.post("/users/")
def create_user(user: User):
    return {"message": f"User {user.name} created successfully"}

# 実行: uvicorn main:app --reload

FastAPIでのデータベース連携

from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session

# データベース設定
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    email = Column(String, unique=True, index=True)

Base.metadata.create_all(bind=engine)

app = FastAPI()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/users/{user_id}")
def read_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    return user

RESTful API開発

FlaskでシンプルなREST API

from flask import Flask, jsonify, request

app = Flask(__name__)

# ダミーデータ
users = [
    {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'},
    {'id': 2, 'name': 'Bob', 'email': 'bob@example.com'}
]

@app.route('/api/users', methods=['GET'])
def get_users():
    return jsonify(users)

@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = next((u for u in users if u['id'] == user_id), None)
    return jsonify(user) if user else ('Not Found', 404)

@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.json
    new_user = {
        'id': max(u['id'] for u in users) + 1,
        'name': data['name'],
        'email': data['email']
    }
    users.append(new_user)
    return jsonify(new_user), 201

if __name__ == '__main__':
    app.run(debug=True)

FastAPIでの高度なAPI

from fastapi import FastAPI, HTTPException, Depends, status
from pydantic import BaseModel, EmailStr
from typing import List, Optional
import jwt
from datetime import datetime, timedelta

app = FastAPI(title="Advanced API", version="1.0.0")

class UserCreate(BaseModel):
    name: str
    email: EmailStr
    password: str

class UserResponse(BaseModel):
    id: int
    name: str
    email: str
    created_at: datetime

# ダミーデータベース
fake_db = []

@app.post("/users/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate):
    new_user = {
        "id": len(fake_db) + 1,
        "name": user.name,
        "email": user.email,
        "created_at": datetime.now()
    }
    fake_db.append(new_user)
    return new_user

@app.get("/users/", response_model=List[UserResponse])
def list_users(skip: int = 0, limit: int = 10):
    return fake_db[skip : skip + limit]

@app.get("/users/{user_id}", response_model=UserResponse)
def get_user(user_id: int):
    user = next((u for u in fake_db if u["id"] == user_id), None)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

データベース連携

FlaskとSQLAlchemy

from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SECRET_KEY'] = 'your-secret-key'
db = SQLAlchemy(app)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)
    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())

@app.route('/')
def index():
    posts = Post.query.order_by(Post.created_at.desc()).all()
    return render_template('index.html', posts=posts)

@app.route('/create', methods=['GET', 'POST'])
def create_post():
    if request.method == 'POST':
        post = Post(
            title=request.form['title'],
            content=request.form['content']
        )
        db.session.add(post)
        db.session.commit()
        return redirect(url_for('index'))
    
    return render_template('create.html')

with app.app_context():
    db.create_all()

if __name__ == '__main__':
    app.run(debug=True)

DjangoのORM

# blog/models.py
from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

# blog/views.py
from django.shortcuts import render, get_object_or_404
from .models import Post, Category

def post_list(request):
    posts = Post.objects.select_related('category').order_by('-created_at')
    return render(request, 'blog/list.html', {'posts': posts})

def post_detail(request, post_id):
    post = get_object_or_404(Post, id=post_id)
    return render(request, 'blog/detail.html', {'post': post})

認証とセキュリティ

Flask-Loginでの認証

from flask import Flask, render_template, request, redirect, url_for, flash
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required
from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)
app.secret_key = 'your-secret-key'

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'

class User(UserMixin):
    def __init__(self, id, username, password_hash):
        self.id = id
        self.username = username
        self.password_hash = password_hash

# ダミーユーザーデータ
users = {
    'admin': User('1', 'admin', generate_password_hash('password'))
}

@login_manager.user_loader
def load_user(user_id):
    return next((u for u in users.values() if u.id == user_id), None)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        
        user = users.get(username)
        if user and check_password_hash(user.password_hash, password):
            login_user(user)
            return redirect(url_for('dashboard'))
        else:
            flash('ログイン失敗')
    
    return render_template('login.html')

@app.route('/dashboard')
@login_required
def dashboard():
    return '<h1>ダッシュボード(ログイン済み)</h1>'

@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))

FastAPIでのJWT認証

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel
import jwt
from datetime import datetime, timedelta
from passlib.context import CryptContext

app = FastAPI()
security = HTTPBearer()
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"

class UserLogin(BaseModel):
    username: str
    password: str

class Token(BaseModel):
    access_token: str
    token_type: str

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(hours=24)
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
    try:
        payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=[ALGORITHM])
        username = payload.get("sub")
        if username is None:
            raise HTTPException(status_code=401, detail="Invalid token")
        return username
    except jwt.PyJWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.post("/login", response_model=Token)
def login(user: UserLogin):
    # ダミー認証(実際はデータベースで確認)
    if user.username == "admin" and user.password == "password":
        access_token = create_access_token(data={"sub": user.username})
        return {"access_token": access_token, "token_type": "bearer"}
    raise HTTPException(status_code=401, detail="Incorrect credentials")

@app.get("/protected")
def protected_route(username: str = Depends(verify_token)):
    return {"message": f"Hello, {username}! This is a protected route."}

フロントエンド連携

Flask + Ajax

from flask import Flask, render_template, request, jsonify

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('ajax_demo.html')

@app.route('/api/search', methods=['POST'])
def search():
    query = request.json.get('query', '')
    # 検索処理(ダミー)
    results = [f"結果{i}: {query}" for i in range(1, 4)]
    return jsonify({'results': results})

@app.route('/api/users/<int:user_id>')
def get_user_api(user_id):
    # ダミーユーザーデータ
    user = {'id': user_id, 'name': f'User {user_id}', 'email': f'user{user_id}@example.com'}
    return jsonify(user)

if __name__ == '__main__':
    app.run(debug=True)

FastAPIでのCORS設定

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

app = FastAPI()

# CORS設定
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],  # React開発サーバーなど
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

class Item(BaseModel):
    name: str
    price: float

@app.get("/api/items")
def get_items():
    return [
        {"id": 1, "name": "商品A", "price": 1000},
        {"id": 2, "name": "商品B", "price": 2000}
    ]

@app.post("/api/items")
def create_item(item: Item):
    return {"message": f"商品 '{item.name}' を作成しました", "item": item}

デプロイメント

Herokuへのデプロイ

# Procfile
web: gunicorn app:app

# requirements.txt
Flask==2.3.3
gunicorn==21.2.0

# runtime.txt
python-3.11.6
# app.py (Heroku対応)
import os
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return '<h1>Hello, Heroku!</h1>'

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 5000))
    app.run(host='0.0.0.0', port=port)

Dockerでのコンテナ化

# Dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/mydb
    depends_on:
      - db
  
  db:
    image: postgres:13
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

パフォーマンス最適化

キャッシング

from flask import Flask
from flask_caching import Cache

app = Flask(__name__)
app.config['CACHE_TYPE'] = 'simple'
cache = Cache(app)

@app.route('/expensive-operation')
@cache.cached(timeout=300)  # 5分間キャッシュ
def expensive_operation():
    # 重い処理のシミュレーション
    import time
    time.sleep(2)
    return {'result': 'heavy computation completed'}

@app.route('/user/<int:user_id>')
@cache.cached(timeout=60, key_prefix='user')
def get_user(user_id):
    # データベースクエリのシミュレーション
    return {'user_id': user_id, 'name': f'User {user_id}'}

if __name__ == '__main__':
    app.run(debug=True)

非同期処理とWebSocket

from fastapi import FastAPI, WebSocket
import asyncio
import json

app = FastAPI()

class ConnectionManager:
    def __init__(self):
        self.active_connections: list[WebSocket] = []
    
    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)
    
    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)
    
    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

manager = ConnectionManager()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.broadcast(f"ブロードキャスト: {data}")
    except Exception:
        manager.disconnect(websocket)

@app.get("/")
def index():
    return {"message": "WebSocket server running"}

テストとデバッグ

Flaskアプリのテスト

import pytest
from app import app

@pytest.fixture
def client():
    app.config['TESTING'] = True
    with app.test_client() as client:
        yield client

def test_index(client):
    response = client.get('/')
    assert response.status_code == 200
    assert b'Hello' in response.data

def test_api_users(client):
    response = client.get('/api/users')
    assert response.status_code == 200
    data = response.get_json()
    assert isinstance(data, list)

def test_create_user(client):
    response = client.post('/api/users', 
                          json={'name': 'Test User', 'email': 'test@example.com'})
    assert response.status_code == 201

FastAPIのテスト

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_read_main():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Hello, FastAPI!"}

def test_create_user():
    response = client.post("/users/", 
                          json={"name": "Test", "age": 25, "email": "test@example.com"})
    assert response.status_code == 200
    assert response.json()["message"] == "User Test created successfully"

まとめ

Python での Web 制作は、用途に応じて適切なフレームワークを選択することで効率的な開発が可能です。各フレームワークの特徴を理解し、プロジェクトの要件に最適な技術選択を行うことが重要です。

フレームワーク選択の指針:

  • Flask: 小規模、カスタマイズ性重視、学習目的
  • Django: 大規模、短期開発、CMS・管理画面
  • FastAPI: API開発、高性能、型安全性

重要な技術要素:

  • ルーティング: URLとビューの関連付け
  • テンプレート: HTMLの動的生成
  • データベース: ORM を使った効率的なデータ操作
  • 認証・セキュリティ: ユーザー管理と保護
  • API設計: RESTful または GraphQL
  • デプロイ: クラウドサービスやコンテナ技術

ベストプラクティス:

  • 仮想環境でのパッケージ管理
  • 環境変数での設定管理
  • 適切なエラーハンドリング
  • テストコードの作成
  • セキュリティ対策の実装
  • パフォーマンス最適化

本記事のサンプルコードを参考に、あなたのプロジェクトに最適な Web アプリケーションを開発してください。継続的な学習により、より高度で実用的な Web システムを構築できます。

参考文献

  • Flask公式ドキュメント
  • Django公式ドキュメント
  • FastAPI公式ドキュメント
  • Python Web Development with Flask
  • Two Scoops of Django

 

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座