maquinaweb-shared-auth 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of maquinaweb-shared-auth might be problematic. Click here for more details.

@@ -0,0 +1,1016 @@
1
+ Metadata-Version: 2.4
2
+ Name: maquinaweb-shared-auth
3
+ Version: 0.1.0
4
+ Summary: Models read-only para autenticaΓ§Γ£o compartilhada entre projetos Django.
5
+ Author-email: Seu Nome <seuemail@dominio.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/maquinaweb/maquinaweb-shared-auth
8
+ Project-URL: Repository, https://github.com/maquinaweb/maquinaweb-shared-auth
9
+ Project-URL: Issues, https://github.com/maquinaweb/maquinaweb-shared-auth/issues
10
+ Keywords: django,auth,models,shared
11
+ Classifier: Framework :: Django
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: django>=5
18
+ Requires-Dist: djangorestframework>=3
19
+ Requires-Dist: setuptools>=80
20
+ Dynamic: requires-python
21
+
22
+ # πŸ“š Guia Completo - Biblioteca Compartilhada de AutenticaΓ§Γ£o
23
+
24
+ ## 🎯 O que Esta Solução Faz?
25
+
26
+ 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.
27
+
28
+ ### Vantagens
29
+
30
+ βœ… **Acesso direto ao banco** - Sem latΓͺncia de API
31
+ βœ… **Interface Django nativa** - `rascunho.user` funciona igual ao Django normal
32
+ βœ… **Read-only** - ImpossΓ­vel modificar dados por engano
33
+ βœ… **Sem duplicaΓ§Γ£o** - Um ΓΊnico banco de autenticaΓ§Γ£o para todos os sistemas
34
+ βœ… **Type-safe** - ValidaΓ§Γ΅es e exceΓ§Γ΅es customizadas
35
+
36
+ ---
37
+
38
+ ## πŸ—οΈ Arquitetura
39
+
40
+ ```
41
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
42
+ β”‚ Sistema de AutenticaΓ§Γ£o (Principal) β”‚
43
+ β”‚ β”‚
44
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
45
+ β”‚ β”‚ Organization β”‚ β”‚ User β”‚ β”‚
46
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
47
+ β”‚ β”‚ β”‚ β”‚
48
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
49
+ β”‚ β”‚ β”‚
50
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”‚
51
+ β”‚ β”‚ Member β”‚ β”‚
52
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
53
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
54
+ β”‚
55
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
56
+ β”‚ Banco de Dados Auth β”‚
57
+ β”‚ (PostgreSQL/MySQL) β”‚
58
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
59
+ β”‚
60
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
61
+ β”‚ β”‚
62
+ β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”
63
+ β”‚ Sistema A β”‚ β”‚ Sistema B β”‚
64
+ β”‚ β”‚ β”‚ β”‚
65
+ β”‚ Rascunho β”‚ β”‚ Pedido β”‚
66
+ β”‚ β”œβ”€ org β”‚ β”‚ β”œβ”€ org β”‚
67
+ β”‚ └─ user β”‚ β”‚ └─ user β”‚
68
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
69
+ ```
70
+
71
+ ---
72
+
73
+ ## πŸ“¦ Passo 1: Criar a Biblioteca
74
+
75
+ ### 1.1. Estrutura de DiretΓ³rios
76
+
77
+ ```bash
78
+ mkdir shared-auth-lib
79
+ cd shared-auth-lib
80
+
81
+ # Criar estrutura
82
+ mkdir shared_auth
83
+ touch setup.py README.md
84
+ touch shared_auth/__init__.py
85
+ touch shared_auth/models.py
86
+ touch shared_auth/managers.py
87
+ touch shared_auth/exceptions.py
88
+ ```
89
+
90
+ ### 1.2. Copiar o CΓ³digo
91
+
92
+ Copie os arquivos do artifact anterior para a estrutura criada.
93
+
94
+ ### 1.3. Instalar Localmente
95
+
96
+ ```bash
97
+ # Modo desenvolvimento (changes refletem automaticamente)
98
+ pip install -e /path/to/shared-auth-lib
99
+
100
+ # Ou adicionar ao requirements.txt
101
+ echo "-e /path/to/shared-auth-lib" >> requirements.txt
102
+ ```
103
+
104
+ ---
105
+
106
+ ## πŸ”§ Passo 2: Configurar Banco de Dados
107
+
108
+ ### 2.1. Criar UsuΓ‘rio Read-Only no PostgreSQL
109
+
110
+ ```sql
111
+ -- No banco do sistema de autenticaΓ§Γ£o
112
+ CREATE USER readonly_user WITH PASSWORD 'senha_segura';
113
+
114
+ -- Conceder permissΓ΅es de leitura
115
+ GRANT CONNECT ON DATABASE sistema_auth_db TO readonly_user;
116
+ GRANT USAGE ON SCHEMA public TO readonly_user;
117
+ GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_user;
118
+
119
+ -- Para tabelas futuras
120
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public
121
+ GRANT SELECT ON TABLES TO readonly_user;
122
+
123
+ -- Garantir read-only
124
+ ALTER USER readonly_user SET default_transaction_read_only = on;
125
+ ```
126
+
127
+ ### 2.2. Para MySQL
128
+
129
+ ```sql
130
+ CREATE USER 'readonly_user'@'%' IDENTIFIED BY 'senha_segura';
131
+ GRANT SELECT ON sistema_auth_db.* TO 'readonly_user'@'%';
132
+ FLUSH PRIVILEGES;
133
+ ```
134
+
135
+ ---
136
+
137
+ ## βš™οΈ Passo 3: Configurar Sistema Cliente
138
+
139
+ ### 3.1. Settings.py
140
+
141
+ ```python
142
+ # settings.py do seu outro sistema
143
+
144
+ DATABASES = {
145
+ 'default': {
146
+ # Banco do sistema atual
147
+ 'ENGINE': 'django.db.backends.postgresql',
148
+ 'NAME': 'meu_sistema_db',
149
+ 'USER': 'meu_user',
150
+ 'PASSWORD': 'senha',
151
+ 'HOST': 'localhost',
152
+ 'PORT': '5432',
153
+ },
154
+ 'auth_db': {
155
+ # Banco do sistema de autenticaΓ§Γ£o (READ-ONLY)
156
+ 'ENGINE': 'django.db.backends.postgresql',
157
+ 'NAME': 'sistema_auth_db',
158
+ 'USER': 'readonly_user',
159
+ 'PASSWORD': 'senha_readonly',
160
+ 'HOST': 'localhost', # ou IP do servidor de auth
161
+ 'PORT': '5432',
162
+ 'OPTIONS': {
163
+ 'options': '-c default_transaction_read_only=on'
164
+ }
165
+ }
166
+ }
167
+
168
+ # Router para direcionar queries
169
+ DATABASE_ROUTERS = ['myapp.routers.SharedAuthRouter']
170
+
171
+ INSTALLED_APPS = [
172
+ # ... suas apps
173
+ 'shared_auth', # Adicionar biblioteca
174
+ ]
175
+ ```
176
+
177
+ ### 3.2. Database Router
178
+
179
+ ```python
180
+ # myapp/routers.py
181
+
182
+ class SharedAuthRouter:
183
+ """
184
+ Direciona queries dos models compartilhados para o banco correto
185
+ """
186
+
187
+ route_app_labels = {'shared_auth'}
188
+
189
+ def db_for_read(self, model, **hints):
190
+ """Direciona leituras para auth_db"""
191
+ if model._meta.app_label in self.route_app_labels:
192
+ return 'auth_db'
193
+ return None
194
+
195
+ def db_for_write(self, model, **hints):
196
+ """Bloqueia escritas"""
197
+ if model._meta.app_label in self.route_app_labels:
198
+ return None # Impede qualquer escrita
199
+ return None
200
+
201
+ def allow_migrate(self, db, app_label, model_name=None, **hints):
202
+ """Bloqueia migrations"""
203
+ if app_label in self.route_app_labels:
204
+ return False
205
+ return None
206
+ ```
207
+
208
+ ---
209
+
210
+ ## πŸ’» Passo 4: Usar nos Seus Models
211
+
212
+ ### 4.1. Model BΓ‘sico
213
+
214
+ ```python
215
+ # myapp/models.py
216
+ from django.db import models
217
+
218
+ class Rascunho(models.Model):
219
+ titulo = models.CharField(max_length=200)
220
+ conteudo = models.TextField()
221
+
222
+ # IDs de referΓͺncia
223
+ organization_id = models.IntegerField()
224
+ user_id = models.IntegerField()
225
+
226
+ created_at = models.DateTimeField(auto_now_add=True)
227
+
228
+ @property
229
+ def organization(self):
230
+ """Acessa organizaΓ§Γ£o do banco de auth"""
231
+ from shared_auth.models import SharedOrganization
232
+ return SharedOrganization.objects.using('auth_db').get_or_fail(
233
+ self.organization_id
234
+ ```
235
+ # πŸš€ Guia PrΓ‘tico - Mixins e Serializers Compartilhados
236
+
237
+ ```python
238
+ # models.py - UMA LINHA!
239
+ from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
240
+ from shared_auth.managers import BaseAuthManager
241
+
242
+ class Rascunho(OrganizationUserMixin, TimestampedMixin):
243
+ titulo = models.CharField(max_length=200)
244
+ conteudo = models.TextField()
245
+
246
+ objects = BaseAuthManager()
247
+
248
+ # Pronto! JΓ‘ tem tudo:
249
+ # - organization_id, user_id
250
+ # - properties: organization, user
251
+ # - mΓ©todos: validate_user_belongs_to_organization()
252
+ # - created_at, updated_at
253
+
254
+
255
+ # serializers.py - UMA LINHA!
256
+ from shared_auth.serializers import OrganizationUserSerializerMixin
257
+
258
+ class RascunhoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
259
+ class Meta:
260
+ model = Rascunho
261
+ fields = [
262
+ 'id', 'titulo', 'conteudo',
263
+ 'organization_name', 'organization_email', # JΓ‘ disponΓ­veis!
264
+ 'user_email', 'user_full_name', # JΓ‘ disponΓ­veis!
265
+ ]
266
+
267
+ # Pronto! Todos os campos jΓ‘ funcionam!
268
+ ```
269
+
270
+ ---
271
+
272
+ ## πŸ“ Exemplos de Uso por CenΓ‘rio
273
+
274
+ ### 1. Model que Pertence APENAS a OrganizaΓ§Γ£o
275
+
276
+ ```python
277
+ # Exemplo: ConfiguraΓ§Γ£o da empresa
278
+ from shared_auth.mixins import OrganizationMixin
279
+
280
+ class EmpresaConfig(OrganizationMixin):
281
+ """ConfiguraΓ§Γ΅es especΓ­ficas da organizaΓ§Γ£o"""
282
+
283
+ tema_cor = models.CharField(max_length=7, default='#3490dc')
284
+ logo = models.ImageField(upload_to='logos/')
285
+ timezone = models.CharField(max_length=50, default='America/Sao_Paulo')
286
+
287
+ objects = BaseAuthManager()
288
+
289
+ def __str__(self):
290
+ return f"Config de {self.organization.name}"
291
+
292
+ # Uso
293
+ config = EmpresaConfig.objects.create(
294
+ organization_id=123,
295
+ tema_cor='#ff0000'
296
+ )
297
+
298
+ print(config.organization.name) # Funciona!
299
+ print(config.organization_is_active()) # MΓ©todo do mixin
300
+ ```
301
+
302
+ ### 2. Model que Pertence APENAS a UsuΓ‘rio
303
+
304
+ ```python
305
+ # Exemplo: PreferΓͺncias do usuΓ‘rio
306
+ from shared_auth.mixins import UserMixin
307
+
308
+ class UserPreferences(UserMixin):
309
+ """PreferΓͺncias pessoais do usuΓ‘rio"""
310
+
311
+ theme = models.CharField(max_length=20, default='light')
312
+ notifications_enabled = models.BooleanField(default=True)
313
+ language = models.CharField(max_length=5, default='pt-BR')
314
+
315
+ objects = BaseAuthManager()
316
+
317
+ # Uso
318
+ prefs = UserPreferences.objects.create(
319
+ user_id=456,
320
+ theme='dark'
321
+ )
322
+
323
+ print(prefs.user.email) # Funciona!
324
+ print(prefs.user_full_name) # Property do mixin
325
+ ```
326
+
327
+ ### 3. Model com OrganizaΓ§Γ£o E UsuΓ‘rio (mais comum)
328
+
329
+ ```python
330
+ # Exemplo: Pedido, Rascunho, Tarefa, etc
331
+ from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
332
+
333
+ class Pedido(OrganizationUserMixin, TimestampedMixin):
334
+ """Pedido pertence a organizaΓ§Γ£o e foi criado por usuΓ‘rio"""
335
+
336
+ numero = models.CharField(max_length=20, unique=True)
337
+ valor_total = models.DecimalField(max_digits=10, decimal_places=2)
338
+ status = models.CharField(max_length=20, default='pending')
339
+
340
+ objects = BaseAuthManager()
341
+
342
+ def __str__(self):
343
+ return f"Pedido {self.numero}"
344
+
345
+ # Uso
346
+ pedido = Pedido.objects.create(
347
+ organization_id=123,
348
+ user_id=456,
349
+ numero='PED-001',
350
+ valor_total=100.00
351
+ )
352
+
353
+ # Acessos automΓ‘ticos
354
+ print(pedido.organization.name) # OrganizaΓ§Γ£o
355
+ print(pedido.user.email) # UsuΓ‘rio que criou
356
+ print(pedido.user_full_name) # Nome completo
357
+
358
+ # ValidaΓ§Γ£o automΓ‘tica
359
+ if pedido.validate_user_belongs_to_organization():
360
+ print("OK - UsuΓ‘rio pertence Γ  organizaΓ§Γ£o")
361
+
362
+ # Verificar se outro usuΓ‘rio pode acessar
363
+ if pedido.user_can_access(outro_user_id):
364
+ print("Pode acessar")
365
+ ```
366
+
367
+ ---
368
+
369
+ ## 🎨 Serializers - Casos de Uso
370
+
371
+ ### Caso 1: Serializer BΓ‘sico
372
+
373
+ ```python
374
+ from shared_auth.serializers import OrganizationUserSerializerMixin
375
+
376
+ class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
377
+ class Meta:
378
+ model = Pedido
379
+ fields = [
380
+ 'id', 'numero', 'valor_total', 'status',
381
+ # Campos automΓ‘ticos do mixin:
382
+ 'organization_name',
383
+ 'organization_cnpj',
384
+ 'organization_email',
385
+ 'user_email',
386
+ 'user_full_name',
387
+ 'created_at', 'updated_at',
388
+ ]
389
+
390
+ # Response JSON automΓ‘tica:
391
+ {
392
+ "id": 1,
393
+ "numero": "PED-001",
394
+ "valor_total": "100.00",
395
+ "status": "pending",
396
+ "organization_name": "Empresa XYZ",
397
+ "organization_cnpj": "12.345.678/0001-90",
398
+ "organization_email": "contato@xyz.com",
399
+ "user_email": "joao@xyz.com",
400
+ "user_full_name": "JoΓ£o Silva",
401
+ "created_at": "2025-10-01T10:00:00Z",
402
+ "updated_at": "2025-10-01T10:00:00Z"
403
+ }
404
+ ```
405
+
406
+ ### Caso 2: Serializer com Fields Customizados
407
+
408
+ ```python
409
+ from shared_auth.serializers import OrganizationUserSerializerMixin
410
+ from shared_auth.fields import OrganizationField, UserField
411
+
412
+ class PedidoDetailSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
413
+ # Fields customizados que retornam objetos completos
414
+ organization_full = OrganizationField(source='*')
415
+ user_full = UserField(source='*')
416
+
417
+ class Meta:
418
+ model = Pedido
419
+ fields = [
420
+ 'id', 'numero', 'valor_total',
421
+ 'organization_full', # Objeto completo
422
+ 'user_full', # Objeto completo
423
+ ]
424
+
425
+ # Response JSON:
426
+ {
427
+ "id": 1,
428
+ "numero": "PED-001",
429
+ "valor_total": "100.00",
430
+ "organization_full": {
431
+ "id": 123,
432
+ "name": "Empresa XYZ",
433
+ "fantasy_name": "XYZ Ltda",
434
+ "cnpj": "12.345.678/0001-90",
435
+ "email": "contato@xyz.com",
436
+ "is_active": true
437
+ },
438
+ "user_full": {
439
+ "id": 456,
440
+ "username": "joao",
441
+ "email": "joao@xyz.com",
442
+ "full_name": "JoΓ£o Silva",
443
+ "is_active": true
444
+ }
445
+ }
446
+ ```
447
+
448
+ ### Caso 3: Serializer Apenas com Organization
449
+
450
+ ```python
451
+ from shared_auth.serializers import OrganizationSerializerMixin
452
+
453
+ class EmpresaConfigSerializer(OrganizationSerializerMixin, serializers.ModelSerializer):
454
+ class Meta:
455
+ model = EmpresaConfig
456
+ fields = [
457
+ 'id', 'tema_cor', 'logo', 'timezone',
458
+ 'organization_name', # Do
459
+ # πŸ“¦ Guia Completo - Serializers com Objetos Aninhados
460
+
461
+ ## Estrutura da Resposta JSON
462
+
463
+ ### βœ… Antes (campos separados)
464
+ ```json
465
+ {
466
+ "id": 1,
467
+ "titulo": "Meu Rascunho",
468
+ "organization_id": 123,
469
+ "organization_name": "Empresa XYZ",
470
+ "organization_cnpj": "12.345.678/0001-90",
471
+ "user_id": 456,
472
+ "user_email": "joao@xyz.com",
473
+ "user_full_name": "JoΓ£o Silva"
474
+ }
475
+ ```
476
+
477
+ ### ✨ Depois (objetos aninhados)
478
+ ```json
479
+ {
480
+ "id": 1,
481
+ "titulo": "Meu Rascunho",
482
+ "organization": {
483
+ "id": 123,
484
+ "name": "Empresa XYZ",
485
+ "cnpj": "12.345.678/0001-90",
486
+ "email": "contato@xyz.com"
487
+ },
488
+ "user": {
489
+ "id": 456,
490
+ "email": "joao@xyz.com",
491
+ "full_name": "JoΓ£o Silva"
492
+ }
493
+ }
494
+ ```
495
+
496
+ ---
497
+
498
+ ## 🎯 CenÑrios de Uso
499
+
500
+ ### 1. Serializer Completo (Detail)
501
+
502
+ ```python
503
+ # serializers.py
504
+ from rest_framework import serializers
505
+ from shared_auth.serializers import OrganizationUserSerializerMixin
506
+ from .models import Rascunho
507
+
508
+ class RascunhoDetailSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
509
+ """Serializer completo para detalhes do rascunho"""
510
+
511
+ class Meta:
512
+ model = Rascunho
513
+ fields = [
514
+ 'id',
515
+ 'titulo',
516
+ 'conteudo',
517
+ 'organization', # Objeto completo
518
+ 'user', # Objeto completo
519
+ 'created_at',
520
+ 'updated_at',
521
+ ]
522
+ read_only_fields = ['organization', 'user', 'created_at', 'updated_at']
523
+
524
+ # views.py
525
+ class RascunhoViewSet(viewsets.ModelViewSet):
526
+ queryset = Rascunho.objects.all()
527
+
528
+ def get_serializer_class(self):
529
+ if self.action == 'retrieve':
530
+ return RascunhoDetailSerializer
531
+ return RascunhoListSerializer
532
+
533
+ # Response GET /api/rascunhos/1/
534
+ {
535
+ "id": 1,
536
+ "titulo": "Meu Rascunho",
537
+ "conteudo": "ConteΓΊdo completo aqui...",
538
+ "organization": {
539
+ "id": 123,
540
+ "name": "Empresa XYZ Ltda",
541
+ "fantasy_name": "XYZ",
542
+ "cnpj": "12.345.678/0001-90",
543
+ "email": "contato@xyz.com",
544
+ "telephone": "11-3333-4444",
545
+ "cellphone": "11-99999-8888",
546
+ "is_branch": false,
547
+ "is_active": true
548
+ },
549
+ "user": {
550
+ "id": 456,
551
+ "username": "joao.silva",
552
+ "email": "joao@xyz.com",
553
+ "first_name": "JoΓ£o",
554
+ "last_name": "Silva",
555
+ "full_name": "JoΓ£o Silva",
556
+ "is_active": true
557
+ },
558
+ "created_at": "2025-10-01T10:00:00Z",
559
+ "updated_at": "2025-10-01T15:30:00Z"
560
+ }
561
+ ```
562
+
563
+ ### 2. Serializer Simplificado (List)
564
+
565
+ ```python
566
+ from shared_auth.serializers import OrganizationSimpleSerializerMixin, UserSimpleSerializerMixin
567
+
568
+ class RascunhoListSerializer(OrganizationSimpleSerializerMixin, UserSimpleSerializerMixin, serializers.ModelSerializer):
569
+ """Serializer simplificado para listagens"""
570
+
571
+ class Meta:
572
+ model = Rascunho
573
+ fields = [
574
+ 'id',
575
+ 'titulo',
576
+ 'organization', # Simplificado
577
+ 'user', # Simplificado
578
+ 'created_at',
579
+ ]
580
+
581
+ # Response GET /api/rascunhos/
582
+ {
583
+ "count": 10,
584
+ "results": [
585
+ {
586
+ "id": 1,
587
+ "titulo": "Rascunho 1",
588
+ "organization": {
589
+ "id": 123,
590
+ "name": "Empresa XYZ",
591
+ "cnpj": "12.345.678/0001-90"
592
+ },
593
+ "user": {
594
+ "id": 456,
595
+ "email": "joao@xyz.com",
596
+ "full_name": "JoΓ£o Silva"
597
+ },
598
+ "created_at": "2025-10-01T10:00:00Z"
599
+ },
600
+ // ... mais resultados
601
+ ]
602
+ }
603
+ ```
604
+
605
+ ### 3. Apenas Organization
606
+
607
+ ```python
608
+ from shared_auth.serializers import OrganizationSerializerMixin
609
+
610
+ class EmpresaConfigSerializer(OrganizationSerializerMixin, serializers.ModelSerializer):
611
+ """ConfiguraΓ§Γ΅es da empresa"""
612
+
613
+ class Meta:
614
+ model = EmpresaConfig
615
+ fields = [
616
+ 'id',
617
+ 'tema_cor',
618
+ 'logo',
619
+ 'timezone',
620
+ 'organization', # Objeto completo
621
+ ]
622
+
623
+ # Response
624
+ {
625
+ "id": 1,
626
+ "tema_cor": "#3490dc",
627
+ "logo": "https://example.com/logos/xyz.png",
628
+ "timezone": "America/Sao_Paulo",
629
+ "organization": {
630
+ "id": 123,
631
+ "name": "Empresa XYZ Ltda",
632
+ "fantasy_name": "XYZ",
633
+ "cnpj": "12.345.678/0001-90",
634
+ "email": "contato@xyz.com",
635
+ "telephone": "11-3333-4444",
636
+ "cellphone": "11-99999-8888",
637
+ "is_branch": false,
638
+ "is_active": true
639
+ }
640
+ }
641
+ ```
642
+
643
+ ### 4. Apenas User
644
+
645
+ ```python
646
+ from shared_auth.serializers import UserSerializerMixin
647
+
648
+ class UserPreferencesSerializer(UserSerializerMixin, serializers.ModelSerializer):
649
+ """PreferΓͺncias do usuΓ‘rio"""
650
+
651
+ class Meta:
652
+ model = UserPreferences
653
+ fields = [
654
+ 'id',
655
+ 'theme',
656
+ 'notifications_enabled',
657
+ 'language',
658
+ 'user', # Objeto completo
659
+ ]
660
+
661
+ # Response
662
+ {
663
+ "id": 1,
664
+ "theme": "dark",
665
+ "notifications_enabled": true,
666
+ "language": "pt-BR",
667
+ "user": {
668
+ "id": 456,
669
+ "username": "joao.silva",
670
+ "email": "joao@xyz.com",
671
+ "first_name": "JoΓ£o",
672
+ "last_name": "Silva",
673
+ "full_name": "JoΓ£o Silva",
674
+ "is_active": true
675
+ }
676
+ }
677
+ ```
678
+
679
+ ---
680
+
681
+ ## πŸ”§ CustomizaΓ§Γ£o AvanΓ§ada
682
+
683
+ ### Adicionar Campos Extras ao Organization
684
+
685
+ ```python
686
+ from shared_auth.serializers import OrganizationUserSerializerMixin
687
+
688
+ class RascunhoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
689
+ # Sobrescrever o mΓ©todo para adicionar campos extras
690
+ def get_organization(self, obj):
691
+ org_data = super().get_organization(obj)
692
+ if org_data:
693
+ # Adicionar campos customizados
694
+ org_data['logo_url'] = f"/logos/{obj.organization_id}.png"
695
+ org_data['member_count'] = obj.organization.members.count()
696
+ return org_data
697
+
698
+ class Meta:
699
+ model = Rascunho
700
+ fields = ['id', 'titulo', 'organization', 'user']
701
+
702
+ # Response
703
+ {
704
+ "id": 1,
705
+ "titulo": "Teste",
706
+ "organization": {
707
+ "id": 123,
708
+ "name": "Empresa XYZ",
709
+ // ... campos padrΓ£o
710
+ "logo_url": "/logos/123.png",
711
+ "member_count": 15
712
+ },
713
+ "user": { ... }
714
+ }
715
+ ```
716
+
717
+ ### Condicional - Mostrar Mais ou Menos Dados
718
+
719
+ ```python
720
+ class RascunhoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
721
+
722
+ def get_organization(self, obj):
723
+ # UsuΓ‘rio logado Γ© da mesma organizaΓ§Γ£o?
724
+ request = self.context.get('request')
725
+ same_org = (request and
726
+ hasattr(request.user, 'logged_organization_id') and
727
+ request.user.logged_organization_id == obj.organization_id)
728
+
729
+ try:
730
+ org = obj.organization
731
+ data = {
732
+ 'id': org.pk,
733
+ 'name': org.name,
734
+ }
735
+
736
+ # Mostrar mais dados se for da mesma organizaΓ§Γ£o
737
+ if same_org:
738
+ data.update({
739
+ 'cnpj': org.cnpj,
740
+ 'email': org.email,
741
+ 'telephone': org.telephone,
742
+ })
743
+
744
+ return data
745
+ except:
746
+ return None
747
+
748
+ class Meta:
749
+ model = Rascunho
750
+ fields = ['id', 'titulo', 'organization']
751
+ ```
752
+
753
+ ---
754
+
755
+ ## πŸš€ Performance - OtimizaΓ§Γ£o
756
+
757
+ ### Problema N+1
758
+
759
+ ```python
760
+ # RUIM: Causa N+1 queries
761
+ class RascunhoViewSet(viewsets.ModelViewSet):
762
+ queryset = Rascunho.objects.all() # 1 query
763
+ serializer_class = RascunhoSerializer
764
+
765
+ # Cada serializaΓ§Γ£o faz 2 queries (org + user)
766
+ # Total: 1 + (N * 2) queries
767
+ ```
768
+
769
+ ### SoluΓ§Γ£o: Prefetch
770
+
771
+ ```python
772
+ # BOM: Usa prefetch do manager
773
+ class RascunhoViewSet(viewsets.ModelViewSet):
774
+ serializer_class = RascunhoSerializer
775
+
776
+ def get_queryset(self):
777
+ # Usa o manager customizado
778
+ return Rascunho.objects.with_auth_data()
779
+ # Total: 3 queries (rascunhos + orgs + users)
780
+
781
+ # Ainda MELHOR: Cache no serializer
782
+ class RascunhoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
783
+
784
+ def get_organization(self, obj):
785
+ # Verifica se jΓ‘ estΓ‘ cacheado
786
+ if hasattr(obj, '_cached_organization'):
787
+ org = obj._cached_organization
788
+ else:
789
+ org = obj.organization
790
+
791
+ return {
792
+ 'id': org.pk,
793
+ 'name': org.name,
794
+ // ...
795
+ }
796
+ ```
797
+
798
+ ---
799
+
800
+ ## πŸ’‘ Casos de Uso Reais
801
+
802
+ ### Sistema de Pedidos
803
+
804
+ ```python
805
+ class ItemPedidoSerializer(serializers.ModelSerializer):
806
+ class Meta:
807
+ model = ItemPedido
808
+ fields = ['id', 'produto', 'quantidade', 'valor_unitario']
809
+
810
+ class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
811
+ itens = ItemPedidoSerializer(many=True, read_only=True)
812
+
813
+ class Meta:
814
+ model = Pedido
815
+ fields = [
816
+ 'id', 'numero', 'valor_total', 'status',
817
+ 'organization', # Cliente
818
+ 'user', # Vendedor
819
+ 'itens',
820
+ 'created_at'
821
+ ]
822
+
823
+ # Response
824
+ {
825
+ "id": 1,
826
+ "numero": "PED-001",
827
+ "valor_total": "1500.00",
828
+ "status": "pending",
829
+ "organization": {
830
+ "id": 123,
831
+ "name": "Cliente ABC",
832
+ "cnpj": "12.345.678/0001-90",
833
+ "email": "contato@abc.com"
834
+ },
835
+ "user": {
836
+ "id": 456,
837
+ "email": "vendedor@empresa.com",
838
+ "full_name": "JoΓ£o Vendedor"
839
+ },
840
+ "itens": [
841
+ {
842
+ "id": 1,
843
+ "produto": "Produto A",
844
+ "quantidade": 2,
845
+ "valor_unitario": "500.00"
846
+ },
847
+ {
848
+ "id": 2,
849
+ "produto": "Produto B",
850
+ "quantidade": 1,
851
+ "valor_unitario": "500.00"
852
+ }
853
+ ],
854
+ "created_at": "2025-10-01T10:00:00Z"
855
+ }
856
+ ```
857
+
858
+ ### Sistema de Tarefas com MΓΊltiplos UsuΓ‘rios
859
+
860
+ ```python
861
+ class TarefaSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
862
+ # user do mixin Γ© o criador
863
+ responsavel = serializers.SerializerMethodField()
864
+
865
+ def get_responsavel(self, obj):
866
+ """Retorna usuΓ‘rio responsΓ‘vel"""
867
+ try:
868
+ resp = obj.responsavel
869
+ return {
870
+ 'id': resp.pk,
871
+ 'email': resp.email,
872
+ 'full_name': resp.get_full_name(),
873
+ }
874
+ except:
875
+ return None
876
+
877
+ class Meta:
878
+ model = Tarefa
879
+ fields = [
880
+ 'id', 'titulo', 'descricao', 'status', 'prioridade',
881
+ 'organization', # OrganizaΓ§Γ£o dona da tarefa
882
+ 'user', # Quem criou
883
+ 'responsavel', # Quem vai fazer
884
+ 'created_at'
885
+ ]
886
+
887
+ # Response
888
+ {
889
+ "id": 1,
890
+ "titulo": "Implementar feature X",
891
+ "descricao": "DescriΓ§Γ£o detalhada...",
892
+ "status": "in_progress",
893
+ "prioridade": "high",
894
+ "organization": {
895
+ "id": 123,
896
+ "name": "Empresa XYZ"
897
+ },
898
+ "user": {
899
+ "id": 456,
900
+ "email": "gerente@xyz.com",
901
+ "full_name": "Gerente Silva"
902
+ },
903
+ "responsavel": {
904
+ "id": 789,
905
+ "email": "dev@xyz.com",
906
+ "full_name": "Dev Junior"
907
+ },
908
+ "created_at": "2025-10-01T10:00:00Z"
909
+ }
910
+ ```
911
+
912
+ ---
913
+
914
+ ## 🎨 Frontend - Consumindo a API
915
+
916
+ ### React Example
917
+
918
+ ```javascript
919
+ // components/RascunhoCard.jsx
920
+ function RascunhoCard({ rascunho }) {
921
+ return (
922
+ <div className="card">
923
+ <h3>{rascunho.titulo}</h3>
924
+ <p>{rascunho.conteudo}</p>
925
+
926
+ {/* Acessar dados aninhados */}
927
+ <div className="meta">
928
+ <span className="organization">
929
+ {rascunho.organization.name}
930
+ </span>
931
+ <span className="user">
932
+ Por: {rascunho.user.full_name}
933
+ </span>
934
+ </div>
935
+
936
+ {/* Verificar status */}
937
+ {!rascunho.organization.is_active && (
938
+ <div className="warning">
939
+ OrganizaΓ§Γ£o inativa
940
+ </div>
941
+ )}
942
+ </div>
943
+ );
944
+ }
945
+
946
+ // Uso
947
+ fetch('/api/rascunhos/1/')
948
+ .then(res => res.json())
949
+ .then(data => {
950
+ console.log(data.organization.name); // FΓ‘cil!
951
+ console.log(data.user.email); // Direto!
952
+ });
953
+ ```
954
+
955
+ ### Vue Example
956
+
957
+ ```vue
958
+ <template>
959
+ <div class="rascunho">
960
+ <h3>{{ rascunho.titulo }}</h3>
961
+
962
+ <!-- Dados da organizaΓ§Γ£o -->
963
+ <div class="organization-info">
964
+ <h4>{{ rascunho.organization.name }}</h4>
965
+ <p>{{ rascunho.organization.cnpj }}</p>
966
+ <p>{{ rascunho.organization.email }}</p>
967
+ </div>
968
+
969
+ <!-- Dados do usuΓ‘rio -->
970
+ <div class="user-info">
971
+ <span>Criado por: {{ rascunho.user.full_name }}</span>
972
+ <span>Email: {{ rascunho.user.email }}</span>
973
+ </div>
974
+ </div>
975
+ </template>
976
+
977
+ <script>
978
+ export default {
979
+ data() {
980
+ return {
981
+ rascunho: null
982
+ }
983
+ },
984
+ async mounted() {
985
+ const response = await fetch('/api/rascunhos/1/');
986
+ this.rascunho = await response.json();
987
+ }
988
+ }
989
+ </script>
990
+ ```
991
+
992
+ ---
993
+
994
+ ## βœ… Vantagens desta Abordagem
995
+
996
+ 1. **Mais SemΓ’ntico**: Dados relacionados agrupados
997
+ 2. **FΓ‘cil de Consumir**: Frontend acessa `obj.organization.name` diretamente
998
+ 3. **FlexΓ­vel**: Pode ter versΓ£o completa e simplificada
999
+ 4. **Type-Safe**: TypeScript/Flow adoram objetos aninhados
1000
+ 5. **Cacheable**: Pode cachear o objeto `organization` inteiro
1001
+ 6. **ReutilizΓ‘vel**: Mesma estrutura em todos os endpoints
1002
+
1003
+ ---
1004
+
1005
+ ## πŸ“‹ Checklist de ImplementaΓ§Γ£o
1006
+
1007
+ - [ ] Atualizar `shared_auth/serializers.py` com novos mixins
1008
+ - [ ] Reinstalar biblioteca: `pip install -e /path/to/shared-auth-lib`
1009
+ - [ ] Atualizar serializers existentes
1010
+ - [ ] Testar responses dos endpoints
1011
+ - [ ] Atualizar documentaΓ§Γ£o da API
1012
+ - [ ] Atualizar cΓ³digo do frontend
1013
+ - [ ] Verificar performance (N+1 queries)
1014
+ - [ ] Adicionar testes
1015
+
1016
+ Pronto! Agora seus serializers retornam dados organizados em objetos `organization` e `user`! πŸŽ‰