oxutils 0.1.11__tar.gz → 0.1.14__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.
- {oxutils-0.1.11 → oxutils-0.1.14}/PKG-INFO +2 -11
- {oxutils-0.1.11 → oxutils-0.1.14}/README.md +0 -8
- {oxutils-0.1.11 → oxutils-0.1.14}/pyproject.toml +2 -3
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/__init__.py +1 -2
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/migrations/0001_initial.py +2 -2
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/models.py +2 -2
- oxutils-0.1.14/src/oxutils/logger/__init__.py +10 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/caches.py +0 -1
- oxutils-0.1.14/src/oxutils/permissions/caches.py +33 -0
- oxutils-0.1.14/src/oxutils/permissions/perms.py +201 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/utils.py +178 -22
- oxutils-0.1.14/src/oxutils/settings.py +76 -0
- oxutils-0.1.14/src/oxutils/users/migrations/0003_user_photo.py +18 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/models.py +1 -0
- oxutils-0.1.11/src/oxutils/permissions/caches.py +0 -19
- oxutils-0.1.11/src/oxutils/permissions/perms.py +0 -95
- oxutils-0.1.11/src/oxutils/s3/settings.py +0 -34
- oxutils-0.1.11/src/oxutils/s3/storages.py +0 -130
- oxutils-0.1.11/src/oxutils/settings.py +0 -264
- oxutils-0.1.11/src/oxutils/users/__init__.py +0 -0
- oxutils-0.1.11/src/oxutils/users/migrations/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/apps.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/apps.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/export.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/masks.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/migrations/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/settings.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/utils.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/celery/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/celery/base.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/celery/settings.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/conf.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/constants.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/context/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/context/site_name_processor.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/admin.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/apps.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/controllers.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/enums.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/migrations/0001_initial.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/migrations/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/models.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/schemas.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/tests.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/utils.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/enums/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/enums/audit.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/enums/invoices.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/exceptions.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/functions.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/jwt/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/jwt/auth.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/jwt/models.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/jwt/tokens.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/jwt/utils.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/locale/fr/LC_MESSAGES/django.po +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/logger/receivers.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/logger/settings.py +0 -0
- {oxutils-0.1.11/src/oxutils/logger → oxutils-0.1.14/src/oxutils/mixins}/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/mixins/base.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/mixins/schemas.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/mixins/services.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/models/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/models/base.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/models/billing.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/models/fields.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/models/invoice.py +0 -0
- {oxutils-0.1.11/src/oxutils/mixins → oxutils-0.1.14/src/oxutils/oxiliere}/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/admin.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/apps.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/authorization.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/cacheops.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/checks.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/constants.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/context.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/controllers.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/enums.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/exceptions.py +0 -0
- {oxutils-0.1.11/src/oxutils/oxiliere → oxutils-0.1.14/src/oxutils/oxiliere/management}/__init__.py +0 -0
- {oxutils-0.1.11/src/oxutils/oxiliere/management → oxutils-0.1.14/src/oxutils/oxiliere/management/commands}/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/management/commands/grant_tenant_owners.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/management/commands/init_oxiliere_system.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/middleware.py +0 -0
- {oxutils-0.1.11/src/oxutils/oxiliere/management/commands → oxutils-0.1.14/src/oxutils/oxiliere/migrations}/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/models.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/permissions.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/schemas.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/settings.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/signals.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/tests.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/utils.py +0 -0
- {oxutils-0.1.11/src/oxutils/oxiliere/migrations → oxutils-0.1.14/src/oxutils/pagination}/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/pagination/cursor.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/pdf/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/pdf/printer.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/pdf/utils.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/pdf/views.py +0 -0
- {oxutils-0.1.11/src/oxutils/pagination → oxutils-0.1.14/src/oxutils/permissions}/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/actions.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/admin.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/apps.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/checks.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/constants.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/controllers.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/exceptions.py +0 -0
- {oxutils-0.1.11/src/oxutils/permissions → oxutils-0.1.14/src/oxutils/permissions/management}/__init__.py +0 -0
- {oxutils-0.1.11/src/oxutils/permissions/management → oxutils-0.1.14/src/oxutils/permissions/management/commands}/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/management/commands/load_permission_preset.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/migrations/0001_initial.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/migrations/0002_alter_grant_role.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/migrations/0003_alter_grant_options_alter_group_options_and_more.py +0 -0
- {oxutils-0.1.11/src/oxutils/permissions/management/commands → oxutils-0.1.14/src/oxutils/permissions/migrations}/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/models.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/queryset.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/schemas.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/services.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/tests.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/py.typed +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/types.py +0 -0
- {oxutils-0.1.11/src/oxutils/permissions/migrations → oxutils-0.1.14/src/oxutils/users}/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/admin.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/apps.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/migrations/0001_initial.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/migrations/0002_alter_user_first_name_alter_user_last_name.py +0 -0
- {oxutils-0.1.11/src/oxutils/s3 → oxutils-0.1.14/src/oxutils/users/migrations}/__init__.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/tests.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/utils.py +0 -0
- {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/utils.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oxutils
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14
|
|
4
4
|
Summary: Production-ready utilities for Django applications in the Oxiliere ecosystem
|
|
5
|
-
Keywords: django,utilities,jwt,
|
|
5
|
+
Keywords: django,utilities,jwt,audit,logging,celery,structlog
|
|
6
6
|
Author: Edimedia Mutoke
|
|
7
7
|
Author-email: Edimedia Mutoke <eddycondor07@gmail.com>
|
|
8
8
|
License-Expression: Apache-2.0
|
|
@@ -24,7 +24,6 @@ Requires-Dist: django-celery-results>=2.6.0
|
|
|
24
24
|
Requires-Dist: django-extensions>=4.1
|
|
25
25
|
Requires-Dist: django-ninja>=1.5.0
|
|
26
26
|
Requires-Dist: django-ninja-extra>=0.30.6
|
|
27
|
-
Requires-Dist: django-storages[s3]>=1.14.6
|
|
28
27
|
Requires-Dist: django-structlog[celery]>=10.0.0
|
|
29
28
|
Requires-Dist: jwcrypto>=1.5.6
|
|
30
29
|
Requires-Dist: pydantic-settings>=2.12.0
|
|
@@ -71,7 +70,6 @@ Description-Content-Type: text/markdown
|
|
|
71
70
|
## Features
|
|
72
71
|
|
|
73
72
|
- 🔐 **JWT Authentication** - RS256 with JWKS caching
|
|
74
|
-
- 📦 **S3 Storage** - Static, media, private, and log backends
|
|
75
73
|
- 📝 **Structured Logging** - JSON logs with automatic request tracking
|
|
76
74
|
- 🔍 **Audit System** - Change tracking with S3 export
|
|
77
75
|
- ⚙️ **Celery Integration** - Pre-configured task processing
|
|
@@ -118,8 +116,6 @@ MIDDLEWARE = [
|
|
|
118
116
|
```bash
|
|
119
117
|
OXI_SERVICE_NAME=my-service
|
|
120
118
|
OXI_JWT_JWKS_URL=https://auth.example.com/.well-known/jwks.json
|
|
121
|
-
OXI_USE_STATIC_S3=True
|
|
122
|
-
OXI_STATIC_STORAGE_BUCKET_NAME=my-bucket
|
|
123
119
|
```
|
|
124
120
|
|
|
125
121
|
### 3. Usage Examples
|
|
@@ -134,10 +130,6 @@ import structlog
|
|
|
134
130
|
logger = structlog.get_logger(__name__)
|
|
135
131
|
logger.info("user_action", user_id=user_id)
|
|
136
132
|
|
|
137
|
-
# S3 Storage
|
|
138
|
-
from oxutils.s3.storages import PrivateMediaStorage
|
|
139
|
-
class Document(models.Model):
|
|
140
|
-
file = models.FileField(storage=PrivateMediaStorage())
|
|
141
133
|
|
|
142
134
|
# Model Mixins
|
|
143
135
|
from oxutils.models.base import BaseModelMixin
|
|
@@ -165,7 +157,6 @@ TEMPLATES = [{
|
|
|
165
157
|
### Core Modules
|
|
166
158
|
- **[Settings](docs/settings.md)** - Configuration reference
|
|
167
159
|
- **[JWT](docs/jwt.md)** - Authentication
|
|
168
|
-
- **[S3](docs/s3.md)** - Storage backends
|
|
169
160
|
- **[Audit](docs/audit.md)** - Change tracking
|
|
170
161
|
- **[Logging](docs/logger.md)** - Structured logs
|
|
171
162
|
- **[Mixins](docs/mixins.md)** - Model/service mixins
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
14
|
- 🔐 **JWT Authentication** - RS256 with JWKS caching
|
|
15
|
-
- 📦 **S3 Storage** - Static, media, private, and log backends
|
|
16
15
|
- 📝 **Structured Logging** - JSON logs with automatic request tracking
|
|
17
16
|
- 🔍 **Audit System** - Change tracking with S3 export
|
|
18
17
|
- ⚙️ **Celery Integration** - Pre-configured task processing
|
|
@@ -59,8 +58,6 @@ MIDDLEWARE = [
|
|
|
59
58
|
```bash
|
|
60
59
|
OXI_SERVICE_NAME=my-service
|
|
61
60
|
OXI_JWT_JWKS_URL=https://auth.example.com/.well-known/jwks.json
|
|
62
|
-
OXI_USE_STATIC_S3=True
|
|
63
|
-
OXI_STATIC_STORAGE_BUCKET_NAME=my-bucket
|
|
64
61
|
```
|
|
65
62
|
|
|
66
63
|
### 3. Usage Examples
|
|
@@ -75,10 +72,6 @@ import structlog
|
|
|
75
72
|
logger = structlog.get_logger(__name__)
|
|
76
73
|
logger.info("user_action", user_id=user_id)
|
|
77
74
|
|
|
78
|
-
# S3 Storage
|
|
79
|
-
from oxutils.s3.storages import PrivateMediaStorage
|
|
80
|
-
class Document(models.Model):
|
|
81
|
-
file = models.FileField(storage=PrivateMediaStorage())
|
|
82
75
|
|
|
83
76
|
# Model Mixins
|
|
84
77
|
from oxutils.models.base import BaseModelMixin
|
|
@@ -106,7 +99,6 @@ TEMPLATES = [{
|
|
|
106
99
|
### Core Modules
|
|
107
100
|
- **[Settings](docs/settings.md)** - Configuration reference
|
|
108
101
|
- **[JWT](docs/jwt.md)** - Authentication
|
|
109
|
-
- **[S3](docs/s3.md)** - Storage backends
|
|
110
102
|
- **[Audit](docs/audit.md)** - Change tracking
|
|
111
103
|
- **[Logging](docs/logger.md)** - Structured logs
|
|
112
104
|
- **[Mixins](docs/mixins.md)** - Model/service mixins
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "oxutils"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.14"
|
|
4
4
|
description = "Production-ready utilities for Django applications in the Oxiliere ecosystem"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -8,7 +8,7 @@ authors = [
|
|
|
8
8
|
{ name = "Edimedia Mutoke", email = "eddycondor07@gmail.com" }
|
|
9
9
|
]
|
|
10
10
|
requires-python = ">=3.12"
|
|
11
|
-
keywords = ["django", "utilities", "jwt", "
|
|
11
|
+
keywords = ["django", "utilities", "jwt", "audit", "logging", "celery", "structlog"]
|
|
12
12
|
classifiers = [
|
|
13
13
|
"Development Status :: 4 - Beta",
|
|
14
14
|
"Framework :: Django",
|
|
@@ -30,7 +30,6 @@ dependencies = [
|
|
|
30
30
|
"django-extensions>=4.1",
|
|
31
31
|
"django-ninja>=1.5.0",
|
|
32
32
|
"django-ninja-extra>=0.30.6",
|
|
33
|
-
"django-storages[s3]>=1.14.6",
|
|
34
33
|
"django-structlog[celery]>=10.0.0",
|
|
35
34
|
"jwcrypto>=1.5.6",
|
|
36
35
|
"pydantic-settings>=2.12.0",
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
This package provides:
|
|
4
4
|
- JWT authentication with JWKS support
|
|
5
|
-
- S3 storage backends (static, media, private, logs)
|
|
6
5
|
- Structured logging with correlation IDs
|
|
7
6
|
- Audit system with S3 export
|
|
8
7
|
- Celery integration
|
|
@@ -11,7 +10,7 @@ This package provides:
|
|
|
11
10
|
- Permission management
|
|
12
11
|
"""
|
|
13
12
|
|
|
14
|
-
__version__ = "0.1.
|
|
13
|
+
__version__ = "0.1.14"
|
|
15
14
|
|
|
16
15
|
from oxutils.settings import oxi_settings
|
|
17
16
|
from oxutils.conf import UTILS_APPS, AUDIT_MIDDLEWARE
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import django.db.models.deletion
|
|
4
4
|
import oxutils.enums.audit
|
|
5
|
-
|
|
5
|
+
from oxutils.logger import get_log_storage
|
|
6
6
|
from django.db import migrations, models
|
|
7
7
|
|
|
8
8
|
|
|
@@ -22,7 +22,7 @@ class Migration(migrations.Migration):
|
|
|
22
22
|
('updated_at', models.DateTimeField(auto_now=True, help_text='Date and time when this record was last updated')),
|
|
23
23
|
('last_export_date', models.DateTimeField(null=True)),
|
|
24
24
|
('status', models.CharField(choices=[(oxutils.enums.audit.ExportStatus['FAILED'], 'Failed'), (oxutils.enums.audit.ExportStatus['PENDING'], 'Pending'), (oxutils.enums.audit.ExportStatus['SUCCESS'], 'Success')], default=oxutils.enums.audit.ExportStatus['PENDING'])),
|
|
25
|
-
('data', models.FileField(storage=
|
|
25
|
+
('data', models.FileField(storage=get_log_storage, upload_to='')),
|
|
26
26
|
('size', models.BigIntegerField()),
|
|
27
27
|
],
|
|
28
28
|
options={
|
|
@@ -3,7 +3,7 @@ from django.utils import timezone
|
|
|
3
3
|
from django.db import models, transaction
|
|
4
4
|
from oxutils.enums.audit import ExportStatus
|
|
5
5
|
from oxutils.models.base import TimestampMixin
|
|
6
|
-
from oxutils.
|
|
6
|
+
from oxutils.logger import get_log_storage
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
@@ -38,7 +38,7 @@ class LogExportState(TimestampMixin):
|
|
|
38
38
|
(ExportStatus.SUCCESS, _('Success'))
|
|
39
39
|
)
|
|
40
40
|
)
|
|
41
|
-
data = models.FileField(storage=
|
|
41
|
+
data = models.FileField(storage=get_log_storage)
|
|
42
42
|
size = models.BigIntegerField()
|
|
43
43
|
|
|
44
44
|
@classmethod
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
CACHE_CHECK_PERMISSION = getattr(settings, 'CACHE_CHECK_PERMISSION', False)
|
|
6
|
+
|
|
7
|
+
if CACHE_CHECK_PERMISSION:
|
|
8
|
+
from cacheops import cached_as
|
|
9
|
+
from .models import Grant
|
|
10
|
+
from .utils import check, any_action_check, any_permission_check
|
|
11
|
+
|
|
12
|
+
@cached_as(Grant, timeout=60*15)
|
|
13
|
+
def cache_check(user, scope, actions, group = None, **context):
|
|
14
|
+
return check(user, scope, actions, group, **context)
|
|
15
|
+
|
|
16
|
+
@cached_as(Grant, timeout=60*15)
|
|
17
|
+
def cache_any_action_check(user, scope, required, group = None, **context):
|
|
18
|
+
return any_action_check(user, scope, required, group, **context)
|
|
19
|
+
|
|
20
|
+
@cached_as(Grant, timeout=60*15)
|
|
21
|
+
def cache_any_permission_check(user, *str_perms):
|
|
22
|
+
return any_permission_check(user, *str_perms)
|
|
23
|
+
else:
|
|
24
|
+
from .utils import check, any_action_check, any_permission_check
|
|
25
|
+
|
|
26
|
+
def cache_check(user, scope, actions, group = None, **context):
|
|
27
|
+
return check(user, scope, actions, group, **context)
|
|
28
|
+
|
|
29
|
+
def cache_any_action_check(user, scope, required, group = None, **context):
|
|
30
|
+
return any_action_check(user, scope, required, group, **context)
|
|
31
|
+
|
|
32
|
+
def cache_any_permission_check(user, *str_perms):
|
|
33
|
+
return any_permission_check(user, *str_perms)
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from django.conf import settings
|
|
3
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
4
|
+
from django.http import HttpRequest
|
|
5
|
+
from ninja_extra.permissions import BasePermission
|
|
6
|
+
from ninja_extra.controllers import ControllerBase
|
|
7
|
+
|
|
8
|
+
from oxutils.permissions.utils import str_check
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ScopePermission(BasePermission):
|
|
13
|
+
"""
|
|
14
|
+
Permission class for checking user permissions using the string format.
|
|
15
|
+
|
|
16
|
+
Format: "<scope>:<actions>:<group>?key=value"
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
@api_controller('/articles', permissions=[ScopePermission('articles:w:staff')])
|
|
20
|
+
class ArticleController:
|
|
21
|
+
pass
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, perm: str, ctx: Optional[dict] = None):
|
|
25
|
+
"""
|
|
26
|
+
Initialize the permission checker.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
perm: Permission string in format "<scope>:<actions>:<group>?context"
|
|
30
|
+
"""
|
|
31
|
+
self.perm = perm
|
|
32
|
+
self.ctx = ctx if ctx else dict()
|
|
33
|
+
|
|
34
|
+
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Check if the user has the required permission.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
request: HTTP request object
|
|
40
|
+
controller: Controller instance
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
True if user has permission, False otherwise
|
|
44
|
+
"""
|
|
45
|
+
return str_check(request.user, self.perm, **self.ctx)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ScopeAnyPermission(BasePermission):
|
|
49
|
+
"""
|
|
50
|
+
Permission class for checking if user has at least one of multiple permissions.
|
|
51
|
+
|
|
52
|
+
Vérifie si l'utilisateur possède au moins une des permissions fournies.
|
|
53
|
+
Utilise any_permission_check pour une vérification optimisée en une seule requête.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
@api_controller('/articles', permissions=[
|
|
57
|
+
ScopeAnyPermission('articles:r', 'articles:w:staff', 'articles:d:admin')
|
|
58
|
+
])
|
|
59
|
+
class ArticleController:
|
|
60
|
+
# User needs either read access, OR staff write access, OR admin delete access
|
|
61
|
+
pass
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, *perms: str):
|
|
65
|
+
"""
|
|
66
|
+
Initialize the permission checker with multiple permission strings.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
*perms: Variable number of permission strings in format "<scope>:<actions>:<group>?context"
|
|
70
|
+
"""
|
|
71
|
+
if not perms:
|
|
72
|
+
raise ValueError("At least one permission string must be provided")
|
|
73
|
+
self.perms = perms
|
|
74
|
+
|
|
75
|
+
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
|
|
76
|
+
"""
|
|
77
|
+
Check if the user has at least one of the required permissions.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
request: HTTP request object
|
|
81
|
+
controller: Controller instance
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
True if user has at least one permission, False otherwise
|
|
85
|
+
"""
|
|
86
|
+
from oxutils.permissions.caches import cache_any_permission_check
|
|
87
|
+
return cache_any_permission_check(request.user, *self.perms)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ScopeAnyActionPermission(BasePermission):
|
|
91
|
+
"""
|
|
92
|
+
Permission class for checking if user has at least one of multiple actions on a scope.
|
|
93
|
+
|
|
94
|
+
Vérifie si l'utilisateur possède au moins une des actions requises pour un scope donné.
|
|
95
|
+
La chaîne d'actions contient plusieurs actions dont au moins une est requise.
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
@api_controller('/articles', permissions=[
|
|
99
|
+
ScopeAnyActionPermission('articles:rwd:staff')
|
|
100
|
+
])
|
|
101
|
+
class ArticleController:
|
|
102
|
+
# User needs read OR write OR delete access on articles in staff group
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
@api_controller('/invoices', permissions=[
|
|
106
|
+
ScopeAnyActionPermission('invoices:rw?tenant_id=123')
|
|
107
|
+
])
|
|
108
|
+
class InvoiceController:
|
|
109
|
+
# User needs read OR write access on invoices with tenant_id=123
|
|
110
|
+
pass
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, perm: str, ctx: Optional[dict] = None):
|
|
114
|
+
"""
|
|
115
|
+
Initialize the permission checker with a permission string.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
perm: Permission string in format "<scope>:<actions>:<group>?context"
|
|
119
|
+
where actions contains multiple characters (e.g., 'rwd' for read OR write OR delete)
|
|
120
|
+
ctx: Optional additional context dict
|
|
121
|
+
"""
|
|
122
|
+
if not perm:
|
|
123
|
+
raise ValueError("Permission string must be provided")
|
|
124
|
+
|
|
125
|
+
self.perm = perm
|
|
126
|
+
self.ctx = ctx if ctx else dict()
|
|
127
|
+
|
|
128
|
+
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
|
|
129
|
+
"""
|
|
130
|
+
Check if the user has at least one of the required actions.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
request: HTTP request object
|
|
134
|
+
controller: Controller instance
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
True if user has at least one action, False otherwise
|
|
138
|
+
"""
|
|
139
|
+
from oxutils.permissions.caches import cache_any_action_check
|
|
140
|
+
from oxutils.permissions.utils import parse_permission
|
|
141
|
+
|
|
142
|
+
scope, actions, group, query_context = parse_permission(self.perm)
|
|
143
|
+
final_context = {**query_context, **self.ctx}
|
|
144
|
+
|
|
145
|
+
return cache_any_action_check(
|
|
146
|
+
request.user,
|
|
147
|
+
scope,
|
|
148
|
+
actions,
|
|
149
|
+
group,
|
|
150
|
+
**final_context
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def access_manager(actions: str):
|
|
155
|
+
"""
|
|
156
|
+
Factory function for creating ScopePermission instances for access manager.
|
|
157
|
+
|
|
158
|
+
Builds a permission string from settings:
|
|
159
|
+
- ACCESS_MANAGER_SCOPE: The scope to check
|
|
160
|
+
- ACCESS_MANAGER_GROUP: Optional group filter
|
|
161
|
+
- ACCESS_MANAGER_CONTEXT: Optional context dict converted to query params
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
actions: Actions required (e.g., 'r', 'rw', 'rwd')
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
ScopePermission instance configured with access manager settings
|
|
168
|
+
|
|
169
|
+
Raises:
|
|
170
|
+
ImproperlyConfigured: If required settings are missing
|
|
171
|
+
|
|
172
|
+
Example:
|
|
173
|
+
@api_controller('/access', permissions=[access_manager('w')])
|
|
174
|
+
class AccessController:
|
|
175
|
+
pass
|
|
176
|
+
"""
|
|
177
|
+
# Validate required settings
|
|
178
|
+
if not hasattr(settings, 'ACCESS_MANAGER_SCOPE'):
|
|
179
|
+
raise ImproperlyConfigured(
|
|
180
|
+
'ACCESS_MANAGER_SCOPE is not defined. '
|
|
181
|
+
'Add ACCESS_MANAGER_SCOPE = "access" to your settings.'
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Build base permission string: scope:actions
|
|
185
|
+
perm = f"{settings.ACCESS_MANAGER_SCOPE}:{actions}"
|
|
186
|
+
|
|
187
|
+
# Add group if defined and not None
|
|
188
|
+
if hasattr(settings, 'ACCESS_MANAGER_GROUP') and settings.ACCESS_MANAGER_GROUP is not None:
|
|
189
|
+
perm += f":{settings.ACCESS_MANAGER_GROUP}"
|
|
190
|
+
|
|
191
|
+
# Get context if defined and not empty
|
|
192
|
+
context = {}
|
|
193
|
+
if hasattr(settings, 'ACCESS_MANAGER_CONTEXT') and settings.ACCESS_MANAGER_CONTEXT:
|
|
194
|
+
context = settings.ACCESS_MANAGER_CONTEXT
|
|
195
|
+
if not isinstance(context, dict):
|
|
196
|
+
raise ImproperlyConfigured(
|
|
197
|
+
'ACCESS_MANAGER_CONTEXT must be a dictionary. '
|
|
198
|
+
f'Got {type(context).__name__} instead.'
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return ScopePermission(perm, context)
|
|
@@ -387,41 +387,156 @@ def check(
|
|
|
387
387
|
# Vérifier l'existence d'un grant correspondant
|
|
388
388
|
return Grant.objects.filter(grant_filter).exists()
|
|
389
389
|
|
|
390
|
-
|
|
390
|
+
|
|
391
|
+
def any_action_check(
|
|
392
|
+
user: AbstractBaseUser,
|
|
393
|
+
scope: str,
|
|
394
|
+
required: list[str],
|
|
395
|
+
group: Optional[str] = None,
|
|
396
|
+
**context: Any
|
|
397
|
+
) -> bool:
|
|
391
398
|
"""
|
|
392
|
-
Vérifie si un utilisateur possède
|
|
399
|
+
Vérifie si un utilisateur possède au moins une des actions requises pour un scope donné.
|
|
400
|
+
|
|
401
|
+
Cette fonction utilise une seule requête optimisée avec des conditions OR pour vérifier
|
|
402
|
+
si l'utilisateur possède au moins une des actions dans la liste.
|
|
393
403
|
|
|
394
404
|
Args:
|
|
395
405
|
user: L'utilisateur dont on vérifie les permissions
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
- query params: (Optionnel) Contexte sous forme de query parameters
|
|
401
|
-
**context: Contexte additionnel pour filtrer les grants (fusionné avec les query params)
|
|
406
|
+
scope: Le scope à vérifier (ex: 'articles', 'invoices')
|
|
407
|
+
required: Liste des actions dont au moins une est requise (ex: ['r', 'w'], ['d'])
|
|
408
|
+
group: Slug du groupe optionnel pour filtrer les grants par groupe
|
|
409
|
+
**context: Contexte additionnel pour filtrer les grants (clés JSON)
|
|
402
410
|
|
|
403
411
|
Returns:
|
|
404
|
-
True si l'utilisateur possède
|
|
412
|
+
True si l'utilisateur possède au moins une des actions requises, False sinon
|
|
405
413
|
|
|
406
414
|
Example:
|
|
407
|
-
>>> # Vérifier
|
|
408
|
-
>>>
|
|
409
|
-
True
|
|
410
|
-
>>> # Vérifier écriture sur articles dans le groupe staff
|
|
411
|
-
>>> str_check(user, 'articles:w:staff')
|
|
415
|
+
>>> # Vérifier si l'utilisateur peut lire OU écrire les articles
|
|
416
|
+
>>> any_action_check(user, 'articles', ['r', 'w'])
|
|
412
417
|
True
|
|
413
|
-
>>> #
|
|
414
|
-
>>>
|
|
418
|
+
>>> # Vérifier avec contexte
|
|
419
|
+
>>> any_action_check(user, 'articles', ['w', 'd'], tenant_id=123)
|
|
415
420
|
False
|
|
416
|
-
>>> #
|
|
417
|
-
>>>
|
|
421
|
+
>>> # Vérifier dans le contexte d'un groupe spécifique
|
|
422
|
+
>>> any_action_check(user, 'articles', ['r', 'w'], group='staff')
|
|
418
423
|
True
|
|
419
|
-
|
|
420
|
-
|
|
424
|
+
|
|
425
|
+
Note:
|
|
426
|
+
Les actions sont automatiquement expandées lors de la création du grant,
|
|
427
|
+
donc si un grant contient ['w'], il contient aussi ['r'] implicitement.
|
|
428
|
+
Cette fonction vérifie si AU MOINS UNE des actions requises est présente.
|
|
429
|
+
"""
|
|
430
|
+
# Construire le filtre de base pour l'utilisateur et le scope
|
|
431
|
+
grant_filter = Q(user__pk=user.pk, scope=scope)
|
|
432
|
+
|
|
433
|
+
# Filtrer par groupe si spécifié
|
|
434
|
+
if group:
|
|
435
|
+
grant_filter &= Q(user_group__group__slug=group)
|
|
436
|
+
|
|
437
|
+
# Ajouter les filtres de contexte si fournis
|
|
438
|
+
if context:
|
|
439
|
+
grant_filter &= Q(context__contains=context)
|
|
440
|
+
|
|
441
|
+
# Vérifier si au moins une des actions requises est présente dans le grant
|
|
442
|
+
# Utilise l'opérateur overlap (&&) pour une requête optimale
|
|
443
|
+
grant_filter &= Q(actions__overlap=required)
|
|
444
|
+
|
|
445
|
+
# Vérifier l'existence d'un grant correspondant
|
|
446
|
+
return Grant.objects.filter(grant_filter).exists()
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def any_permission_check(user: AbstractBaseUser, *str_perms: str) -> bool:
|
|
450
|
+
"""
|
|
451
|
+
Vérifie si un utilisateur possède au moins une des permissions fournies.
|
|
452
|
+
|
|
453
|
+
Cette fonction parse toutes les permissions fournies et effectue une seule requête
|
|
454
|
+
optimisée avec des conditions OR pour vérifier si l'utilisateur possède au moins
|
|
455
|
+
une des permissions.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
user: L'utilisateur dont on vérifie les permissions
|
|
459
|
+
*str_perms: Liste de chaînes de permissions au format standard
|
|
460
|
+
(ex: 'articles:r', 'invoices:w:staff', 'users:d?tenant_id=123')
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
True si l'utilisateur possède au moins une des permissions, False sinon
|
|
464
|
+
|
|
465
|
+
Example:
|
|
466
|
+
>>> # Vérifier si l'utilisateur peut lire les articles OU écrire les factures
|
|
467
|
+
>>> any_permission_check(user, 'articles:r', 'invoices:w')
|
|
468
|
+
True
|
|
469
|
+
>>> # Avec différents groupes et contextes
|
|
470
|
+
>>> any_permission_check(
|
|
471
|
+
... user,
|
|
472
|
+
... 'articles:w:staff',
|
|
473
|
+
... 'invoices:r:admin',
|
|
474
|
+
... 'users:d?tenant_id=123'
|
|
475
|
+
... )
|
|
421
476
|
False
|
|
477
|
+
|
|
478
|
+
Note:
|
|
479
|
+
Toute la vérification se fait au niveau de la base de données avec une seule
|
|
480
|
+
requête utilisant des conditions OR pour optimiser les performances.
|
|
422
481
|
"""
|
|
423
|
-
|
|
482
|
+
if not str_perms:
|
|
483
|
+
return False
|
|
484
|
+
|
|
485
|
+
# Construire le filtre de base pour l'utilisateur
|
|
486
|
+
base_filter = Q(user__pk=user.pk)
|
|
487
|
+
|
|
488
|
+
# Construire les conditions OR pour chaque permission
|
|
489
|
+
permission_filters = Q()
|
|
490
|
+
|
|
491
|
+
for perm in str_perms:
|
|
492
|
+
# Parser la permission
|
|
493
|
+
scope, actions, group, context = parse_permission(perm)
|
|
494
|
+
|
|
495
|
+
# Construire le filtre pour cette permission spécifique
|
|
496
|
+
perm_filter = Q(scope=scope, actions__overlap=actions)
|
|
497
|
+
|
|
498
|
+
# Ajouter le filtre de groupe si spécifié
|
|
499
|
+
if group:
|
|
500
|
+
perm_filter &= Q(user_group__group__slug=group)
|
|
501
|
+
|
|
502
|
+
# Ajouter le filtre de contexte si fourni
|
|
503
|
+
if context:
|
|
504
|
+
perm_filter &= Q(context__contains=context)
|
|
505
|
+
|
|
506
|
+
# Ajouter cette permission aux conditions OR
|
|
507
|
+
permission_filters |= perm_filter
|
|
508
|
+
|
|
509
|
+
# Combiner le filtre de base avec les conditions OR et vérifier l'existence
|
|
510
|
+
return Grant.objects.filter(base_filter & permission_filters).exists()
|
|
511
|
+
|
|
424
512
|
|
|
513
|
+
def parse_permission(perm: str) -> tuple[str, list[str], Optional[str], dict[str, Any]]:
|
|
514
|
+
"""
|
|
515
|
+
Parse une chaîne de permission et retourne ses composants.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
perm: Chaîne de permission au format "<scope>:<actions>:<group>?key=value&key2=value2"
|
|
519
|
+
- scope: Le scope (ex: 'articles')
|
|
520
|
+
- actions: Actions requises (ex: 'rw', 'r', 'rwdx')
|
|
521
|
+
- group: (Optionnel) Slug du groupe
|
|
522
|
+
- query params: (Optionnel) Contexte sous forme de query parameters
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
Tuple contenant (scope, actions_list, group, context_dict)
|
|
526
|
+
|
|
527
|
+
Raises:
|
|
528
|
+
ValueError: Si le format de la permission est invalide
|
|
529
|
+
|
|
530
|
+
Example:
|
|
531
|
+
>>> parse_permission('articles:rw')
|
|
532
|
+
('articles', ['r', 'w'], None, {})
|
|
533
|
+
>>> parse_permission('articles:w:staff')
|
|
534
|
+
('articles', ['w'], 'staff', {})
|
|
535
|
+
>>> parse_permission('articles:rw?tenant_id=123&status=published')
|
|
536
|
+
('articles', ['r', 'w'], None, {'tenant_id': 123, 'status': 'published'})
|
|
537
|
+
>>> parse_permission('articles:w:staff?tenant_id=123')
|
|
538
|
+
('articles', ['w'], 'staff', {'tenant_id': 123})
|
|
539
|
+
"""
|
|
425
540
|
# Séparer la partie principale des query params
|
|
426
541
|
if '?' in perm:
|
|
427
542
|
from urllib.parse import parse_qs
|
|
@@ -455,7 +570,48 @@ def str_check(user: AbstractBaseUser, perm: str, **context: Any) -> bool:
|
|
|
455
570
|
|
|
456
571
|
# Convertir la chaîne d'actions en liste
|
|
457
572
|
# 'rwd' -> ['r', 'w', 'd']
|
|
458
|
-
|
|
573
|
+
actions_list = list(actions_str)
|
|
574
|
+
|
|
575
|
+
return scope, actions_list, group, query_context
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def str_check(user: AbstractBaseUser, perm: str, **context: Any) -> bool:
|
|
579
|
+
"""
|
|
580
|
+
Vérifie si un utilisateur possède les permissions requises à partir d'une chaîne formatée.
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
user: L'utilisateur dont on vérifie les permissions
|
|
584
|
+
perm: Chaîne de permission au format "<scope>:<actions>:<group>?key=value&key2=value2"
|
|
585
|
+
- scope: Le scope à vérifier (ex: 'articles')
|
|
586
|
+
- actions: Actions requises (ex: 'rw', 'r', 'rwdx')
|
|
587
|
+
- group: (Optionnel) Slug du groupe
|
|
588
|
+
- query params: (Optionnel) Contexte sous forme de query parameters
|
|
589
|
+
**context: Contexte additionnel pour filtrer les grants (fusionné avec les query params)
|
|
590
|
+
|
|
591
|
+
Returns:
|
|
592
|
+
True si l'utilisateur possède les permissions requises, False sinon
|
|
593
|
+
|
|
594
|
+
Example:
|
|
595
|
+
>>> # Vérifier lecture sur articles
|
|
596
|
+
>>> str_check(user, 'articles:r')
|
|
597
|
+
True
|
|
598
|
+
>>> # Vérifier écriture sur articles dans le groupe staff
|
|
599
|
+
>>> str_check(user, 'articles:w:staff')
|
|
600
|
+
True
|
|
601
|
+
>>> # Avec contexte via query params
|
|
602
|
+
>>> str_check(user, 'articles:w?tenant_id=123&status=published')
|
|
603
|
+
False
|
|
604
|
+
>>> # Avec groupe et contexte
|
|
605
|
+
>>> str_check(user, 'articles:w:staff?tenant_id=123')
|
|
606
|
+
True
|
|
607
|
+
>>> # Contexte mixte (query params + kwargs)
|
|
608
|
+
>>> str_check(user, 'articles:w?tenant_id=123', level=2)
|
|
609
|
+
False
|
|
610
|
+
"""
|
|
611
|
+
from .caches import cache_check
|
|
612
|
+
|
|
613
|
+
# Parser la chaîne de permission
|
|
614
|
+
scope, required, group, query_context = parse_permission(perm)
|
|
459
615
|
|
|
460
616
|
# Fusionner les contextes (kwargs ont priorité sur query params)
|
|
461
617
|
final_context = {**query_context, **context}
|