oxutils 0.1.7__py3-none-any.whl → 0.1.10__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.
- oxutils/__init__.py +2 -1
- oxutils/constants.py +4 -0
- oxutils/jwt/auth.py +150 -1
- oxutils/jwt/models.py +32 -1
- oxutils/jwt/utils.py +45 -0
- oxutils/logger/receivers.py +10 -6
- oxutils/models/base.py +102 -0
- oxutils/models/fields.py +79 -0
- oxutils/oxiliere/apps.py +6 -1
- oxutils/oxiliere/authorization.py +45 -0
- oxutils/oxiliere/caches.py +8 -7
- oxutils/oxiliere/checks.py +31 -0
- oxutils/oxiliere/constants.py +3 -0
- oxutils/oxiliere/context.py +18 -0
- oxutils/oxiliere/exceptions.py +16 -0
- oxutils/oxiliere/management/commands/grant_tenant_owners.py +19 -0
- oxutils/oxiliere/management/commands/init_oxiliere_system.py +20 -8
- oxutils/oxiliere/middleware.py +29 -13
- oxutils/oxiliere/models.py +130 -19
- oxutils/oxiliere/permissions.py +6 -5
- oxutils/oxiliere/schemas.py +13 -4
- oxutils/oxiliere/signals.py +5 -0
- oxutils/oxiliere/utils.py +18 -0
- oxutils/pagination/__init__.py +0 -0
- oxutils/pagination/cursor.py +367 -0
- oxutils/permissions/__init__.py +0 -0
- oxutils/permissions/actions.py +57 -0
- oxutils/permissions/admin.py +3 -0
- oxutils/permissions/apps.py +10 -0
- oxutils/permissions/caches.py +19 -0
- oxutils/permissions/checks.py +188 -0
- oxutils/permissions/constants.py +0 -0
- oxutils/permissions/controllers.py +344 -0
- oxutils/permissions/exceptions.py +60 -0
- oxutils/permissions/management/__init__.py +0 -0
- oxutils/permissions/management/commands/__init__.py +0 -0
- oxutils/permissions/management/commands/load_permission_preset.py +112 -0
- oxutils/permissions/migrations/0001_initial.py +112 -0
- oxutils/permissions/migrations/0002_alter_grant_role.py +19 -0
- oxutils/permissions/migrations/0003_alter_grant_options_alter_group_options_and_more.py +33 -0
- oxutils/permissions/migrations/__init__.py +0 -0
- oxutils/permissions/models.py +171 -0
- oxutils/permissions/perms.py +95 -0
- oxutils/permissions/queryset.py +92 -0
- oxutils/permissions/schemas.py +276 -0
- oxutils/permissions/services.py +663 -0
- oxutils/permissions/tests.py +3 -0
- oxutils/permissions/utils.py +628 -0
- oxutils/settings.py +1 -0
- oxutils/users/migrations/0002_alter_user_first_name_alter_user_last_name.py +23 -0
- oxutils/users/models.py +2 -0
- oxutils/utils.py +25 -0
- {oxutils-0.1.7.dist-info → oxutils-0.1.10.dist-info}/METADATA +1 -1
- {oxutils-0.1.7.dist-info → oxutils-0.1.10.dist-info}/RECORD +55 -19
- {oxutils-0.1.7.dist-info → oxutils-0.1.10.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
from django.conf import settings
|
|
3
|
+
from django.http import HttpRequest
|
|
4
|
+
from ninja_extra import (
|
|
5
|
+
api_controller,
|
|
6
|
+
ControllerBase,
|
|
7
|
+
http_get,
|
|
8
|
+
http_post,
|
|
9
|
+
http_put,
|
|
10
|
+
http_delete,
|
|
11
|
+
paginate,
|
|
12
|
+
)
|
|
13
|
+
from ninja_extra.permissions import IsAuthenticated
|
|
14
|
+
from ninja_extra.pagination import PageNumberPaginationExtra, PaginatedResponseSchema
|
|
15
|
+
from . import schemas
|
|
16
|
+
from .models import Role, Group, RoleGrant, Grant
|
|
17
|
+
from .services import PermissionService
|
|
18
|
+
from .perms import access_manager
|
|
19
|
+
from oxutils.exceptions import NotFoundException, ValidationException
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@api_controller(
|
|
25
|
+
"/access",
|
|
26
|
+
permissions=[
|
|
27
|
+
IsAuthenticated & access_manager('r')
|
|
28
|
+
]
|
|
29
|
+
)
|
|
30
|
+
class PermissionController(ControllerBase):
|
|
31
|
+
"""
|
|
32
|
+
Contrôleur pour la gestion des permissions, rôles et groupes.
|
|
33
|
+
"""
|
|
34
|
+
service = PermissionService()
|
|
35
|
+
|
|
36
|
+
@http_get('/scopes', response=List[str])
|
|
37
|
+
def list_scopes(self):
|
|
38
|
+
return getattr(settings, 'ACCESS_SCOPES', [])
|
|
39
|
+
|
|
40
|
+
@http_get("/roles", response=PaginatedResponseSchema[schemas.RoleSchema])
|
|
41
|
+
@paginate(PageNumberPaginationExtra, page_size=20)
|
|
42
|
+
def list_roles(self):
|
|
43
|
+
"""
|
|
44
|
+
Liste tous les rôles.
|
|
45
|
+
"""
|
|
46
|
+
return self.service.get_roles()
|
|
47
|
+
|
|
48
|
+
@http_get("/roles/{role_slug}", response=schemas.RoleSchema)
|
|
49
|
+
def get_role(self, role_slug: str):
|
|
50
|
+
"""
|
|
51
|
+
Récupère un rôle par son slug.
|
|
52
|
+
"""
|
|
53
|
+
return self.service.get_role(role_slug)
|
|
54
|
+
|
|
55
|
+
# Groupes
|
|
56
|
+
@http_post(
|
|
57
|
+
"/groups",
|
|
58
|
+
response=schemas.GroupSchema,
|
|
59
|
+
permissions=[
|
|
60
|
+
IsAuthenticated & access_manager('w')
|
|
61
|
+
]
|
|
62
|
+
)
|
|
63
|
+
def create_group(self, group_data: schemas.GroupCreateSchema):
|
|
64
|
+
"""
|
|
65
|
+
Crée un nouveau groupe de rôles.
|
|
66
|
+
"""
|
|
67
|
+
return self.service.create_group(group_data)
|
|
68
|
+
|
|
69
|
+
@http_get(
|
|
70
|
+
"/groups",
|
|
71
|
+
response=PaginatedResponseSchema[schemas.GroupSchema],
|
|
72
|
+
)
|
|
73
|
+
@paginate(PageNumberPaginationExtra, page_size=20)
|
|
74
|
+
def list_groups(self):
|
|
75
|
+
"""
|
|
76
|
+
Liste tous les groupes de rôles.
|
|
77
|
+
"""
|
|
78
|
+
return Group.objects.all()
|
|
79
|
+
|
|
80
|
+
@http_get(
|
|
81
|
+
"/groups/{group_slug}",
|
|
82
|
+
response=schemas.GroupSchema,
|
|
83
|
+
)
|
|
84
|
+
def get_group(self, group_slug: str):
|
|
85
|
+
"""
|
|
86
|
+
Récupère un groupe par son slug.
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
return Group.objects.get(slug=group_slug)
|
|
90
|
+
except Group.DoesNotExist:
|
|
91
|
+
raise NotFoundException("Groupe non trouvé")
|
|
92
|
+
|
|
93
|
+
@http_put(
|
|
94
|
+
"/groups/{group_slug}",
|
|
95
|
+
response=schemas.GroupSchema,
|
|
96
|
+
permissions=[
|
|
97
|
+
IsAuthenticated & access_manager('ru')
|
|
98
|
+
]
|
|
99
|
+
)
|
|
100
|
+
def update_group(self, group_slug: str, group_data: schemas.GroupUpdateSchema):
|
|
101
|
+
"""
|
|
102
|
+
Met à jour un groupe existant.
|
|
103
|
+
"""
|
|
104
|
+
try:
|
|
105
|
+
group = Group.objects.get(slug=group_slug)
|
|
106
|
+
|
|
107
|
+
# Mise à jour des champs simples
|
|
108
|
+
for field, value in group_data.dict(exclude_unset=True, exclude={"roles"}).items():
|
|
109
|
+
setattr(group, field, value)
|
|
110
|
+
|
|
111
|
+
# Mise à jour des rôles si fournis
|
|
112
|
+
if group_data.roles is not None:
|
|
113
|
+
roles = Role.objects.filter(slug__in=group_data.roles)
|
|
114
|
+
group.roles.set(roles)
|
|
115
|
+
|
|
116
|
+
group.save()
|
|
117
|
+
return group
|
|
118
|
+
except Group.DoesNotExist:
|
|
119
|
+
raise NotFoundException("Groupe non trouvé")
|
|
120
|
+
except Exception as e:
|
|
121
|
+
raise ValidationException(str(e))
|
|
122
|
+
|
|
123
|
+
@http_delete(
|
|
124
|
+
"/groups/{group_slug}",
|
|
125
|
+
response={
|
|
126
|
+
"204": None
|
|
127
|
+
},
|
|
128
|
+
permissions=[
|
|
129
|
+
IsAuthenticated & access_manager('d')
|
|
130
|
+
]
|
|
131
|
+
)
|
|
132
|
+
def delete_group(self, group_slug: str):
|
|
133
|
+
"""
|
|
134
|
+
Supprime un groupe.
|
|
135
|
+
"""
|
|
136
|
+
try:
|
|
137
|
+
group = Group.objects.get(slug=group_slug)
|
|
138
|
+
group.delete()
|
|
139
|
+
return None
|
|
140
|
+
except Group.DoesNotExist:
|
|
141
|
+
raise NotFoundException("Groupe non trouvé")
|
|
142
|
+
|
|
143
|
+
# Rôles des utilisateurs
|
|
144
|
+
@http_post(
|
|
145
|
+
"/users/assign-role",
|
|
146
|
+
response=schemas.RoleSchema,
|
|
147
|
+
permissions=[
|
|
148
|
+
IsAuthenticated & access_manager('rw')
|
|
149
|
+
]
|
|
150
|
+
)
|
|
151
|
+
def assign_role_to_user(self, data: schemas.AssignRoleSchema, request: HttpRequest):
|
|
152
|
+
"""
|
|
153
|
+
Assigne un rôle à un utilisateur.
|
|
154
|
+
"""
|
|
155
|
+
return self.service.assign_role_to_user(
|
|
156
|
+
user_id=data.user_id,
|
|
157
|
+
role_slug=data.role,
|
|
158
|
+
by_user=request.user if request.user.is_authenticated else None
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
@http_post(
|
|
162
|
+
"/users/revoke-role",
|
|
163
|
+
response={
|
|
164
|
+
"204": None
|
|
165
|
+
},
|
|
166
|
+
permissions=[
|
|
167
|
+
IsAuthenticated & access_manager('rw')
|
|
168
|
+
]
|
|
169
|
+
)
|
|
170
|
+
def revoke_role_from_user(self, data: schemas.RevokeRoleSchema):
|
|
171
|
+
"""
|
|
172
|
+
Révoque un rôle d'un utilisateur.
|
|
173
|
+
"""
|
|
174
|
+
self.service.revoke_role_from_user(
|
|
175
|
+
user_id=data.user_id,
|
|
176
|
+
role_slug=data.role
|
|
177
|
+
)
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
@http_post(
|
|
181
|
+
"/users/assign-group",
|
|
182
|
+
response=List[schemas.RoleSchema],
|
|
183
|
+
permissions=[
|
|
184
|
+
IsAuthenticated & access_manager('rw')
|
|
185
|
+
]
|
|
186
|
+
)
|
|
187
|
+
def assign_group_to_user(self, data: schemas.AssignGroupSchema, request: HttpRequest):
|
|
188
|
+
"""
|
|
189
|
+
Assigne un groupe de rôles à un utilisateur.
|
|
190
|
+
"""
|
|
191
|
+
return self.service.assign_group_to_user(
|
|
192
|
+
user_id=data.user_id,
|
|
193
|
+
group_slug=data.group,
|
|
194
|
+
by_user=request.user if request.user.is_authenticated else None
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
@http_post(
|
|
198
|
+
"/users/revoke-group",
|
|
199
|
+
response={
|
|
200
|
+
"204": None
|
|
201
|
+
},
|
|
202
|
+
permissions=[
|
|
203
|
+
IsAuthenticated & access_manager('rw')
|
|
204
|
+
]
|
|
205
|
+
)
|
|
206
|
+
def revoke_group_from_user(self, data: schemas.RevokeGroupSchema):
|
|
207
|
+
"""
|
|
208
|
+
Révoque un groupe de rôles d'un utilisateur.
|
|
209
|
+
"""
|
|
210
|
+
self.service.revoke_group_from_user(
|
|
211
|
+
user_id=data.user_id,
|
|
212
|
+
group_slug=data.group
|
|
213
|
+
)
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
@http_post(
|
|
217
|
+
"/groups/{group_slug}/sync",
|
|
218
|
+
response=schemas.GroupSyncResponseSchema,
|
|
219
|
+
permissions=[
|
|
220
|
+
IsAuthenticated & access_manager('rw')
|
|
221
|
+
]
|
|
222
|
+
)
|
|
223
|
+
def sync_group(self, group_slug: str):
|
|
224
|
+
"""
|
|
225
|
+
Synchronise les grants de tous les utilisateurs d'un groupe.
|
|
226
|
+
À appeler après modification des RoleGrants ou des rôles du groupe.
|
|
227
|
+
"""
|
|
228
|
+
return self.service.sync_group(group_slug)
|
|
229
|
+
|
|
230
|
+
# Grants
|
|
231
|
+
@http_post(
|
|
232
|
+
"/grants",
|
|
233
|
+
response=schemas.GrantSchema,
|
|
234
|
+
permissions=[
|
|
235
|
+
IsAuthenticated & access_manager('rw')
|
|
236
|
+
]
|
|
237
|
+
)
|
|
238
|
+
def create_grant(self, grant_data: schemas.GrantCreateSchema):
|
|
239
|
+
"""
|
|
240
|
+
Crée une nouvelle permission personnalisée pour un utilisateur.
|
|
241
|
+
"""
|
|
242
|
+
return self.service.create_grant(grant_data)
|
|
243
|
+
|
|
244
|
+
@http_get(
|
|
245
|
+
"/grants",
|
|
246
|
+
response=PaginatedResponseSchema[schemas.GrantSchema],
|
|
247
|
+
)
|
|
248
|
+
@paginate(PageNumberPaginationExtra, page_size=20)
|
|
249
|
+
def list_grants(self, user_id: Optional[int] = None, role: Optional[str] = None):
|
|
250
|
+
"""
|
|
251
|
+
Liste les grants, avec filtrage optionnel par utilisateur et/ou rôle.
|
|
252
|
+
"""
|
|
253
|
+
queryset = Grant.objects.all()
|
|
254
|
+
if user_id:
|
|
255
|
+
queryset = queryset.filter(user_id=user_id)
|
|
256
|
+
if role:
|
|
257
|
+
queryset = queryset.filter(role__slug=role)
|
|
258
|
+
return queryset
|
|
259
|
+
|
|
260
|
+
@http_put(
|
|
261
|
+
"/grants/{grant_id}",
|
|
262
|
+
response=schemas.GrantSchema,
|
|
263
|
+
permissions=[
|
|
264
|
+
IsAuthenticated & access_manager('ru')
|
|
265
|
+
]
|
|
266
|
+
)
|
|
267
|
+
def update_grant(self, grant_id: int, grant_data: schemas.GrantUpdateSchema):
|
|
268
|
+
"""
|
|
269
|
+
Met à jour une permission personnalisée.
|
|
270
|
+
"""
|
|
271
|
+
return self.service.update_grant(grant_id, grant_data)
|
|
272
|
+
|
|
273
|
+
@http_delete(
|
|
274
|
+
"/grants/{grant_id}",
|
|
275
|
+
response={
|
|
276
|
+
"204": None
|
|
277
|
+
},
|
|
278
|
+
permissions=[
|
|
279
|
+
IsAuthenticated & access_manager('d')
|
|
280
|
+
]
|
|
281
|
+
)
|
|
282
|
+
def delete_grant(self, grant_id: int):
|
|
283
|
+
"""
|
|
284
|
+
Supprime une permission personnalisée.
|
|
285
|
+
"""
|
|
286
|
+
self.service.delete_grant(grant_id)
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
# Role Grants
|
|
290
|
+
@http_post(
|
|
291
|
+
"/role-grants",
|
|
292
|
+
response=schemas.RoleGrantSchema,
|
|
293
|
+
permissions=[
|
|
294
|
+
IsAuthenticated & access_manager('rw')
|
|
295
|
+
]
|
|
296
|
+
)
|
|
297
|
+
def create_role_grant(self, grant_data: schemas.RoleGrantCreateSchema):
|
|
298
|
+
"""
|
|
299
|
+
Crée une nouvelle permission pour un rôle.
|
|
300
|
+
"""
|
|
301
|
+
return self.service.create_role_grant(grant_data)
|
|
302
|
+
|
|
303
|
+
@http_get(
|
|
304
|
+
"/role-grants",
|
|
305
|
+
response=PaginatedResponseSchema[schemas.RoleGrantSchema],
|
|
306
|
+
)
|
|
307
|
+
@paginate(PageNumberPaginationExtra, page_size=20)
|
|
308
|
+
def list_role_grants(self, role: Optional[str] = None):
|
|
309
|
+
"""
|
|
310
|
+
Liste les permissions de rôles, avec filtrage optionnel par rôle.
|
|
311
|
+
"""
|
|
312
|
+
queryset = RoleGrant.objects.select_related('role').all()
|
|
313
|
+
if role:
|
|
314
|
+
queryset = queryset.filter(role__slug=role)
|
|
315
|
+
return queryset
|
|
316
|
+
|
|
317
|
+
@http_put(
|
|
318
|
+
"/role-grants/{grant_id}",
|
|
319
|
+
response=schemas.RoleGrantSchema,
|
|
320
|
+
permissions=[
|
|
321
|
+
IsAuthenticated & access_manager('ru')
|
|
322
|
+
]
|
|
323
|
+
)
|
|
324
|
+
def update_role_grant(self, grant_id: int, grant_data: schemas.RoleGrantUpdateSchema):
|
|
325
|
+
"""
|
|
326
|
+
Met à jour une permission de rôle.
|
|
327
|
+
"""
|
|
328
|
+
return self.service.update_role_grant(grant_id, grant_data)
|
|
329
|
+
|
|
330
|
+
@http_delete(
|
|
331
|
+
"/role-grants/{grant_id}/",
|
|
332
|
+
response={
|
|
333
|
+
"204": None
|
|
334
|
+
},
|
|
335
|
+
permissions=[
|
|
336
|
+
IsAuthenticated & access_manager('d')
|
|
337
|
+
]
|
|
338
|
+
)
|
|
339
|
+
def delete_role_grant(self, grant_id: int):
|
|
340
|
+
"""
|
|
341
|
+
Supprime une permission de rôle.
|
|
342
|
+
"""
|
|
343
|
+
self.service.delete_role_grant(grant_id)
|
|
344
|
+
return None
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
from oxutils.exceptions import (
|
|
3
|
+
APIException,
|
|
4
|
+
NotFoundException,
|
|
5
|
+
ValidationException,
|
|
6
|
+
DuplicateEntryException,
|
|
7
|
+
PermissionDeniedException,
|
|
8
|
+
ExceptionCode
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RoleNotFoundException(NotFoundException):
|
|
13
|
+
"""Exception levée quand un rôle n'est pas trouvé."""
|
|
14
|
+
default_detail = _('Le rôle demandé n\'existe pas')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GroupNotFoundException(NotFoundException):
|
|
18
|
+
"""Exception levée quand un groupe n'est pas trouvé."""
|
|
19
|
+
default_detail = _('Le groupe demandé n\'existe pas')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class GrantNotFoundException(NotFoundException):
|
|
23
|
+
"""Exception levée quand un grant n'est pas trouvé."""
|
|
24
|
+
default_detail = _('Le grant demandé n\'existe pas')
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RoleGrantNotFoundException(NotFoundException):
|
|
28
|
+
"""Exception levée quand un role grant n'est pas trouvé."""
|
|
29
|
+
default_detail = _('Le role grant demandé n\'existe pas')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class RoleAlreadyAssignedException(DuplicateEntryException):
|
|
33
|
+
"""Exception levée quand un rôle est déjà assigné à un utilisateur."""
|
|
34
|
+
default_detail = _('Ce rôle est déjà assigné à l\'utilisateur')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class GroupAlreadyAssignedException(DuplicateEntryException):
|
|
38
|
+
"""Exception levée quand un groupe est déjà assigné à un utilisateur."""
|
|
39
|
+
default_detail = _('Ce groupe est déjà assigné à l\'utilisateur')
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class InvalidActionsException(ValidationException):
|
|
43
|
+
"""Exception levée quand des actions invalides sont fournies."""
|
|
44
|
+
default_detail = _('Les actions fournies sont invalides')
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class InsufficientPermissionsException(PermissionDeniedException):
|
|
48
|
+
"""Exception levée quand l'utilisateur n'a pas les permissions suffisantes."""
|
|
49
|
+
default_code = ExceptionCode.INSUFFICIENT_PERMISSIONS
|
|
50
|
+
default_detail = _('Permissions insuffisantes pour effectuer cette action')
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RoleGrantConflictException(DuplicateEntryException):
|
|
54
|
+
"""Exception levée quand un role grant existe déjà pour ce rôle et scope."""
|
|
55
|
+
default_detail = _('Un role grant existe déjà pour ce rôle et ce scope')
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class GrantConflictException(DuplicateEntryException):
|
|
59
|
+
"""Exception levée quand un grant existe déjà pour cet utilisateur et scope."""
|
|
60
|
+
default_detail = _('Un grant existe déjà pour cet utilisateur et ce scope')
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from django.core.management.base import BaseCommand, CommandError
|
|
3
|
+
from django.db import transaction
|
|
4
|
+
|
|
5
|
+
from oxutils.permissions.utils import load_preset
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Command(BaseCommand):
|
|
9
|
+
"""
|
|
10
|
+
Commande de management Django pour charger un preset de permissions.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python manage.py load_permission_preset
|
|
14
|
+
python manage.py load_permission_preset --dry-run
|
|
15
|
+
python manage.py load_permission_preset --force
|
|
16
|
+
python manage.py load_permission_preset --dry-run --force
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
help = "Charge un preset de permissions depuis settings.PERMISSION_PRESET"
|
|
20
|
+
|
|
21
|
+
def add_arguments(self, parser) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Ajoute les arguments de la commande.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
parser: ArgumentParser de Django
|
|
27
|
+
"""
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
'--dry-run',
|
|
30
|
+
action='store_true',
|
|
31
|
+
help='Simule le chargement sans créer les objets en base de données',
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
'--force',
|
|
35
|
+
action='store_true',
|
|
36
|
+
help='Force le chargement même si des rôles existent déjà en base',
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@transaction.atomic
|
|
40
|
+
def handle(self, *args: Any, **options: Any) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Exécute la commande de chargement du preset.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
*args: Arguments positionnels
|
|
46
|
+
**options: Options de la commande
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
CommandError: Si le preset n'est pas défini ou est invalide
|
|
50
|
+
"""
|
|
51
|
+
dry_run = options.get('dry_run', False)
|
|
52
|
+
force = options.get('force', False)
|
|
53
|
+
|
|
54
|
+
if dry_run:
|
|
55
|
+
self.stdout.write(
|
|
56
|
+
self.style.WARNING('Mode DRY-RUN activé - Aucune modification ne sera effectuée')
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if force:
|
|
60
|
+
self.stdout.write(
|
|
61
|
+
self.style.WARNING('Mode FORCE activé - Les rôles existants seront ignorés')
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
# Charger le preset
|
|
66
|
+
self.stdout.write('Chargement du preset de permissions...')
|
|
67
|
+
|
|
68
|
+
if dry_run:
|
|
69
|
+
# En mode dry-run, on utilise un savepoint pour rollback
|
|
70
|
+
sid = transaction.savepoint()
|
|
71
|
+
|
|
72
|
+
stats = load_preset(force=force)
|
|
73
|
+
|
|
74
|
+
if dry_run:
|
|
75
|
+
# Rollback en mode dry-run
|
|
76
|
+
transaction.savepoint_rollback(sid)
|
|
77
|
+
|
|
78
|
+
# Afficher les statistiques
|
|
79
|
+
self.stdout.write(
|
|
80
|
+
self.style.SUCCESS('\n✓ Preset chargé avec succès!')
|
|
81
|
+
)
|
|
82
|
+
self.stdout.write(f" • Rôles créés: {stats['roles']}")
|
|
83
|
+
self.stdout.write(f" • Groupes créés: {stats['groups']}")
|
|
84
|
+
self.stdout.write(f" • Role grants créés: {stats['role_grants']}")
|
|
85
|
+
|
|
86
|
+
if dry_run:
|
|
87
|
+
self.stdout.write(
|
|
88
|
+
self.style.WARNING('\nAucune modification effectuée (mode dry-run)')
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
except AttributeError as e:
|
|
92
|
+
raise CommandError(
|
|
93
|
+
f"Erreur de configuration: {str(e)}\n"
|
|
94
|
+
"Assurez-vous que PERMISSION_PRESET est défini dans vos settings Django."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
except PermissionError as e:
|
|
98
|
+
raise CommandError(
|
|
99
|
+
f"{str(e)}\n"
|
|
100
|
+
"Utilisez --force pour forcer le chargement malgré les rôles existants."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
except (KeyError, ValueError) as e:
|
|
104
|
+
raise CommandError(
|
|
105
|
+
f"Erreur dans le preset: {str(e)}\n"
|
|
106
|
+
"Vérifiez la structure de votre PERMISSION_PRESET."
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
raise CommandError(
|
|
111
|
+
f"Erreur inattendue lors du chargement du preset: {str(e)}"
|
|
112
|
+
)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Generated by Django 5.2.9 on 2025-12-27 10:49
|
|
2
|
+
|
|
3
|
+
import django.contrib.postgres.fields
|
|
4
|
+
import django.contrib.postgres.indexes
|
|
5
|
+
import django.db.models.deletion
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
from django.db import migrations, models
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Migration(migrations.Migration):
|
|
11
|
+
|
|
12
|
+
initial = True
|
|
13
|
+
|
|
14
|
+
dependencies = [
|
|
15
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
operations = [
|
|
19
|
+
migrations.CreateModel(
|
|
20
|
+
name='Role',
|
|
21
|
+
fields=[
|
|
22
|
+
('created_at', models.DateTimeField(auto_now_add=True, help_text='Date and time when this record was created')),
|
|
23
|
+
('updated_at', models.DateTimeField(auto_now=True, help_text='Date and time when this record was last updated')),
|
|
24
|
+
('slug', models.SlugField(primary_key=True, serialize=False, unique=True)),
|
|
25
|
+
('name', models.CharField(max_length=100)),
|
|
26
|
+
],
|
|
27
|
+
options={
|
|
28
|
+
'indexes': [models.Index(fields=['slug'], name='permissions_slug_ae4163_idx')],
|
|
29
|
+
},
|
|
30
|
+
),
|
|
31
|
+
migrations.CreateModel(
|
|
32
|
+
name='Group',
|
|
33
|
+
fields=[
|
|
34
|
+
('created_at', models.DateTimeField(auto_now_add=True, help_text='Date and time when this record was created')),
|
|
35
|
+
('updated_at', models.DateTimeField(auto_now=True, help_text='Date and time when this record was last updated')),
|
|
36
|
+
('slug', models.SlugField(primary_key=True, serialize=False, unique=True)),
|
|
37
|
+
('name', models.CharField(max_length=100)),
|
|
38
|
+
('roles', models.ManyToManyField(related_name='groups', to='permissions.role')),
|
|
39
|
+
],
|
|
40
|
+
options={
|
|
41
|
+
'abstract': False,
|
|
42
|
+
},
|
|
43
|
+
),
|
|
44
|
+
migrations.CreateModel(
|
|
45
|
+
name='UserGroup',
|
|
46
|
+
fields=[
|
|
47
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
48
|
+
('created_at', models.DateTimeField(auto_now_add=True, help_text='Date and time when this record was created')),
|
|
49
|
+
('updated_at', models.DateTimeField(auto_now=True, help_text='Date and time when this record was last updated')),
|
|
50
|
+
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_groups', to='permissions.group')),
|
|
51
|
+
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_groups', to=settings.AUTH_USER_MODEL)),
|
|
52
|
+
],
|
|
53
|
+
),
|
|
54
|
+
migrations.CreateModel(
|
|
55
|
+
name='Grant',
|
|
56
|
+
fields=[
|
|
57
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
58
|
+
('created_at', models.DateTimeField(auto_now_add=True, help_text='Date and time when this record was created')),
|
|
59
|
+
('updated_at', models.DateTimeField(auto_now=True, help_text='Date and time when this record was last updated')),
|
|
60
|
+
('scope', models.CharField(max_length=100)),
|
|
61
|
+
('actions', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=5), size=None)),
|
|
62
|
+
('context', models.JSONField(blank=True, default=dict)),
|
|
63
|
+
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_grants', to=settings.AUTH_USER_MODEL)),
|
|
64
|
+
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='grants', to=settings.AUTH_USER_MODEL)),
|
|
65
|
+
('role', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='grants', to='permissions.role')),
|
|
66
|
+
('user_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='grants', to='permissions.usergroup')),
|
|
67
|
+
],
|
|
68
|
+
),
|
|
69
|
+
migrations.CreateModel(
|
|
70
|
+
name='RoleGrant',
|
|
71
|
+
fields=[
|
|
72
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
73
|
+
('scope', models.CharField(max_length=100)),
|
|
74
|
+
('actions', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=5), size=None)),
|
|
75
|
+
('context', models.JSONField(blank=True, default=dict)),
|
|
76
|
+
('group', models.ForeignKey(blank=True, help_text='Groupe optionnel pour des comportements spécifiques', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='role_grants', to='permissions.group')),
|
|
77
|
+
('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='grants', to='permissions.role')),
|
|
78
|
+
],
|
|
79
|
+
options={
|
|
80
|
+
'indexes': [models.Index(fields=['role'], name='permissions_role_id_382ed4_idx'), models.Index(fields=['group'], name='permissions_group_i_465f8d_idx'), models.Index(fields=['role', 'group'], name='permissions_role_id_0818de_idx')],
|
|
81
|
+
'constraints': [models.UniqueConstraint(fields=('role', 'scope', 'group'), name='unique_role_scope_group')],
|
|
82
|
+
},
|
|
83
|
+
),
|
|
84
|
+
migrations.AddIndex(
|
|
85
|
+
model_name='usergroup',
|
|
86
|
+
index=models.Index(fields=['user', 'group'], name='permissions_user_id_f1ff5d_idx'),
|
|
87
|
+
),
|
|
88
|
+
migrations.AddConstraint(
|
|
89
|
+
model_name='usergroup',
|
|
90
|
+
constraint=models.UniqueConstraint(fields=('user', 'group'), name='unique_user_group'),
|
|
91
|
+
),
|
|
92
|
+
migrations.AddIndex(
|
|
93
|
+
model_name='grant',
|
|
94
|
+
index=models.Index(fields=['user', 'scope'], name='permissions_user_id_8a615b_idx'),
|
|
95
|
+
),
|
|
96
|
+
migrations.AddIndex(
|
|
97
|
+
model_name='grant',
|
|
98
|
+
index=models.Index(fields=['user_group'], name='permissions_user_gr_ec61ff_idx'),
|
|
99
|
+
),
|
|
100
|
+
migrations.AddIndex(
|
|
101
|
+
model_name='grant',
|
|
102
|
+
index=django.contrib.postgres.indexes.GinIndex(fields=['actions'], name='permissions_actions_541150_gin'),
|
|
103
|
+
),
|
|
104
|
+
migrations.AddIndex(
|
|
105
|
+
model_name='grant',
|
|
106
|
+
index=django.contrib.postgres.indexes.GinIndex(fields=['context'], name='permissions_context_7b1c0e_gin'),
|
|
107
|
+
),
|
|
108
|
+
migrations.AddConstraint(
|
|
109
|
+
model_name='grant',
|
|
110
|
+
constraint=models.UniqueConstraint(fields=('user', 'scope', 'role', 'user_group'), name='unique_user_scope_role'),
|
|
111
|
+
),
|
|
112
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Generated by Django 5.2.9 on 2025-12-29 09:04
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('permissions', '0001_initial'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AlterField(
|
|
15
|
+
model_name='grant',
|
|
16
|
+
name='role',
|
|
17
|
+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='user_grants', to='permissions.role'),
|
|
18
|
+
),
|
|
19
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Generated by Django 5.2.9 on 2025-12-29 13:28
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('permissions', '0002_alter_grant_role'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterModelOptions(
|
|
14
|
+
name='grant',
|
|
15
|
+
options={'ordering': ['scope']},
|
|
16
|
+
),
|
|
17
|
+
migrations.AlterModelOptions(
|
|
18
|
+
name='group',
|
|
19
|
+
options={'ordering': ['slug']},
|
|
20
|
+
),
|
|
21
|
+
migrations.AlterModelOptions(
|
|
22
|
+
name='role',
|
|
23
|
+
options={'ordering': ['slug']},
|
|
24
|
+
),
|
|
25
|
+
migrations.AlterModelOptions(
|
|
26
|
+
name='rolegrant',
|
|
27
|
+
options={'ordering': ['role__slug', 'group__slug']},
|
|
28
|
+
),
|
|
29
|
+
migrations.AddIndex(
|
|
30
|
+
model_name='group',
|
|
31
|
+
index=models.Index(fields=['slug'], name='permissions_slug_ed0901_idx'),
|
|
32
|
+
),
|
|
33
|
+
]
|
|
File without changes
|