oxutils 0.1.0__tar.gz → 0.1.2__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.0 → oxutils-0.1.2}/PKG-INFO +14 -2
- {oxutils-0.1.0 → oxutils-0.1.2}/README.md +13 -1
- {oxutils-0.1.0 → oxutils-0.1.2}/pyproject.toml +1 -1
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/__init__.py +1 -1
- oxutils-0.1.2/src/oxutils/audit/__init__.py +20 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/audit/apps.py +3 -3
- oxutils-0.1.2/src/oxutils/audit/migrations/0001_initial.py +41 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/conf.py +1 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/s3/storages.py +4 -4
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/settings.py +13 -13
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/apps.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/audit/export.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/audit/masks.py +0 -0
- {oxutils-0.1.0/src/oxutils/audit → oxutils-0.1.2/src/oxutils/audit/migrations}/__init__.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/audit/models.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/audit/settings.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/celery/__init__.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/celery/base.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/celery/settings.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/enums/__init__.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/enums/audit.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/enums/invoices.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/exceptions.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/functions.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/jwt/__init__.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/jwt/auth.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/jwt/client.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/jwt/constants.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/locale/fr/LC_MESSAGES/django.mo +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/locale/fr/LC_MESSAGES/django.po +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/logger/__init__.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/logger/receivers.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/logger/settings.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/mixins/__init__.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/mixins/base.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/mixins/schemas.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/mixins/services.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/models/__init__.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/models/base.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/models/billing.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/models/invoice.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/py.typed +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/s3/__init__.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/s3/settings.py +0 -0
- {oxutils-0.1.0 → oxutils-0.1.2}/src/oxutils/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oxutils
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Production-ready utilities for Django applications in the Oxiliere ecosystem
|
|
5
5
|
Keywords: django,utilities,jwt,s3,audit,logging,celery,structlog
|
|
6
6
|
Author: Edimedia Mutoke
|
|
@@ -153,6 +153,18 @@ uv sync
|
|
|
153
153
|
uv run pytest # 126 tests
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
+
### Creating Migrations
|
|
157
|
+
|
|
158
|
+
To generate Django migrations for the audit module:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
make migrations
|
|
162
|
+
# or
|
|
163
|
+
uv run make_migrations.py
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
See [MIGRATIONS.md](MIGRATIONS.md) for detailed documentation.
|
|
167
|
+
|
|
156
168
|
## Advanced Examples
|
|
157
169
|
|
|
158
170
|
### JWT with Django Ninja
|
|
@@ -189,7 +201,7 @@ print(f"Exported to {export.data.url}")
|
|
|
189
201
|
|
|
190
202
|
## License
|
|
191
203
|
|
|
192
|
-
|
|
204
|
+
Apache 2.0 License - see [LICENSE](LICENSE)
|
|
193
205
|
|
|
194
206
|
## Support
|
|
195
207
|
|
|
@@ -111,6 +111,18 @@ uv sync
|
|
|
111
111
|
uv run pytest # 126 tests
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
### Creating Migrations
|
|
115
|
+
|
|
116
|
+
To generate Django migrations for the audit module:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
make migrations
|
|
120
|
+
# or
|
|
121
|
+
uv run make_migrations.py
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
See [MIGRATIONS.md](MIGRATIONS.md) for detailed documentation.
|
|
125
|
+
|
|
114
126
|
## Advanced Examples
|
|
115
127
|
|
|
116
128
|
### JWT with Django Ninja
|
|
@@ -147,7 +159,7 @@ print(f"Exported to {export.data.url}")
|
|
|
147
159
|
|
|
148
160
|
## License
|
|
149
161
|
|
|
150
|
-
|
|
162
|
+
Apache 2.0 License - see [LICENSE](LICENSE)
|
|
151
163
|
|
|
152
164
|
## Support
|
|
153
165
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Oxutils Audit Module
|
|
3
|
+
|
|
4
|
+
Provides audit log export functionality with S3 storage.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Models are imported lazily to avoid AppRegistryNotReady errors
|
|
8
|
+
# Use: from oxutils.audit.models import LogExportState, LogExportHistory
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
'LogExportState',
|
|
12
|
+
'LogExportHistory',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
def __getattr__(name):
|
|
16
|
+
"""Lazy import of models to avoid AppRegistryNotReady errors."""
|
|
17
|
+
if name in __all__:
|
|
18
|
+
from oxutils.audit.models import LogExportState, LogExportHistory
|
|
19
|
+
return locals()[name]
|
|
20
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -3,10 +3,10 @@ from django.utils.translation import gettext_lazy as _
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class
|
|
6
|
+
class OxutilsAuditConfig(AppConfig):
|
|
7
7
|
default_auto_field = 'django.db.models.BigAutoField'
|
|
8
|
-
name = '
|
|
9
|
-
verbose_name = _("Oxutils
|
|
8
|
+
name = 'oxutils.audit'
|
|
9
|
+
verbose_name = _("Oxutils Audit")
|
|
10
10
|
|
|
11
11
|
def ready(self):
|
|
12
12
|
return super().ready()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Generated by Django 5.2.8 on 2025-12-03 21:53
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
import oxutils.enums.audit
|
|
5
|
+
import oxutils.s3.storages
|
|
6
|
+
from django.db import migrations, models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(migrations.Migration):
|
|
10
|
+
|
|
11
|
+
initial = True
|
|
12
|
+
|
|
13
|
+
dependencies = [
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
operations = [
|
|
17
|
+
migrations.CreateModel(
|
|
18
|
+
name='LogExportState',
|
|
19
|
+
fields=[
|
|
20
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
21
|
+
('created_at', models.DateTimeField(auto_now_add=True, help_text='Date and time when this record was created')),
|
|
22
|
+
('updated_at', models.DateTimeField(auto_now=True, help_text='Date and time when this record was last updated')),
|
|
23
|
+
('last_export_date', models.DateTimeField(null=True)),
|
|
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='')),
|
|
26
|
+
('size', models.BigIntegerField()),
|
|
27
|
+
],
|
|
28
|
+
options={
|
|
29
|
+
'abstract': False,
|
|
30
|
+
},
|
|
31
|
+
),
|
|
32
|
+
migrations.CreateModel(
|
|
33
|
+
name='LogExportHistory',
|
|
34
|
+
fields=[
|
|
35
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
36
|
+
('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'])),
|
|
37
|
+
('created_at', models.DateTimeField(auto_now_add=True, help_text='Date and time when this record was created')),
|
|
38
|
+
('state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='log_histories', to='audit.logexportstate')),
|
|
39
|
+
],
|
|
40
|
+
),
|
|
41
|
+
]
|
|
@@ -53,7 +53,7 @@ class PublicMediaStorage(S3Boto3Storage):
|
|
|
53
53
|
self.access_key = oxi_settings.default_s3_access_key_id
|
|
54
54
|
self.secret_key = oxi_settings.default_s3_secret_access_key
|
|
55
55
|
self.bucket_name = oxi_settings.default_s3_storage_bucket_name
|
|
56
|
-
self.custom_domain = oxi_settings.
|
|
56
|
+
self.custom_domain = oxi_settings.default_s3_custom_domain
|
|
57
57
|
|
|
58
58
|
self.location = oxi_settings.default_s3_location
|
|
59
59
|
self.default_acl = oxi_settings.default_s3_default_acl
|
|
@@ -79,7 +79,7 @@ class PrivateMediaStorage(S3Boto3Storage):
|
|
|
79
79
|
self.access_key = oxi_settings.private_s3_access_key_id
|
|
80
80
|
self.secret_key = oxi_settings.private_s3_secret_access_key
|
|
81
81
|
self.bucket_name = oxi_settings.private_s3_storage_bucket_name
|
|
82
|
-
self.custom_domain = oxi_settings.
|
|
82
|
+
self.custom_domain = oxi_settings.private_s3_custom_domain
|
|
83
83
|
self.location = oxi_settings.private_s3_location
|
|
84
84
|
self.default_acl = oxi_settings.private_s3_default_acl
|
|
85
85
|
self.file_overwrite = False
|
|
@@ -107,12 +107,12 @@ class LogStorage(S3Boto3Storage):
|
|
|
107
107
|
self.access_key = oxi_settings.private_s3_access_key_id
|
|
108
108
|
self.secret_key = oxi_settings.private_s3_secret_access_key
|
|
109
109
|
self.bucket_name = oxi_settings.private_s3_storage_bucket_name
|
|
110
|
-
self.custom_domain = oxi_settings.
|
|
110
|
+
self.custom_domain = oxi_settings.private_s3_custom_domain
|
|
111
111
|
else:
|
|
112
112
|
self.access_key = oxi_settings.log_s3_access_key_id
|
|
113
113
|
self.secret_key = oxi_settings.log_s3_secret_access_key
|
|
114
114
|
self.bucket_name = oxi_settings.log_s3_storage_bucket_name
|
|
115
|
-
self.custom_domain = oxi_settings.
|
|
115
|
+
self.custom_domain = oxi_settings.log_s3_custom_domain
|
|
116
116
|
|
|
117
117
|
self.location = f'{oxi_settings.log_s3_location}/{oxi_settings.service_name}'
|
|
118
118
|
self.default_acl = oxi_settings.log_s3_default_acl
|
|
@@ -52,7 +52,7 @@ class OxUtilsSettings(BaseSettings):
|
|
|
52
52
|
default_s3_secret_access_key: Optional[str] = None
|
|
53
53
|
default_s3_storage_bucket_name: Optional[str] = None
|
|
54
54
|
default_s3_default_acl: str = Field('public-read')
|
|
55
|
-
|
|
55
|
+
default_s3_custom_domain: Optional[str] = None
|
|
56
56
|
default_s3_location: str = Field('media')
|
|
57
57
|
default_s3_storage: str = Field('oxutils.s3.storages.PublicMediaStorage')
|
|
58
58
|
|
|
@@ -62,7 +62,7 @@ class OxUtilsSettings(BaseSettings):
|
|
|
62
62
|
private_s3_secret_access_key: Optional[str] = None
|
|
63
63
|
private_s3_storage_bucket_name: Optional[str] = None
|
|
64
64
|
private_s3_default_acl: str = Field('private')
|
|
65
|
-
|
|
65
|
+
private_s3_custom_domain: Optional[str] = None
|
|
66
66
|
private_s3_location: str = Field('private')
|
|
67
67
|
private_s3_storage: str = Field('oxutils.s3.storages.PrivateMediaStorage')
|
|
68
68
|
|
|
@@ -73,7 +73,7 @@ class OxUtilsSettings(BaseSettings):
|
|
|
73
73
|
log_s3_secret_access_key: Optional[str] = None
|
|
74
74
|
log_s3_storage_bucket_name: Optional[str] = None
|
|
75
75
|
log_s3_default_acl: str = Field('private')
|
|
76
|
-
|
|
76
|
+
log_s3_custom_domain: Optional[str] = None
|
|
77
77
|
log_s3_location: str = Field('oxi_logs')
|
|
78
78
|
log_s3_storage: str = Field('oxutils.s3.storages.LogStorage')
|
|
79
79
|
|
|
@@ -102,7 +102,7 @@ class OxUtilsSettings(BaseSettings):
|
|
|
102
102
|
self.default_s3_access_key_id,
|
|
103
103
|
self.default_s3_secret_access_key,
|
|
104
104
|
self.default_s3_storage_bucket_name,
|
|
105
|
-
self.
|
|
105
|
+
self.default_s3_custom_domain
|
|
106
106
|
)
|
|
107
107
|
elif not self.use_static_s3:
|
|
108
108
|
raise ValueError(
|
|
@@ -116,7 +116,7 @@ class OxUtilsSettings(BaseSettings):
|
|
|
116
116
|
self.private_s3_access_key_id,
|
|
117
117
|
self.private_s3_secret_access_key,
|
|
118
118
|
self.private_s3_storage_bucket_name,
|
|
119
|
-
self.
|
|
119
|
+
self.private_s3_custom_domain
|
|
120
120
|
)
|
|
121
121
|
|
|
122
122
|
# Validate log S3
|
|
@@ -127,7 +127,7 @@ class OxUtilsSettings(BaseSettings):
|
|
|
127
127
|
self.log_s3_access_key_id,
|
|
128
128
|
self.log_s3_secret_access_key,
|
|
129
129
|
self.log_s3_storage_bucket_name,
|
|
130
|
-
self.
|
|
130
|
+
self.log_s3_custom_domain
|
|
131
131
|
)
|
|
132
132
|
elif not self.use_private_s3:
|
|
133
133
|
raise ValueError(
|
|
@@ -166,11 +166,11 @@ class OxUtilsSettings(BaseSettings):
|
|
|
166
166
|
"""Validate required S3 configuration fields."""
|
|
167
167
|
missing_fields = []
|
|
168
168
|
if not access_key:
|
|
169
|
-
missing_fields.append(f'OXI_{name.upper()}
|
|
169
|
+
missing_fields.append(f'OXI_{name.upper()}_S3_ACCESS_KEY_ID')
|
|
170
170
|
if not secret_key:
|
|
171
|
-
missing_fields.append(f'OXI_{name.upper()}
|
|
171
|
+
missing_fields.append(f'OXI_{name.upper()}_S3_SECRET_ACCESS_KEY')
|
|
172
172
|
if not bucket:
|
|
173
|
-
missing_fields.append(f'OXI_{name.upper()}
|
|
173
|
+
missing_fields.append(f'OXI_{name.upper()}_S3_STORAGE_BUCKET_NAME')
|
|
174
174
|
if not domain:
|
|
175
175
|
missing_fields.append(f'OXI_{name.upper()}_S3_CUSTOM_DOMAIN')
|
|
176
176
|
|
|
@@ -194,7 +194,7 @@ class OxUtilsSettings(BaseSettings):
|
|
|
194
194
|
# Use static S3 credentials but keep default_s3 specific values (location, etc.)
|
|
195
195
|
domain = self.static_s3_custom_domain
|
|
196
196
|
else:
|
|
197
|
-
domain = self.
|
|
197
|
+
domain = self.default_s3_custom_domain
|
|
198
198
|
return f'https://{domain}/{self.default_s3_location}/'
|
|
199
199
|
|
|
200
200
|
raise ImproperlyConfigured(
|
|
@@ -207,7 +207,7 @@ class OxUtilsSettings(BaseSettings):
|
|
|
207
207
|
raise ImproperlyConfigured(
|
|
208
208
|
"Private S3 is not enabled. Set OXI_USE_PRIVATE_S3=True."
|
|
209
209
|
)
|
|
210
|
-
return f'https://{self.
|
|
210
|
+
return f'https://{self.private_s3_custom_domain}/{self.private_s3_location}/'
|
|
211
211
|
|
|
212
212
|
def get_log_storage_url(self) -> str:
|
|
213
213
|
"""Get log storage URL."""
|
|
@@ -217,9 +217,9 @@ class OxUtilsSettings(BaseSettings):
|
|
|
217
217
|
)
|
|
218
218
|
if self.use_private_s3_as_log:
|
|
219
219
|
# Use private S3 credentials but keep log_s3 specific values (location, etc.)
|
|
220
|
-
domain = self.
|
|
220
|
+
domain = self.private_s3_custom_domain
|
|
221
221
|
else:
|
|
222
|
-
domain = self.
|
|
222
|
+
domain = self.log_s3_custom_domain
|
|
223
223
|
return f'https://{domain}/{self.log_s3_location}/{self.service_name}/'
|
|
224
224
|
|
|
225
225
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|