maquinaweb-shared-auth 0.2.32__tar.gz → 0.2.34__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.

Files changed (30) hide show
  1. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/PKG-INFO +1 -1
  2. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/maquinaweb_shared_auth.egg-info/PKG-INFO +1 -1
  3. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/maquinaweb_shared_auth.egg-info/SOURCES.txt +2 -0
  4. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/pyproject.toml +1 -1
  5. maquinaweb_shared_auth-0.2.34/shared_auth/__init__.py +21 -0
  6. maquinaweb_shared_auth-0.2.32/shared_auth/models.py → maquinaweb_shared_auth-0.2.34/shared_auth/abstract_models.py +123 -30
  7. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/authentication.py +12 -4
  8. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/decorators.py +10 -5
  9. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/managers.py +15 -8
  10. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/middleware.py +18 -8
  11. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/mixins.py +15 -11
  12. maquinaweb_shared_auth-0.2.34/shared_auth/models.py +58 -0
  13. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/permissions.py +4 -3
  14. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/router.py +1 -1
  15. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/serializers.py +29 -4
  16. maquinaweb_shared_auth-0.2.34/shared_auth/utils.py +113 -0
  17. maquinaweb_shared_auth-0.2.32/shared_auth/__init__.py +0 -6
  18. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/README.md +0 -0
  19. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/maquinaweb_shared_auth.egg-info/dependency_links.txt +0 -0
  20. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/maquinaweb_shared_auth.egg-info/requires.txt +0 -0
  21. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/maquinaweb_shared_auth.egg-info/top_level.txt +0 -0
  22. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/setup.cfg +0 -0
  23. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/setup.py +0 -0
  24. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/app.py +0 -0
  25. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/conf.py +0 -0
  26. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/exceptions.py +0 -0
  27. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/fields.py +0 -0
  28. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/storage_backend.py +0 -0
  29. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/urls.py +0 -0
  30. {maquinaweb_shared_auth-0.2.32 → maquinaweb_shared_auth-0.2.34}/shared_auth/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maquinaweb-shared-auth
3
- Version: 0.2.32
3
+ Version: 0.2.34
4
4
  Summary: Models read-only para autenticação compartilhada entre projetos Django.
5
5
  Author-email: Seu Nome <seuemail@dominio.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maquinaweb-shared-auth
3
- Version: 0.2.32
3
+ Version: 0.2.34
4
4
  Summary: Models read-only para autenticação compartilhada entre projetos Django.
5
5
  Author-email: Seu Nome <seuemail@dominio.com>
6
6
  License: MIT
@@ -7,6 +7,7 @@ maquinaweb_shared_auth.egg-info/dependency_links.txt
7
7
  maquinaweb_shared_auth.egg-info/requires.txt
8
8
  maquinaweb_shared_auth.egg-info/top_level.txt
9
9
  shared_auth/__init__.py
10
+ shared_auth/abstract_models.py
10
11
  shared_auth/app.py
11
12
  shared_auth/authentication.py
12
13
  shared_auth/conf.py
@@ -22,4 +23,5 @@ shared_auth/router.py
22
23
  shared_auth/serializers.py
23
24
  shared_auth/storage_backend.py
24
25
  shared_auth/urls.py
26
+ shared_auth/utils.py
25
27
  shared_auth/views.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maquinaweb-shared-auth"
7
- version = "0.2.32"
7
+ version = "0.2.34"
8
8
  description = "Models read-only para autenticação compartilhada entre projetos Django."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -0,0 +1,21 @@
1
+ """
2
+ Biblioteca compartilhada para acesso aos models de autenticação
3
+ """
4
+
5
+ __version__ = '1.0.0'
6
+ default_app_config = "shared_auth.app.SharedAuthConfig"
7
+
8
+ # Exportar utilitários para facilitar importação
9
+ from .utils import (
10
+ get_member_model,
11
+ get_organization_model,
12
+ get_token_model,
13
+ get_user_model,
14
+ )
15
+
16
+ __all__ = [
17
+ 'get_token_model',
18
+ 'get_organization_model',
19
+ 'get_user_model',
20
+ 'get_member_model',
21
+ ]
@@ -1,6 +1,6 @@
1
1
  """
2
- Models READ-ONLY para acesso aos dados de autenticação
3
- ATENÇÃO: Estes models NÃO devem ser usados para criar migrations
2
+ Models abstratos para customização
3
+ Estes models podem ser herdados nos apps clientes para adicionar campos e métodos customizados
4
4
  """
5
5
 
6
6
  import os
@@ -14,10 +14,33 @@ from .managers import SharedMemberManager, SharedOrganizationManager, UserManage
14
14
  from .storage_backend import Storage
15
15
 
16
16
 
