maquinaweb-shared-auth 0.2.60__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.
@@ -0,0 +1,1003 @@
1
+ Metadata-Version: 2.4
2
+ Name: maquinaweb-shared-auth
3
+ Version: 0.2.60
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: pyyaml>=6.0.3
21
+ Requires-Dist: setuptools>=70
22
+ Dynamic: requires-python
23
+
24
+ # 🔐 Maquinaweb Shared Auth
25
+
26
+ > Biblioteca Django para autenticação compartilhada entre múltiplos sistemas usando um único banco de dados centralizado.
27
+
28
+ [![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
29
+ [![Django](https://img.shields.io/badge/django-4.2+-green.svg)](https://www.djangoproject.com/)
30
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
31
+
32
+ ## 📋 Índice
33
+
34
+ - [Visão Geral](#-visão-geral)
35
+ - [Características](#-características)
36
+ - [Arquitetura](#️-arquitetura)
37
+ - [Instalação](#-instalação)
38
+ - [Configuração](#️-configuração)
39
+ - [Uso Básico](#-uso-básico)
40
+ - [Guias Avançados](#-guias-avançados)
41
+ - [API Reference](#-api-reference)
42
+
43
+ ---
44
+
45
+ ## 🎯 Visão Geral
46
+
47
+ A **Maquinaweb Shared Auth** permite que múltiplos sistemas Django compartilhem dados de autenticação, usuários e organizações através de um banco de dados centralizado, sem necessidade de requisições HTTP.
48
+
49
+ ### Problema Resolvido
50
+
51
+ Ao invés de:
52
+ - ❌ Duplicar dados de usuários em cada sistema
53
+ - ❌ Fazer requisições HTTP entre sistemas
54
+ - ❌ Manter múltiplos bancos de autenticação sincronizados
55
+
56
+ Você pode:
57
+ - ✅ Acessar dados de autenticação diretamente do banco central
58
+ - ✅ Usar a interface familiar do Django ORM
59
+ - ✅ Garantir consistência de dados entre sistemas
60
+ - ✅ Trabalhar com models read-only seguros
61
+
62
+ ---
63
+
64
+ ## ✨ Características
65
+
66
+ ### Core Features
67
+
68
+ - **🔐 Autenticação Centralizada**: Token-based authentication compartilhado
69
+ - **🏢 Multi-Tenancy**: Suporte completo a organizações e filiais
70
+ - **👥 Gestão de Membros**: Relacionamento usuários ↔ organizações
71
+ - **🔒 Read-Only Safety**: Proteção contra modificações acidentais
72
+ - **⚡ Performance**: Managers otimizados com prefetch automático
73
+ - **🎨 DRF Ready**: Mixins para serializers com dados aninhados
74
+
75
+ ### Componentes Principais
76
+
77
+ | Componente | Descrição |
78
+ |------------|-----------|
79
+ | **Models** | SharedOrganization, User, SharedMember, SharedToken |
80
+ | **Mixins** | OrganizationMixin, UserMixin, OrganizationUserMixin |
81
+ | **Serializers** | OrganizationSerializerMixin, UserSerializerMixin |
82
+ | **Authentication** | SharedTokenAuthentication |
83
+ | **Middleware** | SharedAuthMiddleware, OrganizationMiddleware |
84
+ | **Permissions** | IsAuthenticated, HasActiveOrganization, IsSameOrganization |
85
+ | **Managers** | Métodos otimizados com prefetch e validações |
86
+
87
+ ---
88
+
89
+ ## 🏗️ Arquitetura
90
+
91
+ ```
92
+ ┌─────────────────────────────────────┐
93
+ │ Sistema de Autenticação Central │
94
+ │ │
95
+ │ ┌──────────────┐ ┌────────────┐ │
96
+ │ │Organization │ │ User │ │
97
+ │ └──────┬───────┘ └─────┬──────┘ │
98
+ │ │ │ │
99
+ │ └────────┬───────┘ │
100
+ │ │ │
101
+ │ ┌──────▼──────┐ │
102
+ │ │ Member │ │
103
+ │ │ Token │ │
104
+ │ └─────────────┘ │
105
+ └──────────────────┬──────────────────┘
106
+
107
+ ┌────────────┴────────────┐
108
+ │ PostgreSQL/MySQL │
109
+ │ (auth_db) │
110
+ └────────────┬────────────┘
111
+
112
+ ┌────────────┴────────────┐
113
+ │ │
114
+ ┌─────▼─────┐ ┌─────▼─────┐
115
+ │ Sistema A │ │ Sistema B │
116
+ │ │ │ │
117
+ │ Pedidos │ │ Estoque │
118
+ │ ├─ org │ │ ├─ org │
119
+ │ └─ user │ │ └─ user │
120
+ └───────────┘ └───────────┘
121
+ ```
122
+
123
+ **Fluxo de Autenticação:**
124
+
125
+ 1. Cliente envia request com token no header
126
+ 2. Middleware valida token no banco `auth_db`
127
+ 3. Dados do usuário e organização são anexados ao `request`
128
+ 4. Sistema cliente acessa dados via ORM (read-only)
129
+
130
+ ---
131
+
132
+ ## 📦 Instalação
133
+
134
+ ### 1. Instalar a Biblioteca
135
+
136
+ ```bash
137
+ # Via pip (quando publicado)
138
+ pip install maquinaweb-shared-auth
139
+
140
+ # Ou modo desenvolvimento
141
+ pip install -e /path/to/maquinaweb-shared-auth
142
+ ```
143
+
144
+ ### 2. Adicionar ao requirements.txt
145
+
146
+ ```txt
147
+ Django>=4.2
148
+ djangorestframework>=3.14
149
+ maquinaweb-shared-auth>=0.2.25
150
+ ```
151
+
152
+ ---
153
+
154
+ ## ⚙️ Configuração
155
+
156
+ ### 1. Settings do Django
157
+
158
+ ```python
159
+ # settings.py
160
+
161
+ INSTALLED_APPS = [
162
+ 'django.contrib.admin',
163
+ 'django.contrib.auth',
164
+ 'django.contrib.contenttypes',
165
+ 'django.contrib.sessions',
166
+ 'rest_framework',
167
+
168
+ # Adicionar shared_auth
169
+ 'shared_auth',
170
+
171
+ # Suas apps
172
+ 'myapp',
173
+ ]
174
+ ```
175
+
176
+ ### 2. Configurar Banco de Dados
177
+
178
+ ```python
179
+ # settings.py
180
+
181
+ DATABASES = {
182
+ 'default': {
183
+ # Banco do sistema atual
184
+ 'ENGINE': 'django.db.backends.postgresql',
185
+ 'NAME': 'meu_sistema_db',
186
+ 'USER': 'meu_user',
187
+ 'PASSWORD': 'senha',
188
+ 'HOST': 'localhost',
189
+ 'PORT': '5432',
190
+ },
191
+ 'auth_db': {
192
+ # Banco centralizado de autenticação (READ-ONLY)
193
+ 'ENGINE': 'django.db.backends.postgresql',
194
+ 'NAME': 'sistema_auth_db',
195
+ 'USER': 'readonly_user',
196
+ 'PASSWORD': 'senha_readonly',
197
+ 'HOST': 'auth-server.example.com',
198
+ 'PORT': '5432',
199
+ }
200
+ }
201
+
202
+ # Router para direcionar queries
203
+ DATABASE_ROUTERS = ['shared_auth.router.SharedAuthRouter']
204
+ ```
205
+
206
+ ### 3. Configurar Autenticação (DRF)
207
+
208
+ ```python
209
+ # settings.py
210
+
211
+ REST_FRAMEWORK = {
212
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
213
+ 'shared_auth.authentication.SharedTokenAuthentication',
214
+ ],
215
+ 'DEFAULT_PERMISSION_CLASSES': [
216
+ 'shared_auth.permissions.IsAuthenticated',
217
+ ],
218
+ }
219
+ ```
220
+
221
+ ### 4. Configurar Middleware (Opcional)
222
+
223
+ ```python
224
+ # settings.py
225
+
226
+ MIDDLEWARE = [
227
+ 'django.middleware.security.SecurityMiddleware',
228
+ 'django.contrib.sessions.middleware.SessionMiddleware',
229
+ 'django.middleware.common.CommonMiddleware',
230
+
231
+ # Middlewares da shared_auth
232
+ 'shared_auth.middleware.SharedAuthMiddleware',
233
+ 'shared_auth.middleware.OrganizationMiddleware',
234
+
235
+ 'django.middleware.csrf.CsrfViewMiddleware',
236
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
237
+ 'django.contrib.messages.middleware.MessageMiddleware',
238
+ ]
239
+ ```
240
+
241
+ ### 5. Configurar Tabelas (Opcional)
242
+
243
+ ```python
244
+ # settings.py
245
+
246
+ # Customizar nomes das tabelas (se necessário)
247
+ SHARED_AUTH_ORGANIZATION_TABLE = 'organization_organization'
248
+ SHARED_AUTH_USER_TABLE = 'auth_user'
249
+ SHARED_AUTH_MEMBER_TABLE = 'organization_member'
250
+ SHARED_AUTH_TOKEN_TABLE = 'authtoken_token'
251
+ ```
252
+
253
+ ### 6. Criar Usuário Read-Only no PostgreSQL
254
+
255
+ ```sql
256
+ -- No servidor de autenticação
257
+ CREATE USER readonly_user WITH PASSWORD 'senha_segura_aqui';
258
+
259
+ -- Conceder permissões
260
+ GRANT CONNECT ON DATABASE sistema_auth_db TO readonly_user;
261
+ GRANT USAGE ON SCHEMA public TO readonly_user;
262
+ GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_user;
263
+
264
+ -- Para tabelas futuras
265
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public
266
+ GRANT SELECT ON TABLES TO readonly_user;
267
+
268
+ -- Garantir read-only
269
+ ALTER USER readonly_user SET default_transaction_read_only = on;
270
+ ```
271
+
272
+ ---
273
+
274
+ ## 🚀 Uso Básico
275
+
276
+ ### 1. Models com Mixins
277
+
278
+ ```python
279
+ # myapp/models.py
280
+ from django.db import models
281
+ from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
282
+ from shared_auth.managers import BaseAuthManager
283
+
284
+ class Pedido(OrganizationUserMixin, TimestampedMixin):
285
+ """Model que pertence a organização e usuário"""
286
+
287
+ numero = models.CharField(max_length=20, unique=True)
288
+ valor_total = models.DecimalField(max_digits=10, decimal_places=2)
289
+ status = models.CharField(max_length=20, default='pending')
290
+
291
+ objects = BaseAuthManager()
292
+
293
+ def __str__(self):
294
+ return f"Pedido {self.numero}"
295
+ ```
296
+
297
+ **O que você ganha automaticamente:**
298
+ - ✅ Campos: `organization_id`, `user_id`, `created_at`, `updated_at`
299
+ - ✅ Properties: `organization`, `user`, `organization_name`, `user_email`
300
+ - ✅ Métodos: `validate_user_belongs_to_organization()`, `user_can_access()`
301
+
302
+ ### 2. Serializers com Dados Aninhados
303
+
304
+ ```python
305
+ # myapp/serializers.py
306
+ from rest_framework import serializers
307
+ from shared_auth.serializers import OrganizationUserSerializerMixin
308
+ from .models import Pedido
309
+
310
+ class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
311
+ class Meta:
312
+ model = Pedido
313
+ fields = [
314
+ 'id', 'numero', 'valor_total', 'status',
315
+ 'organization', # Objeto completo
316
+ 'user', # Objeto completo
317
+ 'created_at',
318
+ ]
319
+ read_only_fields = ['organization', 'user', 'created_at']
320
+ ```
321
+
322
+ **Response JSON:**
323
+ ```json
324
+ {
325
+ "id": 1,
326
+ "numero": "PED-001",
327
+ "valor_total": "1500.00",
328
+ "status": "pending",
329
+ "organization": {
330
+ "id": 123,
331
+ "name": "Empresa XYZ Ltda",
332
+ "fantasy_name": "XYZ",
333
+ "cnpj": "12.345.678/0001-90",
334
+ "email": "contato@xyz.com",
335
+ "is_active": true
336
+ },
337
+ "user": {
338
+ "id": 456,
339
+ "username": "joao.silva",
340
+ "email": "joao@xyz.com",
341
+ "full_name": "João Silva",
342
+ "is_active": true
343
+ },
344
+ "created_at": "2025-10-01T10:00:00Z"
345
+ }
346
+ ```
347
+
348
+ ### 3. ViewSets com Organização
349
+
350
+ ```python
351
+ # myapp/views.py
352
+ from rest_framework import viewsets
353
+ from shared_auth.mixins import LoggedOrganizationMixin
354
+ from shared_auth.permissions import HasActiveOrganization, IsSameOrganization
355
+ from .models import Pedido
356
+ from .serializers import PedidoSerializer
357
+
358
+ class PedidoViewSet(LoggedOrganizationMixin, viewsets.ModelViewSet):
359
+ """
360
+ ViewSet que filtra automaticamente por organização logada
361
+ """
362
+ serializer_class = PedidoSerializer
363
+ permission_classes = [HasActiveOrganization, IsSameOrganization]
364
+
365
+ # get_queryset() já filtra por organization_id automaticamente
366
+ # perform_create() já adiciona organization_id automaticamente
367
+ ```
368
+
369
+ ### 4. Acessar Dados Compartilhados
370
+
371
+ ```python
372
+ # Em qualquer lugar do código
373
+ from shared_auth.models import SharedOrganization, User, SharedMember
374
+
375
+ # Buscar organização
376
+ org = SharedOrganization.objects.get_or_fail(123)
377
+ print(org.name) # "Empresa XYZ"
378
+ print(org.members) # QuerySet de membros
379
+
380
+ # Buscar usuário
381
+ user = User.objects.get_or_fail(456)
382
+ print(user.email) # "joao@xyz.com"
383
+ print(user.organizations) # Organizações do usuário
384
+
385
+ # Verificar membership
386
+ member = SharedMember.objects.filter(
387
+ user_id=456,
388
+ organization_id=123
389
+ ).first()
390
+
391
+ if member:
392
+ print(f"{member.user.email} é membro de {member.organization.name}")
393
+ ```
394
+
395
+ ---
396
+
397
+ ## 📚 Guias Avançados
398
+
399
+ ### Mixins para Models
400
+
401
+ #### 1. OrganizationMixin
402
+ Para models que pertencem apenas a uma organização.
403
+
404
+ ```python
405
+ from shared_auth.mixins import OrganizationMixin
406
+
407
+ class EmpresaConfig(OrganizationMixin):
408
+ tema_cor = models.CharField(max_length=7, default='#3490dc')
409
+ logo = models.ImageField(upload_to='logos/')
410
+
411
+ # Uso
412
+ config = EmpresaConfig.objects.create(organization_id=123, tema_cor='#ff0000')
413
+ print(config.organization.name) # Acesso automático
414
+ print(config.organization_members) # Membros da organização
415
+ ```
416
+
417
+ #### 2. UserMixin
418
+ Para models que pertencem apenas a um usuário.
419
+
420
+ ```python
421
+ from shared_auth.mixins import UserMixin
422
+
423
+ class UserPreferences(UserMixin):
424
+ theme = models.CharField(max_length=20, default='light')
425
+ notifications_enabled = models.BooleanField(default=True)
426
+
427
+ # Uso
428
+ prefs = UserPreferences.objects.create(user_id=456, theme='dark')
429
+ print(prefs.user.email)
430
+ print(prefs.user_full_name)
431
+ ```
432
+
433
+ #### 3. OrganizationUserMixin
434
+ Para models que pertencem a organização E usuário (mais comum).
435
+
436
+ ```python
437
+ from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
438
+
439
+ class Tarefa(OrganizationUserMixin, TimestampedMixin):
440
+ titulo = models.CharField(max_length=200)
441
+ descricao = models.TextField()
442
+ status = models.CharField(max_length=20, default='pending')
443
+
444
+ # Uso
445
+ tarefa = Tarefa.objects.create(
446
+ organization_id=123,
447
+ user_id=456,
448
+ titulo='Implementar feature X'
449
+ )
450
+
451
+ # Validações
452
+ if tarefa.validate_user_belongs_to_organization():
453
+ print("✓ Usuário pertence à organização")
454
+
455
+ if tarefa.user_can_access(outro_user_id):
456
+ print("✓ Outro usuário pode acessar")
457
+ ```
458
+
459
+ ### Managers Otimizados
460
+
461
+ ```python
462
+ from shared_auth.managers import BaseAuthManager
463
+
464
+ class Pedido(OrganizationUserMixin):
465
+ # ...
466
+ objects = BaseAuthManager()
467
+
468
+ # Filtrar por organização
469
+ pedidos = Pedido.objects.for_organization(123)
470
+
471
+ # Filtrar por usuário
472
+ meus_pedidos = Pedido.objects.for_user(456)
473
+
474
+ # Prefetch automático (evita N+1)
475
+ pedidos = Pedido.objects.with_auth_data()
476
+ for pedido in pedidos:
477
+ print(pedido.organization.name) # Sem query adicional
478
+ print(pedido.user.email) # Sem query adicional
479
+ ```
480
+
481
+ ### Serializers - Variações
482
+
483
+ #### Versão Completa (Detail)
484
+ ```python
485
+ from shared_auth.serializers import OrganizationUserSerializerMixin
486
+
487
+ class PedidoDetailSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
488
+ class Meta:
489
+ model = Pedido
490
+ fields = ['id', 'numero', 'organization', 'user', 'created_at']
491
+ ```
492
+
493
+ #### Versão Simplificada (List)
494
+ ```python
495
+ from shared_auth.serializers import (
496
+ OrganizationSimpleSerializerMixin,
497
+ UserSimpleSerializerMixin
498
+ )
499
+
500
+ class PedidoListSerializer(
501
+ OrganizationSimpleSerializerMixin,
502
+ UserSimpleSerializerMixin,
503
+ serializers.ModelSerializer
504
+ ):
505
+ class Meta:
506
+ model = Pedido
507
+ fields = ['id', 'numero', 'organization', 'user']
508
+
509
+ # Response com dados reduzidos
510
+ {
511
+ "id": 1,
512
+ "numero": "PED-001",
513
+ "organization": {
514
+ "id": 123,
515
+ "name": "Empresa XYZ",
516
+ "cnpj": "12.345.678/0001-90"
517
+ },
518
+ "user": {
519
+ "id": 456,
520
+ "email": "joao@xyz.com",
521
+ "full_name": "João Silva"
522
+ }
523
+ }
524
+ ```
525
+
526
+ #### Customização Avançada
527
+ ```python
528
+ class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
529
+
530
+ def get_organization(self, obj):
531
+ """Override para adicionar campos customizados"""
532
+ org_data = super().get_organization(obj)
533
+
534
+ if org_data:
535
+ # Adicionar dados extras
536
+ org_data['logo_url'] = f"/logos/{obj.organization_id}.png"
537
+ org_data['member_count'] = obj.organization.members.count()
538
+
539
+ return org_data
540
+ ```
541
+
542
+ ### Middleware
543
+
544
+ #### SharedAuthMiddleware
545
+ Autentica usuário baseado no token.
546
+
547
+ ```python
548
+ # settings.py
549
+ MIDDLEWARE = [
550
+ # ...
551
+ 'shared_auth.middleware.SharedAuthMiddleware',
552
+ ]
553
+ ```
554
+
555
+ **Busca token em:**
556
+ - Header: `Authorization: Token <token>`
557
+ - Header: `X-Auth-Token: <token>`
558
+ - Cookie: `auth_token`
559
+
560
+ **Adiciona ao request:**
561
+ - `request.user` - Objeto User autenticado
562
+ - `request.auth` - Token object
563
+
564
+ #### OrganizationMiddleware
565
+ Adiciona organização logada ao request.
566
+
567
+ ```python
568
+ # settings.py
569
+ MIDDLEWARE = [
570
+ 'shared_auth.middleware.SharedAuthMiddleware',
571
+ 'shared_auth.middleware.OrganizationMiddleware', # Depois do Auth
572
+ ]
573
+ ```
574
+
575
+ **Busca organização:**
576
+ 1. Header `X-Organization: <org_id>`
577
+ 2. Primeira organização do usuário autenticado
578
+
579
+ **Adiciona ao request:**
580
+ - `request.organization_id` - ID da organização
581
+ - `request.organization` - Objeto SharedOrganization
582
+
583
+ **Uso em views:**
584
+ ```python
585
+ def my_view(request):
586
+ org_id = request.organization_id
587
+ org = request.organization
588
+
589
+ if org:
590
+ print(f"Organização logada: {org.name}")
591
+ ```
592
+
593
+ ### Permissions
594
+
595
+ ```python
596
+ from shared_auth.permissions import (
597
+ IsAuthenticated,
598
+ HasActiveOrganization,
599
+ IsSameOrganization,
600
+ IsOwnerOrSameOrganization,
601
+ )
602
+
603
+ class PedidoViewSet(viewsets.ModelViewSet):
604
+ permission_classes = [
605
+ IsAuthenticated, # Requer autenticação
606
+ HasActiveOrganization, # Requer organização ativa
607
+ IsSameOrganization, # Objeto da mesma org
608
+ ]
609
+
610
+ # Ou combinações
611
+ class TarefaViewSet(viewsets.ModelViewSet):
612
+ permission_classes = [IsOwnerOrSameOrganization]
613
+ # Permite se for dono OU da mesma organização
614
+ ```
615
+
616
+ ### Authentication
617
+
618
+ ```python
619
+ # Em qualquer view/viewset DRF
620
+ from shared_auth.authentication import SharedTokenAuthentication
621
+
622
+ class MyAPIView(APIView):
623
+ authentication_classes = [SharedTokenAuthentication]
624
+
625
+ def get(self, request):
626
+ user = request.user # User autenticado
627
+ token = request.auth # SharedToken object
628
+
629
+ return Response({
630
+ 'user': user.email,
631
+ 'token_created': token.created
632
+ })
633
+ ```
634
+
635
+ ---
636
+
637
+ ## 🔍 API Reference
638
+
639
+ ### Models
640
+
641
+ #### SharedOrganization
642
+
643
+ ```python
644
+ from shared_auth.models import SharedOrganization
645
+
646
+ # Campos
647
+ org.id
648
+ org.name
649
+ org.fantasy_name
650
+ org.cnpj
651
+ org.email
652
+ org.telephone
653
+ org.cellphone
654
+ org.image_organization
655
+ org.is_branch
656
+ org.main_organization_id
657
+ org.created_at
658
+ org.updated_at
659
+ org.deleted_at
660
+
661
+ # Properties
662
+ org.main_organization # SharedOrganization | None
663
+ org.branches # QuerySet[SharedOrganization]
664
+ org.members # QuerySet[SharedMember]
665
+ org.users # QuerySet[User]
666
+
667
+ # Métodos
668
+ org.is_active() # bool
669
+ ```
670
+
671
+ #### User
672
+
673
+ ```python
674
+ from shared_auth.models import User
675
+
676
+ # Campos (AbstractUser + custom)
677
+ user.id
678
+ user.username
679
+ user.email
680
+ user.first_name
681
+ user.last_name
682
+ user.is_active
683
+ user.is_staff
684
+ user.is_superuser
685
+ user.date_joined
686
+ user.last_login
687
+ user.avatar
688
+ user.createdat
689
+ user.updatedat
690
+ user.deleted_at
691
+
692
+ # Properties
693
+ user.organizations # QuerySet[SharedOrganization]
694
+
695
+ # Métodos
696
+ user.get_full_name() # str
697
+ user.get_org(organization_id) # SharedOrganization | raise
698
+ ```
699
+
700
+ #### SharedMember
701
+
702
+ ```python
703
+ from shared_auth.models import SharedMember
704
+
705
+ # Campos
706
+ member.id
707
+ member.user_id
708
+ member.organization_id
709
+ member.metadata # JSONField
710
+
711
+ # Properties
712
+ member.user # User
713
+ member.organization # SharedOrganization
714
+ ```
715
+
716
+ #### SharedToken
717
+
718
+ ```python
719
+ from shared_auth.models import SharedToken
720
+
721
+ # Campos
722
+ token.key # Primary Key
723
+ token.user_id
724
+ token.created
725
+
726
+ # Properties
727
+ token.user # User
728
+
729
+ # Métodos
730
+ token.is_valid() # bool
731
+ ```
732
+
733
+ ### Managers
734
+
735
+ #### SharedOrganizationManager
736
+
737
+ ```python
738
+ from shared_auth.models import SharedOrganization
739
+
740
+ SharedOrganization.objects.get_or_fail(123) # Org | raise OrganizationNotFoundError
741
+ SharedOrganization.objects.active() # QuerySet (deleted_at is null)
742
+ SharedOrganization.objects.branches() # QuerySet (is_branch=True)
743
+ SharedOrganization.objects.main_organizations() # QuerySet (is_branch=False)
744
+ SharedOrganization.objects.by_cnpj('12.345.678/0001-90') # Org | None
745
+ ```
746
+
747
+ #### UserManager
748
+
749
+ ```python
750
+ from shared_auth.models import User
751
+
752
+ User.objects.get_or_fail(456) # User | raise UserNotFoundError
753
+ User.objects.active() # QuerySet (is_active=True, deleted_at is null)
754
+ User.objects.by_email('user@example.com') # User | None
755
+ ```
756
+
757
+ #### SharedMemberManager
758
+
759
+ ```python
760
+ from shared_auth.models import SharedMember
761
+
762
+ SharedMember.objects.for_user(456) # QuerySet
763
+ SharedMember.objects.for_organization(123) # QuerySet
764
+ ```
765
+
766
+ #### BaseAuthManager (para seus models)
767
+
768
+ ```python
769
+ # Quando usa OrganizationMixin
770
+ Model.objects.for_organization(123) # QuerySet
771
+ Model.objects.for_organizations([123, 456]) # QuerySet
772
+ Model.objects.with_organization_data() # List com prefetch
773
+
774
+ # Quando usa UserMixin
775
+ Model.objects.for_user(456) # QuerySet
776
+ Model.objects.for_users([456, 789]) # QuerySet
777
+ Model.objects.with_user_data() # List com prefetch
778
+
779
+ # Quando usa OrganizationUserMixin
780
+ Model.objects.with_auth_data() # List com prefetch de org e user
781
+ Model.objects.create_with_validation(
782
+ organization_id=123,
783
+ user_id=456,
784
+ **kwargs
785
+ ) # Valida membership antes de criar
786
+ ```
787
+
788
+ ### Exceptions
789
+
790
+ ```python
791
+ from shared_auth.exceptions import (
792
+ SharedAuthError,
793
+ OrganizationNotFoundError,
794
+ UserNotFoundError,
795
+ DatabaseConnectionError,
796
+ )
797
+
798
+ try:
799
+ org = SharedOrganization.objects.get_or_fail(999)
800
+ except OrganizationNotFoundError as e:
801
+ print(e) # "Organização com ID 999 não encontrada"
802
+ ```
803
+
804
+ ---
805
+
806
+ ## 🎯 Casos de Uso Reais
807
+
808
+ ### Sistema de Pedidos Multi-Tenant
809
+
810
+ ```python
811
+ # models.py
812
+ from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
813
+
814
+ class Pedido(OrganizationUserMixin, TimestampedMixin):
815
+ numero = models.CharField(max_length=20, unique=True)
816
+ valor_total = models.DecimalField(max_digits=10, decimal_places=2)
817
+ status = models.CharField(max_length=20)
818
+
819
+ objects = BaseAuthManager()
820
+
821
+ class ItemPedido(models.Model):
822
+ pedido = models.ForeignKey(Pedido, related_name='itens')
823
+ produto = models.CharField(max_length=200)
824
+ quantidade = models.IntegerField()
825
+ valor_unitario = models.DecimalField(max_digits=10, decimal_places=2)
826
+
827
+ # serializers.py
828
+ from shared_auth.serializers import OrganizationUserSerializerMixin
829
+
830
+ class ItemPedidoSerializer(serializers.ModelSerializer):
831
+ class Meta:
832
+ model = ItemPedido
833
+ fields = ['id', 'produto', 'quantidade', 'valor_unitario']
834
+
835
+ class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
836
+ itens = ItemPedidoSerializer(many=True, read_only=True)
837
+
838
+ class Meta:
839
+ model = Pedido
840
+ fields = [
841
+ 'id', 'numero', 'valor_total', 'status',
842
+ 'organization', 'user', 'itens', 'created_at'
843
+ ]
844
+
845
+ # views.py
846
+ from shared_auth.mixins import LoggedOrganizationMixin
847
+ from shared_auth.permissions import HasActiveOrganization
848
+
849
+ class PedidoViewSet(LoggedOrganizationMixin, viewsets.ModelViewSet):
850
+ serializer_class = PedidoSerializer
851
+ permission_classes = [HasActiveOrganization]
852
+
853
+ def get_queryset(self):
854
+ # Já filtra por organization_id automaticamente
855
+ return super().get_queryset().with_auth_data()
856
+ ```
857
+
858
+ ### Sistema de Tarefas com Responsáveis
859
+
860
+ ```python
861
+ # models.py
862
+ class Tarefa(OrganizationUserMixin, TimestampedMixin):
863
+ """
864
+ user_id = criador
865
+ responsavel_id = quem vai executar
866
+ """
867
+ titulo = models.CharField(max_length=200)
868
+ descricao = models.TextField()
869
+ responsavel_id = models.IntegerField()
870
+ status = models.CharField(max_length=20, default='pending')
871
+
872
+ objects = BaseAuthManager()
873
+
874
+ @property
875
+ def responsavel(self):
876
+ """Acessa usuário responsável"""
877
+ if not hasattr(self, '_cached_responsavel'):
878
+ from shared_auth.models import User
879
+ self._cached_responsavel = User.objects.get_or_fail(self.responsavel_id)
880
+ return self._cached_responsavel
881
+
882
+ # serializers.py
883
+ class TarefaSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
884
+ responsavel = serializers.SerializerMethodField()
885
+
886
+ def get_responsavel(self, obj):
887
+ try:
888
+ resp = obj.responsavel
889
+ return {
890
+ 'id': resp.pk,
891
+ 'email': resp.email,
892
+ 'full_name': resp.get_full_name(),
893
+ }
894
+ except:
895
+ return None
896
+
897
+ class Meta:
898
+ model = Tarefa
899
+ fields = [
900
+ 'id', 'titulo', 'descricao', 'status',
901
+ 'organization', # Organização dona
902
+ 'user', # Criador
903
+ 'responsavel', # Executor
904
+ 'created_at'
905
+ ]
906
+ ```
907
+
908
+ ---
909
+
910
+ ## 🔧 Troubleshooting
911
+
912
+ ### Problema: Queries lentas (N+1)
913
+
914
+ **Solução:** Use os managers com prefetch
915
+
916
+ ```python
917
+ # ❌ Ruim - Causa N+1
918
+ pedidos = Pedido.objects.all()
919
+ for pedido in pedidos:
920
+ print(pedido.organization.name) # Query por item!
921
+
922
+ # ✅ Bom - 3 queries total
923
+ pedidos = Pedido.objects.with_auth_data()
924
+ for pedido in pedidos:
925
+ print(pedido.organization.name) # Sem query adicional
926
+ ```
927
+
928
+ ### Problema: OrganizationNotFoundError
929
+
930
+ **Causa:** ID de organização inválido ou deletada
931
+
932
+ **Solução:**
933
+ ```python
934
+ # Usar try/except
935
+ try:
936
+ org = SharedOrganization.objects.get_or_fail(org_id)
937
+ except OrganizationNotFoundError:
938
+ # Tratar erro
939
+ return Response({'error': 'Organização não encontrada'}, status=404)
940
+
941
+ # Ou usar filter
942
+ org = SharedOrganization.objects.filter(pk=org_id).first()
943
+ if not org:
944
+ # Tratar
945
+ ```
946
+
947
+ ### Problema: Erro de conexão com auth_db
948
+
949
+ **Solução:** Verificar configuração do database router e permissões
950
+
951
+ ```python
952
+ # Testar conexão
953
+ from django.db import connections
954
+
955
+ connection = connections['auth_db']
956
+ with connection.cursor() as cursor:
957
+ cursor.execute("SELECT 1")
958
+ print("✓ Conexão OK")
959
+ ```
960
+
961
+ ---
962
+
963
+ ## 📝 Changelog
964
+
965
+ ### v0.2.25
966
+ - ✨ Adicionado suporte a imagens (avatar, logo)
967
+ - ✨ StorageBackend para arquivos compartilhados
968
+ - 🐛 Correções nos serializers
969
+ - 📚 Documentação melhorada
970
+
971
+ ### v0.2.0
972
+ - ✨ Middlewares: SharedAuthMiddleware, OrganizationMiddleware
973
+ - ✨ Permissions customizadas
974
+ - ✨ Managers otimizados com prefetch
975
+ - ✨ Serializer mixins com dados aninhados
976
+
977
+ ### v0.1.0
978
+ - 🎉 Versão inicial
979
+ - ✨ Models compartilhados
980
+ - ✨ Mixins básicos
981
+ - ✨ Autenticação via token
982
+
983
+ ---
984
+
985
+ ## 📄 Licença
986
+
987
+ MIT License - veja [LICENSE](LICENSE) para detalhes.
988
+
989
+ ---
990
+
991
+ ## 🤝 Contribuindo
992
+
993
+ Contribuições são bem-vindas! Por favor, abra uma issue ou pull request.
994
+
995
+ ---
996
+
997
+ ## 📧 Suporte
998
+
999
+ Para suporte, abra uma issue no GitHub ou entre em contato com a equipe Maquinaweb.
1000
+
1001
+ ---
1002
+
1003
+ **Desenvolvido com ❤️ por Maquinaweb**