oxutils 0.1.6__py3-none-any.whl → 0.1.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. oxutils/__init__.py +2 -2
  2. oxutils/audit/migrations/0001_initial.py +2 -2
  3. oxutils/audit/models.py +2 -2
  4. oxutils/constants.py +6 -0
  5. oxutils/jwt/auth.py +150 -1
  6. oxutils/jwt/models.py +81 -0
  7. oxutils/jwt/tokens.py +69 -0
  8. oxutils/jwt/utils.py +45 -0
  9. oxutils/logger/__init__.py +10 -0
  10. oxutils/logger/receivers.py +10 -6
  11. oxutils/logger/settings.py +2 -2
  12. oxutils/models/base.py +102 -0
  13. oxutils/models/fields.py +79 -0
  14. oxutils/oxiliere/apps.py +9 -1
  15. oxutils/oxiliere/authorization.py +45 -0
  16. oxutils/oxiliere/caches.py +13 -11
  17. oxutils/oxiliere/checks.py +31 -0
  18. oxutils/oxiliere/constants.py +3 -0
  19. oxutils/oxiliere/context.py +16 -0
  20. oxutils/oxiliere/exceptions.py +16 -0
  21. oxutils/oxiliere/management/commands/grant_tenant_owners.py +19 -0
  22. oxutils/oxiliere/management/commands/init_oxiliere_system.py +30 -11
  23. oxutils/oxiliere/middleware.py +65 -11
  24. oxutils/oxiliere/models.py +146 -9
  25. oxutils/oxiliere/permissions.py +28 -35
  26. oxutils/oxiliere/schemas.py +16 -6
  27. oxutils/oxiliere/signals.py +5 -0
  28. oxutils/oxiliere/utils.py +36 -1
  29. oxutils/pagination/cursor.py +367 -0
  30. oxutils/permissions/__init__.py +0 -0
  31. oxutils/permissions/actions.py +57 -0
  32. oxutils/permissions/admin.py +3 -0
  33. oxutils/permissions/apps.py +10 -0
  34. oxutils/permissions/caches.py +19 -0
  35. oxutils/permissions/checks.py +188 -0
  36. oxutils/permissions/constants.py +0 -0
  37. oxutils/permissions/controllers.py +344 -0
  38. oxutils/permissions/exceptions.py +60 -0
  39. oxutils/permissions/management/__init__.py +0 -0
  40. oxutils/permissions/management/commands/__init__.py +0 -0
  41. oxutils/permissions/management/commands/load_permission_preset.py +112 -0
  42. oxutils/permissions/migrations/0001_initial.py +112 -0
  43. oxutils/permissions/migrations/0002_alter_grant_role.py +19 -0
  44. oxutils/permissions/migrations/0003_alter_grant_options_alter_group_options_and_more.py +33 -0
  45. oxutils/permissions/migrations/__init__.py +0 -0
  46. oxutils/permissions/models.py +171 -0
  47. oxutils/permissions/perms.py +95 -0
  48. oxutils/permissions/queryset.py +92 -0
  49. oxutils/permissions/schemas.py +276 -0
  50. oxutils/permissions/services.py +663 -0
  51. oxutils/permissions/tests.py +3 -0
  52. oxutils/permissions/utils.py +628 -0
  53. oxutils/settings.py +14 -194
  54. oxutils/users/apps.py +1 -1
  55. oxutils/users/migrations/0001_initial.py +47 -0
  56. oxutils/users/migrations/0002_alter_user_first_name_alter_user_last_name.py +23 -0
  57. oxutils/users/models.py +2 -0
  58. oxutils/utils.py +25 -0
  59. {oxutils-0.1.6.dist-info → oxutils-0.1.12.dist-info}/METADATA +14 -11
  60. oxutils-0.1.12.dist-info/RECORD +122 -0
  61. oxutils/jwt/client.py +0 -123
  62. oxutils/jwt/constants.py +0 -1
  63. oxutils/s3/settings.py +0 -34
  64. oxutils/s3/storages.py +0 -130
  65. oxutils-0.1.6.dist-info/RECORD +0 -88
  66. /oxutils/{s3 → pagination}/__init__.py +0 -0
  67. {oxutils-0.1.6.dist-info → oxutils-0.1.12.dist-info}/WHEEL +0 -0
oxutils/settings.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from typing import Optional
2
- from pydantic import Field, model_validator
2
+ from pydantic import Field
3
3
  from pydantic_settings import BaseSettings, SettingsConfigDict
4
- from django.core.exceptions import ImproperlyConfigured
5
4
 
6
5
 
7
6
 
@@ -23,120 +22,31 @@ class OxUtilsSettings(BaseSettings):
23
22
  service_name: Optional[str] = 'Oxutils'
24
23
  site_name: Optional[str] = 'Oxiliere'
25
24
  site_domain: Optional[str] = 'oxiliere.com'
25
+ multitenancy: bool = Field(False)
26
26
 
27
27
  # Auth JWT Settings (JWT_SIGNING_KEY)
28
28
  jwt_signing_key: Optional[str] = None
29
29
  jwt_verifying_key: Optional[str] = None
