Metadata-Version: 2.4
Name: maquinaweb-shared-auth
Version: 0.2.8
Summary: Models read-only para autenticação compartilhada entre projetos Django.
Author-email: Seu Nome <seuemail@dominio.com>
License: MIT
Project-URL: Homepage, https://github.com/maquinaweb/maquinaweb-shared-auth
Project-URL: Repository, https://github.com/maquinaweb/maquinaweb-shared-auth
Project-URL: Issues, https://github.com/maquinaweb/maquinaweb-shared-auth/issues
Keywords: django,auth,models,shared
Classifier: Framework :: Django
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: django>=5
Requires-Dist: djangorestframework>=3
Requires-Dist: setuptools>=70
Dynamic: requires-python

# 📚 Guia Completo - Biblioteca Compartilhada de Autenticação

## 🎯 O que Esta Solução Faz?

Permite que **múltiplos sistemas Django** acessem os dados de **autenticação e organizações** diretamente do banco de dados, sem fazer requisições HTTP, mantendo a mesma interface do Django ORM que você conhece.

### Vantagens

✅ **Acesso direto ao banco** - Sem latência de API  
✅ **Interface Django nativa** - `rascunho.user` funciona igual ao Django normal  
✅ **Read-only** - Impossível modificar dados por engano  
✅ **Sem duplicação** - Um único banco de autenticação para todos os sistemas  
✅ **Type-safe** - Validações e exceções customizadas  

---

## 🏗️ Arquitetura

```
┌─────────────────────────────────────────┐
│   Sistema de Autenticação (Principal)  │
│                                         │
│  ┌──────────────┐    ┌──────────────┐ │
│  │ Organization │    │     User     │ │
│  └──────────────┘    └──────────────┘ │
│           │                   │         │
│           └─────────┬─────────┘         │
│                     │                   │
│              ┌──────▼──────┐           │
│              │   Member    │           │
│              └─────────────┘           │
└──────────────────┬──────────────────────┘
                   │
      ┌────────────┴────────────┐
      │  Banco de Dados Auth    │
      │  (PostgreSQL/MySQL)     │
      └────────────┬────────────┘
                   │
      ┌────────────┴────────────┐
      │                         │
┌─────▼─────┐            ┌─────▼─────┐
│ Sistema A │            │ Sistema B │
│           │            │           │
│ Rascunho  │            │ Pedido    │
│  ├─ org   │            │  ├─ org   │
│  └─ user  │            │  └─ user  │
└───────────┘            └───────────┘
```

---

## 📦 Passo 1: Criar a Biblioteca

### 1.1. Estrutura de Diretórios

```bash
mkdir shared-auth-lib
cd shared-auth-lib

# Criar estrutura
mkdir shared_auth
touch setup.py README.md
touch shared_auth/__init__.py
touch shared_auth/models.py
touch shared_auth/managers.py
touch shared_auth/exceptions.py
```

### 1.2. Copiar o Código

Copie os arquivos do artifact anterior para a estrutura criada.

### 1.3. Instalar Localmente

```bash
# Modo desenvolvimento (changes refletem automaticamente)
pip install -e /path/to/shared-auth-lib

# Ou adicionar ao requirements.txt
echo "-e /path/to/shared-auth-lib" >> requirements.txt
```

---

## 🔧 Passo 2: Configurar Banco de Dados

### 2.1. Criar Usuário Read-Only no PostgreSQL

```sql
-- No banco do sistema de autenticação
CREATE USER readonly_user WITH PASSWORD 'senha_segura';

-- Conceder permissões de leitura
GRANT CONNECT ON DATABASE sistema_auth_db TO readonly_user;
GRANT USAGE ON SCHEMA public TO readonly_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_user;

-- Para tabelas futuras
ALTER DEFAULT PRIVILEGES IN SCHEMA public 
GRANT SELECT ON TABLES TO readonly_user;

-- Garantir read-only
ALTER USER readonly_user SET default_transaction_read_only = on;
```

### 2.2. Para MySQL

```sql
CREATE USER 'readonly_user'@'%' IDENTIFIED BY 'senha_segura';
GRANT SELECT ON sistema_auth_db.* TO 'readonly_user'@'%';
FLUSH PRIVILEGES;
```

---

## ⚙️ Passo 3: Configurar Sistema Cliente

