maquinaweb-shared-auth 0.2.25__py3-none-any.whl → 0.2.27__py3-none-any.whl

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.

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