30
30
  jwt_jwks_url: Optional[str] = None
31
- jwt_access_token_key: str = Field('access_token')
32
- jwt_org_access_token_key: str = Field('org_access_token')
31
+ jwt_access_token_key: str = Field('access')
32
+ jwt_org_access_token_key: str = Field('org_access')
33
+ jwt_service_token_key: str = Field('service')
34
+ jwt_algorithm: Optional[str] = Field('RS256')
35
+ jwt_access_token_lifetime: int = Field(15) # minutes
36
+ jwt_service_token_lifetime: int = Field(3) # minutes
37
+ jwt_org_access_token_lifetime: int = Field(60) # minutes
33
38
 
34
39
 
35
40
  # AuditLog
36
41
  log_access: bool = Field(False)
37
42
  retention_delay: int = Field(7) # one week
38
43
 
44
+ # logger
45
+ log_file_path: Optional[str] = Field('logs/oxiliere.log')
39
46
 
40
- # Static S3
41
- use_static_s3: bool = Field(False)
42
- static_access_key_id: Optional[str] = None
43
- static_secret_access_key: Optional[str] = None
44
- static_storage_bucket_name: Optional[str] = None
45
- static_default_acl: str = Field('public-read')
46
- static_s3_custom_domain: Optional[str] = None
47
- static_location: str = Field('static')
48
- static_storage: str = Field('oxutils.s3.storages.StaticStorage')
49
-
50
- # Default S3 for media
51
- use_default_s3: bool = Field(False)
52
- use_static_s3_as_default: bool = Field(False)
53
- default_s3_access_key_id: Optional[str] = None
54
- default_s3_secret_access_key: Optional[str] = None
55
- default_s3_storage_bucket_name: Optional[str] = None
56
- default_s3_default_acl: str = Field('public-read')
57
- default_s3_custom_domain: Optional[str] = None
58
- default_s3_location: str = Field('media')
59
- default_s3_storage: str = Field('oxutils.s3.storages.PublicMediaStorage')
60
-
61
- # Private S3 for sensible data
62
- use_private_s3: bool = Field(False)
63
- private_s3_access_key_id: Optional[str] = None
64
- private_s3_secret_access_key: Optional[str] = None
65
- private_s3_storage_bucket_name: Optional[str] = None
66
- private_s3_default_acl: str = Field('private')
67
- private_s3_custom_domain: Optional[str] = None
68
- private_s3_location: str = Field('private')
69
- private_s3_storage: str = Field('oxutils.s3.storages.PrivateMediaStorage')
70
-
71
- # Log S3
72
- use_log_s3: bool = Field(False)
73
- use_private_s3_as_log: bool = Field(False)
74
- log_s3_access_key_id: Optional[str] = None
75
- log_s3_secret_access_key: Optional[str] = None
76
- log_s3_storage_bucket_name: Optional[str] = None
77
- log_s3_default_acl: str = Field('private')
78
- log_s3_custom_domain: Optional[str] = None
79
- log_s3_location: str = Field('oxi_logs')
80
- log_s3_storage: str = Field('oxutils.s3.storages.LogStorage')
81
-
82
-
83
- @model_validator(mode='after')
84
- def validate_s3_configurations(self):
85
- """Validate S3 and JWT configurations when enabled."""
86
- # Validate JWT keys if present
47
+ def model_post_init(self, __context):
48
+ """Called after model initialization to perform validation."""
87
49
  self._validate_jwt_keys()
88
-
89
- # Validate static S3
90
- if self.use_static_s3:
91
- self._validate_s3_config(
92
- 'static',
93
- self.static_access_key_id,
94
- self.static_secret_access_key,
95
- self.static_storage_bucket_name,
96
- self.static_s3_custom_domain
97
- )
98
-
99
- # Validate default S3
100
- if self.use_default_s3:
101
- if not self.use_static_s3_as_default:
102
- self._validate_s3_config(
103
- 'default',
104
- self.default_s3_access_key_id,
105
- self.default_s3_secret_access_key,
106
- self.default_s3_storage_bucket_name,
107
- self.default_s3_custom_domain
108
- )
109
- elif not self.use_static_s3:
110
- raise ValueError(
111
- "OXI_USE_STATIC_S3_AS_DEFAULT requires OXI_USE_STATIC_S3 to be True"
112
- )
113
-
114
- # Validate private S3
115
- if self.use_private_s3:
116
- self._validate_s3_config(
117
- 'private',
118
- self.private_s3_access_key_id,
119
- self.private_s3_secret_access_key,
120
- self.private_s3_storage_bucket_name,
121
- self.private_s3_custom_domain
122
- )
123
-
124
- # Validate log S3
125
- if self.use_log_s3:
126
- if not self.use_private_s3_as_log:
127
- self._validate_s3_config(
128
- 'log',
129
- self.log_s3_access_key_id,
130
- self.log_s3_secret_access_key,
131
- self.log_s3_storage_bucket_name,
132
- self.log_s3_custom_domain
133
- )
134
- elif not self.use_private_s3:
135
- raise ValueError(
136
- "OXI_USE_PRIVATE_S3_AS_LOG requires OXI_USE_PRIVATE_S3 to be True"
137
- )
138
-
139
- return self
140
50
 
