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.
- maquinaweb_shared_auth-0.2.60.dist-info/METADATA +1003 -0
- maquinaweb_shared_auth-0.2.60.dist-info/RECORD +28 -0
- maquinaweb_shared_auth-0.2.60.dist-info/WHEEL +5 -0
- maquinaweb_shared_auth-0.2.60.dist-info/top_level.txt +1 -0
- shared_auth/__init__.py +7 -0
- shared_auth/abstract_models.py +897 -0
- shared_auth/app.py +9 -0
- shared_auth/authentication.py +55 -0
- shared_auth/conf.py +33 -0
- shared_auth/decorators.py +122 -0
- shared_auth/exceptions.py +23 -0
- shared_auth/fields.py +51 -0
- shared_auth/management/__init__.py +0 -0
- shared_auth/management/commands/__init__.py +0 -0
- shared_auth/management/commands/generate_permissions.py +147 -0
- shared_auth/managers.py +344 -0
- shared_auth/middleware.py +281 -0
- shared_auth/mixins.py +475 -0
- shared_auth/models.py +191 -0
- shared_auth/permissions.py +266 -0
- shared_auth/permissions_cache.py +249 -0
- shared_auth/permissions_helpers.py +251 -0
- shared_auth/router.py +22 -0
- shared_auth/serializers.py +439 -0
- shared_auth/storage_backend.py +6 -0
- shared_auth/urls.py +8 -0
- shared_auth/utils.py +356 -0
- shared_auth/views.py +40 -0
|
@@ -0,0 +1,897 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Models abstratos para customização
|
|
3
|
+
Estes models podem ser herdados nos apps clientes para adicionar campos e métodos customizados
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from shared_auth.conf import GROUP_ORG_PERMISSIONS_PERMISSIONS_TABLE
|
|
7
|
+
from shared_auth.conf import GROUP_PERMISSIONS_PERMISSIONS_TABLE
|
|
8
|
+
from shared_auth.conf import PLAN_GROUP_PERMISSIONS_TABLE
|
|
9
|
+
from shared_auth.conf import GROUP_ORG_PERMISSIONS_TABLE
|
|
10
|
+
from shared_auth.conf import SUBSCRIPTION_TABLE
|
|
11
|
+
from shared_auth.conf import PLAN_TABLE
|
|
12
|
+
from shared_auth.conf import GROUP_PERMISSIONS_TABLE
|
|
13
|
+
from shared_auth.conf import PERMISSION_TABLE
|
|
14
|
+
from shared_auth.conf import SYSTEM_TABLE
|
|
15
|
+
import os
|
|
16
|
+
from django.utils import timezone
|
|
17
|
+
from django.contrib.auth.models import AbstractUser
|
|
18
|
+
from django.db import models
|
|
19
|
+
|
|
20
|
+
from .conf import MEMBER_TABLE, ORGANIZATION_TABLE, TOKEN_TABLE, USER_TABLE
|
|
21
|
+
from .exceptions import OrganizationNotFoundError
|
|
22
|
+
from .managers import SharedMemberManager, SharedOrganizationManager, UserManager
|
|
23
|
+
from .storage_backend import Storage
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def organization_image_path(instance, filename):
|
|
27
|
+
return os.path.join(
|
|
28
|
+
"organization",
|
|
29
|
+
str(instance.pk),
|
|
30
|
+
"images",
|
|
31
|
+
filename,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AbstractSharedToken(models.Model):
|
|
36
|
+
"""
|
|
37
|
+
Model abstrato READ-ONLY da tabela authtoken_token
|
|
38
|
+
Usado para validar tokens em outros sistemas
|
|
39
|
+
|
|
40
|
+
Para customizar, crie um model no seu app:
|
|
41
|
+
|
|
42
|
+
from shared_auth.abstract_models import AbstractSharedToken
|
|
43
|
+
|
|
44
|
+
class CustomToken(AbstractSharedToken):
|
|
45
|
+
# Adicione campos customizados
|
|
46
|
+
custom_field = models.CharField(max_length=100)
|
|
47
|
+
|
|
48
|
+
class Meta(AbstractSharedToken.Meta):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
E configure no settings.py:
|
|
52
|
+
SHARED_AUTH_TOKEN_MODEL = 'seu_app.CustomToken'
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
key = models.CharField(max_length=40, primary_key=True)
|
|
56
|
+
user_id = models.IntegerField()
|
|
57
|
+
created = models.DateTimeField()
|
|
58
|
+
|
|
59
|
+
objects = models.Manager()
|
|
60
|
+
|
|
61
|
+
class Meta:
|
|
62
|
+
abstract = True
|
|
63
|
+
managed = False
|
|
64
|
+
db_table = TOKEN_TABLE
|
|
65
|
+
|
|
66
|
+
def __str__(self):
|
|
67
|
+
return self.key
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def user(self):
|
|
71
|
+
"""Acessa usuário do token"""
|
|
72
|
+
from .utils import get_user_model
|
|
73
|
+
|
|
74
|
+
if not hasattr(self, "_cached_user"):
|
|
75
|
+
User = get_user_model()
|
|
76
|
+
self._cached_user = User.objects.get_or_fail(self.user_id)
|
|
77
|
+
return self._cached_user
|
|
78
|
+
|
|
79
|
+
def is_valid(self):
|
|
80
|
+
"""Verifica se token ainda é válido"""
|
|
81
|
+
# Implementar lógica de expiração se necessário
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class AbstractSharedOrganization(models.Model):
|
|
86
|
+
"""
|
|
87
|
+
Model abstrato READ-ONLY da tabela organization
|
|
88
|
+
Usado para acessar dados de organizações em outros sistemas
|
|
89
|
+
|
|
90
|
+
Para customizar, crie um model no seu app:
|
|
91
|
+
|
|
92
|
+
from shared_auth.abstract_models import AbstractSharedOrganization
|
|
93
|
+
|
|
94
|
+
class CustomOrganization(AbstractSharedOrganization):
|
|
95
|
+
# Adicione campos customizados
|
|
96
|
+
custom_field = models.CharField(max_length=100)
|
|
97
|
+
|
|
98
|
+
class Meta(AbstractSharedOrganization.Meta):
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
E configure no settings.py:
|
|
102
|
+
SHARED_AUTH_ORGANIZATION_MODEL = 'seu_app.CustomOrganization'
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
# Campos principais
|
|
106
|
+
name = models.CharField(max_length=255)
|
|
107
|
+
fantasy_name = models.CharField(max_length=255, blank=True, null=True)
|
|
108
|
+
cnpj = models.CharField(max_length=255, blank=True, null=True)
|
|
109
|
+
telephone = models.CharField(max_length=50, blank=True, null=True)
|
|
110
|
+
cellphone = models.CharField(max_length=50, blank=True, null=True)
|
|
111
|
+
email = models.EmailField(blank=True, null=True)
|
|
112
|
+
image_organization = models.ImageField(
|
|
113
|
+
storage=Storage, upload_to=organization_image_path, null=True
|
|
114
|
+
)
|
|
115
|
+
logo = models.ImageField(
|
|
116
|
+
storage=Storage, upload_to=organization_image_path, null=True
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Relacionamentos
|
|
120
|
+
organization_group_id = models.IntegerField(null=True, blank=True)
|
|
121
|
+
main_organization_id = models.IntegerField(null=True, blank=True)
|
|
122
|
+
is_branch = models.BooleanField(default=False)
|
|
123
|
+
metadata = models.JSONField(default=dict)
|
|
124
|
+
|
|
125
|
+
# Metadados
|
|
126
|
+
created_at = models.DateTimeField()
|
|
127
|
+
updated_at = models.DateTimeField()
|
|
128
|
+
deleted_at = models.DateTimeField(null=True, blank=True)
|
|
129
|
+
|
|
130
|
+
objects = SharedOrganizationManager()
|
|
131
|
+
|
|
132
|
+
class Meta:
|
|
133
|
+
abstract = True
|
|
134
|
+
managed = False
|
|
135
|
+
db_table = ORGANIZATION_TABLE
|
|
136
|
+
|
|
137
|
+
def __str__(self):
|
|
138
|
+
return self.fantasy_name or self.name or f"Org #{self.pk}"
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def organization_group(self):
|
|
142
|
+
"""
|
|
143
|
+
Acessa grupo de organização (lazy loading)
|
|
144
|
+
|
|
145
|
+
Usage:
|
|
146
|
+
if org.organization_group:
|
|
147
|
+
print(org.organization_group.name)
|
|
148
|
+
"""
|
|
149
|
+
from .utils import get_organization_group_model
|
|
150
|
+
|
|
151
|
+
if self.organization_group_id:
|
|
152
|
+
OrganizationGroup = get_organization_group_model()
|
|
153
|
+
return OrganizationGroup.objects.get_or_fail(self.organization_group_id)
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def main_organization(self):
|
|
158
|
+
"""
|
|
159
|
+
Acessa organização principal (lazy loading)
|
|
160
|
+
|
|
161
|
+
Usage:
|
|
162
|
+
if org.is_branch:
|
|
163
|
+
main = org.main_organization
|
|
164
|
+
"""
|
|
165
|
+
from .utils import get_organization_model
|
|
166
|
+
|
|
167
|
+
if self.main_organization_id:
|
|
168
|
+
Organization = get_organization_model()
|
|
169
|
+
return Organization.objects.get_or_fail(self.main_organization_id)
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def branches(self):
|
|
174
|
+
"""
|
|
175
|
+
Retorna filiais desta organização
|
|
176
|
+
|
|
177
|
+
Usage:
|
|
178
|
+
branches = org.branches
|
|
179
|
+
"""
|
|
180
|
+
from .utils import get_organization_model
|
|
181
|
+
|
|
182
|
+
Organization = get_organization_model()
|
|
183
|
+
return Organization.objects.filter(main_organization_id=self.pk)
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def members(self):
|
|
187
|
+
"""
|
|
188
|
+
Retorna membros desta organização
|
|
189
|
+
|
|
190
|
+
Usage:
|
|
191
|
+
members = org.members
|
|
192
|
+
for member in members:
|
|
193
|
+
print(member.user.email)
|
|
194
|
+
"""
|
|
195
|
+
from .utils import get_member_model
|
|
196
|
+
|
|
197
|
+
Member = get_member_model()
|
|
198
|
+
return Member.objects.for_organization(self.pk)
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def users(self):
|
|
202
|
+
"""
|
|
203
|
+
Retorna usuários desta organização
|
|
204
|
+
|
|
205
|
+
Usage:
|
|
206
|
+
users = org.users
|
|
207
|
+
"""
|
|
208
|
+
from .utils import get_user_model
|
|
209
|
+
|
|
210
|
+
User = get_user_model()
|
|
211
|
+
return User.objects.filter(
|
|
212
|
+
id__in=self.members.values_list("user_id", flat=True)
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def is_active(self):
|
|
216
|
+
"""Verifica se organização está ativa"""
|
|
217
|
+
return self.deleted_at is None
|
|
218
|
+
|
|
219
|
+
def get_permissions_for_system(self, system):
|
|
220
|
+
"""
|
|
221
|
+
Retorna permissões do sistema baseado na assinatura do grupo de organizações.
|
|
222
|
+
Se a organização não pertence a um grupo, retorna vazio.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
system: Instance do System model ou system_id (int)
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
QuerySet de Permission objects
|
|
229
|
+
|
|
230
|
+
Usage:
|
|
231
|
+
from shared_auth.utils import get_system_model
|
|
232
|
+
|
|
233
|
+
System = get_system_model()
|
|
234
|
+
system = System.objects.get(name='MeuSistema')
|
|
235
|
+
permissions = organization.get_permissions_for_system(system)
|
|
236
|
+
"""
|
|
237
|
+
from .utils import get_subscription_model, get_permission_model
|
|
238
|
+
|
|
239
|
+
# Se não pertence a um grupo, sem permissões
|
|
240
|
+
if not self.organization_group_id:
|
|
241
|
+
Permission = get_permission_model()
|
|
242
|
+
return Permission.objects.none()
|
|
243
|
+
|
|
244
|
+
# Extrai system_id se foi passado um objeto
|
|
245
|
+
system_id = system.id if hasattr(system, 'id') else system
|
|
246
|
+
|
|
247
|
+
Subscription = get_subscription_model()
|
|
248
|
+
Permission = get_permission_model()
|
|
249
|
+
|
|
250
|
+
# Busca assinatura ativa do grupo
|
|
251
|
+
subscription = (
|
|
252
|
+
Subscription.objects
|
|
253
|
+
.filter(
|
|
254
|
+
organization_group_id=self.organization_group_id,
|
|
255
|
+
plan__system_id=system_id,
|
|
256
|
+
active=True,
|
|
257
|
+
paid=True
|
|
258
|
+
)
|
|
259
|
+
.select_related('plan')
|
|
260
|
+
.order_by('-started_at')
|
|
261
|
+
.first()
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if not subscription:
|
|
265
|
+
return Permission.objects.none()
|
|
266
|
+
|
|
267
|
+
# Coleta todas as permissões dos grupos de permissões do plano
|
|
268
|
+
permission_ids = set()
|
|
269
|
+
for group in subscription.plan.group_permissions.all():
|
|
270
|
+
permission_ids.update(group.permissions.values_list('id', flat=True))
|
|
271
|
+
|
|
272
|
+
return Permission.objects.filter(id__in=permission_ids, system_id=system_id)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class AbstractUser(AbstractUser):
|
|
276
|
+
"""
|
|
277
|
+
Model abstrato READ-ONLY da tabela auth_user
|
|
278
|
+
|
|
279
|
+
Para customizar, crie um model no seu app:
|
|
280
|
+
|
|
281
|
+
from shared_auth.abstract_models import AbstractUser
|
|
282
|
+
|
|
283
|
+
class CustomUser(AbstractUser):
|
|
284
|
+
# Adicione campos customizados
|
|
285
|
+
custom_field = models.CharField(max_length=100)
|
|
286
|
+
|
|
287
|
+
class Meta(AbstractUser.Meta):
|
|
288
|
+
pass
|
|
289
|
+
|
|
290
|
+
E configure no settings.py:
|
|
291
|
+
SHARED_AUTH_USER_MODEL = 'seu_app.CustomUser'
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
date_joined = models.DateTimeField()
|
|
295
|
+
last_login = models.DateTimeField(null=True, blank=True)
|
|
296
|
+
avatar = models.ImageField(storage=Storage, blank=True, null=True)
|
|
297
|
+
|
|
298
|
+
# Campos customizados
|
|
299
|
+
createdat = models.DateTimeField()
|
|
300
|
+
updatedat = models.DateTimeField()
|
|
301
|
+
deleted_at = models.DateTimeField(null=True, blank=True)
|
|
302
|
+
|
|
303
|
+
objects = UserManager()
|
|
304
|
+
|
|
305
|
+
class Meta:
|
|
306
|
+
abstract = True
|
|
307
|
+
managed = False
|
|
308
|
+
db_table = USER_TABLE
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def organizations(self):
|
|
312
|
+
"""
|
|
313
|
+
Retorna todas as organizações associadas ao usuário.
|
|
314
|
+
"""
|
|
315
|
+
from .utils import get_member_model, get_organization_model
|
|
316
|
+
|
|
317
|
+
Organization = get_organization_model()
|
|
318
|
+
Member = get_member_model()
|
|
319
|
+
|
|
320
|
+
return Organization.objects.filter(
|
|
321
|
+
id__in=Member.objects.filter(user_id=self.id).values_list(
|
|
322
|
+
"organization_id", flat=True
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
def get_org(self, organization_id):
|
|
327
|
+
"""
|
|
328
|
+
Retorna a organização especificada pelo ID, se o usuário for membro.
|
|
329
|
+
"""
|
|
330
|
+
from .utils import get_member_model, get_organization_model
|
|
331
|
+
|
|
332
|
+
Organization = get_organization_model()
|
|
333
|
+
Member = get_member_model()
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
organization = Organization.objects.get(id=organization_id)
|
|
337
|
+
except Organization.DoesNotExist:
|
|
338
|
+
raise OrganizationNotFoundError(
|
|
339
|
+
f"Organização com ID {organization_id} não encontrada."
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
if not Member.objects.filter(
|
|
343
|
+
user_id=self.id, organization_id=organization.id
|
|
344
|
+
).exists():
|
|
345
|
+
raise OrganizationNotFoundError("Usuário não é membro desta organização.")
|
|
346
|
+
|
|
347
|
+
return organization
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
class AbstractSharedMember(models.Model):
|
|
351
|
+
"""
|
|
352
|
+
Model abstrato READ-ONLY da tabela organization_member
|
|
353
|
+
Relacionamento entre User e Organization
|
|
354
|
+
|
|
355
|
+
Para customizar, crie um model no seu app:
|
|
356
|
+
|
|
357
|
+
from shared_auth.abstract_models import AbstractSharedMember
|
|
358
|
+
|
|
359
|
+
class CustomMember(AbstractSharedMember):
|
|
360
|
+
# Adicione campos customizados
|
|
361
|
+
custom_field = models.CharField(max_length=100)
|
|
362
|
+
|
|
363
|
+
class Meta(AbstractSharedMember.Meta):
|
|
364
|
+
pass
|
|
365
|
+
|
|
366
|
+
E configure no settings.py:
|
|
367
|
+
SHARED_AUTH_MEMBER_MODEL = 'seu_app.CustomMember'
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
user_id = models.IntegerField()
|
|
371
|
+
organization_id = models.IntegerField()
|
|
372
|
+
metadata = models.JSONField(default=dict)
|
|
373
|
+
|
|
374
|
+
objects = SharedMemberManager()
|
|
375
|
+
|
|
376
|
+
class Meta:
|
|
377
|
+
abstract = True
|
|
378
|
+
managed = False
|
|
379
|
+
db_table = MEMBER_TABLE
|
|
380
|
+
|
|
381
|
+
def __str__(self):
|
|
382
|
+
return f"Member: User {self.user_id} - Org {self.organization_id}"
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
def user(self):
|
|
386
|
+
"""
|
|
387
|
+
Acessa usuário (lazy loading)
|
|
388
|
+
|
|
389
|
+
Usage:
|
|
390
|
+
member = SharedMember.objects.first()
|
|
391
|
+
user = member.user
|
|
392
|
+
print(user.email)
|
|
393
|
+
"""
|
|
394
|
+
from .utils import get_user_model
|
|
395
|
+
|
|
396
|
+
User = get_user_model()
|
|
397
|
+
return User.objects.get_or_fail(self.user_id)
|
|
398
|
+
|
|
399
|
+
@property
|
|
400
|
+
def organization(self):
|
|
401
|
+
"""
|
|
402
|
+
Acessa organização (lazy loading)
|
|
403
|
+
|
|
404
|
+
Usage:
|
|
405
|
+
member = SharedMember.objects.first()
|
|
406
|
+
org = member.organization
|
|
407
|
+
print(org.name)
|
|
408
|
+
"""
|
|
409
|
+
from .utils import get_organization_model
|
|
410
|
+
|
|
411
|
+
Organization = get_organization_model()
|
|
412
|
+
return Organization.objects.get_or_fail(self.organization_id)
|
|
413
|
+
|
|
414
|
+
class GroupPermissionsPermission(models.Model):
|
|
415
|
+
grouppermissions_id = models.BigIntegerField()
|
|
416
|
+
permissions_id = models.BigIntegerField()
|
|
417
|
+
|
|
418
|
+
class Meta:
|
|
419
|
+
db_table = GROUP_PERMISSIONS_PERMISSIONS_TABLE
|
|
420
|
+
managed = False
|
|
421
|
+
unique_together = ('grouppermissions_id', 'permissions_id')
|
|
422
|
+
app_label = 'shared_auth'
|
|
423
|
+
|
|
424
|
+
def __str__(self):
|
|
425
|
+
return f"GroupPerm {self.grouppermissions_id} → Perm {self.permissions_id}"
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
class PlanGroupPermission(models.Model):
|
|
429
|
+
plan_id = models.BigIntegerField()
|
|
430
|
+
grouppermissions_id = models.BigIntegerField()
|
|
431
|
+
|
|
432
|
+
class Meta:
|
|
433
|
+
db_table = PLAN_GROUP_PERMISSIONS_TABLE
|
|
434
|
+
managed = False
|
|
435
|
+
unique_together = ('plan_id', 'grouppermissions_id')
|
|
436
|
+
app_label = 'shared_auth'
|
|
437
|
+
|
|
438
|
+
def __str__(self):
|
|
439
|
+
return f"Plan {self.plan_id} → GroupPerm {self.grouppermissions_id}"
|
|
440
|
+
|
|
441
|
+
class GroupOrgPermissionsPermission(models.Model):
|
|
442
|
+
grouporganizationpermissions_id = models.BigIntegerField()
|
|
443
|
+
permissions_id = models.BigIntegerField()
|
|
444
|
+
|
|
445
|
+
class Meta:
|
|
446
|
+
db_table = GROUP_ORG_PERMISSIONS_PERMISSIONS_TABLE
|
|
447
|
+
managed = False
|
|
448
|
+
unique_together = ('grouporganizationpermissions_id', 'permissions_id')
|
|
449
|
+
app_label = 'shared_auth'
|
|
450
|
+
|
|
451
|
+
def __str__(self):
|
|
452
|
+
return f"OrgGroup {self.grouporganizationpermissions_id} → Perm {self.permissions_id}"
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
class AbstractSystem(models.Model):
|
|
456
|
+
"""
|
|
457
|
+
Model abstrato READ-ONLY da tabela plans_system
|
|
458
|
+
Representa um sistema externo que usa este serviço de autenticação
|
|
459
|
+
|
|
460
|
+
Para customizar, crie um model no seu app:
|
|
461
|
+
|
|
462
|
+
from shared_auth.abstract_models import AbstractSystem
|
|
463
|
+
|
|
464
|
+
class CustomSystem(AbstractSystem):
|
|
465
|
+
custom_field = models.CharField(max_length=100)
|
|
466
|
+
|
|
467
|
+
class Meta(AbstractSystem.Meta):
|
|
468
|
+
pass
|
|
469
|
+
|
|
470
|
+
E configure no settings.py:
|
|
471
|
+
SHARED_AUTH_SYSTEM_MODEL = 'seu_app.CustomSystem'
|
|
472
|
+
"""
|
|
473
|
+
|
|
474
|
+
name = models.CharField(max_length=100)
|
|
475
|
+
description = models.TextField(blank=True)
|
|
476
|
+
active = models.BooleanField(default=True)
|
|
477
|
+
created_at = models.DateTimeField()
|
|
478
|
+
updated_at = models.DateTimeField()
|
|
479
|
+
|
|
480
|
+
objects = models.Manager() # Will be replaced by SystemManager in concrete model
|
|
481
|
+
|
|
482
|
+
class Meta:
|
|
483
|
+
abstract = True
|
|
484
|
+
managed = False
|
|
485
|
+
db_table = SYSTEM_TABLE
|
|
486
|
+
|
|
487
|
+
def __str__(self):
|
|
488
|
+
return self.name
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
class AbstractPermission(models.Model):
|
|
492
|
+
"""
|
|
493
|
+
Model abstrato READ-ONLY da tabela organization_permissions
|
|
494
|
+
Define permissões específicas de cada sistema
|
|
495
|
+
|
|
496
|
+
Para customizar, crie um model no seu app e configure:
|
|
497
|
+
SHARED_AUTH_PERMISSION_MODEL = 'seu_app.CustomPermission'
|
|
498
|
+
"""
|
|
499
|
+
|
|
500
|
+
codename = models.CharField(max_length=100)
|
|
501
|
+
name = models.CharField(max_length=100)
|
|
502
|
+
description = models.TextField()
|
|
503
|
+
scope = models.CharField(
|
|
504
|
+
max_length=100,
|
|
505
|
+
help_text="Agrupamento da permissão (ex: 'Recebimentos', 'Pagamentos', 'Relatórios')",
|
|
506
|
+
blank=True,
|
|
507
|
+
default=""
|
|
508
|
+
)
|
|
509
|
+
system_id = models.IntegerField()
|
|
510
|
+
|
|
511
|
+
objects = models.Manager()
|
|
512
|
+
|
|
513
|
+
class Meta:
|
|
514
|
+
abstract = True
|
|
515
|
+
managed = False
|
|
516
|
+
db_table = PERMISSION_TABLE
|
|
517
|
+
|
|
518
|
+
def __str__(self):
|
|
519
|
+
return f"{self.codename} ({self.name})"
|
|
520
|
+
|
|
521
|
+
@property
|
|
522
|
+
def system(self):
|
|
523
|
+
"""Acessa sistema (lazy loading)"""
|
|
524
|
+
from .utils import get_system_model
|
|
525
|
+
|
|
526
|
+
if not hasattr(self, "_cached_system"):
|
|
527
|
+
System = get_system_model()
|
|
528
|
+
self._cached_system = System.objects.get_or_fail(self.system_id)
|
|
529
|
+
return self._cached_system
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
class AbstractGroupPermissions(models.Model):
|
|
533
|
+
"""
|
|
534
|
+
Model abstrato READ-ONLY da tabela organization_grouppermissions
|
|
535
|
+
Grupos base de permissões (usados nos planos)
|
|
536
|
+
|
|
537
|
+
Para customizar, configure:
|
|
538
|
+
SHARED_AUTH_GROUP_PERMISSIONS_MODEL = 'seu_app.CustomGroupPermissions'
|
|
539
|
+
"""
|
|
540
|
+
|
|
541
|
+
name = models.CharField(max_length=100)
|
|
542
|
+
description = models.TextField(blank=True)
|
|
543
|
+
system_id = models.IntegerField()
|
|
544
|
+
|
|
545
|
+
objects = models.Manager()
|
|
546
|
+
|
|
547
|
+
class Meta:
|
|
548
|
+
abstract = True
|
|
549
|
+
managed = False
|
|
550
|
+
db_table = GROUP_PERMISSIONS_TABLE
|
|
551
|
+
|
|
552
|
+
def __str__(self):
|
|
553
|
+
return self.name
|
|
554
|
+
|
|
555
|
+
@property
|
|
556
|
+
def system(self):
|
|
557
|
+
"""Acessa sistema (lazy loading)"""
|
|
558
|
+
from .utils import get_system_model
|
|
559
|
+
|
|
560
|
+
if not hasattr(self, "_cached_system"):
|
|
561
|
+
System = get_system_model()
|
|
562
|
+
self._cached_system = System.objects.get_or_fail(self.system_id)
|
|
563
|
+
return self._cached_system
|
|
564
|
+
|
|
565
|
+
@property
|
|
566
|
+
def permissions(self):
|
|
567
|
+
from .utils import get_permission_model
|
|
568
|
+
|
|
569
|
+
Permission = get_permission_model()
|
|
570
|
+
|
|
571
|
+
perm_ids = GroupPermissionsPermission.objects.using('auth_db') \
|
|
572
|
+
.filter(grouppermissions_id=self.pk) \
|
|
573
|
+
.values_list('permissions_id', flat=True)
|
|
574
|
+
|
|
575
|
+
return Permission.objects.using('auth_db').filter(id__in=perm_ids)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
class AbstractPlan(models.Model):
|
|
579
|
+
"""
|
|
580
|
+
Model abstrato READ-ONLY da tabela plans_plan
|
|
581
|
+
Planos oferecidos por cada sistema, com conjunto de permissões
|
|
582
|
+
|
|
583
|
+
Para customizar, configure:
|
|
584
|
+
SHARED_AUTH_PLAN_MODEL = 'seu_app.CustomPlan'
|
|
585
|
+
"""
|
|
586
|
+
|
|
587
|
+
name = models.CharField(max_length=100)
|
|
588
|
+
slug = models.SlugField()
|
|
589
|
+
system_id = models.IntegerField()
|
|
590
|
+
description = models.TextField(blank=True)
|
|
591
|
+
price = models.DecimalField(max_digits=10, decimal_places=2)
|
|
592
|
+
active = models.BooleanField(default=True)
|
|
593
|
+
recurrence = models.CharField(max_length=10)
|
|
594
|
+
created_at = models.DateTimeField()
|
|
595
|
+
updated_at = models.DateTimeField()
|
|
596
|
+
|
|
597
|
+
objects = models.Manager()
|
|
598
|
+
|
|
599
|
+
class Meta:
|
|
600
|
+
abstract = True
|
|
601
|
+
managed = False
|
|
602
|
+
db_table = PLAN_TABLE
|
|
603
|
+
|
|
604
|
+
def __str__(self):
|
|
605
|
+
return f"{self.name} - {self.system.name if hasattr(self, 'system') else self.system_id}"
|
|
606
|
+
|
|
607
|
+
@property
|
|
608
|
+
def system(self):
|
|
609
|
+
"""Acessa sistema (lazy loading)"""
|
|
610
|
+
from .utils import get_system_model
|
|
611
|
+
|
|
612
|
+
if not hasattr(self, "_cached_system"):
|
|
613
|
+
System = get_system_model()
|
|
614
|
+
self._cached_system = System.objects.get_or_fail(self.system_id)
|
|
615
|
+
return self._cached_system
|
|
616
|
+
|
|
617
|
+
@property
|
|
618
|
+
def group_permissions(self):
|
|
619
|
+
from .utils import get_group_permissions_model
|
|
620
|
+
|
|
621
|
+
GroupPermissions = get_group_permissions_model()
|
|
622
|
+
|
|
623
|
+
group_ids = PlanGroupPermission.objects.using('auth_db') \
|
|
624
|
+
.filter(plan_id=self.pk) \
|
|
625
|
+
.values_list('grouppermissions_id', flat=True)
|
|
626
|
+
|
|
627
|
+
return GroupPermissions.objects.using('auth_db').filter(id__in=group_ids)
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
class AbstractOrganizationGroup(models.Model):
|
|
631
|
+
"""
|
|
632
|
+
Model abstrato READ-ONLY da tabela organization_organizationgroup
|
|
633
|
+
Representa um grupo de organizações com assinatura compartilhada
|
|
634
|
+
|
|
635
|
+
Para customizar, crie um model no seu app:
|
|
636
|
+
|
|
637
|
+
from shared_auth.abstract_models import AbstractOrganizationGroup
|
|
638
|
+
|
|
639
|
+
class CustomOrganizationGroup(AbstractOrganizationGroup):
|
|
640
|
+
custom_field = models.CharField(max_length=100)
|
|
641
|
+
|
|
642
|
+
class Meta(AbstractOrganizationGroup.Meta):
|
|
643
|
+
pass
|
|
644
|
+
|
|
645
|
+
E configure no settings.py:
|
|
646
|
+
SHARED_AUTH_ORGANIZATION_GROUP_MODEL = 'seu_app.CustomOrganizationGroup'
|
|
647
|
+
"""
|
|
648
|
+
owner_id = models.IntegerField()
|
|
649
|
+
name = models.CharField(
|
|
650
|
+
max_length=255,
|
|
651
|
+
help_text="Nome do grupo (ex: 'Empresas do João', 'Grupo Acme')"
|
|
652
|
+
)
|
|
653
|
+
created_at = models.DateTimeField()
|
|
654
|
+
updated_at = models.DateTimeField()
|
|
655
|
+
|
|
656
|
+
objects = models.Manager()
|
|
657
|
+
|
|
658
|
+
class Meta:
|
|
659
|
+
abstract = True
|
|
660
|
+
managed = False
|
|
661
|
+
db_table = "organization_organizationgroup" # Will be imported from conf in concrete model
|
|
662
|
+
|
|
663
|
+
def __str__(self):
|
|
664
|
+
return f"{self.name} (Owner: {self.owner_id})"
|
|
665
|
+
|
|
666
|
+
@property
|
|
667
|
+
def owner(self):
|
|
668
|
+
"""Acessa usuário (lazy loading)"""
|
|
669
|
+
from .utils import get_user_model
|
|
670
|
+
|
|
671
|
+
if not hasattr(self, "_cached_owner"):
|
|
672
|
+
User = get_user_model()
|
|
673
|
+
self._cached_owner = User.objects.get_or_fail(self.owner_id)
|
|
674
|
+
return self._cached_owner
|
|
675
|
+
|
|
676
|
+
@property
|
|
677
|
+
def organizations(self):
|
|
678
|
+
"""Retorna organizações pertencentes a este grupo"""
|
|
679
|
+
from .utils import get_organization_model
|
|
680
|
+
|
|
681
|
+
Organization = get_organization_model()
|
|
682
|
+
return Organization.objects.filter(organization_group_id=self.pk)
|
|
683
|
+
|
|
684
|
+
@property
|
|
685
|
+
def subscriptions(self):
|
|
686
|
+
"""Retorna assinaturas ativas deste grupo"""
|
|
687
|
+
from .utils import get_subscription_model
|
|
688
|
+
|
|
689
|
+
Subscription = get_subscription_model()
|
|
690
|
+
return Subscription.objects.filter(organization_group_id=self.pk)
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
class AbstractSubscription(models.Model):
|
|
694
|
+
"""
|
|
695
|
+
Model abstrato READ-ONLY da tabela plans_subscription
|
|
696
|
+
Assinatura de plano por grupo de organizações.
|
|
697
|
+
Uma assinatura cobre TODAS as organizações do grupo.
|
|
698
|
+
|
|
699
|
+
Para customizar, configure:
|
|
700
|
+
SHARED_AUTH_SUBSCRIPTION_MODEL = 'seu_app.CustomSubscription'
|
|
701
|
+
"""
|
|
702
|
+
|
|
703
|
+
organization_group_id = models.IntegerField(
|
|
704
|
+
help_text="Grupo de organizações cobertas por esta assinatura"
|
|
705
|
+
)
|
|
706
|
+
plan_id = models.IntegerField()
|
|
707
|
+
period = models.CharField(max_length=20, help_text="Período da assinatura (ex: '2025-01', 'Q1-2025')")
|
|
708
|
+
payment_date = models.DateTimeField(null=True, blank=True)
|
|
709
|
+
paid = models.BooleanField(default=False)
|
|
710
|
+
active = models.BooleanField(default=True)
|
|
711
|
+
started_at = models.DateTimeField()
|
|
712
|
+
expires_at = models.DateTimeField(null=True, blank=True)
|
|
713
|
+
created_at = models.DateTimeField()
|
|
714
|
+
updated_at = models.DateTimeField()
|
|
715
|
+
|
|
716
|
+
objects = models.Manager() # Will be replaced by SubscriptionManager
|
|
717
|
+
|
|
718
|
+
class Meta:
|
|
719
|
+
abstract = True
|
|
720
|
+
managed = False
|
|
721
|
+
db_table = SUBSCRIPTION_TABLE
|
|
722
|
+
|
|
723
|
+
def __str__(self):
|
|
724
|
+
return f"Subscription {self.pk} - Group {self.organization_group_id}"
|
|
725
|
+
|
|
726
|
+
@property
|
|
727
|
+
def organization_group(self):
|
|
728
|
+
"""Acessa grupo de organizações (lazy loading)"""
|
|
729
|
+
from .utils import get_organization_group_model
|
|
730
|
+
|
|
731
|
+
if not hasattr(self, "_cached_organization_group"):
|
|
732
|
+
OrganizationGroup = get_organization_group_model()
|
|
733
|
+
try:
|
|
734
|
+
self._cached_organization_group = OrganizationGroup.objects.get(pk=self.organization_group_id)
|
|
735
|
+
except OrganizationGroup.DoesNotExist:
|
|
736
|
+
self._cached_organization_group = None
|
|
737
|
+
return self._cached_organization_group
|
|
738
|
+
|
|
739
|
+
@property
|
|
740
|
+
def plan(self):
|
|
741
|
+
"""Acessa plano (lazy loading)"""
|
|
742
|
+
from .utils import get_plan_model
|
|
743
|
+
|
|
744
|
+
if not hasattr(self, "_cached_plan"):
|
|
745
|
+
Plan = get_plan_model()
|
|
746
|
+
try:
|
|
747
|
+
self._cached_plan = Plan.objects.get(pk=self.plan_id)
|
|
748
|
+
except Plan.DoesNotExist:
|
|
749
|
+
self._cached_plan = None
|
|
750
|
+
return self._cached_plan
|
|
751
|
+
|
|
752
|
+
def is_valid(self):
|
|
753
|
+
"""Verifica se assinatura está ativa, paga e não expirada"""
|
|
754
|
+
|
|
755
|
+
if not self.active or not self.paid:
|
|
756
|
+
return False
|
|
757
|
+
|
|
758
|
+
if self.expires_at and self.expires_at < timezone.now():
|
|
759
|
+
return False
|
|
760
|
+
|
|
761
|
+
return True
|
|
762
|
+
|
|
763
|
+
def is_expired(self):
|
|
764
|
+
"""Verifica se a assinatura está expirada"""
|
|
765
|
+
|
|
766
|
+
if not self.expires_at:
|
|
767
|
+
return False
|
|
768
|
+
return timezone.now() > self.expires_at
|
|
769
|
+
|
|
770
|
+
def set_paid(self):
|
|
771
|
+
"""Marca assinatura como paga (apenas para referência, não salva)"""
|
|
772
|
+
self.paid = True
|
|
773
|
+
self.payment_date = timezone.now()
|
|
774
|
+
|
|
775
|
+
def cancel(self):
|
|
776
|
+
"""Cancela assinatura (apenas para referência, não salva)"""
|
|
777
|
+
self.active = False
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
class AbstractGroupOrganizationPermissions(models.Model):
|
|
782
|
+
"""
|
|
783
|
+
Model abstrato READ-ONLY da tabela organization_grouporganizationpermissions
|
|
784
|
+
Grupos de permissões criados pela organização para distribuir aos usuários
|
|
785
|
+
|
|
786
|
+
Para customizar, configure:
|
|
787
|
+
SHARED_AUTH_GROUP_ORG_PERMISSIONS_MODEL = 'seu_app.CustomGroupOrgPermissions'
|
|
788
|
+
"""
|
|
789
|
+
|
|
790
|
+
organization_id = models.IntegerField()
|
|
791
|
+
system_id = models.IntegerField()
|
|
792
|
+
name = models.CharField(max_length=100)
|
|
793
|
+
description = models.TextField(blank=True)
|
|
794
|
+
|
|
795
|
+
objects = models.Manager() # Will be replaced by GroupOrganizationPermissionsManager
|
|
796
|
+
|
|
797
|
+
class Meta:
|
|
798
|
+
abstract = True
|
|
799
|
+
managed = False
|
|
800
|
+
db_table = GROUP_ORG_PERMISSIONS_TABLE
|
|
801
|
+
|
|
802
|
+
def __str__(self):
|
|
803
|
+
return f"{self.name} (Org {self.organization_id})"
|
|
804
|
+
|
|
805
|
+
@property
|
|
806
|
+
def organization(self):
|
|
807
|
+
"""Acessa organização (lazy loading)"""
|
|
808
|
+
from .utils import get_organization_model
|
|
809
|
+
|
|
810
|
+
if not hasattr(self, "_cached_organization"):
|
|
811
|
+
Organization = get_organization_model()
|
|
812
|
+
self._cached_organization = Organization.objects.get_or_fail(self.organization_id)
|
|
813
|
+
return self._cached_organization
|
|
814
|
+
|
|
815
|
+
@property
|
|
816
|
+
def system(self):
|
|
817
|
+
"""Acessa sistema (lazy loading)"""
|
|
818
|
+
from .utils import get_system_model
|
|
819
|
+
|
|
820
|
+
if not hasattr(self, "_cached_system"):
|
|
821
|
+
System = get_system_model()
|
|
822
|
+
self._cached_system = System.objects.get_or_fail(self.system_id)
|
|
823
|
+
return self._cached_system
|
|
824
|
+
|
|
825
|
+
@property
|
|
826
|
+
def permissions(self):
|
|
827
|
+
from .utils import get_permission_model
|
|
828
|
+
|
|
829
|
+
Permission = get_permission_model()
|
|
830
|
+
|
|
831
|
+
perm_ids = GroupOrgPermissionsPermission.objects.using('auth_db') \
|
|
832
|
+
.filter(grouporganizationpermissions_id=self.pk) \
|
|
833
|
+
.values_list('permissions_id', flat=True)
|
|
834
|
+
|
|
835
|
+
return Permission.objects.using('auth_db').filter(id__in=perm_ids)
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
class AbstractMemberSystemGroup(models.Model):
|
|
839
|
+
"""
|
|
840
|
+
Model abstrato READ-ONLY da tabela organization_membersystemgroup
|
|
841
|
+
Relaciona um membro a um grupo de permissões em um sistema específico
|
|
842
|
+
|
|
843
|
+
Para customizar, configure:
|
|
844
|
+
SHARED_AUTH_MEMBER_SYSTEM_GROUP_MODEL = 'seu_app.CustomMemberSystemGroup'
|
|
845
|
+
"""
|
|
846
|
+
|
|
847
|
+
member_id = models.IntegerField()
|
|
848
|
+
group_id = models.IntegerField()
|
|
849
|
+
system_id = models.IntegerField()
|
|
850
|
+
created_at = models.DateTimeField()
|
|
851
|
+
|
|
852
|
+
objects = models.Manager() # Will be replaced by MemberSystemGroupManager
|
|
853
|
+
|
|
854
|
+
class Meta:
|
|
855
|
+
abstract = True
|
|
856
|
+
managed = False
|
|
857
|
+
db_table = GROUP_ORG_PERMISSIONS_TABLE
|
|
858
|
+
|
|
859
|
+
def __str__(self):
|
|
860
|
+
return f"Member {self.member_id} - Group {self.group_id} - System {self.system_id}"
|
|
861
|
+
|
|
862
|
+
@property
|
|
863
|
+
def member(self):
|
|
864
|
+
"""Acessa membro (lazy loading)"""
|
|
865
|
+
from .utils import get_member_model
|
|
866
|
+
|
|
867
|
+
if not hasattr(self, "_cached_member"):
|
|
868
|
+
Member = get_member_model()
|
|
869
|
+
try:
|
|
870
|
+
self._cached_member = Member.objects.get(pk=self.member_id)
|
|
871
|
+
except Member.DoesNotExist:
|
|
872
|
+
self._cached_member = None
|
|
873
|
+
return self._cached_member
|
|
874
|
+
|
|
875
|
+
@property
|
|
876
|
+
def group(self):
|
|
877
|
+
"""Acessa grupo (lazy loading)"""
|
|
878
|
+
from .utils import get_group_organization_permissions_model
|
|
879
|
+
|
|
880
|
+
if not hasattr(self, "_cached_group"):
|
|
881
|
+
GroupOrgPermissions = get_group_organization_permissions_model()
|
|
882
|
+
try:
|
|
883
|
+
self._cached_group = GroupOrgPermissions.objects.get(pk=self.group_id)
|
|
884
|
+
except GroupOrgPermissions.DoesNotExist:
|
|
885
|
+
self._cached_group = None
|
|
886
|
+
return self._cached_group
|
|
887
|
+
|
|
888
|
+
@property
|
|
889
|
+
def system(self):
|
|
890
|
+
"""Acessa sistema (lazy loading)"""
|
|
891
|
+
from .utils import get_system_model
|
|
892
|
+
|
|
893
|
+
if not hasattr(self, "_cached_system"):
|
|
894
|
+
System = get_system_model()
|
|
895
|
+
self._cached_system = System.objects.get_or_fail(self.system_id)
|
|
896
|
+
return self._cached_system
|
|
897
|
+
|