17
- class SharedToken(models.Model):
17
+ def organization_image_path(instance, filename):
18
+ return os.path.join(
19
+ "organization",
20
+ str(instance.pk),
21
+ "images",
22
+ filename,
23
+ )
24
+
25
+
26
+ class AbstractSharedToken(models.Model):
18
27
  """
19
- Model READ-ONLY da tabela authtoken_token
28
+ Model abstrato READ-ONLY da tabela authtoken_token
20
29
  Usado para validar tokens em outros sistemas
30
+
31
+ Para customizar, crie um model no seu app:
32
+
33
+ from shared_auth.abstract_models import AbstractSharedToken
34
+
35
+ class CustomToken(AbstractSharedToken):
36
+ # Adicione campos customizados
37
+ custom_field = models.CharField(max_length=100)
38
+
39
+ class Meta(AbstractSharedToken.Meta):
40
+ pass
41
+
42
+ E configure no settings.py:
43
+ SHARED_AUTH_TOKEN_MODEL = 'seu_app.CustomToken'
21
44
  """
22
45
 
23
46
  key = models.CharField(max_length=40, primary_key=True)
@@ -27,6 +50,7 @@ class SharedToken(models.Model):
27
50
  objects = models.Manager()
28
51
 
29
52
  class Meta:
53
+ abstract = True
30
54
  managed = False
31
55
  db_table = TOKEN_TABLE
32
56
 
@@ -36,7 +60,10 @@ class SharedToken(models.Model):
36
60
  @property
37
61
  def user(self):
38
62
  """Acessa usuário do token"""
63
+ from .utils import get_user_model
64
+
39
65
  if not hasattr(self, "_cached_user"):
66
+ User = get_user_model()
40
67
  self._cached_user = User.objects.get_or_fail(self.user_id)
41
68
  return self._cached_user
42
69
 
@@ -46,19 +73,24 @@ class SharedToken(models.Model):
46
73
  return True
47
74
 
48
75
 
49
- def organization_image_path(instance, filename):
50
- return os.path.join(
51
- "organization",
52
- str(instance.pk),
53
- "images",
54
- filename,
55
- )
56
-
57
-
58
- class SharedOrganization(models.Model):
76
+ class AbstractSharedOrganization(models.Model):
59
77
  """
60
- Model READ-ONLY da tabela organization
78
+ Model abstrato READ-ONLY da tabela organization
61
79
  Usado para acessar dados de organizações em outros sistemas
80
+
81
+ Para customizar, crie um model no seu app:
82
+
83
+ from shared_auth.abstract_models import AbstractSharedOrganization
84
+
85
+ class CustomOrganization(AbstractSharedOrganization):
86
+ # Adicione campos customizados
87
+ custom_field = models.CharField(max_length=100)
88
+
89
+ class Meta(AbstractSharedOrganization.Meta):
90
+ pass
91
+
92
+ E configure no settings.py:
93
+ SHARED_AUTH_ORGANIZATION_MODEL = 'seu_app.CustomOrganization'
62
94
  """
63
95
 
64
96
  # Campos principais
@@ -75,7 +107,8 @@ class SharedOrganization(models.Model):
75
107
  # Relacionamentos
76
108
  main_organization_id = models.IntegerField(null=True, blank=True)
77
109
  is_branch = models.BooleanField(default=False)
78
-
110
+ metadata = models.JSONField(default=dict)
111
+
79
112
  # Metadados
80
113
  created_at = models.DateTimeField()
81
114
  updated_at = models.DateTimeField()
@@ -84,7 +117,8 @@ class SharedOrganization(models.Model):
84
117
  objects = SharedOrganizationManager()
85
118
 
86
119
  class Meta:
87
- managed = False # CRITICAL: Não gera migrations
120
+ abstract = True
121
+ managed = False
88
122
  db_table = ORGANIZATION_TABLE
89
123
 
90
124
  def __str__(self):
@@ -99,8 +133,11 @@ class SharedOrganization(models.Model):
99
133
  if org.is_branch:
100
134
  main = org.main_organization
101
135
  """
136
+ from .utils import get_organization_model
137
+
102
138
  if self.main_organization_id:
103
- return SharedOrganization.objects.get_or_fail(self.main_organization_id)
139
+ Organization = get_organization_model()
140
+ return Organization.objects.get_or_fail(self.main_organization_id)
104
141
  return None
105
142
 
106
143
  @property
@@ -111,7 +148,10 @@ class SharedOrganization(models.Model):
111
148
  Usage:
112
149
  branches = org.branches
113
150
  """
114
- return SharedOrganization.objects.filter(main_organization_id=self.pk)
151
+ from .utils import get_organization_model
152
+
153
+ Organization = get_organization_model()
154
+ return Organization.objects.filter(main_organization_id=self.pk)
115
155
 
116
156
  @property
117
157
  def members(self):
@@ -123,7 +163,10 @@ class SharedOrganization(models.Model):
123
163
  for member in members:
124
164
  print(member.user.email)
125
165
  """
126
- return SharedMember.objects.for_organization(self.pk)
166
+ from .utils import get_member_model
167
+
168
+ Member = get_member_model()
169
+ return Member.objects.for_organization(self.pk)
127
170
 
128
171
  @property
129
172
  def users(self):
@@ -133,6 +176,9 @@ class SharedOrganization(models.Model):
133
176
  Usage:
134
177
  users = org.users
135
178
  """