141
51
  def _validate_jwt_keys(self):
142
52
  """Validate JWT key files if configured."""
@@ -161,96 +71,6 @@ class OxUtilsSettings(BaseSettings):
161
71
  raise ValueError(
162
72
  f"JWT verifying key path is not a file: {self.jwt_verifying_key}"
163
73
  )
164
-
165
- def _validate_s3_config(self, name: str, access_key: Optional[str],
166
- secret_key: Optional[str], bucket: Optional[str],
167
- domain: Optional[str]):
168
- """Validate required S3 configuration fields."""
169
- missing_fields = []
170
- if not access_key:
171
- missing_fields.append(f'OXI_{name.upper()}_S3_ACCESS_KEY_ID')
172
- if not secret_key:
173
- missing_fields.append(f'OXI_{name.upper()}_S3_SECRET_ACCESS_KEY')
174
- if not bucket:
175
- missing_fields.append(f'OXI_{name.upper()}_S3_STORAGE_BUCKET_NAME')
176
- if not domain:
177
- missing_fields.append(f'OXI_{name.upper()}_S3_CUSTOM_DOMAIN')
178
-
179
- if missing_fields:
180
- raise ValueError(
181
- f"Missing required {name} S3 configuration: {', '.join(missing_fields)}"
182
- )
183
-
184
- def get_static_storage_url(self) -> str:
185
- """Get static storage URL."""
186
- if not self.use_static_s3:
187
- raise ImproperlyConfigured(
188
- "Static S3 is not enabled. Set OXI_USE_STATIC_S3=True."
189
- )
190
- return f'https://{self.static_s3_custom_domain}/{self.static_location}/'
191
-
192
- def get_default_storage_url(self) -> str:
193
- """Get default storage URL."""
194
- if self.use_default_s3:
195
- if self.use_static_s3_as_default:
196
- # Use static S3 credentials but keep default_s3 specific values (location, etc.)
197
- domain = self.static_s3_custom_domain
198
- else:
199
- domain = self.default_s3_custom_domain
200
- return f'https://{domain}/{self.default_s3_location}/'
201
-
202
- raise ImproperlyConfigured(
203
- "Default S3 is not enabled. Set OXI_USE_DEFAULT_S3=True."
204
- )
205
-
206
- def get_private_storage_url(self) -> str:
207
- """Get private storage URL."""
208
- if not self.use_private_s3:
209
- raise ImproperlyConfigured(
210
- "Private S3 is not enabled. Set OXI_USE_PRIVATE_S3=True."
211
- )
212
- return f'https://{self.private_s3_custom_domain}/{self.private_s3_location}/'
213
-
214
- def get_log_storage_url(self) -> str:
215
- """Get log storage URL."""
216
- if not self.use_log_s3:
217
- raise ImproperlyConfigured(
218
- "Log S3 is not enabled. Set OXI_USE_LOG_S3=True."
219
- )
220
- if self.use_private_s3_as_log:
221
- # Use private S3 credentials but keep log_s3 specific values (location, etc.)
222
- domain = self.private_s3_custom_domain
223
- else:
224
- domain = self.log_s3_custom_domain
225
- return f'https://{domain}/{self.log_s3_location}/{self.service_name}/'
226
-
227
-
228
- def write_django_settings(self, django_settings_module):
229
- """
230
- Configure Django settings for S3 storages if enabled.
231
-
232
- Sets:
233
- 1. STATIC_URL & STATICFILES_STORAGE (if use_static_s3)
234
- 2. MEDIA_URL & DEFAULT_FILE_STORAGE (if use_default_s3)
235
- 3. PRIVATE_MEDIA_LOCATION & PRIVATE_FILE_STORAGE (if use_private_s3)
236
-
237
- Args:
238
- django_settings_module: The Django settings module to update.
239
- """
240
- # Configure static storage
241
- if self.use_static_s3:
242
- django_settings_module.STATIC_URL = self.get_static_storage_url()
243
- django_settings_module.STATICFILES_STORAGE = self.static_storage
244
-
245
- # Configure default/media storage
246
- if self.use_default_s3:
247
- django_settings_module.MEDIA_URL = self.get_default_storage_url()
248
- django_settings_module.DEFAULT_FILE_STORAGE = self.default_s3_storage
249
-
250
- # Configure private storage
251
- if self.use_private_s3:
252
- django_settings_module.PRIVATE_MEDIA_LOCATION = self.private_s3_location
253
- django_settings_module.PRIVATE_FILE_STORAGE = self.private_s3_storage
254
-
74
+
255
75
 
256
76
  oxi_settings = OxUtilsSettings()
oxutils/users/apps.py CHANGED
@@ -3,4 +3,4 @@ from django.apps import AppConfig
3
3
 
4
4
  class UsersConfig(AppConfig):
5
5
  default_auto_field = 'django.db.models.BigAutoField'
