Files
everything-claude-code/docs/pt-BR/examples/django-api-CLAUDE.md
2026-03-21 14:06:49 +01:00

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 — use logging.getLogger(__name__)
  • f-strings para formatação, nunca % ou .format()
  • Use pathlib.Path e não os.path para 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 --fake em produção
  • Use select_related() e prefetch_related() para prevenir queries N+1
  • Todos os models devem ter auto-fields created_at e updated_at
  • Índices em qualquer campo usado em filter(), order_by() ou cláusulas WHERE
# 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 IsAuthenticated como base e adicione permissões customizadas para acesso por objeto
  • Token blacklisting habilitado para logout

Serializers

  • Use ModelSerializer para CRUD simples, Serializer para 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