djresttoolkit 0.9.0__tar.gz → 0.11.0__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.
Files changed (42) hide show
  1. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/PKG-INFO +163 -9
  2. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/README.md +162 -8
  3. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/pyproject.toml +1 -1
  4. djresttoolkit-0.11.0/src/djresttoolkit/models/mixins/__init__.py +11 -0
  5. djresttoolkit-0.11.0/src/djresttoolkit/models/mixins/_model_choice_fields_mixin.py +84 -0
  6. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/serializers/mixins/__init__.py +2 -0
  7. djresttoolkit-0.9.0/src/djresttoolkit/serializers/_serializer_create_mixin.py → djresttoolkit-0.11.0/src/djresttoolkit/serializers/mixins/_bulk_create_mixin.py +1 -1
  8. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/.gitignore +0 -0
  9. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/LICENSE +0 -0
  10. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/demo/staticfiles/admin/img/LICENSE +0 -0
  11. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/__init__.py +0 -0
  12. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/admin.py +0 -0
  13. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/apps.py +0 -0
  14. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/dbseed/__init__.py +0 -0
  15. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/dbseed/models/__init__.py +0 -0
  16. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/dbseed/models/_choice_field.py +0 -0
  17. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/dbseed/models/_gen.py +0 -0
  18. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/dbseed/models/_seed_model.py +0 -0
  19. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/envconfig/__init__.py +0 -0
  20. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/envconfig/_env_settings.py +0 -0
  21. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/mail/__init__.py +0 -0
  22. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/mail/_email_sender.py +0 -0
  23. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/mail/_models.py +0 -0
  24. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/mail/_types.py +0 -0
  25. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/management/__init__.py +0 -0
  26. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/management/commands/__init__.py +0 -0
  27. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/management/commands/dbflush.py +0 -0
  28. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/management/commands/dbseed.py +0 -0
  29. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/middlewares/__init__.py +0 -0
  30. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/middlewares/_response_time_middleware.py +0 -0
  31. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/migrations/__init__.py +0 -0
  32. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/models/__init__.py +0 -0
  33. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/py.typed +0 -0
  34. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/renderers/__init__.py +0 -0
  35. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/renderers/_throttle_info_json_renderer.py +0 -0
  36. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/serializers/__init__.py +0 -0
  37. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/serializers/mixins/_absolute_url_file_mixin.py +0 -0
  38. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/throttling/__init__.py +0 -0
  39. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/throttling/_throttle_inspector.py +0 -0
  40. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/views/__init__.py +0 -0
  41. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/views/_exceptions/__init__.py +0 -0
  42. {djresttoolkit-0.9.0 → djresttoolkit-0.11.0}/src/djresttoolkit/views/_exceptions/_exception_handler.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djresttoolkit
3
- Version: 0.9.0
3
+ Version: 0.11.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
@@ -258,7 +258,7 @@ Loads configuration from **YAML first**, then overrides with **environment varia
258
258
  ```python
259
259
  from djresttoolkit.envconfig import EnvBaseSettings
260
260
 
261
- class EnvSettings(EnvBaseSettings):
261
+ class EnvSettings(EnvBaseSettings["EnvSettings"]):
262
262
  debug: bool = False
263
263
  database_url: str
264
264
 
@@ -275,7 +275,7 @@ print(settings.database_url)
275
275
  - Supports nested keys: `DATABASE__HOST` → `settings.database.host`.
276
276
  - Designed to be subclassed for project-specific settings.
277
277
 
278
- ### 4. EmailSender
278
+ ### 4. EmailSender — API Reference
279
279
 
280
280
  ```python
281
281
  from djresttoolkit.mail import EmailSender, EmailContent, EmailTemplate
@@ -324,7 +324,7 @@ EmailSender(content).send(to=["user@example.com"])
324
324
 
325
325
  - `text`, `html` — template file paths
326
326
 
327
- ### 5. Custom DRF Exception Handler
327
+ ### 5. Custom DRF Exception Handler — API Reference
328
328
 
329
329
  ```python
330
330
  from djresttoolkit.views import exception_handler
@@ -364,7 +364,7 @@ REST_FRAMEWORK = {
364
364
  - Tracks requests in cache and calculates `retry_after`.
365
365
  - Cleans expired timestamps automatically.
366
366
 
367
- ### 6. Response Time Middleware
367
+ ### 6. Response Time Middleware — API Reference
368
368
 
369
369
  ```python
