oxutils 0.1.6__py3-none-any.whl → 0.1.14__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.
Files changed (68) hide show
  1. oxutils/__init__.py +2 -2
  2. oxutils/audit/migrations/0001_initial.py +2 -2
  3. oxutils/audit/models.py +2 -2
  4. oxutils/constants.py +6 -0
  5. oxutils/jwt/auth.py +150 -1
  6. oxutils/jwt/models.py +81 -0
  7. oxutils/jwt/tokens.py +69 -0
  8. oxutils/jwt/utils.py +45 -0
  9. oxutils/logger/__init__.py +10 -0
  10. oxutils/logger/receivers.py +10 -6
  11. oxutils/logger/settings.py +2 -2
  12. oxutils/models/base.py +102 -0
  13. oxutils/models/fields.py +79 -0
  14. oxutils/oxiliere/apps.py +9 -1
  15. oxutils/oxiliere/authorization.py +45 -0
  16. oxutils/oxiliere/caches.py +13 -11
  17. oxutils/oxiliere/checks.py +31 -0
  18. oxutils/oxiliere/constants.py +3 -0
  19. oxutils/oxiliere/context.py +16 -0
  20. oxutils/oxiliere/exceptions.py +16 -0
  21. oxutils/oxiliere/management/commands/grant_tenant_owners.py +19 -0
  22. oxutils/oxiliere/management/commands/init_oxiliere_system.py +30 -11
  23. oxutils/oxiliere/middleware.py +65 -11
  24. oxutils/oxiliere/models.py +146 -9
  25. oxutils/oxiliere/permissions.py +28 -35
  26. oxutils/oxiliere/schemas.py +16 -6
  27. oxutils/oxiliere/signals.py +5 -0
  28. oxutils/oxiliere/utils.py +36 -1
  29. oxutils/pagination/cursor.py +367 -0
  30. oxutils/permissions/__init__.py +0 -0
  31. oxutils/permissions/actions.py +57 -0
  32. oxutils/permissions/admin.py +3 -0
  33. oxutils/permissions/apps.py +10 -0
  34. oxutils/permissions/caches.py +33 -0
  35. oxutils/permissions/checks.py +188 -0
  36. oxutils/permissions/constants.py +0 -0
  37. oxutils/permissions/controllers.py +344 -0
  38. oxutils/permissions/exceptions.py +60 -0
  39. oxutils/permissions/management/__init__.py +0 -0
  40. oxutils/permissions/management/commands/__init__.py +0 -0
  41. oxutils/permissions/management/commands/load_permission_preset.py +112 -0
  42. oxutils/permissions/migrations/0001_initial.py +112 -0
  43. oxutils/permissions/migrations/0002_alter_grant_role.py +19 -0
  44. oxutils/permissions/migrations/0003_alter_grant_options_alter_group_options_and_more.py +33 -0
  45. oxutils/permissions/migrations/__init__.py +0 -0
  46. oxutils/permissions/models.py +171 -0
  47. oxutils/permissions/perms.py +201 -0
  48. oxutils/permissions/queryset.py +92 -0
  49. oxutils/permissions/schemas.py +276 -0
  50. oxutils/permissions/services.py +663 -0
  51. oxutils/permissions/tests.py +3 -0
  52. oxutils/permissions/utils.py +784 -0
  53. oxutils/settings.py +14 -194
  54. oxutils/users/apps.py +1 -1
  55. oxutils/users/migrations/0001_initial.py +47 -0
  56. oxutils/users/migrations/0002_alter_user_first_name_alter_user_last_name.py +23 -0
  57. oxutils/users/migrations/0003_user_photo.py +18 -0
  58. oxutils/users/models.py +3 -0
  59. oxutils/utils.py +25 -0
  60. {oxutils-0.1.6.dist-info → oxutils-0.1.14.dist-info}/METADATA +14 -11
  61. oxutils-0.1.14.dist-info/RECORD +123 -0
  62. oxutils/jwt/client.py +0 -123
  63. oxutils/jwt/constants.py +0 -1
  64. oxutils/s3/settings.py +0 -34
  65. oxutils/s3/storages.py +0 -130
  66. oxutils-0.1.6.dist-info/RECORD +0 -88
  67. /oxutils/{s3 → pagination}/__init__.py +0 -0
  68. {oxutils-0.1.6.dist-info → oxutils-0.1.14.dist-info}/WHEEL +0 -0