### 3.1. Settings.py

```python
# settings.py do seu outro sistema

DATABASES = {
    'default': {
        # Banco do sistema atual
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'meu_sistema_db',
        'USER': 'meu_user',
        'PASSWORD': 'senha',
        'HOST': 'localhost',
        'PORT': '5432',
    },
    'auth_db': {
        # Banco do sistema de autenticação (READ-ONLY)
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'sistema_auth_db',
        'USER': 'readonly_user',
        'PASSWORD': 'senha_readonly',
        'HOST': 'localhost',  # ou IP do servidor de auth
        'PORT': '5432',
        'OPTIONS': {
            'options': '-c default_transaction_read_only=on'
        }
    }
}

# Router para direcionar queries
DATABASE_ROUTERS = ['myapp.routers.SharedAuthRouter']

INSTALLED_APPS = [
    # ... suas apps
    'shared_auth',  # Adicionar biblioteca
]
```

### 3.2. Database Router

```python
# myapp/routers.py

class SharedAuthRouter:
    """
    Direciona queries dos models compartilhados para o banco correto
    """
    
    route_app_labels = {'shared_auth'}
    
    def db_for_read(self, model, **hints):
        """Direciona leituras para auth_db"""
        if model._meta.app_label in self.route_app_labels:
            return 'auth_db'
        return None
    
    def db_for_write(self, model, **hints):
        """Bloqueia escritas"""
        if model._meta.app_label in self.route_app_labels:
            return None  # Impede qualquer escrita
        return None
    
    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """Bloqueia migrations"""
        if app_label in self.route_app_labels:
            return False
        return None
```

---

## 💻 Passo 4: Usar nos Seus Models

### 4.1. Model Básico

```python
# myapp/models.py
from django.db import models

class Rascunho(models.Model):
    titulo = models.CharField(max_length=200)
    conteudo = models.TextField()
    
    # IDs de referência
    organization_id = models.IntegerField()
    user_id = models.IntegerField()
    
    created_at = models.DateTimeField(auto_now_add=True)
    
    @property
    def organization(self):
        """Acessa organização do banco de auth"""
        from shared_auth.models import SharedOrganization
        return SharedOrganization.objects.using('auth_db').get_or_fail(
            self.organization_id
```
# 🚀 Guia Prático - Mixins e Serializers Compartilhados

```python
# models.py - UMA LINHA!
from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
from shared_auth.managers import BaseAuthManager

class Rascunho(OrganizationUserMixin, TimestampedMixin):
    titulo = models.CharField(max_length=200)
    conteudo = models.TextField()
    
    objects = BaseAuthManager()
    
# Pronto! Já tem tudo:
# - organization_id, user_id
# - properties: organization, user
# - métodos: validate_user_belongs_to_organization()
# - created_at, updated_at


# serializers.py - UMA LINHA!
from shared_auth.serializers import OrganizationUserSerializerMixin

class RascunhoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
    class Meta:
        model = Rascunho
        fields = [
            'id', 'titulo', 'conteudo',
            'organization_name', 'organization_email',  # Já disponíveis!
            'user_email', 'user_full_name',  # Já disponíveis!
        ]
    
# Pronto! Todos os campos já funcionam!
```

---

## 📝 Exemplos de Uso por Cenário

### 1. Model que Pertence APENAS a Organização

```python
# Exemplo: Configuração da empresa
from shared_auth.mixins import OrganizationMixin

class EmpresaConfig(OrganizationMixin):
    """Configurações específicas da organização"""
    
    tema_cor = models.CharField(max_length=7, default='#3490dc')
    logo = models.ImageField(upload_to='logos/')
    timezone = models.CharField(max_length=50, default='America/Sao_Paulo')
    
    objects = BaseAuthManager()
    
    def __str__(self):
        return f"Config de {self.organization.name}"

# Uso
config = EmpresaConfig.objects.create(
    organization_id=123,
    tema_cor='#ff0000'
)

print(config.organization.name)  # Funciona!
print(config.organization_is_active())  # Método do mixin
```

### 2. Model que Pertence APENAS a Usuário

