djresttoolkit 0.10.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.10.0 → djresttoolkit-0.11.0}/PKG-INFO +77 -1
  2. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/README.md +76 -0
  3. {djresttoolkit-0.10.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.10.0 → djresttoolkit-0.11.0}/.gitignore +0 -0
  7. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/LICENSE +0 -0
  8. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/demo/staticfiles/admin/img/LICENSE +0 -0
  9. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/__init__.py +0 -0
  10. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/admin.py +0 -0
  11. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/apps.py +0 -0
  12. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/dbseed/__init__.py +0 -0
  13. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/dbseed/models/__init__.py +0 -0
  14. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/dbseed/models/_choice_field.py +0 -0
  15. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/dbseed/models/_gen.py +0 -0
  16. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/dbseed/models/_seed_model.py +0 -0
  17. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/envconfig/__init__.py +0 -0
  18. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/envconfig/_env_settings.py +0 -0
  19. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/mail/__init__.py +0 -0
  20. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/mail/_email_sender.py +0 -0
  21. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/mail/_models.py +0 -0
  22. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/mail/_types.py +0 -0
  23. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/management/__init__.py +0 -0
  24. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/management/commands/__init__.py +0 -0
  25. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/management/commands/dbflush.py +0 -0
  26. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/management/commands/dbseed.py +0 -0
  27. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/middlewares/__init__.py +0 -0
  28. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/middlewares/_response_time_middleware.py +0 -0
  29. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/migrations/__init__.py +0 -0
  30. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/models/__init__.py +0 -0
  31. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/py.typed +0 -0
  32. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/renderers/__init__.py +0 -0
  33. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/renderers/_throttle_info_json_renderer.py +0 -0
  34. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/serializers/__init__.py +0 -0
  35. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/serializers/mixins/__init__.py +0 -0
  36. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/serializers/mixins/_absolute_url_file_mixin.py +0 -0
  37. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/serializers/mixins/_bulk_create_mixin.py +0 -0
  38. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/throttling/__init__.py +0 -0
  39. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/throttling/_throttle_inspector.py +0 -0
  40. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/views/__init__.py +0 -0
  41. {djresttoolkit-0.10.0 → djresttoolkit-0.11.0}/src/djresttoolkit/views/_exceptions/__init__.py +0 -0
  42. {djresttoolkit-0.10.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.10.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
@@ -605,6 +605,82 @@ products = serializer.save()
605
605
  - Automatically updates field error messages based on Django model definitions.
606
606
  - Bulk creation is optimized using `model.objects.bulk_create()` for efficiency.
607
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
+
608
684
  ## 🛠️ Planned Features
609
685
 
610
686
  - Add more utils
@@ -547,6 +547,82 @@ products = serializer.save()
547
547
  - Automatically updates field error messages based on Django model definitions.
548
548
  - Bulk creation is optimized using `model.objects.bulk_create()` for efficiency.
549
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
+
550
626
  ## 🛠️ Planned Features
551
627
 
552
628
  - Add more utils
@@ -4,7 +4,7 @@
4
4
 
5
5
  [project]
6
6
  name = "djresttoolkit"
7
- version = "0.10.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
File without changes