@@ -0,0 +1,663 @@
1
+ from typing import Optional, Any
2
+ from django.contrib.auth.models import AbstractBaseUser
3
+ from django.db import transaction
4
+ from django.contrib.auth import get_user_model
5
+
6
+ import structlog
7
+
8
+ from oxutils.mixins.services import BaseService
9
+ from oxutils.exceptions import NotFoundException
10
+ from .models import Grant, RoleGrant, Group, Role
11
+ from .utils import (
12
+ assign_role, revoke_role,
13
+ assign_group, revoke_group,
14
+ override_grant, check, group_sync
15
+ )
16
+ from .exceptions import (
17
+ RoleNotFoundException,
18
+ GroupNotFoundException,
19
+ GrantNotFoundException,
20
+ RoleGrantNotFoundException,
21
+ )
22
+
23
+ User = get_user_model()
24
+
25
+
26
+
27
+ logger = structlog.get_logger(__name__)
28
+
29
+
30
+
31
+ class PermissionService(BaseService):
32
+ """
33
+ Service pour la gestion des permissions.
34
+ Encapsule la logique métier liée aux rôles, groupes et grants.
35
+ """
36
+
37
+ def inner_exception_handler(self, exc: Exception, logger):
38
+ """
39
+ Gère les exceptions spécifiques au service de permissions.
40
+ Convertit les exceptions métier en exceptions HTTP appropriées.
41
+
42
+ Args:
43
+ exc: L'exception à gérer
44
+ logger: Logger pour la journalisation
45
+
46
+ Raises:
47
+ APIException: Si l'exception est gérée
48
+ Exception: Re-lève l'exception originale si non gérée
49
+ """
50
+ from oxutils.exceptions import APIException
51
+
52
+ # Si c'est déjà une APIException (incluant nos exceptions personnalisées),
53
+ # on la re-lève directement
54
+ if isinstance(exc, APIException):
55
+ raise exc
56
+
57
+ # Convertir les exceptions Django DoesNotExist en exceptions HTTP appropriées
58
+ from django.core.exceptions import ObjectDoesNotExist
59
+
60
+ if isinstance(exc, ObjectDoesNotExist):
61
+ # Déterminer le type d'objet pour un message plus précis
62
+ exc_name = type(exc).__name__
63
+
64
+ if 'Role' in exc_name:
65
+ raise RoleNotFoundException(detail=str(exc))
66
+ elif 'Group' in exc_name:
67
+ raise GroupNotFoundException(detail=str(exc))
68
+ elif 'Grant' in exc_name:
69
+ raise GrantNotFoundException(detail=str(exc))
70
+ elif 'RoleGrant' in exc_name:
71
+ raise RoleGrantNotFoundException(detail=str(exc))
72
+ else:
73
+ # Exception générique pour les autres cas
74
+ raise NotFoundException(detail=str(exc))
75
+
76
+ # Pour toutes les autres exceptions, laisser le handler parent gérer
77
+ raise exc
78
+
79
+ def get_roles(self):
80
+ return Role.objects.all()
81
+
82
+ def get_role(self, role_slug: str):
83
+ try:
84
+ return Role.objects.get(slug=role_slug)
85
+ except Role.DoesNotExist:
86
+ raise RoleNotFoundException(detail=f"Le rôle '{role_slug}' n'existe pas")
87
+
88
+ def get_user_roles(self, user: AbstractBaseUser):
89
+ return Role.objects.filter(grants__user__pk=user.pk)
90
+
91
+ def assign_role_to_user(
92
+ self,
93
+ user_id: int,
94
+ role_slug: str,
95
+ *,
96
+ by_user: Optional[AbstractBaseUser] = None
97
+ ) -> Role:
98
+ """
99
+ Assigne un rôle à un utilisateur.
100
+
101
+ Args:
102
+ user: L'utilisateur à qui assigner le rôle
103
+ role_slug: Le slug du rôle à assigner
104
+ by_user: L'utilisateur qui effectue l'assignation (pour traçabilité)
105
+
106
+ Returns:
107
+ Dictionnaire avec les informations de l'assignation
108
+
109
+ Raises:
110
+ NotFoundException: Si le rôle n'existe pas
111
+ """
112
+ try:
113
+ user = User.objects.get(pk=user_id)
114
+ role = Role.objects.get(slug=role_slug)
115
+
116
+ assign_role(user, role_slug, by=by_user)
117
+
118
+ grants_count = Grant.objects.filter(user=user, role=role).count()
119
+ logger.info("role_assigned_to_user", user_id=user.pk, role=role_slug, grants_created=grants_count)
120
+
121
+ return role
122
+
123
+ except Role.DoesNotExist:
124
+ raise RoleNotFoundException(detail=f"Le rôle '{role_slug}' n'existe pas")
125
+ except User.DoesNotExist:
126
+ raise NotFoundException(detail=f"L'utilisateur avec l'ID {user_id} n'existe pas")
127
+ except Exception as exc:
128
+ self.exception_handler(exc, self.logger)
129
+
130
+ def revoke_role_from_user(
131
+ self,
132
+ user_id: int,
133
+ role_slug: str
134
+ ) -> None:
135
+ """
136
+ Révoque un rôle d'un utilisateur.
137
+
138
+ Args:
139
+ user: L'utilisateur dont on révoque le rôle
140
+ role_slug: Le slug du rôle à révoquer
141
+
142
+ Returns:
143
+ Dictionnaire avec les informations de la révocation
144
+ """
145
+ try:
146
+ user = User.objects.get(pk=user_id)
147
+ deleted_count, _ = revoke_role(user, role_slug)
148
+
149
+ logger.info("role_revoked_from_user", user_id=user.pk, role=role_slug, grants_deleted=deleted_count)
150
+
151
+ except User.DoesNotExist:
152
+ raise NotFoundException(detail=f"L'utilisateur avec l'ID {user_id} n'existe pas")
153
+ except Exception as exc:
154
+ self.exception_handler(exc, self.logger)
155
+
156
+ def assign_group_to_user(
157
+ self,
158
+ user_id: int,
159
+ group_slug: str,
160
+ by_user: Optional[AbstractBaseUser] = None
161
+ ) -> list[Role]:
162
+ """
163
+ Assigne tous les rôles d'un groupe à un utilisateur.
164
+
165
+ Args:
166
+ user_id: L'ID de l'utilisateur à qui assigner le groupe
167
+ group_slug: Le slug du groupe à assigner
168
+ by_user: L'utilisateur qui effectue l'assignation (pour traçabilité)
169
+
170
+ Returns:
171
+ Liste des rôles du groupe
172
+
173
+ Raises:
174
+ NotFoundException: Si le groupe ou l'utilisateur n'existe pas
175
+ """
176
+ try:
177
+ user = User.objects.get(pk=user_id)
178
+ group = Group.objects.prefetch_related('roles').get(slug=group_slug)
179
+
180
+ assign_group(user, group_slug, by=by_user)
181
+
182
+ logger.info("group_assigned_to_user", user_id=user.pk, group=group_slug, roles_assigned=group.roles.count())
183
+
184
+ return list(group.roles.all())
185
+
186
+ except Group.DoesNotExist:
187
+ raise GroupNotFoundException(detail=f"Le groupe '{group_slug}' n'existe pas")
188
+ except User.DoesNotExist:
189
+ raise NotFoundException(detail=f"L'utilisateur avec l'ID {user_id} n'existe pas")
190
+ except Exception as exc:
191
+ self.exception_handler(exc, self.logger)
192
+
193
+ def revoke_group_from_user(
194
+ self,
195
+ user_id: int,
196
+ group_slug: str
197
+ ) -> None:
198
+ """
199
+ Révoque tous les rôles d'un groupe d'un utilisateur.
200
+
201
+ Args:
202
+ user_id: L'ID de l'utilisateur dont on révoque le groupe
203
+ group_slug: Le slug du groupe à révoquer
204
+
205
+ Raises:
206
+ NotFoundException: Si le groupe ou l'utilisateur n'existe pas
207
+ """
208
+ try:
209
+ user = User.objects.get(pk=user_id)
210
+ deleted_count, _ = revoke_group(user, group_slug)
211
+
212
+ logger.info("group_revoked_from_user", user_id=user.pk, group=group_slug, grants_deleted=deleted_count)
213
+
214
+ except User.DoesNotExist:
215
+ raise NotFoundException(detail=f"L'utilisateur avec l'ID {user_id} n'existe pas")
216
+ except Exception as exc:
217
+ self.exception_handler(exc, self.logger)
218
+
219
+ def sync_group(self, group_slug: str) -> dict[str, int]:
220
+ """
221
+ Synchronise les grants de tous les utilisateurs d'un groupe.
222
+ À appeler après modification des RoleGrants ou des rôles du groupe.
223
+
224
+ Args:
225
+ group_slug: Le slug du groupe à synchroniser
226
+
227
+ Returns:
228
+ Dictionnaire avec les statistiques de synchronisation
229
+
230
+ Raises:
231
+ GroupNotFoundException: Si le groupe n'existe pas
232
+ """
233
+ try:
234
+ stats = group_sync(group_slug)
235
+
236
+ logger.info("group_synced", group=group_slug, **stats)
237
+
238
+ return stats
239
+
240
+ except Exception as exc:
241
+ self.exception_handler(exc, self.logger)
242
+
243
+ def override_user_grant(
244
+ self,
245
+ user: AbstractBaseUser,
246
+ scope: str,
247
+ remove_actions: list[str]
248
+ ) -> dict[str, Any]:
249
+ """
250
+ Modifie un grant en retirant certaines actions.
251
+
252
+ Args:
253
+ user: L'utilisateur dont on modifie le grant
254
+ scope: Le scope du grant à modifier
255
+ remove_actions: Liste des actions à retirer
256
+
257
+ Returns:
258
+ Dictionnaire avec les informations de la modification
259
+ """
260
+ try:
261
+ # Vérifier si le grant existe avant modification
262
+ grant_exists = Grant.objects.filter(user=user, scope=scope).exists()
263
+
264
+ if not grant_exists:
265
+ raise NotFoundException(
266
+ detail=f"Aucun grant trouvé pour l'utilisateur sur le scope '{scope}'"
267
+ )
268
+
269
+ override_grant(user, scope, remove_actions)
270
+
271
+ # Vérifier si le grant existe toujours (peut avoir été supprimé)
272
+ grant_still_exists = Grant.objects.filter(user=user, scope=scope).exists()
273
+
274
+ logger.info("grant_modified", user_id=user.pk, scope=scope, removed_actions=remove_actions, grant_deleted=not grant_still_exists, grant_exists=grant_still_exists)
275
+
276
+ return {
277
+ "user_id": user.pk,
278
+ "scope": scope,
279
+ "removed_actions": remove_actions,
280
+ "grant_deleted": not grant_still_exists,
281
+ "message": "Grant modifié avec succès" if grant_still_exists else "Grant supprimé (plus d'actions)"
282
+ }
283
+
284
+ except Exception as exc:
285
+ self.exception_handler(exc, self.logger)
286
+
287
+ def check_permission(
288
+ self,
289
+ user_id: int,
290
+ scope: str,
291
+ required_actions: list[str],
292
+ context: dict[str, Any] = None
293
+ ) -> dict[str, Any]:
294
+ """
295
+ Vérifie si un utilisateur possède les permissions requises.
296
+
297
+ Args:
298
+ user_id: L'ID de l'utilisateur dont on vérifie les permissions
299
+ scope: Le scope à vérifier
300
+ required_actions: Liste des actions requises
301
+ context: Contexte additionnel pour filtrer les grants
302
+
303
+ Returns:
304
+ Dictionnaire avec le résultat de la vérification
305
+ """
306
+ try:
307
+ user = User.objects.get(pk=user_id)
308
+ allowed = check(user, scope, required_actions, **(context or {}))
309
+
310
+ return {
311
+ "allowed": allowed,
312
+ "user_id": user_id,
313
+ "scope": scope,
314
+ "required_actions": required_actions
315
+ }
316
+
317
+ except User.DoesNotExist:
318
+ raise NotFoundException(detail=f"L'utilisateur avec l'ID {user_id} n'existe pas")
319
+ except Exception as exc:
320
+ self.exception_handler(exc, self.logger)
321
+
322
+ def get_user_grants(
323
+ self,
324
+ user: AbstractBaseUser,
325
+ scope: Optional[str] = None
326
+ ) -> list[Grant]:
327
+ """
328
+ Récupère tous les grants d'un utilisateur.
329
+
330
+ Args:
331
+ user: L'utilisateur dont on récupère les grants
332
+ scope: Optionnel, filtre par scope
333
+
334
+ Returns:
335
+ Liste des grants de l'utilisateur
336
+ """
337
+ try:
338
+ queryset = Grant.objects.filter(user=user).select_related('role')
339
+
340
+ if scope:
341
+ queryset = queryset.filter(scope=scope)
342
+
343
+ return list(queryset)
344
+
345
+ except Exception as exc:
346
+ self.exception_handler(exc, self.logger)
347
+
348
+ def get_user_roles(self, user: AbstractBaseUser) -> list[str]:
349
+ """
350
+ Récupère tous les rôles uniques assignés à un utilisateur.
351
+
352
+ Args:
353
+ user: L'utilisateur dont on récupère les rôles
354
+
355
+ Returns:
356
+ Liste des slugs de rôles
357
+ """
358
+ try:
359
+ role_slugs = Grant.objects.filter(
360
+ user=user,
361
+ role__isnull=False
362
+ ).values_list('role__slug', flat=True).distinct()
363
+
364
+ return list(role_slugs)
365
+
366
+ except Exception as exc:
367
+ self.exception_handler(exc, self.logger)
368
+
369
+ def create_role(self, slug: str, name: str) -> Role:
370
+ """
371
+ Crée un nouveau rôle.
372
+
373
+ Args:
374
+ slug: Identifiant unique du rôle
375
+ name: Nom du rôle
376
+
377
+ Returns:
378
+ Le rôle créé
379
+
380
+ Raises:
381
+ DuplicateEntryException: Si le rôle existe déjà
382
+ """
383
+ try:
384
+ role = Role.objects.create(slug=slug, name=name)
385
+ return role
386
+
387
+ except Exception as exc:
388
+ self.exception_handler(exc, self.logger)
389
+
390
+ def create_group(
391
+ self,
392
+ group_data
393
+ ) -> Group:
394
+ """
395
+ Crée un nouveau groupe et lui assigne des rôles.
396
+
397
+ Args:
398
+ slug: Identifiant unique du groupe
399
+ name: Nom du groupe
400
+ role_slugs: Liste optionnelle des slugs de rôles à assigner
401
+
402
+ Returns:
403
+ Le groupe créé
404
+
405
+ Raises:
406
+ DuplicateEntryException: Si le groupe existe déjà
407
+ NotFoundException: Si un rôle n'existe pas
408
+ """
409
+ try:
410
+ group = Group.objects.create(slug=group_data.slug, name=group_data.name)
411
+
412
+ if group_data.roles:
413
+ roles = Role.objects.filter(slug__in=group_data.roles)
414
+
415
+ if roles.count() != len(group_data.roles):
416
+ found_slugs = set(roles.values_list('slug', flat=True))
417
+ missing_slugs = set(group_data.roles) - found_slugs
418
+ raise RoleNotFoundException(
419
+ detail=f"Rôles non trouvés: {list(missing_slugs)}"
420
+ )
421
+
422
+ group.roles.set(roles)
423
+
424
+ logger.info("group_created", slug=group_data.slug, name=group_data.name, role_slugs=group_data.roles, role_count=len(group_data.roles) if group_data.roles else 0)
425
+ return group
426
+
427
+ except Exception as exc:
428
+ self.exception_handler(exc, self.logger)
429
+
430
+ @transaction.atomic
431
+ def create_role_grant(
432
+ self,
433
+ grant_data
434
+ ) -> RoleGrant:
435
+ """
436
+ Crée un role grant (template de permissions pour un rôle).
437
+
438
+ Args:
439
+ role_slug: Slug du rôle
440
+ scope: Scope du grant
441
+ actions: Liste des actions autorisées
442
+ context: Contexte JSON optionnel
443
+
444
+ Returns:
445
+ Le role grant créé
446
+
447
+ Raises:
448
+ NotFoundException: Si le rôle n'existe pas
449
+ DuplicateEntryException: Si le role grant existe déjà
450
+ """
451
+ try:
452
+ role = Role.objects.get(slug=grant_data.role)
453
+
454
+ role_grant = RoleGrant.objects.create(
455
+ role=role,
456
+ scope=grant_data.scope,
457
+ actions=grant_data.actions,
458
+ context=grant_data.context
459
+ )
460
+
461
+ logger.info("role_grant_created", role_slug=grant_data.role, scope=grant_data.scope, actions=grant_data.actions)
462
+
463
+ return role_grant
464
+
465
+ except Role.DoesNotExist:
466
+ raise RoleNotFoundException(detail=f"Le rôle '{grant_data.role}' n'existe pas")
467
+ except Exception as exc:
468
+ self.exception_handler(exc, self.logger)
469
+
470
+ def get_role_grants(self, role_slug: str) -> list[RoleGrant]:
471
+ """
472
+ Récupère tous les grants d'un rôle.
473
+
474
+ Args:
475
+ role_slug: Slug du rôle
476
+
477
+ Returns:
478
+ Liste des role grants
479
+
480
+ Raises:
481
+ NotFoundException: Si le rôle n'existe pas
482
+ """
483
+ try:
484
+ role = Role.objects.get(slug=role_slug)
485
+ return list(RoleGrant.objects.filter(role=role))
486
+
487
+ except Exception as exc:
488
+ self.exception_handler(exc, self.logger)
489
+
490
+ @transaction.atomic
491
+ def create_grant(
492
+ self,
493
+ grant_data
494
+ ) -> Grant:
495
+ """
496
+ Crée un grant personnalisé pour un utilisateur.
497
+
498
+ Args:
499
+ grant_data: Schéma contenant les données du grant
500
+
501
+ Returns:
502
+ Le grant créé
503
+
504
+ Raises:
505
+ NotFoundException: Si l'utilisateur ou le rôle n'existe pas
506
+ """
507
+ try:
508
+ user = User.objects.get(pk=grant_data.user_id)
509
+
510
+ role_obj = None
511
+ if grant_data.role:
512
+ try:
513
+ role_obj = Role.objects.get(slug=grant_data.role)
514
+ except Role.DoesNotExist:
515
+ raise RoleNotFoundException(detail=f"Le rôle '{grant_data.role}' n'existe pas")
516
+
517
+ grant = Grant.objects.create(
518
+ user=user,
519
+ role=role_obj,
520
+ scope=grant_data.scope,
521
+ actions=grant_data.actions,
522
+ context=grant_data.context,
523
+ user_group=None
524
+ )
525
+
526
+ logger.info("grant_created", user_id=grant_data.user_id, scope=grant_data.scope, actions=grant_data.actions)
527
+
528
+ return grant
529
+
530
+ except User.DoesNotExist:
531
+ raise NotFoundException(detail=f"L'utilisateur avec l'ID {grant_data.user_id} n'existe pas")
532
+ except Exception as exc:
533
+ self.exception_handler(exc, self.logger)
534
+
535
+ def update_grant(
536
+ self,
537
+ grant_id: int,
538
+ grant_data
539
+ ) -> Grant:
540
+ """
541
+ Met à jour un grant existant.
542
+
543
+ Args:
544
+ grant_id: ID du grant à mettre à jour
545
+ grant_data: Schéma contenant les nouvelles données
546
+
547
+ Returns:
548
+ Le grant mis à jour
549
+
550
+ Raises:
551
+ GrantNotFoundException: Si le grant n'existe pas
552
+ """
553
+ try:
554
+ grant = Grant.objects.get(pk=grant_id)
555
+
556
+ if grant_data.actions is not None:
557
+ grant.actions = grant_data.actions
558
+
559
+ if grant_data.context is not None:
560
+ grant.context = grant_data.context
561
+
562
+ if grant_data.role is not None:
563
+ try:
564
+ grant.role = Role.objects.get(slug=grant_data.role)
565
+ except Role.DoesNotExist:
566
+ raise RoleNotFoundException(detail=f"Le rôle '{grant_data.role}' n'existe pas")
567
+
568
+ grant.save()
569
+
570
+ logger.info("grant_updated", grant_id=grant_id)
571
+
572
+ return grant
573
+
574
+ except Grant.DoesNotExist:
575
+ raise GrantNotFoundException(detail=f"Le grant avec l'ID {grant_id} n'existe pas")
576
+ except Exception as exc:
577
+ self.exception_handler(exc, self.logger)
578
+
579
+ def delete_grant(
580
+ self,
581
+ grant_id: int
582
+ ) -> None:
583
+ """
584
+ Supprime un grant.
585
+
586
+ Args:
587
+ grant_id: ID du grant à supprimer
588
+
589
+ Raises:
590
+ GrantNotFoundException: Si le grant n'existe pas
591
+ """
592
+ try:
593
+ grant = Grant.objects.get(pk=grant_id)
594
+ grant.delete()
595
+
596
+ logger.info("grant_deleted", grant_id=grant_id)
597
+
598
+ except Grant.DoesNotExist:
599
+ raise GrantNotFoundException(detail=f"Le grant avec l'ID {grant_id} n'existe pas")
600
+ except Exception as exc:
601
+ self.exception_handler(exc, self.logger)
602
+
603
+ def update_role_grant(
604
+ self,
605
+ grant_id: int,
606
+ grant_data
607
+ ) -> RoleGrant:
608
+ """
609
+ Met à jour un role grant existant.
610
+
611
+ Args:
612
+ grant_id: ID du role grant à mettre à jour
613
+ grant_data: Schéma contenant les nouvelles données
614
+
615
+ Returns:
616
+ Le role grant mis à jour
617
+
618
+ Raises:
619
+ RoleGrantNotFoundException: Si le role grant n'existe pas
620
+ """
621
+ try:
622
+ role_grant = RoleGrant.objects.get(pk=grant_id)
623
+
624
+ if grant_data.actions is not None:
625
+ role_grant.actions = grant_data.actions
626
+
627
+ if grant_data.context is not None:
628
+ role_grant.context = grant_data.context
629
+
630
+ role_grant.save()
631
+
632
+ logger.info("role_grant_updated", grant_id=grant_id)
633
+
634
+ return role_grant
635
+
636
+ except RoleGrant.DoesNotExist:
637
+ raise RoleGrantNotFoundException(detail=f"Le role grant avec l'ID {grant_id} n'existe pas")
638
+ except Exception as exc:
639
+ self.exception_handler(exc, self.logger)
640
+
641
+ def delete_role_grant(
642
+ self,
643
+ grant_id: int
644
+ ) -> None:
645
+ """
646
+ Supprime un role grant.
647
+
648
+ Args:
649
+ grant_id: ID du role grant à supprimer
650
+
651
+ Raises:
652
+ RoleGrantNotFoundException: Si le role grant n'existe pas
653
+ """
654
+ try:
655
+ role_grant = RoleGrant.objects.get(pk=grant_id)
656
+ role_grant.delete()
657
+
658
+ logger.info("role_grant_deleted", grant_id=grant_id)
659
+
660
+ except RoleGrant.DoesNotExist:
661
+ raise RoleGrantNotFoundException(detail=f"Le role grant avec l'ID {grant_id} n'existe pas")
662
+ except Exception as exc:
663
+ self.exception_handler(exc, self.logger)
@@ -0,0 +1,3 @@
1
+ from django.test import TestCase
2
+
3
+ # Create your tests here.