oxutils 0.1.5__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.
- oxutils/__init__.py +2 -2
- oxutils/audit/migrations/0001_initial.py +2 -2
- oxutils/audit/models.py +2 -2
- oxutils/constants.py +6 -0
- oxutils/jwt/auth.py +150 -1
- oxutils/jwt/models.py +81 -0
- oxutils/jwt/tokens.py +69 -0
- oxutils/jwt/utils.py +45 -0
- oxutils/logger/__init__.py +10 -0
- oxutils/logger/receivers.py +10 -6
- oxutils/logger/settings.py +2 -2
- oxutils/models/base.py +102 -0
- oxutils/models/fields.py +79 -0
- oxutils/oxiliere/apps.py +9 -1
- oxutils/oxiliere/authorization.py +45 -0
- oxutils/oxiliere/caches.py +13 -11
- oxutils/oxiliere/checks.py +31 -0
- oxutils/oxiliere/constants.py +3 -0
- oxutils/oxiliere/context.py +16 -0
- oxutils/oxiliere/exceptions.py +16 -0
- oxutils/oxiliere/management/commands/grant_tenant_owners.py +19 -0
- oxutils/oxiliere/management/commands/init_oxiliere_system.py +30 -11
- oxutils/oxiliere/middleware.py +65 -11
- oxutils/oxiliere/models.py +146 -9
- oxutils/oxiliere/permissions.py +28 -35
- oxutils/oxiliere/schemas.py +16 -6
- oxutils/oxiliere/signals.py +5 -0
- oxutils/oxiliere/utils.py +36 -1
- oxutils/pagination/cursor.py +367 -0
- oxutils/permissions/__init__.py +0 -0
- oxutils/permissions/actions.py +57 -0
- oxutils/permissions/admin.py +3 -0
- oxutils/permissions/apps.py +10 -0
- oxutils/permissions/caches.py +19 -0
- oxutils/permissions/checks.py +188 -0
- oxutils/permissions/constants.py +0 -0
- oxutils/permissions/controllers.py +344 -0
- oxutils/permissions/exceptions.py +60 -0
- oxutils/permissions/management/__init__.py +0 -0
- oxutils/permissions/management/commands/__init__.py +0 -0
- oxutils/permissions/management/commands/load_permission_preset.py +112 -0
- oxutils/permissions/migrations/0001_initial.py +112 -0
- oxutils/permissions/migrations/0002_alter_grant_role.py +19 -0
- oxutils/permissions/migrations/0003_alter_grant_options_alter_group_options_and_more.py +33 -0
- oxutils/permissions/migrations/__init__.py +0 -0
- oxutils/permissions/models.py +171 -0
- oxutils/permissions/perms.py +95 -0
- oxutils/permissions/queryset.py +92 -0
- oxutils/permissions/schemas.py +276 -0
- oxutils/permissions/services.py +663 -0
- oxutils/permissions/tests.py +3 -0
- oxutils/permissions/utils.py +628 -0
- oxutils/settings.py +14 -194
- oxutils/users/apps.py +1 -1
- oxutils/users/migrations/0001_initial.py +47 -0
- oxutils/users/migrations/0002_alter_user_first_name_alter_user_last_name.py +23 -0
- oxutils/users/models.py +2 -0
- oxutils/utils.py +25 -0
- {oxutils-0.1.5.dist-info → oxutils-0.1.12.dist-info}/METADATA +21 -11
- oxutils-0.1.12.dist-info/RECORD +122 -0
- oxutils/jwt/client.py +0 -123
- oxutils/jwt/constants.py +0 -1
- oxutils/s3/settings.py +0 -34
- oxutils/s3/storages.py +0 -130
- oxutils-0.1.5.dist-info/RECORD +0 -88
- /oxutils/{s3 → pagination}/__init__.py +0 -0
- {oxutils-0.1.5.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
|
|
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('
|
|
32
|
-
jwt_org_access_token_key: str = Field('
|
|
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
|
-
|
|
41
|
-
|
|
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
|
@@ -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.
|
|
3
|
+
Version: 0.1.12
|
|
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,18 +24,36 @@ 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'
|
|
39
|
+
Requires-Dist: bcc-rates>=1.1.0 ; extra == 'currency'
|
|
40
|
+
Requires-Dist: django-ninja-jwt>=5.4.2 ; extra == 'jwt'
|
|
41
|
+
Requires-Dist: django-cacheops>=7.2 ; extra == 'oxiliere'
|
|
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'
|
|
45
|
+
Requires-Dist: weasyprint>=67.0 ; extra == 'pdf'
|
|
33
46
|
Requires-Python: >=3.12
|
|
34
47
|
Project-URL: Changelog, https://github.com/oxiliere/oxutils/blob/main/CHANGELOG.md
|
|
35
48
|
Project-URL: Documentation, https://github.com/oxiliere/oxutils/tree/main/docs
|
|
36
49
|
Project-URL: Homepage, https://github.com/oxiliere/oxutils
|
|
37
50
|
Project-URL: Issues, https://github.com/oxiliere/oxutils/issues
|
|
38
51
|
Project-URL: Repository, https://github.com/oxiliere/oxutils
|
|
52
|
+
Provides-Extra: all
|
|
53
|
+
Provides-Extra: currency
|
|
54
|
+
Provides-Extra: jwt
|
|
55
|
+
Provides-Extra: oxiliere
|
|
56
|
+
Provides-Extra: pdf
|
|
39
57
|
Description-Content-Type: text/markdown
|
|
40
58
|
|
|
41
59
|
# OxUtils
|
|
@@ -52,7 +70,6 @@ Description-Content-Type: text/markdown
|
|
|
52
70
|
## Features
|
|
53
71
|
|
|
54
72
|
- 🔐 **JWT Authentication** - RS256 with JWKS caching
|
|
55
|
-
- 📦 **S3 Storage** - Static, media, private, and log backends
|
|
56
73
|
- 📝 **Structured Logging** - JSON logs with automatic request tracking
|
|
57
74
|
- 🔍 **Audit System** - Change tracking with S3 export
|
|
58
75
|
- ⚙️ **Celery Integration** - Pre-configured task processing
|
|
@@ -99,8 +116,6 @@ MIDDLEWARE = [
|
|
|
99
116
|
```bash
|
|
100
117
|
OXI_SERVICE_NAME=my-service
|
|
101
118
|
OXI_JWT_JWKS_URL=https://auth.example.com/.well-known/jwks.json
|
|
102
|
-
OXI_USE_STATIC_S3=True
|
|
103
|
-
OXI_STATIC_STORAGE_BUCKET_NAME=my-bucket
|
|
104
119
|
```
|
|
105
120
|
|
|
106
121
|
### 3. Usage Examples
|
|
@@ -115,10 +130,6 @@ import structlog
|
|
|
115
130
|
logger = structlog.get_logger(__name__)
|
|
116
131
|
logger.info("user_action", user_id=user_id)
|
|
117
132
|
|
|
118
|
-
# S3 Storage
|
|
119
|
-
from oxutils.s3.storages import PrivateMediaStorage
|
|
120
|
-
class Document(models.Model):
|
|
121
|
-
file = models.FileField(storage=PrivateMediaStorage())
|
|
122
133
|
|
|
123
134
|
# Model Mixins
|
|
124
135
|
from oxutils.models.base import BaseModelMixin
|
|
@@ -146,7 +157,6 @@ TEMPLATES = [{
|
|
|
146
157
|
### Core Modules
|
|
147
158
|
- **[Settings](docs/settings.md)** - Configuration reference
|
|
148
159
|
- **[JWT](docs/jwt.md)** - Authentication
|
|
149
|
-
- **[S3](docs/s3.md)** - Storage backends
|
|
150
160
|
- **[Audit](docs/audit.md)** - Change tracking
|
|
151
161
|
- **[Logging](docs/logger.md)** - Structured logs
|
|
152
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,,
|