Skip to content

Commit 4095e28

Browse files
committed
add drf specific EnumField serializer
1 parent ce1db2c commit 4095e28

File tree

8 files changed

+308
-85
lines changed

8 files changed

+308
-85
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ jobs:
4848
- name: Remove enum-properties
4949
run: |
5050
poetry run pip uninstall -y enum-properties
51+
- name: Install djangorestframework
52+
run: |
53+
poetry install -E djangorestframework
54+
- name: Run Unit Tests w/ djangorestframework
55+
run: |
56+
poetry run pytest --cov-fail-under=30
5157
- name: Install django-filters
5258
run: |
5359
poetry install -E filters

django_enum/drf.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""Support for django rest framework symmetric serialization"""
2+
3+
4+
try:
5+
from typing import Any, Type, Union
6+
7+
from django.db.models import Choices
8+
from rest_framework.fields import ChoiceField
9+
10+
class EnumField(ChoiceField):
11+
"""
12+
A djangorestframework serializer field for Enumeration types. If
13+
unspecified ModelSerializers will assign django_enum.fields.EnumField
14+
model field types to ChoiceFields. ChoiceFields do not accept
15+
symmetrical values, this field will.
16+
17+
:param enum: The type of the Enumeration of the field
18+
:param strict: If True (default) only values in the Enumeration type
19+
will be acceptable. If False, no errors will be thrown if other
20+
values of the same primitive type are used
21+
:param kwargs: Any other named arguments applicable to a ChoiceField
22+
will be passed up to the base classes.
23+
"""
24+
25+
enum: Type[Choices]
26+
strict: bool = True
27+
28+
def __init__(
29+
self,
30+
enum: Type[Choices],
31+
strict: bool = strict,
32+
**kwargs
33+
):
34+
self.enum = enum
35+
self.strict = strict
36+
self.choices = kwargs.pop('choices', enum.choices)
37+
super().__init__(choices=self.choices, **kwargs)
38+
39+
def to_internal_value(self, data: Any) -> Union[Choices, Any]:
40+
"""
41+
Transform the *incoming* primitive data into an enum instance.
42+
"""
43+
if data == '' and self.allow_blank:
44+
return ''
45+
46+
if not isinstance(data, self.enum):
47+
try:
48+
data = self.enum(data)
49+
except (TypeError, ValueError):
50+
try:
51+
data = type(self.enum.values[0])(data)
52+
data = self.enum(data)
53+
except (TypeError, ValueError):
54+
if self.strict or not isinstance(
55+
data,
56+
type(self.enum.values[0])
57+
):
58+
self.fail('invalid_choice', input=data)
59+
return data
60+
61+
def to_representation(self, value: Any) -> Any:
62+
"""
63+
Transform the *outgoing* enum value into its primitive value.
64+
"""
65+
return getattr(value, 'value', value)
66+
67+
68+
except (ImportError, ModuleNotFoundError):
69+
70+
class _MissingDjangoRestFramework:
71+
"""Throw error if drf support is used without djangorestframework"""
72+
73+
def __init__(self, *args, **kwargs):
74+
raise ImportError(
75+
f'{self.__class__.__name__} requires djangorestframework to '
76+
f'be installed.'
77+
)
78+
79+
EnumField = _MissingDjangoRestFramework # type: ignore

django_enum/tests/djenum/urls.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from django.urls import include, path
22
from django_enum.tests.djenum.models import EnumTester
33
from django_enum.tests.djenum.views import (
4-
DRFView,
54
EnumTesterCreateView,
65
EnumTesterDeleteView,
76
EnumTesterDetailView,
@@ -11,14 +10,10 @@
1110
EnumTesterListView,
1211
EnumTesterUpdateView,
1312
)
14-
from rest_framework import routers
1513

1614
app_name = 'django_enum_tests_djenum'
1715

1816

19-
router = routers.DefaultRouter()
20-
router.register(r'enumtesters', DRFView)
21-
2217
urlpatterns = [
2318
path('enum/<int:pk>', EnumTesterDetailView.as_view(), name='enum-detail'),
2419
path('enum/list/', EnumTesterListView.as_view(), name='enum-list'),
@@ -27,10 +22,22 @@
2722
path('enum/<int:pk>/', EnumTesterUpdateView.as_view(), name='enum-update'),
2823
path('enum/form/<int:pk>/', EnumTesterFormView.as_view(), name='enum-form-update'),
2924
path('enum/<int:pk>/delete/', EnumTesterDeleteView.as_view(), name='enum-delete'),
30-
path('enum/form/<int:pk>/delete/', EnumTesterFormDeleteView.as_view(), name='enum-form-delete'),
31-
path('drf/', include(router.urls))
25+
path('enum/form/<int:pk>/delete/', EnumTesterFormDeleteView.as_view(), name='enum-form-delete')
3226
]
3327

28+
29+
try:
30+
from django_enum.tests.djenum.views import DRFView
31+
from rest_framework import routers
32+
33+
router = routers.DefaultRouter()
34+
router.register(r'enumtesters', DRFView)
35+
urlpatterns.append(path('drf/', include(router.urls)))
36+
37+
except ImportError: # pragma: no cover
38+
pass
39+
40+
3441
try:
3542
from django_enum.tests.djenum.views import EnumTesterFilterViewSet
3643
from django_filters.views import FilterView

django_enum/tests/djenum/views.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from django_enum.tests.djenum import enums as dj_enums
55
from django_enum.tests.djenum.forms import EnumTesterForm
66
from django_enum.tests.djenum.models import EnumTester
7-
from rest_framework import serializers, viewsets
87

98

109
class URLMixin:
@@ -97,15 +96,21 @@ def get_success_url(self): # pragma: no cover
9796
return reverse(f'{self.NAMESPACE}:enum-list')
9897

9998

100-
class EnumTesterSerializer(serializers.ModelSerializer):
101-
class Meta:
102-
model = EnumTester
103-
fields = '__all__'
99+
try:
100+
from rest_framework import serializers, viewsets
101+
102+
class EnumTesterSerializer(serializers.ModelSerializer):
103+
class Meta:
104+
model = EnumTester
105+
fields = '__all__'
104106

105107

106-
class DRFView(viewsets.ModelViewSet):
107-
queryset = EnumTester.objects.all()
108-
serializer_class = EnumTesterSerializer
108+
class DRFView(viewsets.ModelViewSet):
109+
queryset = EnumTester.objects.all()
110+
serializer_class = EnumTesterSerializer
111+
112+
except (ImportError, ModuleNotFoundError): # pragma: no cover
113+
pass
109114

110115

111116
try:

django_enum/tests/enum_prop/urls.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from django.urls import include, path
33
from django_enum.tests.enum_prop.models import EnumTester
44
from django_enum.tests.enum_prop.views import (
5-
DRFView,
65
EnumTesterCreateView,
76
EnumTesterDeleteView,
87
EnumTesterDetailView,
@@ -12,13 +11,9 @@
1211
EnumTesterListView,
1312
EnumTesterUpdateView,
1413
)
15-
from rest_framework import routers
1614

1715
app_name = 'django_enum_tests_enum_prop'
1816

19-
router = routers.DefaultRouter()
20-
router.register(r'enumtesters', DRFView)
21-
2217
urlpatterns = [
2318
path('enum/<int:pk>', EnumTesterDetailView.as_view(), name='enum-detail'),
2419
path('enum/list/', EnumTesterListView.as_view(), name='enum-list'),
@@ -27,10 +22,20 @@
2722
path('enum/<int:pk>/', EnumTesterUpdateView.as_view(), name='enum-update'),
2823
path('enum/form/<int:pk>/', EnumTesterFormView.as_view(), name='enum-form-update'),
2924
path('enum/<int:pk>/delete/', EnumTesterDeleteView.as_view(), name='enum-delete'),
30-
path('enum/form/<int:pk>/delete/', EnumTesterFormDeleteView.as_view(), name='enum-form-delete'),
31-
path('drf/', include(router.urls))
25+
path('enum/form/<int:pk>/delete/', EnumTesterFormDeleteView.as_view(), name='enum-form-delete')
3226
]
3327

28+
try:
29+
from django_enum.tests.enum_prop.views import DRFView
30+
from rest_framework import routers
31+
32+
router = routers.DefaultRouter()
33+
router.register(r'enumtesters', DRFView)
34+
urlpatterns.append(path('drf/', include(router.urls)))
35+
36+
except ImportError: # pragma: no cover
37+
pass
38+
3439
try:
3540
from django_enum.tests.enum_prop.views import EnumTesterFilterViewSet
3641
from django_filters.views import FilterView

django_enum/tests/enum_prop/views.py

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,20 @@
33
from django_enum.filters import FilterSet as EnumFilterSet
44
from django_enum.tests.djenum import views
55
from django_enum.tests.enum_prop import enums as prop_enums
6+
from django_enum.tests.enum_prop.enums import (
7+
BigIntEnum,
8+
BigPosIntEnum,
9+
Constants,
10+
DJIntEnum,
11+
DJTextEnum,
12+
IntEnum,
13+
PosIntEnum,
14+
SmallIntEnum,
15+
SmallPosIntEnum,
16+
TextEnum,
17+
)
618
from django_enum.tests.enum_prop.forms import EnumTesterForm
719
from django_enum.tests.enum_prop.models import EnumTester
8-
from rest_framework import serializers, viewsets
920

1021

1122
class EnumTesterDetailView(views.EnumTesterDetailView):
@@ -66,17 +77,39 @@ class EnumTesterFormDeleteView(views.EnumTesterFormDeleteView):
6677
enums = prop_enums
6778

6879

69-
class EnumTesterSerializer(serializers.ModelSerializer):
70-
class Meta:
71-
model = EnumTester
72-
fields = '__all__'
80+
try:
81+
82+
from django_enum.drf import EnumField
83+
from rest_framework import serializers, viewsets
84+
85+
class EnumTesterSerializer(serializers.ModelSerializer):
7386

87+
small_pos_int = EnumField(SmallPosIntEnum, allow_null=True)
88+
small_int = EnumField(SmallIntEnum)
89+
pos_int = EnumField(PosIntEnum)
90+
int = EnumField(IntEnum, allow_null=True)
91+
big_pos_int = EnumField(BigPosIntEnum, allow_null=True)
92+
big_int = EnumField(BigIntEnum)
93+
constant = EnumField(Constants, allow_null=True)
94+
text = EnumField(TextEnum, allow_null=True)
95+
dj_int_enum = EnumField(DJIntEnum)
96+
dj_text_enum = EnumField(DJTextEnum)
97+
non_strict_int = EnumField(SmallPosIntEnum, strict=False, allow_null=True)
98+
non_strict_text = EnumField(TextEnum, strict=False, allow_blank=True)
99+
no_coerce = EnumField(SmallPosIntEnum, allow_null=True)
74100

75-
class DRFView(viewsets.ModelViewSet):
76-
queryset = EnumTester.objects.all()
77-
serializer_class = EnumTesterSerializer
101+
class Meta:
102+
model = EnumTester
103+
fields = '__all__'
78104

79105

106+
class DRFView(viewsets.ModelViewSet):
107+
queryset = EnumTester.objects.all()
108+
serializer_class = EnumTesterSerializer
109+
110+
except (ImportError, ModuleNotFoundError): # pragma: no cover
111+
pass
112+
80113
try:
81114

82115
from django_enum.tests.djenum.views import EnumTesterFilterViewSet

0 commit comments

Comments
 (0)