django-timezone-field: TypeError: is not JSON serializable with django rest framework

Hello,

I’m using the timezone field along with django rest framework. With the 1.2 version everything worked out of the box. But with the 1.3 version i got this error when my model is rendered:

TypeError: <DstTzInfo ‘Europe/Zurich’ BMT+0:30:00 STD> is not JSON serializable

I had to use a home made DRF Field to return the string value of the timezone field of my model.

class BBTZField(serializers.Field):
    def to_representation(self, obj):
        return six.text_type(obj.zone)

Is this change wanted or known ?

Here is my pip freeze:

djangorestframework==3.3.1
django-timezone-field==1.3
Django==1.8.6

Thanks for your help.

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 7
  • Comments: 16 (2 by maintainers)

Most upvoted comments

For future reference, here’s the serializer field mentioned

https://github.com/encode/django-rest-framework/pull/3778#issuecomment-167831933

class TimezoneField(serializers.Field):
    def to_representation(self, obj):
        return six.text_type(obj)

    def to_internal_value(self, data):
        try:
            return pytz.timezone(str(data))
        except pytz.exceptions.UnknownTimeZoneError:
            raise ValidationError('Unknown timezone')

For a quick fix, you can add add timezone = serializers.SerializerMethodField() to your serializer and then add something like:

def get_timezone(self, obj):
    return six.text_type(obj.timezone)

So an example serializer would look like:

class BusinessSerializer(serializers.ModelSerializer):
    timezone = serializers.SerializerMethodField()

    class Meta:
        model = Business
        fields = (
            'id', 
            'timezone'
        )

    def get_timezone(self, obj):
        return six.text_type(obj.timezone)

the above example from @bob-r works great. However, if you need to allow null or blank, adding the blank choice will work with the validators. Here is the edited class


class TimeZoneField(serializers.ChoiceField):
    def __init__(self, **kwargs):
        super().__init__(TimeZoneField_.CHOICES + [(None, "")], **kwargs)

    def to_representation(self, value):
        return six.text_type(super().to_representation(value))

from rest_framework import serializers
from django.utils import six
from timezone_field import TimeZoneField as TimeZoneField_


class TimeZoneField(serializers.ChoiceField):
    def __init__(self, **kwargs):
        super().__init__(TimeZoneField_.CHOICES, **kwargs)

    def to_representation(self, value):
        return six.text_type(super().to_representation(value))

the above works with six removed since now depreciated in django. Any reason why this can’t be incorporated in this package by default?

from rest_framework import serializers
from timezone_field import TimeZoneField as TimeZoneField_

class TimeZoneField(serializers.ChoiceField):
    def __init__(self, **kwargs):
        super().__init__(TimeZoneField_.CHOICES + [(None, "")], **kwargs)

    def to_representation(self, value):
        return str(super().to_representation(value))

Looking into this some more, I’m not entirely certain it can be fixed in this package; at least, not directly.

The JSONEncoder class in DRF (https://github.com/tomchristie/django-rest-framework/blob/7351a3f6ca7028868a79a6f2dae36ccb2fc7dd65/rest_framework/utils/encoders.py#L19) handles serialization of several specific types of objects, but that list does not include pytz objects. It also does not look for any methods or attributes which would be appropriate to attach via this module.

I think the most appropriate fix is going to be in django-rest-framework, either by making it explicitly handle pytz objects, or by a more generic method, such as looking for a to_json method on objects.

Alternatively, this package would likely need to provide its own JSONEncoder, something like:

import datetime
from django.utils import six
from rest_framework.utils import encoders


class JSONEncoder(encoders.JSONEncoder):

    def default(self, obj):
        if isinstance(obj, datetime.tzinfo):
            return six.text_type(obj)
        return super(JSONEncoder, self).default(obj)

And then this encoder would then need to be included in a renderer class, which would be set in the user’s DEFAULT_RENDERER_CLASSES. In other words, a bit excessive.

That said, I’m submitting a PR right now on django-rest-framework to fix this issue.