6
- name = 'users'
6
+ name = 'oxutils.users'
@@ -0,0 +1,47 @@
1
+ # Generated by Django 5.2.9 on 2025-12-23 10:52
2
+
3
+ import django.utils.timezone
4
+ import uuid
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ initial = True
11
+
12
+ dependencies = [
13
+ ('auth', '0012_alter_user_first_name_max_length'),
14
+ ]
15
+
16
+ operations = [
17
+ migrations.CreateModel(
18
+ name='User',
19
+ fields=[
20
+ ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
21
+ ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
22
+ ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
23
+ ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
24
+ ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
25
+ ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
26
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Unique identifier for this record', primary_key=True, serialize=False)),
27
+ ('created_at', models.DateTimeField(auto_now_add=True, help_text='Date and time when this record was created')),
28
+ ('updated_at', models.DateTimeField(auto_now=True, help_text='Date and time when this record was last updated')),
29
+ ('deleted', models.DateTimeField(db_index=True, editable=False, null=True)),
30
+ ('deleted_by_cascade', models.BooleanField(default=False, editable=False)),
31
+ ('oxi_id', models.UUIDField(unique=True)),
32
+ ('email', models.EmailField(max_length=254, unique=True)),
33
+ ('is_active', models.BooleanField(default=True)),
34
+ ('subscription_plan', models.CharField(blank=True, max_length=255, null=True)),
35
+ ('subscription_status', models.CharField(blank=True, max_length=255, null=True)),
36
+ ('subscription_end_date', models.DateTimeField(blank=True, null=True)),
37
+ ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
38
+ ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
39
+ ],
40
+ options={
41
+ 'verbose_name': 'utilisateur',
42
+ 'verbose_name_plural': 'utilisateurs',
43
+ 'ordering': ['-created_at'],
44
+ 'indexes': [models.Index(fields=['oxi_id'], name='users_user_oxi_id_389c72_idx'), models.Index(fields=['email'], name='users_user_email_6f2530_idx')],
45
+ },
46
+ ),
47
+ ]
@@ -0,0 +1,23 @@
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
+ ('users', '0001_initial'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='user',
15
+ name='first_name',
16
+ field=models.CharField(blank=True, max_length=255, null=True),
17
+ ),
18
+ migrations.AlterField(
19
+ model_name='user',
20
+ name='last_name',
21
+ field=models.CharField(blank=True, max_length=255, null=True),
22
+ ),
23
+ ]
oxutils/users/models.py CHANGED
@@ -57,6 +57,8 @@ class User(AbstractUser, SafeDeleteModel, BaseModelMixin):
57
57
 
58
58
  oxi_id = models.UUIDField(unique=True) # id venant de auth.oxi.com
59
59
  email = models.EmailField(unique=True)
60
+ first_name = models.CharField(max_length=255, blank=True, null=True)
61
+ last_name = models.CharField(max_length=255, blank=True, null=True)
60
62
  is_active = models.BooleanField(default=True)
61
63
  subscription_plan = models.CharField(max_length=255, null=True, blank=True)
62
64
  subscription_status = models.CharField(max_length=255, null=True, blank=True)
oxutils/utils.py ADDED
@@ -0,0 +1,25 @@
1
+ from django.http import HttpRequest
2
+
3
+
4
+ def get_client_ip(request: HttpRequest) -> str:
5
+ """
6
+ Extract client IP address from request metadata.
7
+
8
+ Priority:
9
+
10
+ 1. X-Forwarded-For header (first entry if multiple)
11
+ 2. REMOTE_ADDR meta value
12
+
13
+ Args:
14
+ request (HttpRequest): Django request object
15
+
16
+ Returns:
17
+ str: Client IP address or None if not found
18
+ """
19
+ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
20
+ if x_forwarded_for:
21
+ ip = x_forwarded_for.split(',')[0]
22
+ else:
23
+ ip = request.META.get('REMOTE_ADDR')
24
+ return ip
25
+
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oxutils
3
- Version: 0.1.6
3
+ Version: 0.1.12
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,15 +24,24 @@ 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
31
30
  Requires-Dist: pyjwt>=2.10.1
32
31
  Requires-Dist: requests>=2.32.5
32
+ Requires-Dist: bcc-rates>=1.1.0 ; extra == 'all'
33
+ Requires-Dist: django-cacheops>=7.2 ; extra == 'all'
34
+ Requires-Dist: django-tenants>=3.9.0 ; extra == 'all'
35
+ Requires-Dist: django-safedelete>=1.4.1 ; extra == 'all'
36
+ Requires-Dist: django-auditlog>=3.4.1 ; extra == 'all'
37
+ Requires-Dist: django-ninja-jwt>=5.4.2 ; extra == 'all'
38
+ Requires-Dist: weasyprint>=67.0 ; extra == 'all'
33
39
  Requires-Dist: bcc-rates>=1.1.0 ; extra == 'currency'
40
+ Requires-Dist: django-ninja-jwt>=5.4.2 ; extra == 'jwt'
34
41
  Requires-Dist: django-cacheops>=7.2 ; extra == 'oxiliere'
35
42
  Requires-Dist: django-tenants>=3.9.0 ; extra == 'oxiliere'