```python
# Exemplo: Preferências do usuário
from shared_auth.mixins import UserMixin

class UserPreferences(UserMixin):
    """Preferências pessoais do usuário"""
    
    theme = models.CharField(max_length=20, default='light')
    notifications_enabled = models.BooleanField(default=True)
    language = models.CharField(max_length=5, default='pt-BR')
    
    objects = BaseAuthManager()

# Uso
prefs = UserPreferences.objects.create(
    user_id=456,
    theme='dark'
)

print(prefs.user.email)  # Funciona!
print(prefs.user_full_name)  # Property do mixin
```

### 3. Model com Organização E Usuário (mais comum)

```python
# Exemplo: Pedido, Rascunho, Tarefa, etc
from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin

class Pedido(OrganizationUserMixin, TimestampedMixin):
    """Pedido pertence a organização e foi criado por usuário"""
    
    numero = models.CharField(max_length=20, unique=True)
    valor_total = models.DecimalField(max_digits=10, decimal_places=2)
    status = models.CharField(max_length=20, default='pending')
    
    objects = BaseAuthManager()
    
    def __str__(self):
        return f"Pedido {self.numero}"

# Uso
pedido = Pedido.objects.create(
    organization_id=123,
    user_id=456,
    numero='PED-001',
    valor_total=100.00
)

# Acessos automáticos
print(pedido.organization.name)  # Organização
print(pedido.user.email)  # Usuário que criou
print(pedido.user_full_name)  # Nome completo

# Validação automática
if pedido.validate_user_belongs_to_organization():
    print("OK - Usuário pertence à organização")

# Verificar se outro usuário pode acessar
if pedido.user_can_access(outro_user_id):
    print("Pode acessar")
```

---

## 🎨 Serializers - Casos de Uso

### Caso 1: Serializer Básico

```python
from shared_auth.serializers import OrganizationUserSerializerMixin

class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
    class Meta:
        model = Pedido
        fields = [
            'id', 'numero', 'valor_total', 'status',
            # Campos automáticos do mixin:
            'organization_name',
            'organization_cnpj',
            'organization_email',
            'user_email',
            'user_full_name',
            'created_at', 'updated_at',
        ]

# Response JSON automática:
{
    "id": 1,
    "numero": "PED-001",
    "valor_total": "100.00",
    "status": "pending",
    "organization_name": "Empresa XYZ",
    "organization_cnpj": "12.345.678/0001-90",
    "organization_email": "contato@xyz.com",
    "user_email": "joao@xyz.com",
    "user_full_name": "João Silva",
    "created_at": "2025-10-01T10:00:00Z",
    "updated_at": "2025-10-01T10:00:00Z"
}
```

### Caso 2: Serializer com Fields Customizados

```python
from shared_auth.serializers import OrganizationUserSerializerMixin
from shared_auth.fields import OrganizationField, UserField

class PedidoDetailSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
    # Fields customizados que retornam objetos completos
    organization_full = OrganizationField(source='*')
    user_full = UserField(source='*')
    
    class Meta:
        model = Pedido
        fields = [
            'id', 'numero', 'valor_total',
            'organization_full',  # Objeto completo
            'user_full',  # Objeto completo
        ]

# Response JSON:
{
    "id": 1,
    "numero": "PED-001",
    "valor_total": "100.00",
    "organization_full": {
        "id": 123,
        "name": "Empresa XYZ",
        "fantasy_name": "XYZ Ltda",
        "cnpj": "12.345.678/0001-90",
        "email": "contato@xyz.com",
        "is_active": true
    },
    "user_full": {
        "id": 456,
        "username": "joao",
        "email": "joao@xyz.com",
        "full_name": "João Silva",
        "is_active": true
    }
}
```

### Caso 3: Serializer Apenas com Organization

```python
from shared_auth.serializers import OrganizationSerializerMixin

class EmpresaConfigSerializer(OrganizationSerializerMixin, serializers.ModelSerializer):
    class Meta:
        model = EmpresaConfig
        fields = [
            'id', 'tema_cor', 'logo', 'timezone',
            'organization_name',  # Do
# 📦 Guia Completo - Serializers com Objetos Aninhados

## Estrutura da Resposta JSON

### ✅ Antes (campos separados)
```json
{
  "id": 1,
  "titulo": "Meu Rascunho",
  "organization_id": 123,
  "organization_name": "Empresa XYZ",
  "organization_cnpj": "12.345.678/0001-90",
  "user_id": 456,
  "user_email": "joao@xyz.com",
  "user_full_name": "João Silva"
}
```

### ✨ Depois (objetos aninhados)
```json
{
  "id": 1,
  "titulo": "Meu Rascunho",
  "organization": {
    "id": 123,
    "name": "Empresa XYZ",
    "cnpj": "12.345.678/0001-90",
    "email": "contato@xyz.com"
  },
  "user": {
    "id": 456,
    "email": "joao@xyz.com",
    "full_name": "João Silva"
  }
}
```

---

## 🎯 Cenários de Uso

### 1. Serializer Completo (Detail)

```python
# serializers.py
from rest_framework import serializers
from shared_auth.serializers import OrganizationUserSerializerMixin
from .models import Rascunho

class RascunhoDetailSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
    """Serializer completo para detalhes do rascunho"""
    
    class Meta:
        model = Rascunho
        fields = [
            'id',
            'titulo',
            'conteudo',
            'organization',  # Objeto completo
            'user',  # Objeto completo
            'created_at',
            'updated_at',
        ]
        read_only_fields = ['organization', 'user', 'created_at', 'updated_at']

# views
class RascunhoViewSet(viewsets.ModelViewSet):
    queryset = Rascunho.objects.all()
    
    def get_serializer_class(self):
        if self.action == 'retrieve':
            return RascunhoDetailSerializer
        return RascunhoListSerializer

# Response GET /api/rascunhos/1/
{
    "id": 1,
    "titulo": "Meu Rascunho",
    "conteudo": "Conteúdo completo aqui...",
    "organization": {
        "id": 123,
        "name": "Empresa XYZ Ltda",
        "fantasy_name": "XYZ",
        "cnpj": "12.345.678/0001-90",
        "email": "contato@xyz.com",
        "telephone": "11-3333-4444",
        "cellphone": "11-99999-8888",
        "is_branch": false,
        "is_active": true
    },
    "user": {
        "id": 456,
        "username": "joao.silva",
        "email": "joao@xyz.com",
        "first_name": "João",
        "last_name": "Silva",
        "full_name": "João Silva",
        "is_active": true
    },
    "created_at": "2025-10-01T10:00:00Z",
    "updated_at": "2025-10-01T15:30:00Z"
}
```

### 2. Serializer Simplificado (List)

```python
from shared_auth.serializers import OrganizationSimpleSerializerMixin, UserSimpleSerializerMixin

class RascunhoListSerializer(OrganizationSimpleSerializerMixin, UserSimpleSerializerMixin, serializers.ModelSerializer):
    """Serializer simplificado para listagens"""
    
    class Meta:
        model = Rascunho
        fields = [
            'id',
            'titulo',
            'organization',  # Simplificado
            'user',  # Simplificado
            'created_at',
        ]

# Response GET /api/rascunhos/
{
    "count": 10,
    "results": [
        {
            "id": 1,
            "titulo": "Rascunho 1",
            "organization": {
                "id": 123,
                "name": "Empresa XYZ",
                "cnpj": "12.345.678/0001-90"
            },
            "user": {
                "id": 456,
                "email": "joao@xyz.com",
                "full_name": "João Silva"
            },
            "created_at": "2025-10-01T10:00:00Z"
        },
        // ... mais resultados
    ]
}
```

### 3. Apenas Organization

```python
from shared_auth.serializers import OrganizationSerializerMixin

class EmpresaConfigSerializer(OrganizationSerializerMixin, serializers.ModelSerializer):
    """Configurações da empresa"""
    
    class Meta:
        model = EmpresaConfig
        fields = [
            'id',
            'tema_cor',
            'logo',
            'timezone',
            'organization',  # Objeto completo
        ]

# Response
{
    "id": 1,
    "tema_cor": "#3490dc",
    "logo": "https://example.com/logos/xyz.png",
    "timezone": "America/Sao_Paulo",
    "organization": {
        "id": 123,
        "name": "Empresa XYZ Ltda",
        "fantasy_name": "XYZ",
        "cnpj": "12.345.678/0001-90",
        "email": "contato@xyz.com",
        "telephone": "11-3333-4444",
        "cellphone": "11-99999-8888",
        "is_branch": false,
        "is_active": true
    }
}
```

### 4. Apenas User

```python
from shared_auth.serializers import UserSerializerMixin

