Skip to content

Commit 97738e4

Browse files
committed
change drf flagfield to multiplechoicefield, work on docs
1 parent c8e21f1 commit 97738e4

File tree

10 files changed

+172
-17
lines changed

10 files changed

+172
-17
lines changed

doc/source/howto/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ are possible with :class:`~django_enum.fields.EnumField`. See :ref:`enum_props`.
4040

4141
external
4242
options
43-
integrations
4443
flags
4544
forms
45+
integrations
4646
migrations
4747
urls

doc/source/howto/integrations.rst

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,13 @@ By default `DRF ModelSerializer
8484
`ChoiceField <https://www.django-rest-framework.org/api-guide/fields/#choicefield>`_ to represent an
8585
:class:`~django_enum.fields.EnumField`. This works great, but it will not accept :ref:`symmetric
8686
enumeration values <enum-properties:howto_symmetric_properties>`. A serializer field
87-
:class:`~django_enum.drf.EnumField` is provided that will. The dependency on DRF_ is optional so
88-
to use the provided serializer field you must install DRF_:
87+
:class:`django_enum.drf.EnumField` is provided that will. :class:`~django_enum.fields.FlagField`
88+
fields do not work well with DRF's builtin
89+
`MultipleChoiceField <https://www.django-rest-framework.org/api-guide/fields/#multiplechoicefield>`_
90+
so we provide also provide a :class:`django_enum.drf.FlagField`.
91+
92+
The dependency on DRF_ is optional so to use the provided serializer field you must install DRF_:
93+
8994

9095
.. code:: bash
9196
@@ -97,7 +102,7 @@ to use the provided serializer field you must install DRF_:
97102

98103

99104

100-
The serializer :class:`~django_enum.drf.EnumField` accepts any arguments that
105+
The serializer :class:`django_enum.drf.EnumField` accepts any arguments that
101106
`ChoiceField <https://www.django-rest-framework.org/api-guide/fields/#choicefield>`_ does. It also
102107
accepts the ``strict`` parameter which behaves the same way as it does on the model field.
103108

@@ -110,14 +115,28 @@ accepts the ``strict`` parameter which behaves the same way as it does on the mo
110115
2. You have non-strict model fields and want to allow your API to accept values outside of
111116
the enumeration.
112117

118+
The :class:`django_enum.drf.EnumField` must be used for any :class:`~django_enum.fields.FlagField`
119+
fields. It will accept a composite integer or a list of any values coercible to a flag. The
120+
serialized output will be an composite integer holding the full bitfield.
121+
122+
ModelSerializers
123+
~~~~~~~~~~~~~~~~
124+
125+
An :class:`django_enum.drf.EnumFieldMixin` class is provided that when added to
126+
`ModelSerializers <https://www.django-rest-framework.org/api-guide/serializers/#modelserializer>`_
127+
will be sure that the serializer instantiates the correct django-enum serializer field type:
128+
129+
.. literalinclude:: ../../../tests/examples/drf_modelserializer_howto.py
130+
:lines: 3-
131+
113132
.. _filtering:
114133

115134
django-filter
116135
-------------
117136

118137
As shown above, filtering by any value, enumeration type instance or symmetric value works with
119138
:doc:`Django's ORM <django:topics/db/queries>`. This is not natively true for the default
120-
:doc:`FilterSet <django-filter:ref/filterset>` from :doc:`django-filter <django-filter:index>`.
139+
:class:`django_filters.filterset.FilterSet` from :doc:`django-filter <django-filter:index>`.
121140
Those filter sets will only be filterable by direct enumeration value by default. An
122141
:class:`~django_enum.filters.EnumFilter` class is provided to enable filtering by symmetric property
123142
values, but since the dependency on :doc:`django-filter <django-filter:index>` is optional, you must
@@ -128,11 +147,24 @@ first install it:
128147
pip install django-filter
129148
130149
.. literalinclude:: ../../../tests/examples/filterfield_howto.py
131-
:lines: 3-
150+
:lines: 2-
132151

133-
An :class:`~django_enum.filters.FilterSet` class is also provided that uses
152+
A :class:`~django_enum.filters.FilterSet` class is also provided that uses
134153
:class:`~django_enum.filters.EnumFilter` for :class:`~django_enum.fields.EnumField` by default.
135154
So the above is also equivalent to:
136155

