Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DurationField with choices #4430

Closed
mheppner opened this issue Aug 21, 2016 · 15 comments
Closed

DurationField with choices #4430

mheppner opened this issue Aug 21, 2016 · 15 comments

Comments

@mheppner
Copy link

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

Steps to reproduce

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.

Expected behavior

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.

Actual behavior

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.

@tomchristie tomchristie added this to the 3.5.0 Release milestone Aug 23, 2016
@tomchristie
Copy link
Member

tomchristie commented Aug 23, 2016

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.

@rpkilby
Copy link
Member

rpkilby commented Aug 23, 2016

Any reason there isn't a TypedChoiceField?

@tomchristie
Copy link
Member

No particular reason, no. First time this has been raised. That's a possibility.

@mheppner
Copy link
Author

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.

@nanuxbe
Copy link
Contributor

nanuxbe commented Jun 27, 2017

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'

@carltongibson carltongibson removed this from the 3.6.5 Release milestone Sep 28, 2017
@carltongibson
Copy link
Collaborator

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.)

@auvipy
Copy link
Member

auvipy commented Nov 6, 2019

I am going to tackle this.

@auvipy
Copy link
Member

auvipy commented Jul 29, 2020

Based on this PR django/django#12449 Django is going to allow Dictionaries in choices & introduced Enum, what should be the best move to resolve this issue? any pointer?

@auvipy
Copy link
Member

auvipy commented Jul 29, 2020

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?

@sarathak
Copy link
Contributor

already completed item still open

@auvipy
Copy link
Member

auvipy commented Oct 26, 2021

already completed item still open

so this can be closed right?

@sarathak
Copy link
Contributor

@auvipy
i think so

@stale
Copy link

stale bot commented Apr 18, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Apr 18, 2022
@sevdog
Copy link
Contributor

sevdog commented May 23, 2022

I am still having this issue with timedelta objects.

from django.db import models
from rest_framework import serializers


class ScheduleIntervals(models.Choices):
    WEEKLY = timedelta(weeks=1)
    BIWEEKLY = timedelta(weeks=2) 
    MONTHLY = timedelta(weeks=4)


class PeriodicModel(models.Model):
    period = models.DurationField(choices=ScheduleIntervals.choices)



class PeriodicModelSerializer(serializer.ModelSerializer):

    class Meta:
        model = PeriodicModel
        fields = serializers.ALL

When providing a string-representation of a timedelta it raises a validation error when providing both P7D or 7 days to serializer

serializer = PeriodicModelSerializer(data={'period': 'P7D'})
serializer.is_valid()
# False
serializer.errors
# {'period': [ErrorDetail(string='"P7D" is not a valid choice.', code='invalid_choice')]}

serializer = PeriodicModelSerializer(data={'period': '7 days'})
serializer.is_valid()
# False
serializer.errors
# {'period': [ErrorDetail(string='"7 days" is not a valid choice.', code='invalid_choice')]}

Tested with djangorestframework==3.13.1.

@stale stale bot removed the stale label May 23, 2022
@stale
Copy link

stale bot commented Jul 31, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Jul 31, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants