djresttoolkit 0.10.0__py3-none-any.whl → 0.12.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 CHANGED
@@ -547,6 +547,154 @@ 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
+
626
+ Here’s a concise **docs entry** for your `ChoiceFieldsAPIView` suitable for `djresttoolkit` documentation:
627
+
628
+ ---
629
+
630
+ ### 11. ChoiceFieldsAPIView — API Reference
631
+
632
+ ```python
633
+ from djresttoolkit.views import ChoiceFieldsAPIView
634
+ ```
635
+
636
+ #### `ChoiceFieldsAPIView`
637
+
638
+ A **generic DRF API view** to return all choices for specified model fields.
639
+
640
+ #### Class Attributes of Choice Fields APIView
641
+
642
+ - `model_class: type[Model] | None` — The Django model to inspect. **Must be set.**
643
+ - `choice_fields: list[str] | None` — List of fields on the model with choices. **Must be set.**
644
+
645
+ ---
646
+
647
+ #### Choice Fields APIView Methods
648
+
649
+ - `get(request: Request) -> Response`
650
+
651
+ Fetches the choices for the configured model fields.
652
+
653
+ - **Returns:**
654
+ - `200 OK` — JSON object containing all choices:
655
+
656
+ ```json
657
+ {
658
+ "choices": {
659
+ "status": {"draft": "Draft", "published": "Published"},
660
+ "category": {"a": "Category A", "b": "Category B"}
661
+ }
662
+ }
663
+ ```
664
+
665
+ - `400 Bad Request` — If any error occurs while retrieving choices.
666
+
667
+ - **Raises:**
668
+ - `AttributeDoesNotExist` — If `model_class` or `choice_fields` is not set.
669
+
670
+ ---
671
+
672
+ ### Example of Choice Fields APIView
673
+
674
+ ```python
675
+ from django.urls import path
676
+ from djresttoolkit.views import ChoiceFieldsAPIView
677
+ from myapp.models import Product
678
+
679
+ class ProductChoiceAPI(ChoiceFieldsAPIView):
680
+ model_class = Product
681
+ choice_fields = ["status", "category"]
682
+
683
+ urlpatterns = [
684
+ path(
685
+ "api/v1/product-choices/",
686
+ ProductChoiceAPI.as_view(),
687
+ name="product-choices"
688
+ ),
689
+ ]
690
+ ```
691
+
692
+ #### Choice Fields APIView Features
693
+
694
+ - Dynamically returns all choices for selected fields in a model.
695
+ - Useful for frontend forms or API consumers that need selectable options.
696
+ - Integrates seamlessly with `ModelChoiceFieldMixin` from `djresttoolkit`.
697
+
550
698
  ## 🛠️ Planned Features
551
699
 
552
700
  - Add more utils
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djresttoolkit
3
- Version: 0.10.0
3
+ Version: 0.12.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,154 @@ 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
+
684
+ Here’s a concise **docs entry** for your `ChoiceFieldsAPIView` suitable for `djresttoolkit` documentation:
685
+
686
+ ---
687
+
688
+ ### 11. ChoiceFieldsAPIView — API Reference
689
+
690
+ ```python
691
+ from djresttoolkit.views import ChoiceFieldsAPIView
692
+ ```
693
+
694
+ #### `ChoiceFieldsAPIView`
695
+
696
+ A **generic DRF API view** to return all choices for specified model fields.
697
+
698
+ #### Class Attributes of Choice Fields APIView
699
+
700
+ - `model_class: type[Model] | None` — The Django model to inspect. **Must be set.**
701
+ - `choice_fields: list[str] | None` — List of fields on the model with choices. **Must be set.**
702
+
703
+ ---
704
+
705
+ #### Choice Fields APIView Methods
706
+
707
+ - `get(request: Request) -> Response`
708
+
709
+ Fetches the choices for the configured model fields.
710
+
711
+ - **Returns:**
712
+ - `200 OK` — JSON object containing all choices:
713
+
714
+ ```json
715
+ {
716
+ "choices": {
717
+ "status": {"draft": "Draft", "published": "Published"},
718
+ "category": {"a": "Category A", "b": "Category B"}
719
+ }
720
+ }
721
+ ```
722
+
723
+ - `400 Bad Request` — If any error occurs while retrieving choices.
724
+
725
+ - **Raises:**
726
+ - `AttributeDoesNotExist` — If `model_class` or `choice_fields` is not set.
727
+
728
+ ---
729
+
730
+ ### Example of Choice Fields APIView
731
+
732
+ ```python
733
+ from django.urls import path
734
+ from djresttoolkit.views import ChoiceFieldsAPIView
735
+ from myapp.models import Product
736
+
737
+ class ProductChoiceAPI(ChoiceFieldsAPIView):
738
+ model_class = Product
739
+ choice_fields = ["status", "category"]
740
+
741
+ urlpatterns = [
742
+ path(
743
+ "api/v1/product-choices/",
744
+ ProductChoiceAPI.as_view(),
745
+ name="product-choices"
746
+ ),
747
+ ]
748
+ ```
749
+
750
+ #### Choice Fields APIView Features
751
+
752
+ - Dynamically returns all choices for selected fields in a model.
753
+ - Useful for frontend forms or API consumers that need selectable options.
754
+ - Integrates seamlessly with `ModelChoiceFieldMixin` from `djresttoolkit`.
755
+
608
756
  ## 🛠️ Planned Features
609
757
 
610
758
  - Add more utils
@@ -1,5 +1,5 @@
1
1
  LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
2
- README.md,sha256=OzvflId2K79hvwsZr4VIf393Fvm1PhTExUHAAnBvpzo,14651
2
+ README.md,sha256=swcg_Yd0tKwp4PDXQn7gfj7HKpwLs-UPV4jvqOHvHvQ,18485
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
@@ -24,6 +24,8 @@ src/djresttoolkit/middlewares/__init__.py,sha256=GZHU3Yy4xXoEi62tHn0UJNxN6XgGM2_
24
24
  src/djresttoolkit/middlewares/_response_time_middleware.py,sha256=1wCwdkW5Ng6HJo8zx0F7ylms84OGP-1K0kbyG6Vacuk,908
25
25
  src/djresttoolkit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  src/djresttoolkit/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ src/djresttoolkit/models/mixins/__init__.py,sha256=MHwv36f3nHwI0bXeejuO7MTYuV93ln2tSyCCipS2xVw,223
28
+ src/djresttoolkit/models/mixins/_model_choice_fields_mixin.py,sha256=9FZbe3PwrtIUZYGQh1gcOix5bfeyvKEOaNmkemvZX8E,2843
27
29
  src/djresttoolkit/renderers/__init__.py,sha256=kmFMPRiMfD8CuJTN1_-6Z_Hqil3x8GBM0IN1roZESm0,107
28
30
  src/djresttoolkit/renderers/_throttle_info_json_renderer.py,sha256=aP2cN4cB_Imcpy732zsPBQrMQqcKEs5R3dld5Y_4AMU,1089
29
31
  src/djresttoolkit/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -32,11 +34,13 @@ src/djresttoolkit/serializers/mixins/_absolute_url_file_mixin.py,sha256=5ewael0_
32
34
  src/djresttoolkit/serializers/mixins/_bulk_create_mixin.py,sha256=9ZWm2MNaZOhmhKlWOu6VECtlDbUtaPeceGHmivDYwYQ,3248
33
35
  src/djresttoolkit/throttling/__init__.py,sha256=01sjMymjx8XjqnAw3bEBLc-JtfhCDrp5dGxSNXMvPpU,84
34
36
  src/djresttoolkit/throttling/_throttle_inspector.py,sha256=Kss6ZxKy-EXq9UGaGprGDhpSuJ5992bmEYZSWmUVBHo,6480
35
- src/djresttoolkit/views/__init__.py,sha256=XrxBrs6sH4HmUzp41omcmy_y94pSaXAVn01ttQ022-4,76
37
+ src/djresttoolkit/views/__init__.py,sha256=QuJ9C0Vfzkfrwyqzunxh-A-aErmS6yOoy0uzjDu3oG8,177
38
+ src/djresttoolkit/views/_api_views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
+ src/djresttoolkit/views/_api_views/_choice_fields_apiview.py,sha256=zABPgqxMVaWd814B_sC64bWL61fDJkyYQZmJXQCa6Xc,1395
36
40
  src/djresttoolkit/views/_exceptions/__init__.py,sha256=DrCUxuPNyBR4WhzNutn5HDxLa--q51ykIxSG7_bFsOI,83
37
41
  src/djresttoolkit/views/_exceptions/_exception_handler.py,sha256=_o7If47bzWLl57LeSXSWsIDsJGo2RIpwYAwNQ-hsHVY,2839
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,,
42
+ djresttoolkit-0.12.0.dist-info/METADATA,sha256=QP01vFRs3pOUXn7AuZ4kCVC37ZVo06HgiFm48wdaWVE,21495
43
+ djresttoolkit-0.12.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
44
+ djresttoolkit-0.12.0.dist-info/entry_points.txt,sha256=YMhfTF-7mYppO8QqqWnvR_hyMWvoYxD6XI94_ViFu3k,60
45
+ djresttoolkit-0.12.0.dist-info/licenses/LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
46
+ djresttoolkit-0.12.0.dist-info/RECORD,,
@@ -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,3 +1,7 @@
1
1
  from ._exceptions import exception_handler
2
+ from ._api_views._choice_fields_apiview import ChoiceFieldsAPIView
2
3
 
3
- __all__ = ["exception_handler"]
4
+ __all__ = [
5
+ "exception_handler",
6
+ "ChoiceFieldsAPIView",
7
+ ]
File without changes
@@ -0,0 +1,42 @@
1
+ # views.py
2
+ from rest_framework.views import APIView
3
+ from rest_framework.response import Response
4
+ from rest_framework.request import Request
5
+ from rest_framework import status
6
+ from djresttoolkit.models.mixins import ModelChoiceFieldMixin, AttributeDoesNotExist
7
+ from django.db.models import Model
8
+
9
+
10
+ class ChoiceFieldsAPIView(APIView):
11
+ """
12
+ Generic API view to return choice fields from a model.
13
+ """
14
+
15
+ model_class: type[Model] | None = None
16
+ choice_fields: list[str] | None = None
17
+
18
+ def get(self, request: Request) -> Response:
19
+ """
20
+ Return a JSON response with all choices for the specified fields.
21
+ """
22
+ if not self.model_class or not self.choice_fields:
23
+ raise AttributeDoesNotExist(
24
+ "model_class and choice_fields must be set.",
25
+ )
26
+
27
+ # Dynamically create a mixin instance
28
+ class DynamicChoiceMixin(ModelChoiceFieldMixin):
29
+ model = self.model_class
30
+ choice_fields = self.choice_fields
31
+
32
+ try:
33
+ choices = DynamicChoiceMixin.get_choices()
34
+ return Response(
35
+ {"choices": choices},
36
+ status=status.HTTP_200_OK,
37
+ )
38
+ except Exception:
39
+ return Response(
40
+ {"detail": "An error occurred while retrieving choices."},
41
+ status=status.HTTP_400_BAD_REQUEST,
42
+ )