137156
.. literalinclude:: ../../../tests/examples/filterset_howto.py
138157
:lines: 3-
158+
159+
.. tip::
160+
161+
:class:`~django_enum.filters.FilterSet` may also be used as a mixin.
162+
163+
164+
FlagFields
165+
~~~~~~~~~~
166+
167+
An :class:`~django_enum.filters.EnumFlagFilter` field for flag fields is also provided:
168+
169+
.. literalinclude:: ../../../tests/examples/flagfilterfield_howto.py
170+
:lines: 2-

doc/source/howto/migrations.rst

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ Write Migrations
99
.. important::
1010

1111
There is one rule for writing custom migration files for EnumFields:
12-
*Never reference or import your enumeration classes in a migration file,
13-
work with the primitive values instead*.
12+
**Never reference or import your enumeration classes in a migration file,
13+
work with the primitive values instead**. If your :class:`~enum.Enum` class
14+
changes over time, this can break older migration files. Always working with
15+
primitive values in migrations files will ensure that the migration will be
16+
valid to the data as it existed when the migration was generated.
1417

1518
The deconstructed :class:`~django_enum.fields.EnumField` only include the choices tuple in the
1619
migration files. This is because :class:`enum.Enum` classes may come and go or be
@@ -21,3 +24,15 @@ with choices.
2124
:class:`~django_enum.fields.EnumField` in migration files will not resolve the field values to
2225
enumeration types. The fields will be the primitive enumeration values as they
2326
are with any field with choices.
27+
28+
29+
Using :class:`enum.auto`
30+
------------------------
31+
32+
If your :class:`~django_enum.fields.EnumField` is storing the value as the database column
33+
(default) it is best to avoid the usage of :class:`enum.auto` because the value for each
34+
enumerated instance may change which would bring your database out of sync with your
35+
codebase.
36+
37+
If you have to use :class:`enum.auto` it is best to add integration tests to check for value
38+
changes.

doc/source/reference/forms.rst

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,52 @@
66
Forms
77
=====
88

9-
.. automodule:: django_enum.forms
9+
Fields
10+
------
11+
12+
.. autoclass:: django_enum.forms.ChoiceFieldMixin
13+
:members:
14+
:show-inheritance:
15+
16+
.. autoclass:: django_enum.forms.EnumChoiceField
17+
:members:
18+
:show-inheritance:
19+
20+
.. autoclass:: django_enum.forms.EnumFlagField
21+
:members:
22+
:show-inheritance:
23+
24+
.. autoclass:: django_enum.forms.EnumMultipleChoiceField
25+
:members:
26+
:show-inheritance:
27+
28+
Widgets
29+
-------
30+
31+
.. autoclass:: django_enum.forms.NonStrictSelect
32+
:members:
33+
:show-inheritance:
34+
35+
.. autoclass:: django_enum.forms.NonStrictSelectMultiple
36+
:members:
37+
:show-inheritance:
38+
39+
.. autoclass:: django_enum.forms.FlagSelectMultiple
40+
:members:
41+
:show-inheritance:
42+
43+
.. autoclass:: django_enum.forms.FlagCheckbox
44+
:members:
45+
:show-inheritance:
46+
47+
.. autoclass:: django_enum.forms.NonStrictFlagSelectMultiple
48+
:members:
49+
:show-inheritance:
50+
51+
.. autoclass:: django_enum.forms.NonStrictFlagCheckbox
52+
:members:
53+
:show-inheritance:
54+
55+
.. autoclass:: django_enum.forms.NonStrictRadioSelect
1056
:members:
11-
:undoc-members:
1257
:show-inheritance:
13-
:private-members:

src/django_enum/drf.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
Field,
2121
FloatField,
2222
IntegerField,
23+
MultipleChoiceField,
2324
TimeField,
2425
)
2526
from rest_framework.serializers import ModelSerializer
@@ -175,7 +176,7 @@ def to_representation(self, value: Any) -> Any:
175176
return getattr(value, "value", value)
176177

177178

