Models read-only para autenticação compartilhada entre projetos Django.
Project description
📚 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
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
# 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
-- 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
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
# 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
# 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
# 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
# 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
# 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
# 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)
# 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
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
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
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)
{
"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)
# 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)
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
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
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
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
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
# 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
# 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
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
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
// 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
<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
- Mais Semântico: Dados relacionados agrupados
- Fácil de Consumir: Frontend acessa
obj.organization.namediretamente - Flexível: Pode ter versão completa e simplificada
- Type-Safe: TypeScript/Flow adoram objetos aninhados
- Cacheable: Pode cachear o objeto
organizationinteiro - Reutilizável: Mesma estrutura em todos os endpoints
📋 Checklist de Implementação
- Atualizar
shared_auth/serializers.pycom 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! 🎉
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters