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.
Files changed (130) hide show
  1. {oxutils-0.1.11 → oxutils-0.1.14}/PKG-INFO +2 -11
  2. {oxutils-0.1.11 → oxutils-0.1.14}/README.md +0 -8
  3. {oxutils-0.1.11 → oxutils-0.1.14}/pyproject.toml +2 -3
  4. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/__init__.py +1 -2
  5. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/migrations/0001_initial.py +2 -2
  6. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/models.py +2 -2
  7. oxutils-0.1.14/src/oxutils/logger/__init__.py +10 -0
  8. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/caches.py +0 -1
  9. oxutils-0.1.14/src/oxutils/permissions/caches.py +33 -0
  10. oxutils-0.1.14/src/oxutils/permissions/perms.py +201 -0
  11. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/utils.py +178 -22
  12. oxutils-0.1.14/src/oxutils/settings.py +76 -0
  13. oxutils-0.1.14/src/oxutils/users/migrations/0003_user_photo.py +18 -0
  14. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/models.py +1 -0
  15. oxutils-0.1.11/src/oxutils/permissions/caches.py +0 -19
  16. oxutils-0.1.11/src/oxutils/permissions/perms.py +0 -95
  17. oxutils-0.1.11/src/oxutils/s3/settings.py +0 -34
  18. oxutils-0.1.11/src/oxutils/s3/storages.py +0 -130
  19. oxutils-0.1.11/src/oxutils/settings.py +0 -264
  20. oxutils-0.1.11/src/oxutils/users/__init__.py +0 -0
  21. oxutils-0.1.11/src/oxutils/users/migrations/__init__.py +0 -0
  22. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/apps.py +0 -0
  23. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/__init__.py +0 -0
  24. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/apps.py +0 -0
  25. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/export.py +0 -0
  26. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/masks.py +0 -0
  27. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/migrations/__init__.py +0 -0
  28. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/settings.py +0 -0
  29. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/audit/utils.py +0 -0
  30. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/celery/__init__.py +0 -0
  31. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/celery/base.py +0 -0
  32. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/celery/settings.py +0 -0
  33. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/conf.py +0 -0
  34. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/constants.py +0 -0
  35. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/context/__init__.py +0 -0
  36. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/context/site_name_processor.py +0 -0
  37. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/__init__.py +0 -0
  38. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/admin.py +0 -0
  39. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/apps.py +0 -0
  40. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/controllers.py +0 -0
  41. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/enums.py +0 -0
  42. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/migrations/0001_initial.py +0 -0
  43. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/migrations/__init__.py +0 -0
  44. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/models.py +0 -0
  45. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/schemas.py +0 -0
  46. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/tests.py +0 -0
  47. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/currency/utils.py +0 -0
  48. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/enums/__init__.py +0 -0
  49. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/enums/audit.py +0 -0
  50. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/enums/invoices.py +0 -0
  51. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/exceptions.py +0 -0
  52. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/functions.py +0 -0
  53. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/jwt/__init__.py +0 -0
  54. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/jwt/auth.py +0 -0
  55. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/jwt/models.py +0 -0
  56. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/jwt/tokens.py +0 -0
  57. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/jwt/utils.py +0 -0
  58. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/locale/fr/LC_MESSAGES/django.po +0 -0
  59. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/logger/receivers.py +0 -0
  60. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/logger/settings.py +0 -0
  61. {oxutils-0.1.11/src/oxutils/logger → oxutils-0.1.14/src/oxutils/mixins}/__init__.py +0 -0
  62. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/mixins/base.py +0 -0
  63. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/mixins/schemas.py +0 -0
  64. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/mixins/services.py +0 -0
  65. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/models/__init__.py +0 -0
  66. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/models/base.py +0 -0
  67. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/models/billing.py +0 -0
  68. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/models/fields.py +0 -0
  69. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/models/invoice.py +0 -0
  70. {oxutils-0.1.11/src/oxutils/mixins → oxutils-0.1.14/src/oxutils/oxiliere}/__init__.py +0 -0
  71. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/admin.py +0 -0
  72. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/apps.py +0 -0
  73. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/authorization.py +0 -0
  74. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/cacheops.py +0 -0
  75. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/checks.py +0 -0
  76. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/constants.py +0 -0
  77. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/context.py +0 -0
  78. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/controllers.py +0 -0
  79. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/enums.py +0 -0
  80. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/exceptions.py +0 -0
  81. {oxutils-0.1.11/src/oxutils/oxiliere → oxutils-0.1.14/src/oxutils/oxiliere/management}/__init__.py +0 -0
  82. {oxutils-0.1.11/src/oxutils/oxiliere/management → oxutils-0.1.14/src/oxutils/oxiliere/management/commands}/__init__.py +0 -0
  83. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/management/commands/grant_tenant_owners.py +0 -0
  84. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/management/commands/init_oxiliere_system.py +0 -0
  85. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/middleware.py +0 -0
  86. {oxutils-0.1.11/src/oxutils/oxiliere/management/commands → oxutils-0.1.14/src/oxutils/oxiliere/migrations}/__init__.py +0 -0
  87. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/models.py +0 -0
  88. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/permissions.py +0 -0
  89. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/schemas.py +0 -0
  90. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/settings.py +0 -0
  91. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/signals.py +0 -0
  92. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/tests.py +0 -0
  93. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/oxiliere/utils.py +0 -0
  94. {oxutils-0.1.11/src/oxutils/oxiliere/migrations → oxutils-0.1.14/src/oxutils/pagination}/__init__.py +0 -0
  95. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/pagination/cursor.py +0 -0
  96. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/pdf/__init__.py +0 -0
  97. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/pdf/printer.py +0 -0
  98. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/pdf/utils.py +0 -0
  99. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/pdf/views.py +0 -0
  100. {oxutils-0.1.11/src/oxutils/pagination → oxutils-0.1.14/src/oxutils/permissions}/__init__.py +0 -0
  101. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/actions.py +0 -0
  102. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/admin.py +0 -0
  103. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/apps.py +0 -0
  104. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/checks.py +0 -0
  105. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/constants.py +0 -0
  106. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/controllers.py +0 -0
  107. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/exceptions.py +0 -0
  108. {oxutils-0.1.11/src/oxutils/permissions → oxutils-0.1.14/src/oxutils/permissions/management}/__init__.py +0 -0
  109. {oxutils-0.1.11/src/oxutils/permissions/management → oxutils-0.1.14/src/oxutils/permissions/management/commands}/__init__.py +0 -0
  110. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/management/commands/load_permission_preset.py +0 -0
  111. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/migrations/0001_initial.py +0 -0
  112. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/migrations/0002_alter_grant_role.py +0 -0
  113. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/migrations/0003_alter_grant_options_alter_group_options_and_more.py +0 -0
  114. {oxutils-0.1.11/src/oxutils/permissions/management/commands → oxutils-0.1.14/src/oxutils/permissions/migrations}/__init__.py +0 -0
  115. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/models.py +0 -0
  116. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/queryset.py +0 -0
  117. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/schemas.py +0 -0
  118. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/services.py +0 -0
  119. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/permissions/tests.py +0 -0
  120. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/py.typed +0 -0
  121. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/types.py +0 -0
  122. {oxutils-0.1.11/src/oxutils/permissions/migrations → oxutils-0.1.14/src/oxutils/users}/__init__.py +0 -0
  123. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/admin.py +0 -0
  124. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/apps.py +0 -0
  125. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/migrations/0001_initial.py +0 -0
  126. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/migrations/0002_alter_user_first_name_alter_user_last_name.py +0 -0
  127. {oxutils-0.1.11/src/oxutils/s3 → oxutils-0.1.14/src/oxutils/users/migrations}/__init__.py +0 -0
  128. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/tests.py +0 -0
  129. {oxutils-0.1.11 → oxutils-0.1.14}/src/oxutils/users/utils.py +0 -0
  130. {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.11
3
+ Version: 0.1.14
4
4
  Summary: Production-ready utilities for Django applications in the Oxiliere ecosystem
5
- Keywords: django,utilities,jwt,s3,audit,logging,celery,structlog
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.11"
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", "s3", "audit", "logging", "celery", "structlog"]
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.10"
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
- import oxutils.s3.storages
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=oxutils.s3.storages.LogStorage(), upload_to='')),
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.s3.storages import LogStorage
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=LogStorage())
41
+ data = models.FileField(storage=get_log_storage)
42
42
  size = models.BigIntegerField()
43
43
 
44
44
  @classmethod
@@ -0,0 +1,10 @@
1
+ from django.core.files.storage import storages, default_storage
2
+
3
+
4
+ def get_log_storage():
5
+ try:
6
+ return storages['logs']
7
+ except:
8
+ pass
9
+
10
+ return default_storage
@@ -1,4 +1,3 @@
1
- from django.conf import settings
2
1
  from cacheops import cached_as, cached
3
2
  from oxutils.oxiliere.utils import (
4
3
  get_tenant_model,
@@ -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
- def str_check(user: AbstractBaseUser, perm: str, **context: Any) -> bool:
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 les permissions requises à partir d'une chaîne formatée.
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
- perm: Chaîne de permission au format "<scope>:<actions>:<group>?key=value&key2=value2"
397
- - scope: Le scope à vérifier (ex: 'articles')
398
- - actions: Actions requises (ex: 'rw', 'r', 'rwdx')
399
- - group: (Optionnel) Slug du groupe
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 les permissions requises, False sinon
412
+ True si l'utilisateur possède au moins une des actions requises, False sinon
405
413
 
406
414
  Example:
407
- >>> # Vérifier lecture sur articles
408
- >>> str_check(user, 'articles:r')
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
- >>> # Avec contexte via query params
414
- >>> str_check(user, 'articles:w?tenant_id=123&status=published')
418
+ >>> # Vérifier avec contexte
419
+ >>> any_action_check(user, 'articles', ['w', 'd'], tenant_id=123)
415
420
  False
416
- >>> # Avec groupe et contexte
417
- >>> str_check(user, 'articles:w:staff?tenant_id=123')
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
- >>> # Contexte mixte (query params + kwargs)
420
- >>> str_check(user, 'articles:w?tenant_id=123', level=2)
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
- from .caches import cache_check
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
- required = list(actions_str)
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}