oxutils 0.1.4__tar.gz → 0.1.6__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.4 → oxutils-0.1.6}/PKG-INFO +81 -4
- {oxutils-0.1.4 → oxutils-0.1.6}/README.md +73 -2
- {oxutils-0.1.4 → oxutils-0.1.6}/pyproject.toml +18 -2
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/__init__.py +1 -1
- oxutils-0.1.6/src/oxutils/constants.py +2 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/currency/controllers.py +3 -2
- oxutils-0.1.6/src/oxutils/currency/utils.py +69 -0
- oxutils-0.1.6/src/oxutils/oxiliere/admin.py +3 -0
- oxutils-0.1.6/src/oxutils/oxiliere/apps.py +6 -0
- oxutils-0.1.6/src/oxutils/oxiliere/cacheops.py +7 -0
- oxutils-0.1.6/src/oxutils/oxiliere/caches.py +33 -0
- oxutils-0.1.6/src/oxutils/oxiliere/controllers.py +36 -0
- oxutils-0.1.6/src/oxutils/oxiliere/enums.py +10 -0
- oxutils-0.1.6/src/oxutils/oxiliere/management/commands/__init__.py +0 -0
- oxutils-0.1.6/src/oxutils/oxiliere/management/commands/init_oxiliere_system.py +86 -0
- oxutils-0.1.6/src/oxutils/oxiliere/middleware.py +97 -0
- oxutils-0.1.6/src/oxutils/oxiliere/migrations/__init__.py +0 -0
- oxutils-0.1.6/src/oxutils/oxiliere/models.py +55 -0
- oxutils-0.1.6/src/oxutils/oxiliere/permissions.py +104 -0
- oxutils-0.1.6/src/oxutils/oxiliere/schemas.py +65 -0
- oxutils-0.1.6/src/oxutils/oxiliere/settings.py +17 -0
- oxutils-0.1.6/src/oxutils/oxiliere/tests.py +3 -0
- oxutils-0.1.6/src/oxutils/oxiliere/utils.py +76 -0
- oxutils-0.1.6/src/oxutils/pdf/__init__.py +10 -0
- oxutils-0.1.6/src/oxutils/pdf/printer.py +81 -0
- oxutils-0.1.6/src/oxutils/pdf/utils.py +94 -0
- oxutils-0.1.6/src/oxutils/pdf/views.py +208 -0
- oxutils-0.1.6/src/oxutils/py.typed +0 -0
- oxutils-0.1.6/src/oxutils/s3/__init__.py +0 -0
- oxutils-0.1.6/src/oxutils/users/__init__.py +0 -0
- oxutils-0.1.6/src/oxutils/users/admin.py +3 -0
- oxutils-0.1.6/src/oxutils/users/apps.py +6 -0
- oxutils-0.1.6/src/oxutils/users/migrations/__init__.py +0 -0
- oxutils-0.1.6/src/oxutils/users/models.py +88 -0
- oxutils-0.1.6/src/oxutils/users/tests.py +3 -0
- oxutils-0.1.6/src/oxutils/users/utils.py +15 -0
- oxutils-0.1.4/src/oxutils/currency/utils.py +0 -31
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/apps.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/audit/__init__.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/audit/apps.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/audit/export.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/audit/masks.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/audit/migrations/0001_initial.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/audit/migrations/__init__.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/audit/models.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/audit/settings.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/audit/utils.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/celery/__init__.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/celery/base.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/celery/settings.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/conf.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/context/__init__.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/context/site_name_processor.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/currency/__init__.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/currency/admin.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/currency/apps.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/currency/enums.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/currency/migrations/0001_initial.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/currency/migrations/__init__.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/currency/models.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/currency/schemas.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/currency/tests.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/enums/__init__.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/enums/audit.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/enums/invoices.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/exceptions.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/functions.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/jwt/__init__.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/jwt/auth.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/jwt/client.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/jwt/constants.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/locale/fr/LC_MESSAGES/django.po +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/logger/__init__.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/logger/receivers.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/logger/settings.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/mixins/__init__.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/mixins/base.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/mixins/schemas.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/mixins/services.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/models/__init__.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/models/base.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/models/billing.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/models/invoice.py +0 -0
- {oxutils-0.1.4/src/oxutils/s3 → oxutils-0.1.6/src/oxutils/oxiliere}/__init__.py +0 -0
- /oxutils-0.1.4/src/oxutils/py.typed → /oxutils-0.1.6/src/oxutils/oxiliere/management/__init__.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/s3/settings.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/s3/storages.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/src/oxutils/settings.py +0 -0
- {oxutils-0.1.4 → oxutils-0.1.6}/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.6
|
|
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
|
|
@@ -16,7 +16,6 @@ Classifier: Programming Language :: Python :: 3
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
18
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
19
|
-
Requires-Dist: bcc-rates>=1.1.0
|
|
20
19
|
Requires-Dist: boto3>=1.41.5
|
|
21
20
|
Requires-Dist: celery>=5.5.3
|
|
22
21
|
Requires-Dist: cryptography>=46.0.3
|
|
@@ -31,12 +30,19 @@ Requires-Dist: jwcrypto>=1.5.6
|
|
|
31
30
|
Requires-Dist: pydantic-settings>=2.12.0
|
|
32
31
|
Requires-Dist: pyjwt>=2.10.1
|
|
33
32
|
Requires-Dist: requests>=2.32.5
|
|
33
|
+
Requires-Dist: bcc-rates>=1.1.0 ; extra == 'currency'
|
|
34
|
+
Requires-Dist: django-cacheops>=7.2 ; extra == 'oxiliere'
|
|
35
|
+
Requires-Dist: django-tenants>=3.9.0 ; extra == 'oxiliere'
|
|
36
|
+
Requires-Dist: weasyprint>=67.0 ; extra == 'pdf'
|
|
34
37
|
Requires-Python: >=3.12
|
|
35
38
|
Project-URL: Changelog, https://github.com/oxiliere/oxutils/blob/main/CHANGELOG.md
|
|
36
39
|
Project-URL: Documentation, https://github.com/oxiliere/oxutils/tree/main/docs
|
|
37
40
|
Project-URL: Homepage, https://github.com/oxiliere/oxutils
|
|
38
41
|
Project-URL: Issues, https://github.com/oxiliere/oxutils/issues
|
|
39
42
|
Project-URL: Repository, https://github.com/oxiliere/oxutils
|
|
43
|
+
Provides-Extra: currency
|
|
44
|
+
Provides-Extra: oxiliere
|
|
45
|
+
Provides-Extra: pdf
|
|
40
46
|
Description-Content-Type: text/markdown
|
|
41
47
|
|
|
42
48
|
# OxUtils
|
|
@@ -46,7 +52,7 @@ Description-Content-Type: text/markdown
|
|
|
46
52
|
[](https://pypi.org/project/oxutils/)
|
|
47
53
|
[](https://www.python.org/)
|
|
48
54
|
[](https://www.djangoproject.com/)
|
|
49
|
-
[](tests/)
|
|
50
56
|
[](LICENSE)
|
|
51
57
|
[](https://github.com/astral-sh/ruff)
|
|
52
58
|
|
|
@@ -60,6 +66,9 @@ Description-Content-Type: text/markdown
|
|
|
60
66
|
- 🛠️ **Django Mixins** - UUID, timestamps, user tracking
|
|
61
67
|
- ⚡ **Custom Exceptions** - Standardized API errors
|
|
62
68
|
- 🎨 **Context Processors** - Site name and domain for templates
|
|
69
|
+
- 💱 **Currency Module** - Multi-source exchange rates (BCC/OXR)
|
|
70
|
+
- 📄 **PDF Generation** - WeasyPrint integration for Django
|
|
71
|
+
- 🏢 **Multi-Tenant** - PostgreSQL schema-based isolation
|
|
63
72
|
|
|
64
73
|
---
|
|
65
74
|
|
|
@@ -141,6 +150,7 @@ TEMPLATES = [{
|
|
|
141
150
|
|
|
142
151
|
## Documentation
|
|
143
152
|
|
|
153
|
+
### Core Modules
|
|
144
154
|
- **[Settings](docs/settings.md)** - Configuration reference
|
|
145
155
|
- **[JWT](docs/jwt.md)** - Authentication
|
|
146
156
|
- **[S3](docs/s3.md)** - Storage backends
|
|
@@ -149,6 +159,11 @@ TEMPLATES = [{
|
|
|
149
159
|
- **[Mixins](docs/mixins.md)** - Model/service mixins
|
|
150
160
|
- **[Celery](docs/celery.md)** - Task processing
|
|
151
161
|
|
|
162
|
+
### Additional Modules
|
|
163
|
+
- **[Currency](docs/currency.md)** - Exchange rates management
|
|
164
|
+
- **[PDF](docs/pdf.md)** - PDF generation with WeasyPrint
|
|
165
|
+
- **[Oxiliere](docs/oxiliere.md)** - Multi-tenant architecture
|
|
166
|
+
|
|
152
167
|
## Requirements
|
|
153
168
|
|
|
154
169
|
- Python 3.12+
|
|
@@ -161,7 +176,7 @@ TEMPLATES = [{
|
|
|
161
176
|
git clone https://github.com/oxiliere/oxutils.git
|
|
162
177
|
cd oxutils
|
|
163
178
|
uv sync
|
|
164
|
-
uv run pytest #
|
|
179
|
+
uv run pytest # 201 tests passing, 4 skipped
|
|
165
180
|
```
|
|
166
181
|
|
|
167
182
|
### Creating Migrations
|
|
@@ -176,6 +191,19 @@ uv run make_migrations.py
|
|
|
176
191
|
|
|
177
192
|
See [MIGRATIONS.md](MIGRATIONS.md) for detailed documentation.
|
|
178
193
|
|
|
194
|
+
## Optional Dependencies
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# Multi-tenant support
|
|
198
|
+
uv add oxutils[oxiliere]
|
|
199
|
+
|
|
200
|
+
# PDF generation
|
|
201
|
+
uv add oxutils[pdf]
|
|
202
|
+
|
|
203
|
+
# Development tools
|
|
204
|
+
uv add oxutils[dev]
|
|
205
|
+
```
|
|
206
|
+
|
|
179
207
|
## Advanced Examples
|
|
180
208
|
|
|
181
209
|
### JWT with Django Ninja
|
|
@@ -210,6 +238,55 @@ export = export_logs_from_date(from_date=from_date)
|
|
|
210
238
|
print(f"Exported to {export.data.url}")
|
|
211
239
|
```
|
|
212
240
|
|
|
241
|
+
### Currency Exchange Rates
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
from oxutils.currency.models import CurrencyState
|
|
245
|
+
|
|
246
|
+
# Sync rates from BCC (with OXR fallback)
|
|
247
|
+
state = CurrencyState.sync()
|
|
248
|
+
|
|
249
|
+
# Get latest rates
|
|
250
|
+
latest = CurrencyState.objects.latest()
|
|
251
|
+
usd_rate = latest.currencies.get(code='USD').rate
|
|
252
|
+
eur_rate = latest.currencies.get(code='EUR').rate
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### PDF Generation
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from oxutils.pdf.printer import Printer
|
|
259
|
+
from oxutils.pdf.views import WeasyTemplateView
|
|
260
|
+
|
|
261
|
+
# Standalone PDF generation
|
|
262
|
+
printer = Printer(
|
|
263
|
+
template_name='invoice.html',
|
|
264
|
+
context={'invoice': invoice},
|
|
265
|
+
stylesheets=['css/invoice.css']
|
|
266
|
+
)
|
|
267
|
+
pdf_bytes = printer.write_pdf()
|
|
268
|
+
|
|
269
|
+
# Class-based view
|
|
270
|
+
class InvoicePDFView(WeasyTemplateView):
|
|
271
|
+
template_name = 'invoice.html'
|
|
272
|
+
pdf_filename = 'invoice.pdf'
|
|
273
|
+
pdf_stylesheets = ['css/invoice.css']
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Multi-Tenant Setup
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
# settings.py
|
|
280
|
+
TENANT_MODEL = "oxiliere.Tenant"
|
|
281
|
+
MIDDLEWARE = [
|
|
282
|
+
'oxutils.oxiliere.middleware.TenantMainMiddleware', # First!
|
|
283
|
+
# other middleware...
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
# All requests must include X-Organization-ID header
|
|
287
|
+
# Data is automatically isolated per tenant schema
|
|
288
|
+
```
|
|
289
|
+
|
|
213
290
|
## License
|
|
214
291
|
|
|
215
292
|
Apache 2.0 License - see [LICENSE](LICENSE)
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://pypi.org/project/oxutils/)
|
|
6
6
|
[](https://www.python.org/)
|
|
7
7
|
[](https://www.djangoproject.com/)
|
|
8
|
-
[](tests/)
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](https://github.com/astral-sh/ruff)
|
|
11
11
|
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
- 🛠️ **Django Mixins** - UUID, timestamps, user tracking
|
|
20
20
|
- ⚡ **Custom Exceptions** - Standardized API errors
|
|
21
21
|
- 🎨 **Context Processors** - Site name and domain for templates
|
|
22
|
+
- 💱 **Currency Module** - Multi-source exchange rates (BCC/OXR)
|
|
23
|
+
- 📄 **PDF Generation** - WeasyPrint integration for Django
|
|
24
|
+
- 🏢 **Multi-Tenant** - PostgreSQL schema-based isolation
|
|
22
25
|
|
|
23
26
|
---
|
|
24
27
|
|
|
@@ -100,6 +103,7 @@ TEMPLATES = [{
|
|
|
100
103
|
|
|
101
104
|
## Documentation
|
|
102
105
|
|
|
106
|
+
### Core Modules
|
|
103
107
|
- **[Settings](docs/settings.md)** - Configuration reference
|
|
104
108
|
- **[JWT](docs/jwt.md)** - Authentication
|
|
105
109
|
- **[S3](docs/s3.md)** - Storage backends
|
|
@@ -108,6 +112,11 @@ TEMPLATES = [{
|
|
|
108
112
|
- **[Mixins](docs/mixins.md)** - Model/service mixins
|
|
109
113
|
- **[Celery](docs/celery.md)** - Task processing
|
|
110
114
|
|
|
115
|
+
### Additional Modules
|
|
116
|
+
- **[Currency](docs/currency.md)** - Exchange rates management
|
|
117
|
+
- **[PDF](docs/pdf.md)** - PDF generation with WeasyPrint
|
|
118
|
+
- **[Oxiliere](docs/oxiliere.md)** - Multi-tenant architecture
|
|
119
|
+
|
|
111
120
|
## Requirements
|
|
112
121
|
|
|
113
122
|
- Python 3.12+
|
|
@@ -120,7 +129,7 @@ TEMPLATES = [{
|
|
|
120
129
|
git clone https://github.com/oxiliere/oxutils.git
|
|
121
130
|
cd oxutils
|
|
122
131
|
uv sync
|
|
123
|
-
uv run pytest #
|
|
132
|
+
uv run pytest # 201 tests passing, 4 skipped
|
|
124
133
|
```
|
|
125
134
|
|
|
126
135
|
### Creating Migrations
|
|
@@ -135,6 +144,19 @@ uv run make_migrations.py
|
|
|
135
144
|
|
|
136
145
|
See [MIGRATIONS.md](MIGRATIONS.md) for detailed documentation.
|
|
137
146
|
|
|
147
|
+
## Optional Dependencies
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Multi-tenant support
|
|
151
|
+
uv add oxutils[oxiliere]
|
|
152
|
+
|
|
153
|
+
# PDF generation
|
|
154
|
+
uv add oxutils[pdf]
|
|
155
|
+
|
|
156
|
+
# Development tools
|
|
157
|
+
uv add oxutils[dev]
|
|
158
|
+
```
|
|
159
|
+
|
|
138
160
|
## Advanced Examples
|
|
139
161
|
|
|
140
162
|
### JWT with Django Ninja
|
|
@@ -169,6 +191,55 @@ export = export_logs_from_date(from_date=from_date)
|
|
|
169
191
|
print(f"Exported to {export.data.url}")
|
|
170
192
|
```
|
|
171
193
|
|
|
194
|
+
### Currency Exchange Rates
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from oxutils.currency.models import CurrencyState
|
|
198
|
+
|
|
199
|
+
# Sync rates from BCC (with OXR fallback)
|
|
200
|
+
state = CurrencyState.sync()
|
|
201
|
+
|
|
202
|
+
# Get latest rates
|
|
203
|
+
latest = CurrencyState.objects.latest()
|
|
204
|
+
usd_rate = latest.currencies.get(code='USD').rate
|
|
205
|
+
eur_rate = latest.currencies.get(code='EUR').rate
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### PDF Generation
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from oxutils.pdf.printer import Printer
|
|
212
|
+
from oxutils.pdf.views import WeasyTemplateView
|
|
213
|
+
|
|
214
|
+
# Standalone PDF generation
|
|
215
|
+
printer = Printer(
|
|
216
|
+
template_name='invoice.html',
|
|
217
|
+
context={'invoice': invoice},
|
|
218
|
+
stylesheets=['css/invoice.css']
|
|
219
|
+
)
|
|
220
|
+
pdf_bytes = printer.write_pdf()
|
|
221
|
+
|
|
222
|
+
# Class-based view
|
|
223
|
+
class InvoicePDFView(WeasyTemplateView):
|
|
224
|
+
template_name = 'invoice.html'
|
|
225
|
+
pdf_filename = 'invoice.pdf'
|
|
226
|
+
pdf_stylesheets = ['css/invoice.css']
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Multi-Tenant Setup
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
# settings.py
|
|
233
|
+
TENANT_MODEL = "oxiliere.Tenant"
|
|
234
|
+
MIDDLEWARE = [
|
|
235
|
+
'oxutils.oxiliere.middleware.TenantMainMiddleware', # First!
|
|
236
|
+
# other middleware...
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
# All requests must include X-Organization-ID header
|
|
240
|
+
# Data is automatically isolated per tenant schema
|
|
241
|
+
```
|
|
242
|
+
|
|
172
243
|
## License
|
|
173
244
|
|
|
174
245
|
Apache 2.0 License - see [LICENSE](LICENSE)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "oxutils"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.6"
|
|
4
4
|
description = "Production-ready utilities for Django applications in the Oxiliere ecosystem"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -22,7 +22,6 @@ classifiers = [
|
|
|
22
22
|
"Topic :: Internet :: WWW/HTTP",
|
|
23
23
|
]
|
|
24
24
|
dependencies = [
|
|
25
|
-
"bcc-rates>=1.1.0",
|
|
26
25
|
"boto3>=1.41.5",
|
|
27
26
|
"celery>=5.5.3",
|
|
28
27
|
"cryptography>=46.0.3",
|
|
@@ -55,6 +54,22 @@ members = [
|
|
|
55
54
|
".",
|
|
56
55
|
]
|
|
57
56
|
|
|
57
|
+
[tool.uv.sources]
|
|
58
|
+
oxutils = { workspace = true }
|
|
59
|
+
|
|
60
|
+
[project.optional-dependencies]
|
|
61
|
+
currency = [
|
|
62
|
+
"bcc-rates>=1.1.0",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
oxiliere = [
|
|
66
|
+
"django-cacheops>=7.2",
|
|
67
|
+
"django-tenants>=3.9.0",
|
|
68
|
+
]
|
|
69
|
+
pdf = [
|
|
70
|
+
"weasyprint>=67.0",
|
|
71
|
+
]
|
|
72
|
+
|
|
58
73
|
[dependency-groups]
|
|
59
74
|
dev = [
|
|
60
75
|
"pytest>=8.0.0",
|
|
@@ -66,6 +81,7 @@ dev = [
|
|
|
66
81
|
"ruff>=0.8.0",
|
|
67
82
|
]
|
|
68
83
|
|
|
84
|
+
|
|
69
85
|
[tool.ruff]
|
|
70
86
|
line-length = 100
|
|
71
87
|
target-version = "py312"
|
|
@@ -11,12 +11,13 @@ from ninja_extra.pagination import (
|
|
|
11
11
|
from ninja.errors import HttpError
|
|
12
12
|
from uuid import UUID
|
|
13
13
|
import structlog
|
|
14
|
-
from currency.models import CurrencyState
|
|
15
|
-
from currency.schemas import (
|
|
14
|
+
from oxutils.currency.models import CurrencyState
|
|
15
|
+
from oxutils.currency.schemas import (
|
|
16
16
|
CurrencyStateSchema,
|
|
17
17
|
CurrencyStateDetailSchema,
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
+
|
|
20
21
|
logger = structlog.get_logger(__name__)
|
|
21
22
|
|
|
22
23
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from django.conf import settings
|
|
3
|
+
from django.db import transaction
|
|
4
|
+
from bcc_rates import BCCBankSource, OXRBankSource, SourceValue
|
|
5
|
+
from oxutils.currency.enums import CurrencySource
|
|
6
|
+
from oxutils.currency.schemas import CurrencyStateDetailSchema
|
|
7
|
+
import structlog
|
|
8
|
+
|
|
9
|
+
logger = structlog.get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def load_rates() -> tuple[list[SourceValue], CurrencySource]:
|
|
13
|
+
max_retries = 3
|
|
14
|
+
retry_count = 0
|
|
15
|
+
|
|
16
|
+
while retry_count < max_retries:
|
|
17
|
+
try:
|
|
18
|
+
bcc_source = BCCBankSource()
|
|
19
|
+
rates = bcc_source.sync(cache=True)
|
|
20
|
+
return rates, CurrencySource.BCC
|
|
21
|
+
except Exception as e:
|
|
22
|
+
retry_count += 1
|
|
23
|
+
if retry_count < max_retries:
|
|
24
|
+
time.sleep(1)
|
|
25
|
+
else:
|
|
26
|
+
if not getattr(settings, 'OXI_BCC_FALLBACK_ON_OXR', False):
|
|
27
|
+
raise Exception(f"Failed to load rates from BCC: {str(e)}")
|
|
28
|
+
break
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
oxr_source = OXRBankSource()
|
|
32
|
+
rates = oxr_source.sync(cache=True)
|
|
33
|
+
return rates, CurrencySource.OXR
|
|
34
|
+
except Exception as e:
|
|
35
|
+
raise Exception(f"Failed to load rates from both BCC and OXR: {str(e)}")
|
|
36
|
+
|
|
37
|
+
@transaction.atomic
|
|
38
|
+
def update_rates(state: CurrencyStateDetailSchema):
|
|
39
|
+
from oxutils.currency.models import CurrencyState, Currency
|
|
40
|
+
|
|
41
|
+
if CurrencyState.objects.filter(id=state.id).exists():
|
|
42
|
+
logger.info("currency_state_exists", id=state.id)
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
if not len(state.currencies.keys()):
|
|
46
|
+
logger.info("currency_state_no_currencies", id=state.id)
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
_state = CurrencyState.objects.create(
|
|
50
|
+
id=state.id,
|
|
51
|
+
source=state.source,
|
|
52
|
+
created_at=state.created_at,
|
|
53
|
+
updated_at=state.updated_at,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
currencies = []
|
|
57
|
+
|
|
58
|
+
for key, value in state.currencies.items():
|
|
59
|
+
currencies.append(
|
|
60
|
+
Currency(
|
|
61
|
+
code=key,
|
|
62
|
+
rate=value,
|
|
63
|
+
state=_state
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
Currency.objects.bulk_create(currencies)
|
|
68
|
+
|
|
69
|
+
logger.info("currency_state_updated", id=state.id)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from cacheops import cached_as, cached
|
|
2
|
+
from oxutils.oxiliere.models import Tenant, TenantUser
|
|
3
|
+
from django_tenants.utils import get_tenant_model
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
TenantModel = get_tenant_model()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@cached_as(TenantModel, timeout=60*15)
|
|
10
|
+
def get_tenant_by_oxi_id(oxi_id: str):
|
|
11
|
+
return TenantModel.objects.get(oxi_id=oxi_id)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@cached_as(TenantModel, timeout=60*15)
|
|
15
|
+
def get_tenant_by_schema_name(schema_name: str) -> Tenant:
|
|
16
|
+
return TenantModel.objects.get(schema_name=schema_name)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@cached_as(TenantUser, timeout=60*15)
|
|
20
|
+
def get_tenant_user(oxi_org_id: str, oxi_user_id: str):
|
|
21
|
+
return TenantUser.objects.get(
|
|
22
|
+
tenant__oxi_id=oxi_org_id,
|
|
23
|
+
user__oxi_id=oxi_user_id
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
@cached(timeout=60*15)
|
|
27
|
+
def get_system_tenant():
|
|
28
|
+
from oxutils.oxiliere.utils import oxid_to_schema_name
|
|
29
|
+
|
|
30
|
+
system_schema_name = oxid_to_schema_name(
|
|
31
|
+
getattr(settings, 'OXI_SYSTEM_TENANT', 'tenant_oxisystem')
|
|
32
|
+
)
|
|
33
|
+
return get_tenant_model().objects.get(schema_name=system_schema_name)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from ninja_extra import (
|
|
2
|
+
api_controller,
|
|
3
|
+
ControllerBase,
|
|
4
|
+
http_post,
|
|
5
|
+
)
|
|
6
|
+
from .permissions import OxiliereServicePermission
|
|
7
|
+
from .schemas import CreateTenantSchema
|
|
8
|
+
from oxutils.mixins.schemas import ResponseSchema
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@api_controller(
|
|
12
|
+
'/setup',
|
|
13
|
+
tags=['Setup'],
|
|
14
|
+
auth=None,
|
|
15
|
+
permissions=[
|
|
16
|
+
OxiliereServicePermission(),
|
|
17
|
+
]
|
|
18
|
+
)
|
|
19
|
+
class SetupController(ControllerBase):
|
|
20
|
+
|
|
21
|
+
@http_post(
|
|
22
|
+
'/init',
|
|
23
|
+
response=ResponseSchema,
|
|
24
|
+
)
|
|
25
|
+
def init(self, payload: CreateTenantSchema):
|
|
26
|
+
try:
|
|
27
|
+
payload.create_tenant()
|
|
28
|
+
except Exception as e:
|
|
29
|
+
return ResponseSchema(
|
|
30
|
+
code='initialization_failed',
|
|
31
|
+
detail=str(e)
|
|
32
|
+
)
|
|
33
|
+
return ResponseSchema(
|
|
34
|
+
code='success',
|
|
35
|
+
detail='Tenant initialized successfully'
|
|
36
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from django.core.management.base import BaseCommand
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from django.db import transaction
|
|
5
|
+
from django.contrib.auth import get_user_model
|
|
6
|
+
from django_tenants.utils import get_tenant_model
|
|
7
|
+
from oxutils.oxiliere.models import Domain, TenantUser
|
|
8
|
+
from oxutils.oxiliere.utils import oxid_to_schema_name
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Command(BaseCommand):
|
|
13
|
+
help = 'Initialise le tenant système Oxiliere'
|
|
14
|
+
|
|
15
|
+
@transaction.atomic
|
|
16
|
+
def handle(self, *args, **options):
|
|
17
|
+
TenantModel = get_tenant_model()
|
|
18
|
+
UserModel = get_user_model()
|
|
19
|
+
|
|
20
|
+
# Configuration du tenant système depuis settings
|
|
21
|
+
system_slug = getattr(settings, 'OXI_SYSTEM_TENANT', 'tenant_oxisystem')
|
|
22
|
+
schema_name = oxid_to_schema_name(system_slug)
|
|
23
|
+
system_domain = getattr(settings, 'OXI_SYSTEM_DOMAIN', 'system.oxiliere.com')
|
|
24
|
+
owner_email = getattr(settings, 'OXI_SYSTEM_OWNER_EMAIL', 'dev@oxiliere.com')
|
|
25
|
+
owner_oxi_id = uuid.uuid4()
|
|
26
|
+
|
|
27
|
+
self.stdout.write(self.style.WARNING(f'Initialisation du tenant système...'))
|
|
28
|
+
|
|
29
|
+
# Vérifier si le tenant système existe déjà
|
|
30
|
+
if TenantModel.objects.filter(schema_name=schema_name).exists():
|
|
31
|
+
self.stdout.write(self.style.ERROR(f'Le tenant système "{schema_name}" existe déjà!'))
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
# Créer le tenant système
|
|
35
|
+
self.stdout.write(f'Création du tenant système: {schema_name}')
|
|
36
|
+
tenant = TenantModel.objects.create(
|
|
37
|
+
name='Oxiliere System',
|
|
38
|
+
schema_name=schema_name,
|
|
39
|
+
oxi_id=system_slug,
|
|
40
|
+
subscription_plan='system',
|
|
41
|
+
subscription_status='active',
|
|
42
|
+
)
|
|
43
|
+
self.stdout.write(self.style.SUCCESS(f'✓ Tenant système créé: {tenant.name} ({tenant.schema_name})'))
|
|
44
|
+
|
|
45
|
+
# Créer le domaine pour le tenant système
|
|
46
|
+
self.stdout.write(f'Création du domaine: {system_domain}')
|
|
47
|
+
domain = Domain.objects.create(
|
|
48
|
+
domain=system_domain,
|
|
49
|
+
tenant=tenant,
|
|
50
|
+
is_primary=True
|
|
51
|
+
)
|
|
52
|
+
self.stdout.write(self.style.SUCCESS(f'✓ Domaine créé: {domain.domain}'))
|
|
53
|
+
|
|
54
|
+
self.stdout.write(f'Création du superuser: {owner_email}')
|
|
55
|
+
try:
|
|
56
|
+
superuser = UserModel.objects.get(email=owner_email)
|
|
57
|
+
self.stdout.write(self.style.WARNING(f'⚠ Superuser existe déjà: {superuser.email}'))
|
|
58
|
+
except UserModel.DoesNotExist:
|
|
59
|
+
superuser = UserModel.objects.create_superuser(
|
|
60
|
+
email=owner_email,
|
|
61
|
+
oxi_id=owner_oxi_id,
|
|
62
|
+
first_name='System',
|
|
63
|
+
last_name='Admin'
|
|
64
|
+
)
|
|
65
|
+
self.stdout.write(self.style.SUCCESS(f'✓ Superuser créé: {superuser.email}'))
|
|
66
|
+
|
|
67
|
+
# Lier le superuser au tenant système
|
|
68
|
+
self.stdout.write('Liaison du superuser au tenant système')
|
|
69
|
+
tenant_user, created = TenantUser.objects.get_or_create(
|
|
70
|
+
tenant=tenant,
|
|
71
|
+
user=superuser,
|
|
72
|
+
defaults={
|
|
73
|
+
'is_owner': True,
|
|
74
|
+
'is_admin': True,
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
if created:
|
|
78
|
+
self.stdout.write(self.style.SUCCESS(f'✓ Superuser lié au tenant système'))
|
|
79
|
+
else:
|
|
80
|
+
self.stdout.write(self.style.WARNING(f'⚠ Liaison existe déjà'))
|
|
81
|
+
|
|
82
|
+
self.stdout.write(self.style.SUCCESS('\n=== Initialisation terminée avec succès ==='))
|
|
83
|
+
self.stdout.write(f'Tenant: {tenant.name}')
|
|
84
|
+
self.stdout.write(f'Schema: {tenant.schema_name}')
|
|
85
|
+
self.stdout.write(f'Domain: {domain.domain}')
|
|
86
|
+
self.stdout.write(f'Superuser: {owner_email}')
|