class UserPreferencesSerializer(UserSerializerMixin, serializers.ModelSerializer):
    """Preferências do usuário"""
    
    class Meta:
        model = UserPreferences
        fields = [
            'id',
            'theme',
            'notifications_enabled',
            'language',
            'user',  # Objeto completo
        ]

# Response
{
    "id": 1,
    "theme": "dark",
    "notifications_enabled": true,
    "language": "pt-BR",
    "user": {
        "id": 456,
        "username": "joao.silva",
        "email": "joao@xyz.com",
        "first_name": "João",
        "last_name": "Silva",
        "full_name": "João Silva",
        "is_active": true
    }
}
```

---

## 🔧 Customização Avançada

### Adicionar Campos Extras ao Organization

```python
from shared_auth.serializers import OrganizationUserSerializerMixin

class RascunhoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
    # Sobrescrever o método para adicionar campos extras
    def get_organization(self, obj):
        org_data = super().get_organization(obj)
        if org_data:
            # Adicionar campos customizados
            org_data['logo_url'] = f"/logos/{obj.organization_id}.png"
            org_data['member_count'] = obj.organization.members.count()
        return org_data
    
    class Meta:
        model = Rascunho
        fields = ['id', 'titulo', 'organization', 'user']

# Response
{
    "id": 1,
    "titulo": "Teste",
    "organization": {
        "id": 123,
        "name": "Empresa XYZ",
        // ... campos padrão
        "logo_url": "/logos/123.png",
        "member_count": 15
    },
    "user": { ... }
}
```

### Condicional - Mostrar Mais ou Menos Dados

```python
class RascunhoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
    
    def get_organization(self, obj):
        # Usuário logado é da mesma organização?
        request = self.context.get('request')
        same_org = (request and 
                   hasattr(request.user, 'logged_organization_id') and
                   request.user.logged_organization_id == obj.organization_id)
        
        try:
            org = obj.organization
            data = {
                'id': org.pk,
                'name': org.name,
            }
            
            # Mostrar mais dados se for da mesma organização
            if same_org:
                data.update({
                    'cnpj': org.cnpj,
                    'email': org.email,
                    'telephone': org.telephone,
                })
            
            return data
        except:
            return None
    
    class Meta:
        model = Rascunho
        fields = ['id', 'titulo', 'organization']
```

---

## 🚀 Performance - Otimização

### Problema N+1

```python
# RUIM: Causa N+1 queries
class RascunhoViewSet(viewsets.ModelViewSet):
    queryset = Rascunho.objects.all()  # 1 query
    serializer_class = RascunhoSerializer
    
    # Cada serialização faz 2 queries (org + user)
    # Total: 1 + (N * 2) queries
```

### Solução: Prefetch

```python
# BOM: Usa prefetch do manager
class RascunhoViewSet(viewsets.ModelViewSet):
    serializer_class = RascunhoSerializer
    
    def get_queryset(self):
        # Usa o manager customizado
        return Rascunho.objects.with_auth_data()
        # Total: 3 queries (rascunhos + orgs + users)

# Ainda MELHOR: Cache no serializer
class RascunhoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
    
    def get_organization(self, obj):
        # Verifica se já está cacheado
        if hasattr(obj, '_cached_organization'):
            org = obj._cached_organization
        else:
            org = obj.organization
        
        return {
            'id': org.pk,
            'name': org.name,
            // ...
        }
```

---

## 💡 Casos de Uso Reais

### Sistema de Pedidos

```python
class ItemPedidoSerializer(serializers.ModelSerializer):
    class Meta:
        model = ItemPedido
        fields = ['id', 'produto', 'quantidade', 'valor_unitario']

class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
    itens = ItemPedidoSerializer(many=True, read_only=True)
    
    class Meta:
        model = Pedido
        fields = [
            'id', 'numero', 'valor_total', 'status',
            'organization',  # Cliente
            'user',  # Vendedor
            'itens',
            'created_at'
        ]

