mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-19 05:41:14 +08:00
Add remaining files to match zh-CN documentation structure: - hooks/README.md — hooks architecture and customization guide - examples/ — 8 project CLAUDE.md templates (general, user, django, go, harmonyos, laravel, rust, saas-nextjs) - CHANGELOG.md — version history - the-openclaw-guide.md — OpenClaw guide (471 lines) Total: 11 files, 2362 insertions ja-JP now has full parity with zh-CN directory structure.
309 lines
10 KiB
Markdown
309 lines
10 KiB
Markdown
# Django REST API — プロジェクト CLAUDE.md
|
||
|
||
> PostgreSQL と Celery を使用した Django REST Framework API の実世界サンプル。
|
||
> これをプロジェクトのルートにコピーしてサービスに合わせてカスタマイズしてください。
|
||
|
||
## プロジェクト概要
|
||
|
||
**スタック:** Python 3.12+, Django 5.x, Django REST Framework, PostgreSQL, Celery + Redis, pytest, Docker Compose
|
||
|
||
**アーキテクチャ:** ビジネスドメインごとにアプリを持つドメイン駆動設計。APIレイヤーにDRF、非同期タスクにCelery、テストにpytestを使用。すべてのエンドポイントはJSONを返す — テンプレートレンダリングなし。
|
||
|
||
## 重要なルール
|
||
|
||
### Python の規約
|
||
|
||
- すべての関数シグネチャに型ヒントを付ける — `from __future__ import annotations` を使用
|
||
- `print()` 文は使用しない — `logging.getLogger(__name__)` を使用
|
||
- 文字列フォーマットにはf-stringを使用し、`%` や `.format()` は使用しない
|
||
- ファイル操作には `os.path` ではなく `pathlib.Path` を使用
|
||
- isortでインポートをソートする: stdlib、サードパーティ、ローカル(ruffにより強制)
|
||
|
||
### データベース
|
||
|
||
- すべてのクエリはDjango ORMを使用 — 生SQLは `.raw()` とパラメータ化クエリのみ
|
||
- マイグレーションはgitにコミットする — 本番環境では `--fake` を絶対に使用しない
|
||
- N+1クエリを防ぐために `select_related()` と `prefetch_related()` を使用する
|
||
- すべてのモデルには `created_at` と `updated_at` の自動フィールドが必要
|
||
- `filter()`, `order_by()`, または `WHERE` 句で使用されるフィールドにはインデックスを付ける
|
||
|
||
```python
|
||
# 悪い例: N+1クエリ
|
||
orders = Order.objects.all()
|
||
for order in orders:
|
||
print(order.customer.name) # 各注文ごとにDBをヒット
|
||
|
||
# 良い例: JOINによる単一クエリ
|
||
orders = Order.objects.select_related("customer").all()
|
||
```
|
||
|
||
### 認証
|
||
|
||
- `djangorestframework-simplejwt` によるJWT — アクセストークン(15分)+ リフレッシュトークン(7日)
|
||
- すべてのビューにパーミッションクラスを設定 — デフォルトに依存しない
|
||
- `IsAuthenticated` をベースとして使用し、オブジェクトレベルのアクセスにはカスタムパーミッションを追加
|
||
- ログアウト用のトークンブラックリストを有効にする
|
||
|
||
### シリアライザー
|
||
|
||
- シンプルなCRUDには `ModelSerializer` を、複雑なバリデーションには `Serializer` を使用
|
||
- 入出力の形状が異なる場合は読み取りと書き込みのシリアライザーを分ける
|
||
- バリデーションはシリアライザーレベルで行い、ビューでは行わない — ビューは薄くするべき
|
||
|
||
```python
|
||
class CreateOrderSerializer(serializers.Serializer):
|
||
product_id = serializers.UUIDField()
|
||
quantity = serializers.IntegerField(min_value=1, max_value=100)
|
||
|
||
def validate_product_id(self, value):
|
||
if not Product.objects.filter(id=value, active=True).exists():
|
||
raise serializers.ValidationError("Product not found or inactive")
|
||
return value
|
||
|
||
class OrderDetailSerializer(serializers.ModelSerializer):
|
||
customer = CustomerSerializer(read_only=True)
|
||
product = ProductSerializer(read_only=True)
|
||
|
||
class Meta:
|
||
model = Order
|
||
fields = ["id", "customer", "product", "quantity", "total", "status", "created_at"]
|
||
```
|
||
|
||
### エラーハンドリング
|
||
|
||
- 一貫したエラーレスポンスのためにDRF例外ハンドラーを使用する
|
||
- `core/exceptions.py` にビジネスロジック用のカスタム例外を定義する
|
||
- 内部エラーの詳細をクライアントに公開しない
|
||
|
||
```python
|
||
# core/exceptions.py
|
||
from rest_framework.exceptions import APIException
|
||
|
||
class InsufficientStockError(APIException):
|
||
status_code = 409
|
||
default_detail = "Insufficient stock for this order"
|
||
default_code = "insufficient_stock"
|
||
```
|
||
|
||
### コードスタイル
|
||
|
||
- コードやコメントに絵文字を使用しない
|
||
- 最大行長: 120文字(ruffにより強制)
|
||
- クラス: PascalCase、関数/変数: snake_case、定数: UPPER_SNAKE_CASE
|
||
- ビューは薄く — ビジネスロジックはサービス関数またはモデルメソッドに置く
|
||
|
||
## ファイル構成
|
||
|
||
```
|
||
config/
|
||
settings/
|
||
base.py # 共通設定
|
||
local.py # 開発用オーバーライド(DEBUG=True)
|
||
production.py # 本番設定
|
||
urls.py # ルートURL設定
|
||
celery.py # Celeryアプリ設定
|
||
apps/
|
||
accounts/ # ユーザー認証、登録、プロフィール
|
||
models.py
|
||
serializers.py
|
||
views.py
|
||
services.py # ビジネスロジック
|
||
tests/
|
||
test_views.py
|
||
test_services.py
|
||
factories.py # Factory Boy ファクトリー
|
||
orders/ # 注文管理
|
||
models.py
|
||
serializers.py
|
||
views.py
|
||
services.py
|
||
tasks.py # Celeryタスク
|
||
tests/
|
||
products/ # 商品カタログ
|
||
models.py
|
||
serializers.py
|
||
views.py
|
||
tests/
|
||
core/
|
||
exceptions.py # カスタムAPI例外
|
||
permissions.py # 共有パーミッションクラス
|
||
pagination.py # カスタムページネーション
|
||
middleware.py # リクエストロギング、タイミング
|
||
tests/
|
||
```
|
||
|
||
## 主要なパターン
|
||
|
||
### サービスレイヤー
|
||
|
||
```python
|
||
# apps/orders/services.py
|
||
from django.db import transaction
|
||
|
||
def create_order(*, customer, product_id: uuid.UUID, quantity: int) -> Order:
|
||
"""在庫バリデーションと支払い保留付きで注文を作成する。"""
|
||
product = Product.objects.select_for_update().get(id=product_id)
|
||
|
||
if product.stock < quantity:
|
||
raise InsufficientStockError()
|
||
|
||
with transaction.atomic():
|
||
order = Order.objects.create(
|
||
customer=customer,
|
||
product=product,
|
||
quantity=quantity,
|
||
total=product.price * quantity,
|
||
)
|
||
product.stock -= quantity
|
||
product.save(update_fields=["stock", "updated_at"])
|
||
|
||
# 非同期: 確認メールを送信
|
||
send_order_confirmation.delay(order.id)
|
||
return order
|
||
```
|
||
|
||
### ビューパターン
|
||
|
||
```python
|
||
# apps/orders/views.py
|
||
class OrderViewSet(viewsets.ModelViewSet):
|
||
permission_classes = [IsAuthenticated]
|
||
pagination_class = StandardPagination
|
||
|
||
def get_serializer_class(self):
|
||
if self.action == "create":
|
||
return CreateOrderSerializer
|
||
return OrderDetailSerializer
|
||
|
||
def get_queryset(self):
|
||
return (
|
||
Order.objects
|
||
.filter(customer=self.request.user)
|
||
.select_related("product", "customer")
|
||
.order_by("-created_at")
|
||
)
|
||
|
||
def perform_create(self, serializer):
|
||
order = create_order(
|
||
customer=self.request.user,
|
||
product_id=serializer.validated_data["product_id"],
|
||
quantity=serializer.validated_data["quantity"],
|
||
)
|
||
serializer.instance = order
|
||
```
|
||
|
||
### テストパターン(pytest + Factory Boy)
|
||
|
||
```python
|
||
# apps/orders/tests/factories.py
|
||
import factory
|
||
from apps.accounts.tests.factories import UserFactory
|
||
from apps.products.tests.factories import ProductFactory
|
||
|
||
class OrderFactory(factory.django.DjangoModelFactory):
|
||
class Meta:
|
||
model = "orders.Order"
|
||
|
||
customer = factory.SubFactory(UserFactory)
|
||
product = factory.SubFactory(ProductFactory, stock=100)
|
||
quantity = 1
|
||
total = factory.LazyAttribute(lambda o: o.product.price * o.quantity)
|
||
|
||
# apps/orders/tests/test_views.py
|
||
import pytest
|
||
from rest_framework.test import APIClient
|
||
|
||
@pytest.mark.django_db
|
||
class TestCreateOrder:
|
||
def setup_method(self):
|
||
self.client = APIClient()
|
||
self.user = UserFactory()
|
||
self.client.force_authenticate(self.user)
|
||
|
||
def test_create_order_success(self):
|
||
product = ProductFactory(price=29_99, stock=10)
|
||
response = self.client.post("/api/orders/", {
|
||
"product_id": str(product.id),
|
||
"quantity": 2,
|
||
})
|
||
assert response.status_code == 201
|
||
assert response.data["total"] == 59_98
|
||
|
||
def test_create_order_insufficient_stock(self):
|
||
product = ProductFactory(stock=0)
|
||
response = self.client.post("/api/orders/", {
|
||
"product_id": str(product.id),
|
||
"quantity": 1,
|
||
})
|
||
assert response.status_code == 409
|
||
|
||
def test_create_order_unauthenticated(self):
|
||
self.client.force_authenticate(None)
|
||
response = self.client.post("/api/orders/", {})
|
||
assert response.status_code == 401
|
||
```
|
||
|
||
## 環境変数
|
||
|
||
```bash
|
||
# Django
|
||
SECRET_KEY=
|
||
DEBUG=False
|
||
ALLOWED_HOSTS=api.example.com
|
||
|
||
# データベース
|
||
DATABASE_URL=postgres://user:pass@localhost:5432/myapp
|
||
|
||
# Redis(Celeryブローカー + キャッシュ)
|
||
REDIS_URL=redis://localhost:6379/0
|
||
|
||
# JWT
|
||
JWT_ACCESS_TOKEN_LIFETIME=15 # 分
|
||
JWT_REFRESH_TOKEN_LIFETIME=10080 # 分(7日)
|
||
|
||
# メール
|
||
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
|
||
EMAIL_HOST=smtp.example.com
|
||
```
|
||
|
||
## テスト戦略
|
||
|
||
```bash
|
||
# すべてのテストを実行
|
||
pytest --cov=apps --cov-report=term-missing
|
||
|
||
# 特定のアプリのテストを実行
|
||
pytest apps/orders/tests/ -v
|
||
|
||
# 並列実行で実行
|
||
pytest -n auto
|
||
|
||
# 前回の失敗したテストのみ
|
||
pytest --lf
|
||
```
|
||
|
||
## ECCワークフロー
|
||
|
||
```bash
|
||
# 計画
|
||
/plan "Add order refund system with Stripe integration"
|
||
|
||
# TDDによる開発
|
||
/tdd # pytest ベースのTDDワークフロー
|
||
|
||
# レビュー
|
||
/python-review # Python固有のコードレビュー
|
||
/security-scan # Djangoセキュリティ監査
|
||
/code-review # 全般的な品質チェック
|
||
|
||
# 検証
|
||
/verify # ビルド、リント、テスト、セキュリティスキャン
|
||
```
|
||
|
||
## Git ワークフロー
|
||
|
||
- `feat:` 新機能、`fix:` バグ修正、`refactor:` コード変更
|
||
- `main` からフィーチャーブランチを切り、PRが必要
|
||
- CI: ruff(リント + フォーマット)、mypy(型)、pytest(テスト)、safety(依存関係チェック)
|
||
- デプロイ: DockerイメージをKubernetesまたはRailway経由で管理
|