370
370
  from djresttoolkit.middlewares import ResponseTimeMiddleware
@@ -411,7 +411,7 @@ X-Response-Time: 0.01234 seconds
411
411
  INFO: Request processed in 0.01234 seconds
412
412
  ```
413
413
 
414
- ### 7. Throttle Utilities
414
+ ### 7. Throttle Utilities — API Reference
415
415
 
416
416
  #### `ThrottleInfoJSONRenderer`
417
417
 
@@ -527,6 +527,160 @@ data = serializer.data
527
527
  - Relative file paths are automatically converted to absolute URLs.
528
528
  - Can manually specify fields via `file_fields` for non-model serializers.
529
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
+
608
+ ### 10. ModelChoiceFieldMixin — API Reference
609
+
610
+ ```python
611
+ from djresttoolkit.models.mixins import ModelChoiceFieldMixin
612
+ ```
613
+
614
+ ### `ModelChoiceFieldMixin`
615
+
616
+ A **Django model mixin** to retrieve **choice fields** from a model, designed to work seamlessly with Django's `TextChoices`.
617
+
618
+ #### Class Attributes in Model Choice Field Mixin
619
+
620
+ - `model: type[Model] | None` — The Django model class to inspect. **Must be set.**
621
+ - `choice_fields: list[str] | None` — List of model field names that contain choices. **Must be set.**
622
+
623
+ #### Model Choice Field Mixin Methods
624
+
625
+ `get_choices() -> dict[str, dict[str, str]]`
626
+
627
+ Retrieve the choice fields from the model as a dictionary.
628
+
629
+ - **Returns:**
630
+
631
+ ```python
632
+ {
633
+ "field_name": {
634
+ "choice_value": "Choice Label",
635
+ ...
636
+ },
637
+ ...
638
+ }
639
+ ```
640
+
641
+ - **Raises:**
642
+
643
+ - `AttributeDoesNotExist` — If `model` or `choice_fields` is not set.
644
+ - `ChoiceFieldNotFound` — If a field does not exist, has no choices, or has invalid choice format.
645
+
646
+ ---
647
+
648
+ ### Model Choice Field Mixin Example
649
+
650
+ ```python
651
+ from django.db import models
652
+ from djresttoolkit.serializers.mixins import ModelChoiceFieldMixin
653
+
654
+ class Product(models.Model):
655
+ class Status(models.TextChoices):
656
+ DRAFT = "draft", "Draft"
657
+ PUBLISHED = "published", "Published"
658
+
659
+ status = models.CharField(max_length=20, choices=Status.choices)
660
+ category = models.CharField(max_length=50, choices=[
661
+ ("a", "Category A"),
662
+ ("b", "Category B"),
663
+ ])
664
+
665
+ class ProductChoiceMixin(ModelChoiceFieldMixin):
666
+ model = Product
667
+ choice_fields = ["status", "category"]
668
+
669
+ choices = ProductChoiceMixin.get_choices()
670
+ print(choices)
671
+ # Output:
672
+ # {
673
+ # "status": {"draft": "Draft", "published": "Published"},
674
+ # "category": {"a": "Category A", "b": "Category B"}
675
+ # }
676
+ ```
677
+
678
+ #### Features of Model Choice Field Mixin
679
+
680
+ - Safely validates that fields exist and have valid choices.
681
+ - Returns a ready-to-use dictionary mapping values to labels.
682
+ - Ideal for DRF serializers, forms, and admin customization.
683
+
530
684
  ## 🛠️ Planned Features
531
685
 
532
686
  - Add more utils
@@ -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
@@ -200,7 +200,7 @@ Loads configuration from **YAML first**, then overrides with **environment varia
200
200
  ```python
201
201
  from djresttoolkit.envconfig import EnvBaseSettings
202
202
 
203
- class EnvSettings(EnvBaseSettings):
203
+ class EnvSettings(EnvBaseSettings["EnvSettings"]):
204
204
  debug: bool = False
205
205
  database_url: str
206
206
 
@@ -217,7 +217,7 @@ print(settings.database_url)
217
217
  - Supports nested keys: `DATABASE__HOST` → `settings.database.host`.
218
218
  - Designed to be subclassed for project-specific settings.
219
219
 
220
- ### 4. EmailSender
220
+ ### 4. EmailSender — API Reference
221
221
 