# Response
{
    "id": 1,
    "numero": "PED-001",
    "valor_total": "1500.00",
    "status": "pending",
    "organization": {
        "id": 123,
        "name": "Cliente ABC",
        "cnpj": "12.345.678/0001-90",
        "email": "contato@abc.com"
    },
    "user": {
        "id": 456,
        "email": "vendedor@empresa.com",
        "full_name": "João Vendedor"
    },
    "itens": [
        {
            "id": 1,
            "produto": "Produto A",
            "quantidade": 2,
            "valor_unitario": "500.00"
        },
        {
            "id": 2,
            "produto": "Produto B",
            "quantidade": 1,
            "valor_unitario": "500.00"
        }
    ],
    "created_at": "2025-10-01T10:00:00Z"
}
```

### Sistema de Tarefas com Múltiplos Usuários

```python
class TarefaSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
    # user do mixin é o criador
    responsavel = serializers.SerializerMethodField()
    
    def get_responsavel(self, obj):
        """Retorna usuário responsável"""
        try:
            resp = obj.responsavel
            return {
                'id': resp.pk,
                'email': resp.email,
                'full_name': resp.get_full_name(),
            }
        except:
            return None
    
    class Meta:
        model = Tarefa
        fields = [
            'id', 'titulo', 'descricao', 'status', 'prioridade',
            'organization',  # Organização dona da tarefa
            'user',  # Quem criou
            'responsavel',  # Quem vai fazer
            'created_at'
        ]

# Response
{
    "id": 1,
    "titulo": "Implementar feature X",
    "descricao": "Descrição detalhada...",
    "status": "in_progress",
    "prioridade": "high",
    "organization": {
        "id": 123,
        "name": "Empresa XYZ"
    },
    "user": {
        "id": 456,
        "email": "gerente@xyz.com",
        "full_name": "Gerente Silva"
    },
    "responsavel": {
        "id": 789,
        "email": "dev@xyz.com",
        "full_name": "Dev Junior"
    },
    "created_at": "2025-10-01T10:00:00Z"
}
```

---

## 🎨 Frontend - Consumindo a API

### React Example

```javascript
// components/RascunhoCard.jsx
function RascunhoCard({ rascunho }) {
  return (
    <div className="card">
      <h3>{rascunho.titulo}</h3>
      <p>{rascunho.conteudo}</p>
      
      {/* Acessar dados aninhados */}
      <div className="meta">
        <span className="organization">
          {rascunho.organization.name}
        </span>
        <span className="user">
          Por: {rascunho.user.full_name}
        </span>
      </div>
      
      {/* Verificar status */}
      {!rascunho.organization.is_active && (
        <div className="warning">
          Organização inativa
        </div>
      )}
    </div>
  );
}

// Uso
fetch('/api/rascunhos/1/')
  .then(res => res.json())
  .then(data => {
    console.log(data.organization.name);  // Fácil!
    console.log(data.user.email);  // Direto!
  });
```

### Vue Example

```vue
<template>
  <div class="rascunho">
    <h3>{{ rascunho.titulo }}</h3>
    
    <!-- Dados da organização -->
    <div class="organization-info">
      <h4>{{ rascunho.organization.name }}</h4>
      <p>{{ rascunho.organization.cnpj }}</p>
      <p>{{ rascunho.organization.email }}</p>
    </div>
    
    <!-- Dados do usuário -->
    <div class="user-info">
      <span>Criado por: {{ rascunho.user.full_name }}</span>
      <span>Email: {{ rascunho.user.email }}</span>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      rascunho: null
    }
  },
  async mounted() {
    const response = await fetch('/api/rascunhos/1/');
    this.rascunho = await response.json();
  }
}
</script>
```

---

## ✅ Vantagens desta Abordagem

1. **Mais Semântico**: Dados relacionados agrupados
2. **Fácil de Consumir**: Frontend acessa `obj.organization.name` diretamente
3. **Flexível**: Pode ter versão completa e simplificada
4. **Type-Safe**: TypeScript/Flow adoram objetos aninhados
5. **Cacheable**: Pode cachear o objeto `organization` inteiro
6. **Reutilizável**: Mesma estrutura em todos os endpoints

---

## 📋 Checklist de Implementação

- [ ] Atualizar `shared_auth/serializers.py` com novos mixins
- [ ] Reinstalar biblioteca: `pip install -e /path/to/shared-auth-lib`
- [ ] Atualizar serializers existentes
- [ ] Testar responses dos endpoints
- [ ] Atualizar documentação da API
- [ ] Atualizar código do frontend
- [ ] Verificar performance (N+1 queries)
- [ ] Adicionar testes

Pronto! Agora seus serializers retornam dados organizados em objetos `organization` e `user`! 🎉
