mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 21:53:28 +08:00
9.0 KiB
9.0 KiB
Django REST API — CLAUDE.md de Projeto
Exemplo real para uma API Django REST Framework com PostgreSQL e Celery. Copie para a raiz do seu projeto e customize para seu serviço.
Visão Geral do Projeto
Stack: Python 3.12+, Django 5.x, Django REST Framework, PostgreSQL, Celery + Redis, pytest, Docker Compose
Arquitetura: Design orientado a domínio com apps por domínio de negócio. DRF para camada de API, Celery para tarefas assíncronas, pytest para testes. Todos os endpoints retornam JSON — sem renderização de templates.
Regras Críticas
Convenções Python
- Type hints em todas as assinaturas de função — use
from __future__ import annotations - Sem
print()statements — uselogging.getLogger(__name__) - f-strings para formatação, nunca
%ou.format() - Use
pathlib.Pathe nãoos.pathpara operações de arquivo - Imports ordenados com isort: stdlib, third-party, local (enforced by ruff)
Banco de Dados
- Todas as queries usam Django ORM — SQL bruto só com
.raw()e queries parametrizadas - Migrations versionadas no git — nunca use
--fakeem produção - Use
select_related()eprefetch_related()para prevenir queries N+1 - Todos os models devem ter auto-fields
created_ateupdated_at - Índices em qualquer campo usado em
filter(),order_by()ou cláusulasWHERE
# 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()
Autenticação
- JWT via
djangorestframework-simplejwt— access token (15 min) + refresh token (7 days) - Permission classes em toda view — nunca confiar no padrão
- Use
IsAuthenticatedcomo base e adicione permissões customizadas para acesso por objeto - Token blacklisting habilitado para logout
Serializers
- Use
ModelSerializerpara CRUD simples,Serializerpara validação complexa - Separe serializers de leitura e escrita quando input/output diferirem
- Valide no nível de serializer, não na view — views devem ser enxutas
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"]
Tratamento de Erro
- Use DRF exception handler para respostas de erro consistentes
- Exceções customizadas de regra de negócio em
core/exceptions.py - Nunca exponha detalhes internos de erro para clientes
# 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"
Estilo de Código
- Sem emojis em código ou comentários
- Tamanho máximo de linha: 120 caracteres (enforced by ruff)
- Classes: PascalCase, funções/variáveis: snake_case, constantes: UPPER_SNAKE_CASE
- Views enxutas — lógica de negócio em funções de serviço ou métodos do model
Estrutura de Arquivos
config/
settings/
base.py # Shared settings
local.py # Dev overrides (DEBUG=True)
production.py # Production settings
urls.py # Root URL config
celery.py # Celery app configuration
apps/
accounts/ # User auth, registration, profile
models.py
serializers.py
views.py
services.py # Business logic
tests/
test_views.py
test_services.py
factories.py # Factory Boy factories
orders/ # Order management
models.py
serializers.py
views.py
services.py
tasks.py # Celery tasks
tests/
products/ # Product catalog
models.py
serializers.py
views.py
tests/
core/
exceptions.py # Custom API exceptions
permissions.py # Shared permission classes
pagination.py # Custom pagination
middleware.py # Request logging, timing
tests/
Padrões-Chave
Camada de Serviço
# 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
Padrão de View
# 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
Padrão de Teste (pytest + Factory Boy)
# 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
Variáveis de Ambiente
# 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
Estratégia de Teste
# 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
Workflow ECC
# 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
Fluxo Git
feat:novas features,fix:correções de bug,refactor:mudanças de código- Branches de feature a partir da
main, PRs obrigatórios - CI: ruff (lint + format), mypy (types), pytest (tests), safety (dep check)
- Deploy: imagem Docker, gerenciada via Kubernetes ou Railway