I might be missing something, but there seems to be some inconsistencies with using choices for a DurationField. Without specifying a DRF DurationField in the serializer, it serializes out as a float, but does not accept those float values back on save. When you specify a DRF DurationField, it serializes out according to the docs, but does not add any choices in an OPTIONS request.
djangorestframework==3.4.5, python2.7 with Postgres
Create a model with a duration field that has choices:
class MyModel(models.Model):
EMAIL_FREQUENCY_CHOICES = (
(datetime.timedelta(days=1), 'Daily'),
(datetime.timedelta(weeks=1), 'Weekly'),
(datetime.timedelta(weeks=4), 'Monthly'),
)
email_frequency = models.DurationField(blank=True, null=True, choices=EMAIL_FREQUENCY_CHOICES)
and a corresponding model serializer (and a viewset, omitted):
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('__all__')
Then make an OPTIONS request to see the serialized choices:
"...",
"email_frequency": {
"...",
"choices": [
{ "display_name": "Daily", "value": "86400.0" },
{ "display_name": "Weekly", "value": "604800.0" },
{ "display_name": "Monthly", "value": "2419200.0" }
]
},
Try to POST or PUT with one of the float values for the field. I get an error:
"email_frequency": [
"'604800.0' is not a valid choice."
]
If you change the serializer to use a DRF DurationField:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('__all__')
email_frequency = serializers.DurationField(allow_null=True)
It serializes out the value on a GET according to the docs: 1 00:00:00, but then when calling OPTIONS, nothing appears in the choices array for the field.
I'm not sure if this is a bug or if I'm supposed to be using a ChoiceField.
I would assume DRF would pick up on choices for a native DurationField, serialize per the docs as [DD] [HH:[MM:]]ss[.uuuuuu], and provide the choices in the same format on OPTIONS.
Serializers give out floats, choices in OPTIONS are floats, but the serializers won't accept the float values.
Specifying a DRF DurationField in the serializer doesn't pick up on the choices in OPTIONS.
Okay. This issue will come down to ChoiceField not having a type itself, so it ends up being suitable for any primitive types (strings/integers/floats) but not necessarily for an input that ends up being coerced into a more complex type.
To work around this, you'd either need to subclass ChoiceField, overriding possible to_internal_value and/or to_representation in a DurationChoiceField class. Or else live with using DurationField, and not having the choices presented in OPTIONS requests.
We _might_ be able to deal with this by having a child field that can be attached to a ChoiceField, which handles coercing the choice into the underlying value, and serializing an internal value back into a primitive representation.
Any reason there isn't a TypedChoiceField?
No particular reason, no. First time this has been raised. That's a possibility.
Thanks for the options @tomchristie, I appreciate your quick responses to the issues :)
If I can figure out how to make a DRF TypedChoiceField, I'll send you a pull request.
In recent versions of DRF (I'm pretty sure it used to work), a field with choices other than a text field fails validation, even for simple types like decimal.
(if this should be considered a separate issue, please tell me and I'll create a new one).
Internal Server Error: /api/v1/crm/events/11/
Traceback (most recent call last):
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/django/core/handlers/exception.py", line 39, in inner
response = get_response(request)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 249, in _legacy_get_response
response = self._get_response(request)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 187, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 185, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/lib/python3.5/contextlib.py", line 30, in inner
return func(*args, **kwds)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/viewsets.py", line 86, in view
return self.dispatch(request, *args, **kwargs)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/views.py", line 489, in dispatch
response = self.handle_exception(exc)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/views.py", line 449, in handle_exception
self.raise_uncaught_exception(exc)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/views.py", line 486, in dispatch
response = handler(request, *args, **kwargs)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/mixins.py", line 69, in update
serializer.is_valid(raise_exception=True)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/serializers.py", line 237, in is_valid
self._validated_data = self.run_validation(self.initial_data)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/serializers.py", line 432, in run_validation
value = self.to_internal_value(data)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/serializers.py", line 462, in to_internal_value
validated_value = field.run_validation(primitive_value)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/fields.py", line 526, in run_validation
self.run_validators(value)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/fields.py", line 540, in run_validators
validator(value)
File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/django/core/validators.py", line 417, in __call__
digit_tuple, exponent = value.as_tuple()[1:]
AttributeError: 'int' object has no attribute 'as_tuple'
I'm going to de-milestone this for now. I'd be really happy to see a PR making this work with TypedChoiceField (assuming that's the needed solution.)
I am going to tackle this.
Based on this PR https://github.com/django/django/pull/12449 Django is going to allow Dictionaries in choices & introduced Enum, what should be the best move to resolve this issue? any pointer?
In recent versions of DRF (I'm pretty sure it used to work), a field with choices other than a text field fails validation, even for simple types like decimal.
(if this should be considered a separate issue, please tell me and I'll create a new one).Internal Server Error: /api/v1/crm/events/11/ Traceback (most recent call last): File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/django/core/handlers/exception.py", line 39, in inner response = get_response(request) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 249, in _legacy_get_response response = self._get_response(request) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 187, in _get_response response = self.process_exception_by_middleware(e, request) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 185, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/usr/lib/python3.5/contextlib.py", line 30, in inner return func(*args, **kwds) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view return view_func(*args, **kwargs) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/viewsets.py", line 86, in view return self.dispatch(request, *args, **kwargs) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/views.py", line 489, in dispatch response = self.handle_exception(exc) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/views.py", line 449, in handle_exception self.raise_uncaught_exception(exc) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/views.py", line 486, in dispatch response = handler(request, *args, **kwargs) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/mixins.py", line 69, in update serializer.is_valid(raise_exception=True) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/serializers.py", line 237, in is_valid self._validated_data = self.run_validation(self.initial_data) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/serializers.py", line 432, in run_validation value = self.to_internal_value(data) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/serializers.py", line 462, in to_internal_value validated_value = field.run_validation(primitive_value) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/fields.py", line 526, in run_validation self.run_validators(value) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/rest_framework/fields.py", line 540, in run_validators validator(value) File "/home/emma/dev/levit_intanet/venv/lib/python3.5/site-packages/django/core/validators.py", line 417, in __call__ digit_tuple, exponent = value.as_tuple()[1:] AttributeError: 'int' object has no attribute 'as_tuple'
can you share your recent thoughts on this?
already completed item still open
Most helpful comment
In recent versions of DRF (I'm pretty sure it used to work), a field with choices other than a text field fails validation, even for simple types like decimal.
(if this should be considered a separate issue, please tell me and I'll create a new one).