Files
everything-claude-code/docs/zh-CN/examples/django-api-CLAUDE.md
2026-03-22 15:39:24 -07:00

309 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
**架构:** 采用领域驱动设计每个业务领域对应一个应用。DRF 用于 API 层Celery 用于异步任务pytest 用于测试。所有端点返回 JSON — 无模板渲染。
## 关键规则
### Python 约定
* 所有函数签名使用类型提示 — 使用 `from __future__ import annotations`
* 不使用 `print()` 语句 — 使用 `logging.getLogger(__name__)`
* 字符串格式化使用 f-strings绝不使用 `%``.format()`
* 文件操作使用 `pathlib.Path` 而非 `os.path`
* 导入排序使用 isort标准库、第三方库、本地库由 ruff 强制执行)
### 数据库
* 所有查询使用 Django ORM — 原始 SQL 仅与 `.raw()` 和参数化查询一起使用
* 迁移文件提交到 git — 生产中绝不使用 `--fake`
* 使用 `select_related()``prefetch_related()` 防止 N+1 查询
* 所有模型必须具有 `created_at``updated_at` 自动字段
*`filter()``order_by()``WHERE` 子句中使用的任何字段上建立索引
```python
# BAD: N+1 query
orders = Order.objects.all()
for order in orders:
print(order.customer.name) # hits DB for each order
# GOOD: Single query with 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:
"""Create an order with stock validation and payment hold."""
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"])
# Async: send confirmation email
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
DATABASE_URL=postgres://user:pass@localhost:5432/myapp
# Redis (Celery broker + cache)
REDIS_URL=redis://localhost:6379/0
# JWT
JWT_ACCESS_TOKEN_LIFETIME=15 # minutes
JWT_REFRESH_TOKEN_LIFETIME=10080 # minutes (7 days)
# Email
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
EMAIL_HOST=smtp.example.com
```
## 测试策略
```bash
# Run all tests
pytest --cov=apps --cov-report=term-missing
# Run specific app tests
pytest apps/orders/tests/ -v
# Run with parallel execution
pytest -n auto
# Only failing tests from last run
pytest --lf
```
## ECC 工作流
```bash
# Planning
/plan "Add order refund system with Stripe integration"
# Development with TDD
/tdd # pytest-based TDD workflow
# Review
/python-review # Python-specific code review
/security-scan # Django security audit
/code-review # General quality check
# Verification
/verify # Build, lint, test, security scan
```
## Git 工作流
* `feat:` 新功能,`fix:` 错误修复,`refactor:` 代码变更
* 功能分支从 `main` 创建,需要 PR
* CIruff代码检查 + 格式化、mypy类型检查、pytest测试、safety依赖检查
* 部署Docker 镜像,通过 Kubernetes 或 Railway 管理