179
+ from .utils import get_user_model
180
+
181
+ User = get_user_model()
136
182
  return User.objects.filter(
137
183
  id__in=self.members.values_list("user_id", flat=True)
138
184
  )
@@ -142,14 +188,29 @@ class SharedOrganization(models.Model):
142
188
  return self.deleted_at is None
143
189
 
144
190
 
145
- class User(AbstractUser):
191
+ class AbstractUser(AbstractUser):
146
192
  """
147
- Model READ-ONLY da tabela auth_user
193
+ Model abstrato READ-ONLY da tabela auth_user
194
+
195
+ Para customizar, crie um model no seu app:
196
+
197
+ from shared_auth.abstract_models import AbstractUser
198
+
199
+ class CustomUser(AbstractUser):
200
+ # Adicione campos customizados
201
+ custom_field = models.CharField(max_length=100)
202
+
203
+ class Meta(AbstractUser.Meta):
204
+ pass
205
+
206
+ E configure no settings.py:
207
+ SHARED_AUTH_USER_MODEL = 'seu_app.CustomUser'
148
208
  """
149
209
 
150
210
  date_joined = models.DateTimeField()
151
211
  last_login = models.DateTimeField(null=True, blank=True)
152
212
  avatar = models.ImageField(storage=Storage, blank=True, null=True)
213
+
153
214
  # Campos customizados
154
215
  createdat = models.DateTimeField()
155
216
  updatedat = models.DateTimeField()
@@ -158,6 +219,7 @@ class User(AbstractUser):
158
219
  objects = UserManager()
159
220
 
160
221
  class Meta:
222
+ abstract = True
161
223
  managed = False
162
224
  db_table = USER_TABLE
163
225
 
@@ -166,8 +228,13 @@ class User(AbstractUser):
166
228
  """
167
229
  Retorna todas as organizações associadas ao usuário.
168
230
  """