222
222
  ```python
223
223
  from djresttoolkit.mail import EmailSender, EmailContent, EmailTemplate
@@ -266,7 +266,7 @@ EmailSender(content).send(to=["user@example.com"])
266
266
 
267
267
  - `text`, `html` — template file paths
268
268
 
269
- ### 5. Custom DRF Exception Handler
269
+ ### 5. Custom DRF Exception Handler — API Reference
270
270
 
271
271
  ```python
272
272
  from djresttoolkit.views import exception_handler
@@ -306,7 +306,7 @@ REST_FRAMEWORK = {
306
306
  - Tracks requests in cache and calculates `retry_after`.
307
307
  - Cleans expired timestamps automatically.
308
308
 
309
- ### 6. Response Time Middleware
309
+ ### 6. Response Time Middleware — API Reference
310
310
 
311
311
  ```python
312
312
  from djresttoolkit.middlewares import ResponseTimeMiddleware
@@ -353,7 +353,7 @@ X-Response-Time: 0.01234 seconds
353
353
  INFO: Request processed in 0.01234 seconds
354
354
  ```
355
355
 
356
- ### 7. Throttle Utilities
356
+ ### 7. Throttle Utilities — API Reference
357
357
 
358
358
  #### `ThrottleInfoJSONRenderer`
359
359
 
@@ -469,6 +469,160 @@ data = serializer.data
469
469
  - Relative file paths are automatically converted to absolute URLs.
470
470
  - Can manually specify fields via `file_fields` for non-model serializers.
471
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
+
550
+ ### 10. ModelChoiceFieldMixin — API Reference
551
+
552
+ ```python
553
+ from djresttoolkit.models.mixins import ModelChoiceFieldMixin
554
+ ```
555
+
556
+ ### `ModelChoiceFieldMixin`
557
+
558
+ A **Django model mixin** to retrieve **choice fields** from a model, designed to work seamlessly with Django's `TextChoices`.
559
+
560
+ #### Class Attributes in Model Choice Field Mixin
561
+
562
+ - `model: type[Model] | None` — The Django model class to inspect. **Must be set.**
563
+ - `choice_fields: list[str] | None` — List of model field names that contain choices. **Must be set.**
564
+
565
+ #### Model Choice Field Mixin Methods
566
+
567
+ `get_choices() -> dict[str, dict[str, str]]`
568
+
569
+ Retrieve the choice fields from the model as a dictionary.
570
+
571
+ - **Returns:**
572
+
573
+ ```python
574
+ {
575
+ "field_name": {
576
+ "choice_value": "Choice Label",
577
+ ...
578
+ },
579
+ ...
580
+ }
581
+ ```
582
+
583
+ - **Raises:**
584
+
585
+ - `AttributeDoesNotExist` — If `model` or `choice_fields` is not set.
586
+ - `ChoiceFieldNotFound` — If a field does not exist, has no choices, or has invalid choice format.
587
+
588
+ ---
589
+
590
+ ### Model Choice Field Mixin Example
591
+
592
+ ```python
593
+ from django.db import models
594
+ from djresttoolkit.serializers.mixins import ModelChoiceFieldMixin
595
+
596
+ class Product(models.Model):
597
+ class Status(models.TextChoices):
598
+ DRAFT = "draft", "Draft"
599
+ PUBLISHED = "published", "Published"
600
+
601
+ status = models.CharField(max_length=20, choices=Status.choices)
602
+ category = models.CharField(max_length=50, choices=[
603
+ ("a", "Category A"),
604
+ ("b", "Category B"),
605
+ ])
606
+
607
+ class ProductChoiceMixin(ModelChoiceFieldMixin):
608
+ model = Product
609
+ choice_fields = ["status", "category"]
610
+
611
+ choices = ProductChoiceMixin.get_choices()
612
+ print(choices)
613
+ # Output:
614
+ # {
615
+ # "status": {"draft": "Draft", "published": "Published"},
616
+ # "category": {"a": "Category A", "b": "Category B"}
617
+ # }
618
+ ```
619
+
620
+ #### Features of Model Choice Field Mixin
621
+
622
+ - Safely validates that fields exist and have valid choices.
623
+ - Returns a ready-to-use dictionary mapping values to labels.
624
+ - Ideal for DRF serializers, forms, and admin customization.
625
+
472
626
  ## 🛠️ Planned Features
473
627
 
474
628
  - Add more utils
@@ -4,7 +4,7 @@
4
4
 
5
5
  [project]
6
6
  name = "djresttoolkit"
7
- version = "0.9.0"
7
+ version = "0.11.0"
8
8
  description = "A collection of Django and DRF utilities to simplify API development."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { file = "LICENSE" }
@@ -0,0 +1,11 @@
1
+ from ._model_choice_fields_mixin import (
2
+ AttributeDoesNotExist,
3
+ ChoiceFieldNotFound,
4
+ ModelChoiceFieldMixin,
5
+ )
6
+
7
+ __all__ = [
8
+ "AttributeDoesNotExist",
9
+ "ChoiceFieldNotFound",
10
+ "ModelChoiceFieldMixin",
11
+ ]
@@ -0,0 +1,84 @@
1
+ from typing import Iterable, Tuple, cast
2
+
3
+ from django.db.models import Model
4
+ from django.core.exceptions import FieldDoesNotExist
5
+
6
+
7
+ class AttributeDoesNotExist(Exception):
8
+ """
9
+ Exception raised when a required attribute is missing in the class.
10
+ """
11
+
12
+
13
+ class ChoiceFieldNotFound(Exception):
14
+ """
15
+ Exception raised when a specified choice field is missing
16
+ or has invalid/empty choices in the model.
17
+ """
18
+
19
+
20
+ class ModelChoiceFieldMixin:
21
+ """
22
+ Mixin to retrieve choice fields from a Django model.
23
+ Designed to work seamlessly with Django's TextChoices.
24
+ """
25
+
26
+ model: type[Model] | None = None
27
+ choice_fields: list[str] | None = None
28
+
29
+ @classmethod
30
+ def get_choices(cls) -> dict[str, dict[str, str]]:
31
+ """
32
+ Retrieve the choice fields from the model class.
33
+
34
+ Returns:
35
+ dict[str, dict[str, str]]: A dictionary where keys are field names
36
+ and values are dictionaries of choices (value => label).
37
+
38
+ Raises:
39
+ ModelAttributeNotFound: If the model attribute is not set.
40
+ ChoiceFieldAttributeNotFound: If the choice_fields attribute is not set.
41
+ ChoiceFieldNotFound: If a field does not exist, has no choices,
42
+ or has an invalid choice format.
43
+ """
44
+
45
+ if cls.model is None:
46
+ raise AttributeDoesNotExist("Model attribute is not set in the class.")
47
+
48
+ if cls.choice_fields is None:
49
+ raise AttributeDoesNotExist(
50
+ "The choice_fields attribute must be set in the class."
51
+ )
52
+
53
+ choices_as_dict: dict[str, dict[str, str]] = {}
54
+
55
+ for field in cls.choice_fields:
56
+ try:
57
+ field_obj = cls.model._meta.get_field(field) # type: ignore[attr-defined]
58
+ except FieldDoesNotExist as e:
59
+ raise ChoiceFieldNotFound(
60
+ f"The field '{field}' does not exist in model '{cls.model.__name__}'."
61
+ ) from e
62
+
63
+ raw_choices = cast(
64
+ Iterable[Tuple[str, str]],
65
+ field_obj.choices or [], # type: ignore[union-attr]
66
+ )
67
+
68
+ if not raw_choices:
69
+ raise ChoiceFieldNotFound(
70
+ f"The field '{field}' in model '{cls.model.__name__}' has no choices defined."
71
+ )
72
+
73
+ if not all(
74
+ isinstance(choice, (list, tuple)) and len(choice) == 2 # type: ignore[misc]
75
+ for choice in raw_choices
76
+ ):
77
+ raise ChoiceFieldNotFound(
78
+ f"The field '{field}' in model '{cls.model.__name__}' has invalid choice format. "
79
+ "Expected an iterable of 2-tuples (value, label)."
80
+ )
81
+
82
+ choices_as_dict[field] = dict(raw_choices)
83
+
84
+ return choices_as_dict
@@ -1,6 +1,8 @@
1
1
  from ._absolute_url_file_mixin import AbsoluteUrlFileMixin, MissingRequestContext
2
+ from ._bulk_create_mixin import BulkCreateMixin
2
3
 
3
4
  __all__ = [
4
5
  "AbsoluteUrlFileMixin",
5
6
  "MissingRequestContext",
7
+ "BulkCreateMixin",
6
8
  ]
@@ -10,7 +10,7 @@ from django.db.models import Field as ModelField
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
12
 
13
- class RecordsCreationMixin:
13
+ class BulkCreateMixin:
14
14
  """
15
15
  A mixin for DRF serializers that supports:
16
16
  - Single instance creation with extra context fields
File without changes
File without changes