43
+ Requires-Dist: django-safedelete>=1.4.1 ; extra == 'oxiliere'
44
+ Requires-Dist: django-auditlog>=3.4.1 ; extra == 'oxiliere'
36
45
  Requires-Dist: weasyprint>=67.0 ; extra == 'pdf'
37
46
  Requires-Python: >=3.12
38
47
  Project-URL: Changelog, https://github.com/oxiliere/oxutils/blob/main/CHANGELOG.md
@@ -40,7 +49,9 @@ Project-URL: Documentation, https://github.com/oxiliere/oxutils/tree/main/docs
40
49
  Project-URL: Homepage, https://github.com/oxiliere/oxutils
41
50
  Project-URL: Issues, https://github.com/oxiliere/oxutils/issues
42
51
  Project-URL: Repository, https://github.com/oxiliere/oxutils
52
+ Provides-Extra: all
43
53
  Provides-Extra: currency
54
+ Provides-Extra: jwt
44
55
  Provides-Extra: oxiliere
45
56
  Provides-Extra: pdf
46
57
  Description-Content-Type: text/markdown
@@ -59,7 +70,6 @@ Description-Content-Type: text/markdown
59
70
  ## Features
60
71
 
61
72
  - 🔐 **JWT Authentication** - RS256 with JWKS caching
62
- - 📦 **S3 Storage** - Static, media, private, and log backends
63
73
  - 📝 **Structured Logging** - JSON logs with automatic request tracking
64
74
  - 🔍 **Audit System** - Change tracking with S3 export
65
75
  - ⚙️ **Celery Integration** - Pre-configured task processing