178-
class FlagField(ChoiceField):
179+
class FlagField(MultipleChoiceField):
179180
"""
180181
A djangorestframework serializer field for :class:`~enum.Flag` types. If
181182
unspecified ModelSerializers will assign :class:`~django_enum.fields.FlagField`
@@ -264,7 +265,7 @@ def build_standard_field(self, field_name, model_field):
264265
To use this mixin, include it before ModelSerializer in your
265266
serializer's class hierarchy:
266267
267-
..code-block:: python
268+
.. code-block:: python
268269
269270
from django_enum.drf import EnumFieldMixin
270271
from rest_framework.serializers import ModelSerializer
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from .models import TextChoicesExample
2+
3+
from django_enum.drf import EnumFieldMixin
4+
from rest_framework import serializers
5+
6+
7+
class ExampleModelSerializer(EnumFieldMixin, serializers.Serializer):
8+
9+
class Meta:
10+
model = TextChoicesExample
11+
12+
13+
ser = ExampleModelSerializer(
14+
data={
15+
'color': (1, 0, 0),
16+
}
17+
)
18+
assert ser.is_valid()
Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1-
from .models import TextChoicesExample
1+
from .models import TextChoicesExample, Constellation
22

3-
from django_enum.drf import EnumField
3+
from django_enum.drf import EnumField, FlagField
44
from rest_framework import serializers
55

66

77
class ExampleSerializer(serializers.Serializer):
88

99
color = EnumField(TextChoicesExample.Color)
10+
11+
# from the flags tutorial
12+
constellation = FlagField(Constellation)
1013

1114

12-
ser = ExampleSerializer(data={'color': (1, 0, 0)})
15+
ser = ExampleSerializer(
16+
data={
17+
'color': (1, 0, 0),
18+
'constellation': [
19+
Constellation.GALILEO.name,
20+
Constellation.GPS.name
21+
]
22+
}
23+
)
1324
assert ser.is_valid()
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from .models import Constellation, GNSSReceiver
2+
from django_enum.filters import EnumFlagFilter
3+
from django_filters.views import FilterView
4+
from django_filters import FilterSet
5+
6+
7+
class FlagExampleFilterViewSet(FilterView):
8+
9+
class FlagExampleFilter(FilterSet):
10+
11+
constellation = EnumFlagFilter(enum=Constellation)
12+
13+
class Meta:
14+
model = GNSSReceiver
15+
fields = '__all__'
16+
17+
filterset_class = FlagExampleFilter
18+
model = GNSSReceiver
19+
20+
# now filtering by flags works:
21+
# e.g.: /?constellation=GPS&constellation=GLONASS

tests/examples/urls_howto.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .filterset_howto import (
55
TextChoicesExampleFilterViewSet as TextChoicesExampleFilterSetViewSet
66
)
7+
from .flagfilterfield_howto import FlagExampleFilterViewSet
78

89
app_name = "howto"
910

@@ -18,6 +19,7 @@
1819
TextChoicesExampleFilterSetViewSet.as_view(),
1920
name='filterset'
2021
),
22+
path('flagfilterfield/', FlagExampleFilterViewSet.as_View(), name="flagfilterfield")
2123
]
2224
except ImportError:
2325
urlpatterns = []

tests/test_examples.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,10 @@ def test_text_choices_howto(self):
305305
def test_drf_serializer_howto(self):
306306
from tests.examples import drf_serializer_howto
307307

308+
@pytest.mark.skipif(not DJANGO_REST_FRAMEWORK, reason="DRF not installed")
309+
def test_drf_modelserializer_howto(self):
310+
from tests.examples import drf_modelserializer_howto
311+
308312
def _filterset_webcheck(self, name):
309313
from tests.examples.models import TextChoicesExample
310314
from django.urls import reverse
@@ -358,6 +362,13 @@ def test_filterfield_howto(self):
358362

359363
self._filterset_webcheck("howto:filterfield")
360364

365+
@pytest.mark.skipif(not DJANGO_FILTERS, reason="django-filters not installed")
366+
def test_flagfilterfield_howto(self):
367+
from tests.examples import flagfilterfield_howto
368+
369+
# TODO
370+
# self._filterset_webcheck("howto:flagfilterfield")
371+
361372
@pytest.mark.skipif(not DJANGO_FILTERS, reason="django-filters not installed")
362373
def test_filterset_howto(self):
363374
from tests.examples import filterset_howto

0 commit comments

Comments
 (0)