169
- return SharedOrganization.objects.filter(
170
- id__in=SharedMember.objects.filter(user_id=self.id).values_list(
231
+ from .utils import get_organization_model, get_member_model
232
+
233
+ Organization = get_organization_model()
234
+ Member = get_member_model()
235
+
236
+ return Organization.objects.filter(
237
+ id__in=Member.objects.filter(user_id=self.id).values_list(
171
238
  "organization_id", flat=True
172
239
  )
173
240
  )
@@ -176,14 +243,19 @@ class User(AbstractUser):
176
243
  """
177
244
  Retorna a organização especificada pelo ID, se o usuário for membro.
178
245
  """
246
+ from .utils import get_organization_model, get_member_model
247
+
248
+ Organization = get_organization_model()
249
+ Member = get_member_model()
250
+
179
251
  try:
180
- organization = SharedOrganization.objects.get(id=organization_id)
181
- except SharedOrganization.DoesNotExist:
252
+ organization = Organization.objects.get(id=organization_id)
253
+ except Organization.DoesNotExist:
182
254
  raise OrganizationNotFoundError(
183
255
  f"Organização com ID {organization_id} não encontrada."
184
256
  )
185
257
 
186
- if not SharedMember.objects.filter(
258
+ if not Member.objects.filter(
187
259
  user_id=self.id, organization_id=organization.id
188
260
  ).exists():
189
261
  raise OrganizationNotFoundError("Usuário não é membro desta organização.")
@@ -191,10 +263,24 @@ class User(AbstractUser):
191
263
  return organization
192
264
 
193
265
 
194
- class SharedMember(models.Model):
266
+ class AbstractSharedMember(models.Model):
195
267
  """
196
- Model READ-ONLY da tabela organization_member
268
+ Model abstrato READ-ONLY da tabela organization_member
197
269
  Relacionamento entre User e Organization
270
+
271
+ Para customizar, crie um model no seu app:
272
+
273
+ from shared_auth.abstract_models import AbstractSharedMember
274
+
275
+ class CustomMember(AbstractSharedMember):
276
+ # Adicione campos customizados
277
+ custom_field = models.CharField(max_length=100)
278
+
279
+ class Meta(AbstractSharedMember.Meta):
280
+ pass
281
+
282
+ E configure no settings.py:
283
+ SHARED_AUTH_MEMBER_MODEL = 'seu_app.CustomMember'
198
284
  """
199
285
 
200
286
  user_id = models.IntegerField()
@@ -204,6 +290,7 @@ class SharedMember(models.Model):
204
290
  objects = SharedMemberManager()
205
291
 
206
292
  class Meta:
293
+ abstract = True
207
294
  managed = False
208
295
  db_table = MEMBER_TABLE
209
296
 
@@ -220,6 +307,9 @@ class SharedMember(models.Model):
220
307
  user = member.user
221
308
  print(user.email)
222
309
  """
310
+ from .utils import get_user_model
311
+
312
+ User = get_user_model()
223
313
  return User.objects.get_or_fail(self.user_id)
224
314
 
225
315
  @property
@@ -232,4 +322,7 @@ class SharedMember(models.Model):
232
322
  org = member.organization
233
323
  print(org.name)
234
324
  """
235
- return SharedOrganization.objects.get_or_fail(self.organization_id)
325
+ from .utils import get_organization_model
326
+
327
+ Organization = get_organization_model()
328
+ return Organization.objects.get_or_fail(self.organization_id)
@@ -6,13 +6,15 @@ from django.utils.translation import gettext_lazy as _
6
6
  from rest_framework import exceptions
7
7
  from rest_framework.authentication import TokenAuthentication
8
8
 
9
- from .models import SharedToken, User
9
+ from .utils import get_token_model, get_user_model
10
10
 
11
11
 
12
12
  class SharedTokenAuthentication(TokenAuthentication):
13
13
  """
14
14
  Autentica usando tokens do banco de dados compartilhado
15
15
 
16
+ Usa get_token_model() e get_user_model() para suportar models customizados.
17
+
16
18
  Usage em settings.py:
17
19
  REST_FRAMEWORK = {
18
20
  'DEFAULT_AUTHENTICATION_CLASSES': [
@@ -21,15 +23,21 @@ class SharedTokenAuthentication(TokenAuthentication):
21
23
  }
22
24
  """
23
25
 
24
- model = SharedToken
26
+ @property
27
+ def model(self):
28
+ """Retorna o model de Token configurado"""
29
+ return get_token_model()
25
30
 
26
31
  def authenticate_credentials(self, key):
27
32
  """
28
33
  Valida o token no banco de dados compartilhado
29
34
  """
35
+ Token = get_token_model()
36
+ User = get_user_model()
37
+
30
38
  try:
31
- token = SharedToken.objects.get(key=key)
32
- except SharedToken.DoesNotExist:
39
+ token = Token.objects.get(key=key)
40
+ except Token.DoesNotExist:
33
41
  raise exceptions.AuthenticationFailed(_("Token inválido."))
34
42
 
35
43
  # Buscar usuário completo
@@ -6,7 +6,7 @@ from functools import wraps
6
6
 
7
7
  from django.http import JsonResponse
8
8
 
9
- from .models import SharedOrganization, SharedToken, User
9
+ from .utils import get_organization_model, get_token_model, get_user_model
10
10
 
11
11
 
12
12
  def require_auth(view_func):
@@ -28,8 +28,11 @@ def require_auth(view_func):
28
28
  return JsonResponse({"error": "Token não fornecido"}, status=401)
29
29
 
30
30
  # Validar token
31
+ Token = get_token_model()
32
+ User = get_user_model()
33
+
31
34
  try:
32
- token_obj = SharedToken.objects.get(key=token)
35
+ token_obj = Token.objects.get(key=token)
33
36
  user = User.objects.get(pk=token_obj.user_id)
34
37
 
35
38
  if not user.is_active or user.deleted_at is not None:
@@ -38,7 +41,7 @@ def require_auth(view_func):
38
41
  request.user = user
39
42
  request.auth = token_obj
40
43
 
41
- except (SharedToken.DoesNotExist, User.DoesNotExist):
44
+ except (Token.DoesNotExist, User.DoesNotExist):
42
45
  return JsonResponse({"error": "Token inválido"}, status=401)
43
46
 
44
47
  return view_func(request, *args, **kwargs)
@@ -58,15 +61,17 @@ def require_organization(view_func):
58
61
  return JsonResponse({"error": "Organização não definida"}, status=403)
59
62
 
60
63
  # Buscar organização
64
+ Organization = get_organization_model()
65
+
61
66
  try:
62
- org = SharedOrganization.objects.get(pk=request.organization_id)
67
+ org = Organization.objects.get(pk=request.organization_id)
63
68
 
64
69
  if not org.is_active():
65
70
  return JsonResponse({"error": "Organização inativa"}, status=403)
66
71
 
67
72
  request.organization = org
68
73
 
69
- except SharedOrganization.DoesNotExist:
74
+ except Organization.DoesNotExist:
70
75
  return JsonResponse({"error": "Organização não encontrada"}, status=404)
71
76
 
72
77
  return view_func(request, *args, **kwargs)
@@ -95,7 +95,7 @@ class OrganizationQuerySetMixin:
95
95
  Lista de objetos com _cached_organization
96
96
  """
97
97
  objects = list(self.all())
98
- from .models import SharedOrganization
98
+ from .utils import get_organization_model
99
99
 
100
100
  if not objects:
101
101
  return objects
@@ -104,8 +104,9 @@ class OrganizationQuerySetMixin:
104
104
  org_ids = set(obj.organization_id for obj in objects)
105
105
 
106
106
  # Buscar todas de uma vez
107
+ Organization = get_organization_model()
107
108
  organizations = {
108
- org.pk: org for org in SharedOrganization.objects.filter(pk__in=org_ids)
109
+ org.pk: org for org in Organization.objects.filter(pk__in=org_ids)
109
110
  }
110
111
 
111
112
  # Cachear nos objetos
@@ -130,7 +131,7 @@ class UserQuerySetMixin:
130
131
  """
131
132
  Pré-carrega dados de usuários (evita N+1)
132
133
  """
133
- from .models import User
134
+ from .utils import get_user_model
134
135
 
135
136
  objects = list(self.all())
136
137
 
@@ -139,6 +140,7 @@ class UserQuerySetMixin:
139
140
 
140
141
  user_ids = set(obj.user_id for obj in objects)
141
142
 
143
+ User = get_user_model()
142
144
  users = {user.pk: user for user in User.objects.filter(pk__in=user_ids)}
143
145
 
144
146
  for obj in objects:
@@ -154,7 +156,7 @@ class OrganizationUserQuerySetMixin(OrganizationQuerySetMixin, UserQuerySetMixin
154
156
  """
155
157
  Pré-carrega dados de organizações E usuários (evita N+1)
156
158
  """
157
- from .models import SharedOrganization, User
159
+ from .utils import get_organization_model, get_user_model
158
160
 
159
161
  objects = list(self.all())
160
162
 
@@ -166,8 +168,11 @@ class OrganizationUserQuerySetMixin(OrganizationQuerySetMixin, UserQuerySetMixin
166
168
  user_ids = set(obj.user_id for obj in objects)
167
169
 
168
170
  # Buscar em batch
171
+ Organization = get_organization_model()
172
+ User = get_user_model()
173
+
169
174
  organizations = {
170
- org.pk: org for org in SharedOrganization.objects.filter(pk__in=org_ids)
175
+ org.pk: org for org in Organization.objects.filter(pk__in=org_ids)
171
176
  }
172
177
 
173
178
  users = {user.pk: user for user in User.objects.filter(pk__in=user_ids)}
@@ -183,13 +188,15 @@ class OrganizationUserQuerySetMixin(OrganizationQuerySetMixin, UserQuerySetMixin
183
188
  """
184
189
  Cria objeto com validação de organização e usuário
185
190
  """
186
- from .models import SharedMember, SharedOrganization
191
+ from .utils import get_member_model, get_organization_model
187
192
 
188
193
  # Valida organização
189
- SharedOrganization.objects.get_or_fail(organization_id)
194
+ Organization = get_organization_model()
195
+ Organization.objects.get_or_fail(organization_id)
190
196
 
191
197
  # Valida usuário pertence à organização
192
- if not SharedMember.objects.filter(
198
+ Member = get_member_model()
199
+ if not Member.objects.filter(
193
200
  user_id=user_id, organization_id=organization_id
194
201
  ).exists():
195
202
  raise ValueError(
@@ -6,7 +6,12 @@ from django.http import JsonResponse
6
6
  from django.utils.deprecation import MiddlewareMixin
7
7
 
8
8
  from .authentication import SharedTokenAuthentication
9
- from .models import SharedMember, SharedOrganization, SharedToken, User
9
+ from .utils import (
10
+ get_member_model,
11
+ get_organization_model,
12
+ get_token_model,
13
+ get_user_model,
14
+ )
10
15
 
11
16
 
12
17
  class SharedAuthMiddleware(MiddlewareMixin):
@@ -50,8 +55,11 @@ class SharedAuthMiddleware(MiddlewareMixin):
50
55
  return None
51
56
 
52
57
  # Validar token e buscar usuário
58
+ Token = get_token_model()
59
+ User = get_user_model()
60
+
53
61
  try:
54
- token_obj = SharedToken.objects.get(key=token)
62
+ token_obj = Token.objects.get(key=token)
55
63
  user = User.objects.get(pk=token_obj.user_id)
56
64
 
57
65
  if not user.is_active or user.deleted_at is not None:
@@ -63,7 +71,7 @@ class SharedAuthMiddleware(MiddlewareMixin):
63
71
  # request.user = user
64
72
  request.auth = token_obj
65
73
 
66
- except (SharedToken.DoesNotExist, User.DoesNotExist):
74
+ except (Token.DoesNotExist, User.DoesNotExist):
67
75
  # request.user = None
68
76
  request.auth = None
69
77
 
@@ -159,9 +167,8 @@ class OrganizationMiddleware(MiddlewareMixin):
159
167
  return
160
168
 
161
169
  request.organization_id = organization_id
162
- request.organization = SharedOrganization.objects.filter(
163
- pk=organization_id
164
- ).first()
170
+ Organization = get_organization_model()
171
+ request.organization = Organization.objects.filter(pk=organization_id).first()
165
172
 
166
173
  @staticmethod
167
174
  def _authenticate_user(request):
@@ -194,7 +201,8 @@ class OrganizationMiddleware(MiddlewareMixin):
194
201
  return None
195
202
 
196
203
  # Buscar a primeira organização que o usuário pertence
197
- member = SharedMember.objects.filter(user_id=request.user.pk).first()
204
+ Member = get_member_model()
205
+ member = Member.objects.filter(user_id=request.user.pk).first()
198
206
 
199
207
  return member.organization_id if member else None
200
208
 
@@ -210,6 +218,8 @@ class OrganizationMiddleware(MiddlewareMixin):
210
218
 
211
219
 
212
220
  def get_member(user_id, organization_id):
213
- return SharedMember.objects.filter(
221
+ """Busca membro usando o model configurado"""
222
+ Member = get_member_model()
223
+ return Member.objects.filter(
214
224
  user_id=user_id, organization_id=organization_id
215
225
  ).first()
@@ -44,9 +44,10 @@ class OrganizationMixin(models.Model):
44
44
  Acessa organização do banco de auth (lazy loading com cache)
45
45
  """
46
46
  if not hasattr(self, "_cached_organization"):
47
- from shared_auth.models import SharedOrganization
47
+ from shared_auth.utils import get_organization_model
48
48
 
49
- self._cached_organization = SharedOrganization.objects.get_or_fail(
49
+ Organization = get_organization_model()
50
+ self._cached_organization = Organization.objects.get_or_fail(
50
51
  self.organization_id
51
52
  )
52
53
  return self._cached_organization
@@ -69,7 +70,7 @@ class OrganizationMixin(models.Model):
69
70
  """Retorna nome da organização (safe)"""
70
71
  try:
71
72
  return self.organization.name
72
- except:
73
+ except Exception:
73
74
  return None
74
75
 
75
76
 
@@ -108,8 +109,9 @@ class UserMixin(models.Model):
108
109
  Acessa usuário do banco de auth (lazy loading com cache)
109
110
  """
110
111
  if not hasattr(self, "_cached_user"):
111
- from shared_auth.models import User
112
+ from shared_auth.utils import get_user_model
112
113
 
114
+ User = get_user_model()
113
115
  self._cached_user = User.objects.get_or_fail(self.user_id)
114
116
  return self._cached_user
115
117
 
@@ -118,7 +120,7 @@ class UserMixin(models.Model):
118
120
  """Retorna email do usuário (safe)"""
119
121
  try:
120
122
  return self.user.email
121
- except:
123
+ except Exception:
122
124
  return None
123
125
 
124
126
  @property
@@ -126,7 +128,7 @@ class UserMixin(models.Model):
126
128
  """Retorna nome completo do usuário (safe)"""
127
129
  try:
128
130
  return self.user.get_full_name()
129
- except:
131
+ except Exception:
130
132
  return None
131
133
 
132
134
  @property
@@ -138,7 +140,7 @@ class UserMixin(models.Model):
138
140
  """Verifica se o usuário está ativo"""
139
141
  try:
140
142
  return self.user.is_active and self.user.deleted_at is None
141
- except:
143
+ except Exception:
142
144
  return False
143
145
 
144
146
 
@@ -171,9 +173,10 @@ class OrganizationUserMixin(OrganizationMixin, UserMixin):
171
173
  Returns:
172
174
  bool: True se pertence, False caso contrário
173
175
  """
174
- from shared_auth.models import SharedMember
176
+ from shared_auth.utils import get_member_model
175
177
 
176
- return SharedMember.objects.filter(
178
+ Member = get_member_model()
179
+ return Member.objects.filter(
177
180
  user_id=self.user_id, organization_id=self.organization_id
178
181
  ).exists()
179
182
 
@@ -182,9 +185,10 @@ class OrganizationUserMixin(OrganizationMixin, UserMixin):
182
185
  Verifica se um usuário pode acessar este registro
183
186
  (se pertence à mesma organização)
184
187
  """
185
- from shared_auth.models import SharedMember
188
+ from shared_auth.utils import get_member_model
186
189
 
187
- return SharedMember.objects.filter(
190
+ Member = get_member_model()
191
+ return Member.objects.filter(
188
192
  user_id=user_id, organization_id=self.organization_id
189
193
  ).exists()
190
194
 
@@ -0,0 +1,58 @@
1
+ """
2
+ Models READ-ONLY para acesso aos dados de autenticação
3
+ ATENÇÃO: Estes models NÃO devem ser usados para criar migrations
4
+
5
+ Para customizar estes models, herde dos models abstratos em shared_auth.abstract_models
6
+ e configure no settings.py. Veja a documentação em abstract_models.py
7
+ """
8
+
9
+ from .abstract_models import (
10
+ AbstractSharedMember,
11
+ AbstractSharedOrganization,
12
+ AbstractSharedToken,
13
+ AbstractUser,
14
+ )
15
+
16
+
17
+ class SharedToken(AbstractSharedToken):
18
+ """
19
+ Model READ-ONLY padrão da tabela authtoken_token
20
+
21
+ Para customizar, crie seu próprio model herdando de AbstractSharedToken
22
+ """
23
+
24
+ class Meta(AbstractSharedToken.Meta):
25
+ pass
26
+
27
+
28
+ class SharedOrganization(AbstractSharedOrganization):
29
+ """
30
+ Model READ-ONLY padrão da tabela organization
31
+
32
+ Para customizar, crie seu próprio model herdando de AbstractSharedOrganization
33
+ """
34
+
35
+ class Meta(AbstractSharedOrganization.Meta):
36
+ pass
37
+
38
+
39
+ class User(AbstractUser):
40
+ """
41
+ Model READ-ONLY padrão da tabela auth_user
42
+
43
+ Para customizar, crie seu próprio model herdando de AbstractUser
44
+ """
45
+
46
+ class Meta(AbstractUser.Meta):
47
+ pass
48
+
49
+
50
+ class SharedMember(AbstractSharedMember):
51
+ """
52
+ Model READ-ONLY padrão da tabela organization_member
53
+
54
+ Para customizar, crie seu próprio model herdando de AbstractSharedMember
55
+ """
56
+
57
+ class Meta(AbstractSharedMember.Meta):
58
+ pass
@@ -5,6 +5,7 @@ Permissões customizadas para DRF
5
5
  from rest_framework import permissions
6
6
 
7
7
  from shared_auth.middleware import get_member
8
+ from shared_auth.utils import get_organization_model
8
9
 
9
10
 
10
11
  class IsAuthenticated(permissions.BasePermission):
@@ -35,12 +36,12 @@ class HasActiveOrganization(permissions.BasePermission):
35
36
  return False
36
37
 
37
38
  # Verificar se organização está ativa
38
- from .models import SharedOrganization
39
+ Organization = get_organization_model()
39
40
 
40
41
  try:
41
- org = SharedOrganization.objects.get(pk=request.organization_id)
42
+ org = Organization.objects.get(pk=request.organization_id)
42
43
  return org.is_active()
43
- except SharedOrganization.DoesNotExist:
44
+ except Organization.DoesNotExist:
44
45
  return False
45
46
 
46
47
 
@@ -3,7 +3,7 @@ class SharedAuthRouter:
3
3
  Direciona queries dos models compartilhados para o banco correto
4
4
  """
5
5
 
6
- route_app_labels = {"shared_auth", "auth", "admin"}
6
+ route_app_labels = {"shared_auth", "auth", "admin", "contenttypes"}
7
7
 
8
8
  def db_for_read(self, model, **hints):
9
9
  if model._meta.app_label in self.route_app_labels:
@@ -13,7 +13,7 @@ Se quiser usar separadamente:
13
13
 
14
14
  from rest_framework import serializers
15
15
 
16
- from .models import SharedOrganization, User
16
+ from .utils import get_organization_model, get_user_model
17
17
 
18
18
 
19
19
  class OrganizationCreateSerializerMixin(serializers.ModelSerializer):
@@ -28,6 +28,8 @@ class OrganizationCreateSerializerMixin(serializers.ModelSerializer):
28
28
  fields = ['id', 'titulo', 'conteudo']
29
29
  """
30
30
 
31
+ organization_id = serializers.IntegerField(required=False)
32
+
31
33
  def create(self, validated_data):
32
34
  """Automatically set organization_id from request context"""
33
35
  if self.context.get("request") and hasattr(
@@ -116,8 +118,17 @@ class OrganizationListCreateSerializerMixin(
116
118
 
117
119
 
118
120
  class OrganizationSerializer(serializers.ModelSerializer):
121
+ """
122
+ Serializer para o model de Organization configurado.
123
+ Usa get_organization_model() para suportar models customizados.
124
+ """
125
+
126
+ def __init__(self, *args, **kwargs):
127
+ super().__init__(*args, **kwargs)
128
+ self.Meta.model = get_organization_model()
129
+
119
130
  class Meta:
120
- model = SharedOrganization
131
+ model = None # Será definido dinamicamente no __init__
121
132
  fields = "__all__"
122
133
 
123
134
 
@@ -133,6 +144,8 @@ class UserCreateSerializerMixin(serializers.ModelSerializer):
133
144
  fields = ['id', 'titulo', 'conteudo']
134
145
  """
135
146
 
147
+ user_id = serializers.IntegerField(required=False)
148
+
136
149
  def create(self, validated_data):
137
150
  """Automatically set user_id from request context"""
138
151
  if self.context.get("request") and hasattr(self.context["request"], "user"):
@@ -182,7 +195,7 @@ class UserSerializerMixin(serializers.ModelSerializer):
182
195
  def get_user(self, obj):
183
196
  """Retorna dados do usuário como objeto"""
184
197
  try:
185
- user: User = obj.user
198
+ user = obj.user
186
199
  return {
187
200
  "id": user.pk,
188
201
  "username": user.username,
@@ -213,8 +226,17 @@ class UserListCreateSerializerMixin(UserSerializerMixin, UserCreateSerializerMix
213
226
 
214
227
 
215
228
  class UserSerializer(serializers.ModelSerializer):
229
+ """
230
+ Serializer para o model de User configurado.
231
+ Usa get_user_model() para suportar models customizados.
232
+ """
233
+
234
+ def __init__(self, *args, **kwargs):
235
+ super().__init__(*args, **kwargs)
236
+ self.Meta.model = get_user_model()
237
+
216
238
  class Meta:
217
- model = User
239
+ model = None # Será definido dinamicamente no __init__
218
240
  fields = [
219
241
  "id",
220
242
  "username",
@@ -241,6 +263,9 @@ class OrganizationUserCreateSerializerMixin(serializers.ModelSerializer):
241
263
  fields = ['id', 'titulo', 'conteudo']
242
264
  """
243
265
 
266
+ organization_id = serializers.IntegerField(required=False)
267
+ user_id = serializers.IntegerField(required=False)
268
+
244
269
  def create(self, validated_data):
245
270
  """Automatically set both organization_id and user_id from request context"""
246
271
  if self.context.get("request"):
@@ -0,0 +1,113 @@
1
+ """
2
+ Utilitários para obter os models configurados
3
+ Similar ao get_user_model() do Django
4
+ """
5
+
6
+ from django.apps import apps
7
+ from django.core.exceptions import ImproperlyConfigured
8
+
9
+ from .conf import get_setting
10
+
11
+
12
+ def get_token_model():
13
+ """
14
+ Retorna o model de Token configurado ou o padrão.
15
+
16
+ Usage:
17
+ from shared_auth.utils import get_token_model
18
+
19
+ Token = get_token_model()
20
+ token = Token.objects.get(key='abc123')
21
+ """
22
+ model_string = get_setting('SHARED_AUTH_TOKEN_MODEL', 'shared_auth.SharedToken')
23
+
24
+ try:
25
+ return apps.get_model(model_string, require_ready=False)
26
+ except ValueError:
27
+ raise ImproperlyConfigured(
28
+ f"SHARED_AUTH_TOKEN_MODEL deve estar no formato 'app_label.model_name'. "
29
+ f"Recebido: '{model_string}'"
30
+ )
31
+ except LookupError:
32
+ raise ImproperlyConfigured(
33
+ f"SHARED_AUTH_TOKEN_MODEL refere-se ao model '{model_string}' "
34
+ f"que não foi instalado ou é inválido."
35
+ )
36
+
37
+
38
+ def get_organization_model():
39
+ """
40
+ Retorna o model de Organization configurado ou o padrão.
41
+
42
+ Usage:
43
+ from shared_auth.utils import get_organization_model
44
+
45
+ Organization = get_organization_model()
46
+ org = Organization.objects.get(id=1)
47
+ """
48
+ model_string = get_setting('SHARED_AUTH_ORGANIZATION_MODEL', 'shared_auth.SharedOrganization')
49
+
50
+ try:
51
+ return apps.get_model(model_string, require_ready=False)
52
+ except ValueError:
53
+ raise ImproperlyConfigured(
54
+ f"SHARED_AUTH_ORGANIZATION_MODEL deve estar no formato 'app_label.model_name'. "
55
+ f"Recebido: '{model_string}'"
56
+ )
57
+ except LookupError:
58
+ raise ImproperlyConfigured(
59
+ f"SHARED_AUTH_ORGANIZATION_MODEL refere-se ao model '{model_string}' "
60
+ f"que não foi instalado ou é inválido."
61
+ )
62
+
63
+
64
+ def get_user_model():
65
+ """
66
+ Retorna o model de User configurado ou o padrão.
67
+
68
+ Usage:
69
+ from shared_auth.utils import get_user_model
70
+
71
+ User = get_user_model()
72
+ user = User.objects.get(id=1)
73
+ """
74
+ model_string = get_setting('SHARED_AUTH_USER_MODEL', 'shared_auth.User')
75
+
76
+ try:
77
+ return apps.get_model(model_string, require_ready=False)
78
+ except ValueError:
79
+ raise ImproperlyConfigured(
80
+ f"SHARED_AUTH_USER_MODEL deve estar no formato 'app_label.model_name'. "
81
+ f"Recebido: '{model_string}'"
82
+ )
83
+ except LookupError:
84
+ raise ImproperlyConfigured(
85
+ f"SHARED_AUTH_USER_MODEL refere-se ao model '{model_string}' "
86
+ f"que não foi instalado ou é inválido."
87
+ )
88
+
89
+
90
+ def get_member_model():
91
+ """
92
+ Retorna o model de Member configurado ou o padrão.
93
+
94
+ Usage:
95
+ from shared_auth.utils import get_member_model
96
+
97
+ Member = get_member_model()
98
+ member = Member.objects.get(id=1)
99
+ """
100
+ model_string = get_setting('SHARED_AUTH_MEMBER_MODEL', 'shared_auth.SharedMember')
101
+
102
+ try:
103
+ return apps.get_model(model_string, require_ready=False)
104
+ except ValueError:
105
+ raise ImproperlyConfigured(
106
+ f"SHARED_AUTH_MEMBER_MODEL deve estar no formato 'app_label.model_name'. "
107
+ f"Recebido: '{model_string}'"
108
+ )
109
+ except LookupError:
110
+ raise ImproperlyConfigured(
111
+ f"SHARED_AUTH_MEMBER_MODEL refere-se ao model '{model_string}' "
112
+ f"que não foi instalado ou é inválido."
113
+ )
@@ -1,6 +0,0 @@
1
- """
2
- Biblioteca compartilhada para acesso aos models de autenticação
3
- """
4
-
5
- __version__ = '1.0.0'
6
- default_app_config = "shared_auth.app.SharedAuthConfig"