@@ -106,8 +116,6 @@ MIDDLEWARE = [
106
116
  ```bash
107
117
  OXI_SERVICE_NAME=my-service
108
118
  OXI_JWT_JWKS_URL=https://auth.example.com/.well-known/jwks.json
109
- OXI_USE_STATIC_S3=True
110
- OXI_STATIC_STORAGE_BUCKET_NAME=my-bucket
111
119
  ```
112
120
 
113
121
  ### 3. Usage Examples
@@ -122,10 +130,6 @@ import structlog
122
130
  logger = structlog.get_logger(__name__)
123
131
  logger.info("user_action", user_id=user_id)
124
132
 
125
- # S3 Storage
126
- from oxutils.s3.storages import PrivateMediaStorage
127
- class Document(models.Model):
128
- file = models.FileField(storage=PrivateMediaStorage())
129
133
 
130
134
  # Model Mixins
131
135
  from oxutils.models.base import BaseModelMixin
@@ -153,7 +157,6 @@ TEMPLATES = [{
153
157
  ### Core Modules
154
158
  - **[Settings](docs/settings.md)** - Configuration reference
155
159
  - **[JWT](docs/jwt.md)** - Authentication
156
- - **[S3](docs/s3.md)** - Storage backends
157
160
  - **[Audit](docs/audit.md)** - Change tracking
158
161
  - **[Logging](docs/logger.md)** - Structured logs
159
162
  - **[Mixins](docs/mixins.md)** - Model/service mixins
@@ -0,0 +1,122 @@
1
+ oxutils/__init__.py,sha256=yiRt0OGkiw1AUyOxAYBpNisZ4VD2AsqkuKSz73VBocQ,508
2
+ oxutils/apps.py,sha256=8pO8eXUZeKYn8fPo0rkoytmHACwDNuTNhdRcpkPTxGM,347
3
+ oxutils/audit/__init__.py,sha256=uonc00G73Xm7RwRHVWD-wBn8lJYNCq3iBgnRGMWAEWs,583
4
+ oxutils/audit/apps.py,sha256=xvnmB5Z6nLV7ejzhSeQbesTkwRoFygoPFob8H5QTHgU,304
5
+ oxutils/audit/export.py,sha256=MVf2RhLzXatBaGK7bIEvSY1VTwEojrPEKwYMvH1stwE,7992
6
+ oxutils/audit/masks.py,sha256=BRCz2m8dbaLgqn5umxpWCwn9mT6Z_ww_WIedl36AmPM,2345
7
+ oxutils/audit/migrations/0001_initial.py,sha256=w0fPooy3ztO0_CtQ7gj5mqa73KpA0vLgzfYr5fZCp_A,2150
8
+ oxutils/audit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ oxutils/audit/models.py,sha256=qssWM4drMK4o6YbVoHKksfqgYQi1EtyXp_2htW5HCfw,2103
10
+ oxutils/audit/settings.py,sha256=NNCFaN6tilDj-0WsVRK9HJNcr2nJOBYJT9E8cY5n9iI,133
11
+ oxutils/audit/utils.py,sha256=VJESUbMb5bC155XNlkIvr8DMc_o6ZTAIn5r8NDr7k_E,581
12
+ oxutils/celery/__init__.py,sha256=29jo4DfdvOoThX-jfL0ZiDjsy3-Z_fNhwHVJaLO5rsk,29
13
+ oxutils/celery/base.py,sha256=qLlBU2XvT2zj8qszy8togqH7hM_wUYyWWA3JAQPPJx0,3378
14
+ oxutils/celery/settings.py,sha256=njhHBErpcFczV2e23NCPX_Jxs015jr4dIig4Is_wbgE,33
15
+ oxutils/conf.py,sha256=TR0RIVaLMHvG0gm3NgbKsoU25eJFBjItAhknFJdiOiQ,231
16
+ oxutils/constants.py,sha256=-mAUy_VhPVRFNLWEKq6kGGg22-WJq9RWommspwrHpYc,264
17
+ oxutils/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ oxutils/context/site_name_processor.py,sha256=1gc0Td_3HVlUn9ThhQBCQ8kfnRnI88bEflK9vEzTvEc,225
19
+ oxutils/currency/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ oxutils/currency/admin.py,sha256=cCd4tXIZTctr3Ksa1jTEpaECw-on7aRHibAfYxEjGek,1831
21
+ oxutils/currency/apps.py,sha256=w35QaiH3BIjCYv2IU6AadENAyBOw7Hmejy9kT5e_law,194
22
+ oxutils/currency/controllers.py,sha256=xDvPBpgXYjCiWyhRQ09fifbdPRSZAG_HC28_sGRfRYc,3042
23
+ oxutils/currency/enums.py,sha256=AHm8zpYCyGv0WMnh8XHLa3cAJnQx3aNOCjGug2Q94VQ,141
24
+ oxutils/currency/migrations/0001_initial.py,sha256=GHypakExy4jW5iQ6hB9x2UUjBoPgaz0OaHNTcBU64Pw,1707
25
+ oxutils/currency/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ oxutils/currency/models.py,sha256=-NrVq-degRc4sAO2BZ_LqSuIAP-IzCwwZJVxfYlNL8o,2448
27
+ oxutils/currency/schemas.py,sha256=4DyEjpL_HaMNDhKyu48fZtcXfRdemVP4rdyHUFit8uI,658
28
+ oxutils/currency/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
29
+ oxutils/currency/utils.py,sha256=eNa9CNlOFC6NMZLJn8VTwLrkME9q7quMTX7NMSZtTv8,2074
30
+ oxutils/enums/__init__.py,sha256=gFhZG8ER6ArGZO5agWhdfs7NiD2h9FzrzfQRHq18dD0,40
31
+ oxutils/enums/audit.py,sha256=ju2Z9CrtdyPziRQ7oOe4Ygw85t9sW3jynO_1DkgZoAM,126
32
+ oxutils/enums/invoices.py,sha256=E33QGQeutZUqvlovJY0VGDxWUb0i_kdfhEiir1ARKuQ,201
33
+ oxutils/exceptions.py,sha256=CCjENOD0of6_noif2ajrpfbBLoG16DWa46iB9_uEe3M,3592
34
+ oxutils/functions.py,sha256=4stHj94VebWX0s1XeWshubMD2v8w8QztTWppbkTE_Gg,3246
35
+ oxutils/jwt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
+ oxutils/jwt/auth.py,sha256=h3rm7nSEweMgyzy5HBRwqC1gPvZ-EZuwdJISSvnltXY,6349
37
+ oxutils/jwt/models.py,sha256=Q0zRnWpK0trFoPDv5ZEY2ROCRaNn83W-K8SbrmSg1E8,2122
38
+ oxutils/jwt/tokens.py,sha256=kWgtPl4XxV0xHkjbhd5QteQy8Wv5MsvyLcLVyO-gzuo,1822
39
+ oxutils/jwt/utils.py,sha256=Wuy-PnCcUw6MpY6z1Isy2vOx-_u1o6LjUfRJgf_cqbY,1202
40
+ oxutils/locale/fr/LC_MESSAGES/django.po,sha256=APXt_8R99seCWjJyS5ELOawvRLvUqqBT32O252BaG5s,7971
41
+ oxutils/logger/__init__.py,sha256=lhPCC8G1aHZTt-FWRqTWptVrqONln-ty9ufVDeOBHYs,183
42
+ oxutils/logger/receivers.py,sha256=QddJ1hWbzUKToFdmNmEePlcTMSz9U1LrQChbjhsBJfo,674
43
+ oxutils/logger/settings.py,sha256=VILlpWtq9JYYSUtU-sulP8sCqTXWzn7Aae8GRAwSDms,1857
44
+ oxutils/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
+ oxutils/mixins/base.py,sha256=0cGY4mGKhL-hJTEBsbETYiaKMVuUgio5DISCv5iYtGI,589
46
+ oxutils/mixins/schemas.py,sha256=DGW0GQBJc9K7hwOJLX-fEryi7KCoY1QaCLny8fjtQMI,319
47
+ oxutils/mixins/services.py,sha256=i4MrkCE3y1W7Xrrz6YeMMaY9xMtCQs4WgBIpM7hrDTI,5399
48
+ oxutils/models/__init__.py,sha256=mR9hhncZMGKefovg9xSPBtb9Yu2AssAdta26ihcNvI4,77
49
+ oxutils/models/base.py,sha256=fIlgrM6rB98FfrFNO5oZP-_L99NnUoz6X6e5jW9N8as,5869
50
+ oxutils/models/billing.py,sha256=aCDZcMx4CUyAwh3wgJGypAJl_fSEuWrL27-cSYv3gCs,3323
51
+ oxutils/models/fields.py,sha256=Nf29OVU1JhK59-d2kCuaMaqKx_hBAYumJ4kDfnVmnw0,1843
52
+ oxutils/models/invoice.py,sha256=nqphkhlBhssODm2H4dBYyb1cOmHS29fToER40UN0cXo,13216
53
+ oxutils/oxiliere/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
+ oxutils/oxiliere/admin.py,sha256=suMo4x8I3JBxAFBVIdE-5qnqZ6JAZV0FESABHOSc-vg,63
55
+ oxutils/oxiliere/apps.py,sha256=sPZFBFyCM7nbvDdIv15VbdPLl_-Apm1OsaaByfGpP3o,327
56
+ oxutils/oxiliere/authorization.py,sha256=52-ch4T443sC1ZdXvYgzgbQRfotVyQJbfna5-ubBnFM,1408
57
+ oxutils/oxiliere/cacheops.py,sha256=VGG5qG5IsxvWJTu1aTlmsaDXV2aiuIMVdVcvHGHeY3g,163
58
+ oxutils/oxiliere/caches.py,sha256=cmxFC1r-aRslBez4XbYund_12Zi8n2kSVBMzp89C2wg,857
59
+ oxutils/oxiliere/checks.py,sha256=fV1DCdLmtL35CUCsisyCf4FRw8yZMl66ntpuksrjoL8,930
60
+ oxutils/oxiliere/constants.py,sha256=IMLhZN7rWHooE9Y9nB4nYsDticNuUscvgvEPUScG58M,125
61
+ oxutils/oxiliere/context.py,sha256=TZ_dNlYhpdSG8r8MNjv75SA8dTUEw2hdZ8fMkW0zEmU,447
62
+ oxutils/oxiliere/controllers.py,sha256=hL_snutY1EuSO0n06NDbjkz-3A3hkqa3sYGie3Grbmg,847
63
+ oxutils/oxiliere/enums.py,sha256=etpHgzsHTQKMrPIxVH-d592_DYSRJ2_o0c8bOdIza-8,201
64
+ oxutils/oxiliere/exceptions.py,sha256=d-J-beEend4L49SJcoGRO7SKLyxlQeuLkLnFU5oTCPQ,178
65
+ oxutils/oxiliere/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
+ oxutils/oxiliere/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
+ oxutils/oxiliere/management/commands/grant_tenant_owners.py,sha256=U0tc-b677kEFA7KC6xah3Ufbg6qYkW21nRikC0FJRQI,774
68
+ oxutils/oxiliere/management/commands/init_oxiliere_system.py,sha256=7ZmKOwL2TOIaPYBpGEoqcw2XslpG1VikUnTJwpu84Lo,4247
69
+ oxutils/oxiliere/middleware.py,sha256=c0C1aalshhYNfe4SBglJkWfUo4Ct-d391GoWP_NhPOw,6412
70
+ oxutils/oxiliere/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
+ oxutils/oxiliere/models.py,sha256=dN3q-8W2gUcUra49b3R33o0ZNn3stc-pfkoVMAKR4gE,6260
72
+ oxutils/oxiliere/permissions.py,sha256=6lJ_43a0-Z5O0B9cttA2cQFjZTGBucJk7w5rgtfd-lQ,3122
73
+ oxutils/oxiliere/schemas.py,sha256=eV9MzkIpHFHmZFtv8Ck2xaGoVUDJXr-yl7w50U-Tj_8,2260
74
+ oxutils/oxiliere/settings.py,sha256=ZuKppEyrucWxvvYC2-wLap4RzKfaEfaRdjJnsNZzpuY,440
75
+ oxutils/oxiliere/signals.py,sha256=il6twTzbmv14SxukLx7dLw2QzuNDyVAsIgHmqbYspjw,97
76
+ oxutils/oxiliere/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
77
+ oxutils/oxiliere/utils.py,sha256=3Q7c_KFiw3iRrGtP8mVtWpuPqCzS4YFmM6V69BIeHvg,3403
78
+ oxutils/pagination/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
+ oxutils/pagination/cursor.py,sha256=l1KuKnILkN96JSoDRJGwiXIpdjxSk-tqQhJREK1TBUM,13159
80
+ oxutils/pdf/__init__.py,sha256=Uu_yOEd-FcNHIB7CV6y76c53wjL5Hce2GMjho8gnkbM,236
81
+ oxutils/pdf/printer.py,sha256=VVvpGU5GdSNTOP06wLqgm36ICDfRTRaRjmmiS-vJ0wM,2449
82
+ oxutils/pdf/utils.py,sha256=cn1Yc7KnjuATSQKM3hrYptCo0IsQurBdrNHh7Nu08b8,3786
83
+ oxutils/pdf/views.py,sha256=DBxCh_UXVywlvlWZh9sVBtYc0gZxMo7ut1j1U_gqPA4,6523
84
+ oxutils/permissions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
+ oxutils/permissions/actions.py,sha256=YmwiKOxhHl6GJ9YxrCzftWfd1ddrYR2GSZx4VeLtv5g,1273
86
+ oxutils/permissions/admin.py,sha256=suMo4x8I3JBxAFBVIdE-5qnqZ6JAZV0FESABHOSc-vg,63
87
+ oxutils/permissions/apps.py,sha256=nuTziz75_t-GToyZpJv2uloEDxzoz-v8ZBglzzdQeG8,272
88
+ oxutils/permissions/caches.py,sha256=05fvTBc9pisWBwUEnnxCtiJU13PB0b55WalR9mzK24I,565
89
+ oxutils/permissions/checks.py,sha256=eNnm2RF0IcZwzdpQqsoEdY9684tQ0r4I839DIFoQfRQ,6590
90
+ oxutils/permissions/constants.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
+ oxutils/permissions/controllers.py,sha256=W9iOsjWPPXUX9c1ZR_v3aOEpT_ZEztQbICQ_KcXQU70,9966
92
+ oxutils/permissions/exceptions.py,sha256=3748Ma7JwQdhleB_rkTBNKj_y5hYzUqbEIoqEQ3kk6c,2295
93
+ oxutils/permissions/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
+ oxutils/permissions/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
+ oxutils/permissions/management/commands/load_permission_preset.py,sha256=Uk251BJW4n7v3Xo8uhoBBB9RPdzY1U0_jFQhSCA8Xfs,3864
96
+ oxutils/permissions/migrations/0001_initial.py,sha256=WjxLZlOlY7xeuE0memvfkBQ4Ch5kX-XRh7obOkknIZg,6460
97
+ oxutils/permissions/migrations/0002_alter_grant_role.py,sha256=uTWAEgCYszHGw3fKZrLnY4uRxCyMhmYLFNEhctLuvlI,515
98
+ oxutils/permissions/migrations/0003_alter_grant_options_alter_group_options_and_more.py,sha256=W5QoxO2klWnze9p0yV9WUbLPIodZoc9CRcrihg6c6RI,893
99
+ oxutils/permissions/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
+ oxutils/permissions/models.py,sha256=FzTsecGcBdlZU6SPlT-aZmbocFDxJ0pWsHVTOvtQvUo,4474
101
+ oxutils/permissions/perms.py,sha256=yVX7OGxCo1uoc4KDHvLy8b55i6PcS3WGgjr1uzzWtk8,3153
102
+ oxutils/permissions/queryset.py,sha256=c_iYIO5ZX4jp3lWP9WucloAr2RPA72XuI_OM4SNhKpw,3173
103
+ oxutils/permissions/schemas.py,sha256=Y6lwP9FkxSmklhwq5_VL6zORxvTmUXShsClm_FM2B-w,6122
104
+ oxutils/permissions/services.py,sha256=d0oCeluzU_7YHpllphMymm-6gY_joKK-gxNDfJ_rD-Q,22140
105
+ oxutils/permissions/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
106
+ oxutils/permissions/utils.py,sha256=n-naz4mlUPihC8WnBAQnqQaVRpYg1ooVwxGAI-EKJLc,22049
107
+ oxutils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
108
+ oxutils/settings.py,sha256=EZOHxvlj-RCLlble7il0SUcuOAbPlmxMe8cRgu74xfA,2404
109
+ oxutils/types.py,sha256=DIz8YK8xMpLc7FYbf88yEElyLsYN_-rbvaZXvENQkOQ,234
110
+ oxutils/users/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
111
+ oxutils/users/admin.py,sha256=suMo4x8I3JBxAFBVIdE-5qnqZ6JAZV0FESABHOSc-vg,63
112
+ oxutils/users/apps.py,sha256=zfWHq8f0DIh8skbnqskDSoHG9nrvVrCegSz22Mw4BGI,150
113
+ oxutils/users/migrations/0001_initial.py,sha256=7l5xgJnms2D8Nnazh38iBQ7I1W9NgNTF228-og_tOVw,3136
114
+ oxutils/users/migrations/0002_alter_user_first_name_alter_user_last_name.py,sha256=o65pUPXu5m9tX_pHkeyKreRUPrByzQRU803Xz4D8v94,577
115
+ oxutils/users/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
+ oxutils/users/models.py,sha256=y5SF5tSqri11LRqRBDorSv5IYRa7noBHjFqQUJ5Jkkg,3105
117
+ oxutils/users/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
118
+ oxutils/users/utils.py,sha256=jY-zL8vLT5U3E2FV3DqCvrPORjKLutbkPZTQ-z96dCw,376
119
+ oxutils/utils.py,sha256=6yGX2d1ajU5RqgfqiaS4McYm7ip2KEgADABo3M-yA3U,595
120
+ oxutils-0.1.12.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
121
+ oxutils-0.1.12.dist-info/METADATA,sha256=M79Xi3iC54CkegN9QcU68qIRJgAQdnYt_2YmK19hE28,8389
122
+ oxutils-0.1.12.dist-info/RECORD,,