djresttoolkit 0.8.0__py3-none-any.whl → 0.10.0__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.
- README.md +158 -13
- {djresttoolkit-0.8.0.dist-info → djresttoolkit-0.10.0.dist-info}/METADATA +159 -14
- {djresttoolkit-0.8.0.dist-info → djresttoolkit-0.10.0.dist-info}/RECORD +11 -7
- src/djresttoolkit/envconfig/_env_settings.py +3 -3
- src/djresttoolkit/serializers/__init__.py +0 -0
- src/djresttoolkit/serializers/mixins/__init__.py +8 -0
- src/djresttoolkit/serializers/mixins/_absolute_url_file_mixin.py +94 -0
- src/djresttoolkit/serializers/mixins/_bulk_create_mixin.py +87 -0
- {djresttoolkit-0.8.0.dist-info → djresttoolkit-0.10.0.dist-info}/WHEEL +0 -0
- {djresttoolkit-0.8.0.dist-info → djresttoolkit-0.10.0.dist-info}/entry_points.txt +0 -0
- {djresttoolkit-0.8.0.dist-info → djresttoolkit-0.10.0.dist-info}/licenses/LICENSE +0 -0
README.md
CHANGED
@@ -30,7 +30,7 @@ djresttoolkit is a collection of utilities and helpers for Django and Django RES
|
|
30
30
|
|
31
31
|
## 📚 All API Reference
|
32
32
|
|
33
|
-
### 1. DB Seed Utilities
|
33
|
+
### 1. DB Seed Utilities — API Reference
|
34
34
|
|
35
35
|
#### `Generator`
|
36
36
|
|
@@ -89,7 +89,7 @@ Here’s a **concise API reference** for your database flush management command
|
|
89
89
|
|
90
90
|
---
|
91
91
|
|
92
|
-
### 2. DB Flush Command
|
92
|
+
### 2. DB Flush Command — API Reference
|
93
93
|
|
94
94
|
```python
|
95
95
|
from djresttoolkit.management.commands import flush
|
@@ -145,7 +145,7 @@ or
|
|
145
145
|
Flushed 120 records from all models and reset IDs.
|
146
146
|
```
|
147
147
|
|
148
|
-
### 3. EnvBaseSettings
|
148
|
+
### 3. EnvBaseSettings — API Reference
|
149
149
|
|
150
150
|
```python
|
151
151
|
from djresttoolkit.envconfig import EnvBaseSettings
|
@@ -162,11 +162,18 @@ Supports **nested configuration** using double underscores (`__`) in environment
|
|
162
162
|
|
163
163
|
#### Class Attributes
|
164
164
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
165
|
+
- Attributes
|
166
|
+
- `env_file`
|
167
|
+
- Type: `str`
|
168
|
+
- Default: `.env`
|
169
|
+
- Description: Environment variable file path.
|
170
|
+
- `yaml_file`
|
171
|
+
- Type: `str`
|
172
|
+
- Default: `.environ.yaml`
|
173
|
+
- Description: YAML configuration file path.
|
174
|
+
- `model_config`
|
175
|
+
- Type: `SettingsConfigDict`
|
176
|
+
- Description: Pydantic settings configuration (file encoding, nested delimiter).
|
170
177
|
|
171
178
|
#### Methods
|
172
179
|
|
@@ -193,7 +200,7 @@ Loads configuration from **YAML first**, then overrides with **environment varia
|
|
193
200
|
```python
|
194
201
|
from djresttoolkit.envconfig import EnvBaseSettings
|
195
202
|
|
196
|
-
class EnvSettings(EnvBaseSettings):
|
203
|
+
class EnvSettings(EnvBaseSettings["EnvSettings"]):
|
197
204
|
debug: bool = False
|
198
205
|
database_url: str
|
199
206
|
|
@@ -210,7 +217,7 @@ print(settings.database_url)
|
|
210
217
|
- Supports nested keys: `DATABASE__HOST` → `settings.database.host`.
|
211
218
|
- Designed to be subclassed for project-specific settings.
|
212
219
|
|
213
|
-
### 4. EmailSender
|
220
|
+
### 4. EmailSender — API Reference
|
214
221
|
|
215
222
|
```python
|
216
223
|
from djresttoolkit.mail import EmailSender, EmailContent, EmailTemplate
|
@@ -259,7 +266,7 @@ EmailSender(content).send(to=["user@example.com"])
|
|
259
266
|
|
260
267
|
- `text`, `html` — template file paths
|
261
268
|
|
262
|
-
### 5. Custom DRF Exception Handler
|
269
|
+
### 5. Custom DRF Exception Handler — API Reference
|
263
270
|
|
264
271
|
```python
|
265
272
|
from djresttoolkit.views import exception_handler
|
@@ -299,7 +306,7 @@ REST_FRAMEWORK = {
|
|
299
306
|
- Tracks requests in cache and calculates `retry_after`.
|
300
307
|
- Cleans expired timestamps automatically.
|
301
308
|
|
302
|
-
### 6. Response Time Middleware
|
309
|
+
### 6. Response Time Middleware — API Reference
|
303
310
|
|
304
311
|
```python
|
305
312
|
from djresttoolkit.middlewares import ResponseTimeMiddleware
|
@@ -346,7 +353,7 @@ X-Response-Time: 0.01234 seconds
|
|
346
353
|
INFO: Request processed in 0.01234 seconds
|
347
354
|
```
|
348
355
|
|
349
|
-
### 7. Throttle Utilities
|
356
|
+
### 7. Throttle Utilities — API Reference
|
350
357
|
|
351
358
|
#### `ThrottleInfoJSONRenderer`
|
352
359
|
|
@@ -402,6 +409,144 @@ ThrottleInspector(
|
|
402
409
|
- `attach_headers(response: Response, throttle_info: dict | None)`
|
403
410
|
Attaches throttle data to HTTP headers.
|
404
411
|
|
412
|
+
### 8. AbsoluteUrlFileMixin — API Reference
|
413
|
+
|
414
|
+
```python
|
415
|
+
from djresttoolkit.serializers.mixins import AbsoluteUrlFileMixin
|
416
|
+
```
|
417
|
+
|
418
|
+
### `AbsoluteUrlFileMixin`
|
419
|
+
|
420
|
+
A **serializer mixin** that converts **FileField** and **ImageField** URLs to **absolute URLs**, ensuring compatibility with cloud storage backends.
|
421
|
+
|
422
|
+
---
|
423
|
+
|
424
|
+
### Attributes
|
425
|
+
|
426
|
+
- `file_fields`
|
427
|
+
- type: `list[str] | None`
|
428
|
+
- default: `None`
|
429
|
+
- description: Manual list of file field names for non-model serializers.
|
430
|
+
|
431
|
+
### Absolute Url File Mixin Methods
|
432
|
+
|
433
|
+
#### `to_representation(self, instance: Any) -> dict[str, Any]`
|
434
|
+
|
435
|
+
- Overrides default serializer `to_representation`.
|
436
|
+
- Enhances all file-related fields in the serialized output to **absolute URLs**.
|
437
|
+
|
438
|
+
#### `enhance_file_fields(self, instance: Any, representation: dict[str, Any], request: Any) -> dict[str, Any]`
|
439
|
+
|
440
|
+
- Core logic to process each file field.
|
441
|
+
- Converts relative URLs to absolute URLs using `request.build_absolute_uri()`.
|
442
|
+
- Supports model serializers or manual `file_fields`.
|
443
|
+
- Logs warnings if request context is missing or file is not found.
|
444
|
+
|
445
|
+
#### Exceptions
|
446
|
+
|
447
|
+
- `MissingRequestContext`: Raised if the request object is missing in serializer context and `DEBUG=True`.
|
448
|
+
|
449
|
+
### Absolute Url File Mixin Example
|
450
|
+
|
451
|
+
```python
|
452
|
+
from rest_framework import serializers
|
453
|
+
from djresttoolkit.serializers.mixins import AbsoluteUrlFileMixin
|
454
|
+
from myapp.models import Document
|
455
|
+
|
456
|
+
class DocumentSerializer(AbsoluteUrlFileMixin, serializers.ModelSerializer):
|
457
|
+
class Meta:
|
458
|
+
model = Document
|
459
|
+
fields = ["id", "title", "file"]
|
460
|
+
|
461
|
+
# Output will convert `file` field to an absolute URL
|
462
|
+
serializer = DocumentSerializer(instance, context={"request": request})
|
463
|
+
data = serializer.data
|
464
|
+
```
|
465
|
+
|
466
|
+
#### Notes
|
467
|
+
|
468
|
+
- Works with both Django model serializers and custom serializers.
|
469
|
+
- Relative file paths are automatically converted to absolute URLs.
|
470
|
+
- Can manually specify fields via `file_fields` for non-model serializers.
|
471
|
+
|
472
|
+
### 9. BulkCreateMixin — API Reference
|
473
|
+
|
474
|
+
```python
|
475
|
+
from djresttoolkit.serializers.mixins import BulkCreateMixin
|
476
|
+
```
|
477
|
+
|
478
|
+
#### `BulkCreateMixin`
|
479
|
+
|
480
|
+
A **DRF serializer mixin** that adds support for:
|
481
|
+
|
482
|
+
- **Single instance creation** with extra context fields
|
483
|
+
- **Bulk creation** from a list of validated data dictionaries
|
484
|
+
- **Updating serializer field error messages** with model-specific messages
|
485
|
+
|
486
|
+
#### Bulk Create Mixin Notes
|
487
|
+
|
488
|
+
- `bulk_create()` does **not trigger model signals** or call `.save()` on instances.
|
489
|
+
- `Meta.model` **must** be defined in the serializer.
|
490
|
+
|
491
|
+
#### Bulk Create Mixin Methods
|
492
|
+
|
493
|
+
#### `create(self, validated_data: dict[str, Any] | list[dict[str, Any]]) -> Model | list[Model]`
|
494
|
+
|
495
|
+
- Creates single or multiple model instances.
|
496
|
+
- **Parameters:**
|
497
|
+
- `validated_data`: dict for single instance or list of dicts for bulk creation.
|
498
|
+
|
499
|
+
- **Returns:**
|
500
|
+
- Single model instance or list of instances.
|
501
|
+
|
502
|
+
- **Raises:**
|
503
|
+
- `AttributeError` if `Meta.model` is not defined.
|
504
|
+
- `NotImplementedError` if used with a serializer that does not implement `create()`.
|
505
|
+
|
506
|
+
#### `get_fields(self) -> dict[str, SerializerField]`
|
507
|
+
|
508
|
+
- Extends DRF serializer `get_fields()` to update **error messages** using model field definitions.
|
509
|
+
- **Returns:**
|
510
|
+
- Dictionary of serializer fields.
|
511
|
+
|
512
|
+
- **Warning:**
|
513
|
+
- Logs a warning if a serializer field is not present on the model.
|
514
|
+
|
515
|
+
### Bulk Create Mixin Example
|
516
|
+
|
517
|
+
```python
|
518
|
+
from rest_framework import serializers
|
519
|
+
from djresttoolkit.serializers.mixins import BulkCreateMixin
|
520
|
+
from myapp.models import Product
|
521
|
+
|
522
|
+
class ProductSerializer(BulkCreateMixin, serializers.ModelSerializer):
|
523
|
+
class Meta:
|
524
|
+
model = Product
|
525
|
+
fields = ["id", "name", "price"]
|
526
|
+
|
527
|
+
# Single creation
|
528
|
+
serializer = ProductSerializer(data={"name": "Item1", "price": 10})
|
529
|
+
serializer.is_valid(raise_exception=True)
|
530
|
+
product = serializer.save()
|
531
|
+
|
532
|
+
# Bulk creation
|
533
|
+
serializer = ProductSerializer(
|
534
|
+
data=[
|
535
|
+
{"name": "Item2", "price": 20},
|
536
|
+
{"name": "Item3", "price": 30},
|
537
|
+
],
|
538
|
+
many=True
|
539
|
+
)
|
540
|
+
serializer.is_valid(raise_exception=True)
|
541
|
+
products = serializer.save()
|
542
|
+
```
|
543
|
+
|
544
|
+
#### Bulk Create Mixin Features
|
545
|
+
|
546
|
+
- Works seamlessly with DRF `ModelSerializer`.
|
547
|
+
- Automatically updates field error messages based on Django model definitions.
|
548
|
+
- Bulk creation is optimized using `model.objects.bulk_create()` for efficiency.
|
549
|
+
|
405
550
|
## 🛠️ Planned Features
|
406
551
|
|
407
552
|
- Add more utils
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: djresttoolkit
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.10.0
|
4
4
|
Summary: A collection of Django and DRF utilities to simplify API development.
|
5
5
|
Project-URL: Homepage, https://github.com/shaileshpandit141/djresttoolkit
|
6
6
|
Project-URL: Documentation, https://shaileshpandit141.github.io/djresttoolkit
|
@@ -88,7 +88,7 @@ djresttoolkit is a collection of utilities and helpers for Django and Django RES
|
|
88
88
|
|
89
89
|
## 📚 All API Reference
|
90
90
|
|
91
|
-
### 1. DB Seed Utilities
|
91
|
+
### 1. DB Seed Utilities — API Reference
|
92
92
|
|
93
93
|
#### `Generator`
|
94
94
|
|
@@ -147,7 +147,7 @@ Here’s a **concise API reference** for your database flush management command
|
|
147
147
|
|
148
148
|
---
|
149
149
|
|
150
|
-
### 2. DB Flush Command
|
150
|
+
### 2. DB Flush Command — API Reference
|
151
151
|
|
152
152
|
```python
|
153
153
|
from djresttoolkit.management.commands import flush
|
@@ -203,7 +203,7 @@ or
|
|
203
203
|
Flushed 120 records from all models and reset IDs.
|
204
204
|
```
|
205
205
|
|
206
|
-
### 3. EnvBaseSettings
|
206
|
+
### 3. EnvBaseSettings — API Reference
|
207
207
|
|
208
208
|
```python
|
209
209
|
from djresttoolkit.envconfig import EnvBaseSettings
|
@@ -220,11 +220,18 @@ Supports **nested configuration** using double underscores (`__`) in environment
|
|
220
220
|
|
221
221
|
#### Class Attributes
|
222
222
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
223
|
+
- Attributes
|
224
|
+
- `env_file`
|
225
|
+
- Type: `str`
|
226
|
+
- Default: `.env`
|
227
|
+
- Description: Environment variable file path.
|
228
|
+
- `yaml_file`
|
229
|
+
- Type: `str`
|
230
|
+
- Default: `.environ.yaml`
|
231
|
+
- Description: YAML configuration file path.
|
232
|
+
- `model_config`
|
233
|
+
- Type: `SettingsConfigDict`
|
234
|
+
- Description: Pydantic settings configuration (file encoding, nested delimiter).
|
228
235
|
|
229
236
|
#### Methods
|
230
237
|
|
@@ -251,7 +258,7 @@ Loads configuration from **YAML first**, then overrides with **environment varia
|
|
251
258
|
```python
|
252
259
|
from djresttoolkit.envconfig import EnvBaseSettings
|
253
260
|
|
254
|
-
class EnvSettings(EnvBaseSettings):
|
261
|
+
class EnvSettings(EnvBaseSettings["EnvSettings"]):
|
255
262
|
debug: bool = False
|
256
263
|
database_url: str
|
257
264
|
|
@@ -268,7 +275,7 @@ print(settings.database_url)
|
|
268
275
|
- Supports nested keys: `DATABASE__HOST` → `settings.database.host`.
|
269
276
|
- Designed to be subclassed for project-specific settings.
|
270
277
|
|
271
|
-
### 4. EmailSender
|
278
|
+
### 4. EmailSender — API Reference
|
272
279
|
|
273
280
|
```python
|
274
281
|
from djresttoolkit.mail import EmailSender, EmailContent, EmailTemplate
|
@@ -317,7 +324,7 @@ EmailSender(content).send(to=["user@example.com"])
|
|
317
324
|
|
318
325
|
- `text`, `html` — template file paths
|
319
326
|
|
320
|
-
### 5. Custom DRF Exception Handler
|
327
|
+
### 5. Custom DRF Exception Handler — API Reference
|
321
328
|
|
322
329
|
```python
|
323
330
|
from djresttoolkit.views import exception_handler
|
@@ -357,7 +364,7 @@ REST_FRAMEWORK = {
|
|
357
364
|
- Tracks requests in cache and calculates `retry_after`.
|
358
365
|
- Cleans expired timestamps automatically.
|
359
366
|
|
360
|
-
### 6. Response Time Middleware
|
367
|
+
### 6. Response Time Middleware — API Reference
|
361
368
|
|
362
369
|
```python
|
363
370
|
from djresttoolkit.middlewares import ResponseTimeMiddleware
|
@@ -404,7 +411,7 @@ X-Response-Time: 0.01234 seconds
|
|
404
411
|
INFO: Request processed in 0.01234 seconds
|
405
412
|
```
|
406
413
|
|
407
|
-
### 7. Throttle Utilities
|
414
|
+
### 7. Throttle Utilities — API Reference
|
408
415
|
|
409
416
|
#### `ThrottleInfoJSONRenderer`
|
410
417
|
|
@@ -460,6 +467,144 @@ ThrottleInspector(
|
|
460
467
|
- `attach_headers(response: Response, throttle_info: dict | None)`
|
461
468
|
Attaches throttle data to HTTP headers.
|
462
469
|
|
470
|
+
### 8. AbsoluteUrlFileMixin — API Reference
|
471
|
+
|
472
|
+
```python
|
473
|
+
from djresttoolkit.serializers.mixins import AbsoluteUrlFileMixin
|
474
|
+
```
|
475
|
+
|
476
|
+
### `AbsoluteUrlFileMixin`
|
477
|
+
|
478
|
+
A **serializer mixin** that converts **FileField** and **ImageField** URLs to **absolute URLs**, ensuring compatibility with cloud storage backends.
|
479
|
+
|
480
|
+
---
|
481
|
+
|
482
|
+
### Attributes
|
483
|
+
|
484
|
+
- `file_fields`
|
485
|
+
- type: `list[str] | None`
|
486
|
+
- default: `None`
|
487
|
+
- description: Manual list of file field names for non-model serializers.
|
488
|
+
|
489
|
+
### Absolute Url File Mixin Methods
|
490
|
+
|
491
|
+
#### `to_representation(self, instance: Any) -> dict[str, Any]`
|
492
|
+
|
493
|
+
- Overrides default serializer `to_representation`.
|
494
|
+
- Enhances all file-related fields in the serialized output to **absolute URLs**.
|
495
|
+
|
496
|
+
#### `enhance_file_fields(self, instance: Any, representation: dict[str, Any], request: Any) -> dict[str, Any]`
|
497
|
+
|
498
|
+
- Core logic to process each file field.
|
499
|
+
- Converts relative URLs to absolute URLs using `request.build_absolute_uri()`.
|
500
|
+
- Supports model serializers or manual `file_fields`.
|
501
|
+
- Logs warnings if request context is missing or file is not found.
|
502
|
+
|
503
|
+
#### Exceptions
|
504
|
+
|
505
|
+
- `MissingRequestContext`: Raised if the request object is missing in serializer context and `DEBUG=True`.
|
506
|
+
|
507
|
+
### Absolute Url File Mixin Example
|
508
|
+
|
509
|
+
```python
|
510
|
+
from rest_framework import serializers
|
511
|
+
from djresttoolkit.serializers.mixins import AbsoluteUrlFileMixin
|
512
|
+
from myapp.models import Document
|
513
|
+
|
514
|
+
class DocumentSerializer(AbsoluteUrlFileMixin, serializers.ModelSerializer):
|
515
|
+
class Meta:
|
516
|
+
model = Document
|
517
|
+
fields = ["id", "title", "file"]
|
518
|
+
|
519
|
+
# Output will convert `file` field to an absolute URL
|
520
|
+
serializer = DocumentSerializer(instance, context={"request": request})
|
521
|
+
data = serializer.data
|
522
|
+
```
|
523
|
+
|
524
|
+
#### Notes
|
525
|
+
|
526
|
+
- Works with both Django model serializers and custom serializers.
|
527
|
+
- Relative file paths are automatically converted to absolute URLs.
|
528
|
+
- Can manually specify fields via `file_fields` for non-model serializers.
|
529
|
+
|
530
|
+
### 9. BulkCreateMixin — API Reference
|
531
|
+
|
532
|
+
```python
|
533
|
+
from djresttoolkit.serializers.mixins import BulkCreateMixin
|
534
|
+
```
|
535
|
+
|
536
|
+
#### `BulkCreateMixin`
|
537
|
+
|
538
|
+
A **DRF serializer mixin** that adds support for:
|
539
|
+
|
540
|
+
- **Single instance creation** with extra context fields
|
541
|
+
- **Bulk creation** from a list of validated data dictionaries
|
542
|
+
- **Updating serializer field error messages** with model-specific messages
|
543
|
+
|
544
|
+
#### Bulk Create Mixin Notes
|
545
|
+
|
546
|
+
- `bulk_create()` does **not trigger model signals** or call `.save()` on instances.
|
547
|
+
- `Meta.model` **must** be defined in the serializer.
|
548
|
+
|
549
|
+
#### Bulk Create Mixin Methods
|
550
|
+
|
551
|
+
#### `create(self, validated_data: dict[str, Any] | list[dict[str, Any]]) -> Model | list[Model]`
|
552
|
+
|
553
|
+
- Creates single or multiple model instances.
|
554
|
+
- **Parameters:**
|
555
|
+
- `validated_data`: dict for single instance or list of dicts for bulk creation.
|
556
|
+
|
557
|
+
- **Returns:**
|
558
|
+
- Single model instance or list of instances.
|
559
|
+
|
560
|
+
- **Raises:**
|
561
|
+
- `AttributeError` if `Meta.model` is not defined.
|
562
|
+
- `NotImplementedError` if used with a serializer that does not implement `create()`.
|
563
|
+
|
564
|
+
#### `get_fields(self) -> dict[str, SerializerField]`
|
565
|
+
|
566
|
+
- Extends DRF serializer `get_fields()` to update **error messages** using model field definitions.
|
567
|
+
- **Returns:**
|
568
|
+
- Dictionary of serializer fields.
|
569
|
+
|
570
|
+
- **Warning:**
|
571
|
+
- Logs a warning if a serializer field is not present on the model.
|
572
|
+
|
573
|
+
### Bulk Create Mixin Example
|
574
|
+
|
575
|
+
```python
|
576
|
+
from rest_framework import serializers
|
577
|
+
from djresttoolkit.serializers.mixins import BulkCreateMixin
|
578
|
+
from myapp.models import Product
|
579
|
+
|
580
|
+
class ProductSerializer(BulkCreateMixin, serializers.ModelSerializer):
|
581
|
+
class Meta:
|
582
|
+
model = Product
|
583
|
+
fields = ["id", "name", "price"]
|
584
|
+
|
585
|
+
# Single creation
|
586
|
+
serializer = ProductSerializer(data={"name": "Item1", "price": 10})
|
587
|
+
serializer.is_valid(raise_exception=True)
|
588
|
+
product = serializer.save()
|
589
|
+
|
590
|
+
# Bulk creation
|
591
|
+
serializer = ProductSerializer(
|
592
|
+
data=[
|
593
|
+
{"name": "Item2", "price": 20},
|
594
|
+
{"name": "Item3", "price": 30},
|
595
|
+
],
|
596
|
+
many=True
|
597
|
+
)
|
598
|
+
serializer.is_valid(raise_exception=True)
|
599
|
+
products = serializer.save()
|
600
|
+
```
|
601
|
+
|
602
|
+
#### Bulk Create Mixin Features
|
603
|
+
|
604
|
+
- Works seamlessly with DRF `ModelSerializer`.
|
605
|
+
- Automatically updates field error messages based on Django model definitions.
|
606
|
+
- Bulk creation is optimized using `model.objects.bulk_create()` for efficiency.
|
607
|
+
|
463
608
|
## 🛠️ Planned Features
|
464
609
|
|
465
610
|
- Add more utils
|
@@ -1,5 +1,5 @@
|
|
1
1
|
LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
|
2
|
-
README.md,sha256=
|
2
|
+
README.md,sha256=OzvflId2K79hvwsZr4VIf393Fvm1PhTExUHAAnBvpzo,14651
|
3
3
|
demo/staticfiles/admin/img/LICENSE,sha256=0RT6_zSIwWwxmzI13EH5AjnT1j2YU3MwM9j3U19cAAQ,1081
|
4
4
|
src/djresttoolkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
src/djresttoolkit/admin.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -11,7 +11,7 @@ src/djresttoolkit/dbseed/models/_choice_field.py,sha256=T7LAzbyXqlYp2mtCAKL8E1Da
|
|
11
11
|
src/djresttoolkit/dbseed/models/_gen.py,sha256=qBPQaLvh1rcEam0YmE4JBJqpa-Vv5IFlIIagkEMHDVw,206
|
12
12
|
src/djresttoolkit/dbseed/models/_seed_model.py,sha256=0cmbi0VNKjmJbwhjeCFsvb3iKYjok6TJOk6Y2MF_3N4,2443
|
13
13
|
src/djresttoolkit/envconfig/__init__.py,sha256=PcLaPaVfQmz3-4m6SwoOQF2W4U7F0agBGJ4Qjqbcyfw,74
|
14
|
-
src/djresttoolkit/envconfig/_env_settings.py,sha256=
|
14
|
+
src/djresttoolkit/envconfig/_env_settings.py,sha256=X-pgHqNtUNQLBHiGpbYB4ViCuY14n6aff5HZqYL4Tlc,3236
|
15
15
|
src/djresttoolkit/mail/__init__.py,sha256=tB9SdMlhfWQ640q4aobZ0H1c7fTWalpDL2I-onkr2VI,268
|
16
16
|
src/djresttoolkit/mail/_email_sender.py,sha256=bPMqgD5HibJcOZgO6xxHOhdK9HEhnGNC6BoMPpo-h7k,3096
|
17
17
|
src/djresttoolkit/mail/_models.py,sha256=of5KsLGvsN2OWgDYgdtLEijulg817TXgsLKuUdsnDQc,1447
|
@@ -26,13 +26,17 @@ src/djresttoolkit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
26
26
|
src/djresttoolkit/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
27
|
src/djresttoolkit/renderers/__init__.py,sha256=kmFMPRiMfD8CuJTN1_-6Z_Hqil3x8GBM0IN1roZESm0,107
|
28
28
|
src/djresttoolkit/renderers/_throttle_info_json_renderer.py,sha256=aP2cN4cB_Imcpy732zsPBQrMQqcKEs5R3dld5Y_4AMU,1089
|
29
|
+
src/djresttoolkit/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
|
+
src/djresttoolkit/serializers/mixins/__init__.py,sha256=dRT0kXDckOkZo1RQHrT1gXbGFMIv5M8TBHGF2uF-81Q,225
|
31
|
+
src/djresttoolkit/serializers/mixins/_absolute_url_file_mixin.py,sha256=5ewael0_RsJZ9b36IfXacxjb-Vx1eQ9Dk6dWuj5D_dc,3261
|
32
|
+
src/djresttoolkit/serializers/mixins/_bulk_create_mixin.py,sha256=9ZWm2MNaZOhmhKlWOu6VECtlDbUtaPeceGHmivDYwYQ,3248
|
29
33
|
src/djresttoolkit/throttling/__init__.py,sha256=01sjMymjx8XjqnAw3bEBLc-JtfhCDrp5dGxSNXMvPpU,84
|
30
34
|
src/djresttoolkit/throttling/_throttle_inspector.py,sha256=Kss6ZxKy-EXq9UGaGprGDhpSuJ5992bmEYZSWmUVBHo,6480
|
31
35
|
src/djresttoolkit/views/__init__.py,sha256=XrxBrs6sH4HmUzp41omcmy_y94pSaXAVn01ttQ022-4,76
|
32
36
|
src/djresttoolkit/views/_exceptions/__init__.py,sha256=DrCUxuPNyBR4WhzNutn5HDxLa--q51ykIxSG7_bFsOI,83
|
33
37
|
src/djresttoolkit/views/_exceptions/_exception_handler.py,sha256=_o7If47bzWLl57LeSXSWsIDsJGo2RIpwYAwNQ-hsHVY,2839
|
34
|
-
djresttoolkit-0.
|
35
|
-
djresttoolkit-0.
|
36
|
-
djresttoolkit-0.
|
37
|
-
djresttoolkit-0.
|
38
|
-
djresttoolkit-0.
|
38
|
+
djresttoolkit-0.10.0.dist-info/METADATA,sha256=A91Vw-rZ7cni31PQzgw568wk5tGRLROJkBe9mLXYa14,17661
|
39
|
+
djresttoolkit-0.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
40
|
+
djresttoolkit-0.10.0.dist-info/entry_points.txt,sha256=YMhfTF-7mYppO8QqqWnvR_hyMWvoYxD6XI94_ViFu3k,60
|
41
|
+
djresttoolkit-0.10.0.dist-info/licenses/LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
|
42
|
+
djresttoolkit-0.10.0.dist-info/RECORD,,
|
@@ -6,7 +6,7 @@ import yaml
|
|
6
6
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
7
7
|
|
8
8
|
|
9
|
-
class EnvBaseSettings(BaseSettings):
|
9
|
+
class EnvBaseSettings[T: "EnvBaseSettings"](BaseSettings):
|
10
10
|
""" "
|
11
11
|
EnvBaseSettings is a base settings class for managing application configuration
|
12
12
|
using both YAML files and environment variables.
|
@@ -60,12 +60,12 @@ class EnvBaseSettings(BaseSettings):
|
|
60
60
|
|
61
61
|
@classmethod
|
62
62
|
def load(
|
63
|
-
cls,
|
63
|
+
cls: type[T],
|
64
64
|
*,
|
65
65
|
env_file: str | None = None,
|
66
66
|
ymal_file: str | None = None,
|
67
67
|
warning: bool = True,
|
68
|
-
) ->
|
68
|
+
) -> T:
|
69
69
|
"""Load from YAML first, then override with .env."""
|
70
70
|
if env_file:
|
71
71
|
cls.env_file = env_file
|
File without changes
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Any, cast
|
3
|
+
from urllib.parse import urlparse
|
4
|
+
|
5
|
+
from django.conf import settings
|
6
|
+
from django.db import models
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
|
11
|
+
class MissingRequestContext(Exception):
|
12
|
+
"""Custom exception for missing request in serializer context."""
|
13
|
+
|
14
|
+
...
|
15
|
+
|
16
|
+
|
17
|
+
class AbsoluteUrlFileMixin:
|
18
|
+
"""
|
19
|
+
A mixin that updates FileField and ImageField URLs in serializer output
|
20
|
+
to be absolute URLs, compatible with cloud storage backends.
|
21
|
+
"""
|
22
|
+
|
23
|
+
# manually specify file fields for non-model serializers
|
24
|
+
file_fields: list[str] | None = None
|
25
|
+
|
26
|
+
def to_representation(self, instance: Any) -> dict[str, Any]:
|
27
|
+
"""Extend serializer representation to enhance file field URLs."""
|
28
|
+
representation = cast(dict[str, Any], super().to_representation(instance)) # type: ignore[misc]
|
29
|
+
request = self.context.get("request") # type: ignore
|
30
|
+
return self.enhance_file_fields(instance, representation, request)
|
31
|
+
|
32
|
+
def enhance_file_fields(
|
33
|
+
self,
|
34
|
+
instance: Any,
|
35
|
+
representation: dict[str, Any],
|
36
|
+
request: Any,
|
37
|
+
) -> dict[str, Any]:
|
38
|
+
if request is None:
|
39
|
+
logger.warning("Request not found in serializer context.")
|
40
|
+
if settings.DEBUG:
|
41
|
+
raise MissingRequestContext("Request not found in serializer context.")
|
42
|
+
return representation
|
43
|
+
|
44
|
+
# Collect only file-related model fields if available
|
45
|
+
model_fields = (
|
46
|
+
{
|
47
|
+
field.name: field
|
48
|
+
for field in instance._meta.get_fields()
|
49
|
+
if isinstance(field, (models.FileField, models.ImageField))
|
50
|
+
}
|
51
|
+
if hasattr(instance, "_meta")
|
52
|
+
else {}
|
53
|
+
)
|
54
|
+
|
55
|
+
manual_fields = getattr(self, "file_fields", []) or []
|
56
|
+
|
57
|
+
for field_name, field_value in representation.items():
|
58
|
+
model_field = model_fields.get(field_name)
|
59
|
+
|
60
|
+
is_file_field = model_field or (field_name in manual_fields)
|
61
|
+
if not is_file_field:
|
62
|
+
continue
|
63
|
+
|
64
|
+
try:
|
65
|
+
# Get file URL from instance or raw serializer value
|
66
|
+
if model_field:
|
67
|
+
file_instance = getattr(instance, field_name, None)
|
68
|
+
file_url = (
|
69
|
+
getattr(file_instance, "url", None) if file_instance else None
|
70
|
+
)
|
71
|
+
else:
|
72
|
+
file_url = field_value
|
73
|
+
|
74
|
+
if not file_url:
|
75
|
+
logger.info("No file found for field: %s", field_name)
|
76
|
+
representation[field_name] = None
|
77
|
+
continue
|
78
|
+
|
79
|
+
# Only build absolute URL if it's relative
|
80
|
+
parsed_url = urlparse(str(file_url))
|
81
|
+
if not parsed_url.netloc: # relative path
|
82
|
+
file_url = request.build_absolute_uri(file_url)
|
83
|
+
|
84
|
+
representation[field_name] = file_url
|
85
|
+
logger.debug("Enhanced URL for %s: %s", field_name, file_url)
|
86
|
+
|
87
|
+
except Exception as error:
|
88
|
+
logger.error(
|
89
|
+
"Unexpected error processing file field %s: %s",
|
90
|
+
field_name,
|
91
|
+
error,
|
92
|
+
)
|
93
|
+
|
94
|
+
return representation
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Any, cast
|
3
|
+
|
4
|
+
from django.core.exceptions import FieldDoesNotExist
|
5
|
+
from django.db.models import Model
|
6
|
+
from rest_framework.serializers import Field as SerializerField
|
7
|
+
from django.db.models import Field as ModelField
|
8
|
+
|
9
|
+
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
|
13
|
+
class BulkCreateMixin:
|
14
|
+
"""
|
15
|
+
A mixin for DRF serializers that supports:
|
16
|
+
- Single instance creation with extra context fields
|
17
|
+
- Bulk creation from a list of validated_data dicts
|
18
|
+
- Updating field error messages with model-specific messages
|
19
|
+
|
20
|
+
Notes:
|
21
|
+
- bulk_create() does not trigger model signals or .save()
|
22
|
+
- Meta.model must be defined
|
23
|
+
"""
|
24
|
+
|
25
|
+
def create(
|
26
|
+
self, validated_data: dict[str, Any] | list[dict[str, Any]]
|
27
|
+
) -> Model | list[Model]:
|
28
|
+
logger.debug("Starting creation", extra={"validated_data": validated_data})
|
29
|
+
|
30
|
+
model: type[Model] | None = getattr(getattr(self, "Meta", None), "model", None)
|
31
|
+
if model is None:
|
32
|
+
logger.error("Meta.model not defined.")
|
33
|
+
raise AttributeError(f"{self.__class__.__name__} missing Meta.model.")
|
34
|
+
|
35
|
+
# Bulk creation
|
36
|
+
if isinstance(validated_data, list):
|
37
|
+
instances = [model(**item) for item in validated_data]
|
38
|
+
if instances:
|
39
|
+
logger.info(
|
40
|
+
"Bulk creating instances",
|
41
|
+
extra={"count": len(instances), "model": model.__name__},
|
42
|
+
)
|
43
|
+
return model.objects.bulk_create(instances)
|
44
|
+
logger.info("No instances to create.")
|
45
|
+
return []
|
46
|
+
|
47
|
+
# Single instance creation
|
48
|
+
if not hasattr(super(), "create"):
|
49
|
+
raise NotImplementedError(
|
50
|
+
f"{self.__class__.__name__} must be used with a DRF serializer "
|
51
|
+
"that implements create()."
|
52
|
+
)
|
53
|
+
|
54
|
+
logger.info("Creating a single instance", extra={"model": model.__name__})
|
55
|
+
return super().create({**validated_data}) # type: ignore[misc]
|
56
|
+
|
57
|
+
def get_fields(self) -> dict[str, SerializerField[Any, Any, Any, Any]]:
|
58
|
+
# DRF serializer fields
|
59
|
+
fields = cast(
|
60
|
+
dict[str, SerializerField[Any, Any, Any, Any]],
|
61
|
+
super().get_fields(), # type: ignore
|
62
|
+
)
|
63
|
+
|
64
|
+
meta = getattr(self, "Meta", None)
|
65
|
+
model: type[Model] | None = getattr(meta, "model", None)
|
66
|
+
|
67
|
+
if model is None:
|
68
|
+
raise ValueError(f"{self.__class__.__name__}.Meta.model must be defined.")
|
69
|
+
|
70
|
+
logger.debug("Setting up serializer fields", extra={"model": model.__name__})
|
71
|
+
|
72
|
+
for field_name, serializer_field in fields.items():
|
73
|
+
try:
|
74
|
+
# Django model field
|
75
|
+
model_field = cast(
|
76
|
+
ModelField[Any, Any],
|
77
|
+
model._meta.get_field(field_name), # type: ignore
|
78
|
+
)
|
79
|
+
if hasattr(model_field, "error_messages"):
|
80
|
+
serializer_field.error_messages.update(model_field.error_messages)
|
81
|
+
except FieldDoesNotExist:
|
82
|
+
logger.warning(
|
83
|
+
"Skipping serializer field not present on model",
|
84
|
+
extra={"field_name": field_name, "model": model.__name__},
|
85
|
+
)
|
86
|
+
|
87
|
+
return fields
|
File without changes
|
File without changes
|
File without changes
|