Django-rest-framework timezone aware renderers/par

2019-02-02 10:23发布

问题:

I'm building an app that would serve people located in different places arround the world.
I'm using Django-Rest-Framwork for the communication between the clients and the server.
All DateTime values are saved to the DB as UTC (I have USE_TZ = True and TIME_ZONE = 'Greenwich' in settings.py).

I will be getting from the user his/her local timezone.

To test DRF for timezone awareness I wrote this middleware with fixed timezone:

import pytz
from django.utils import timezone

class TimezoneMiddleware(object):
    def process_request(self, request):
        timezone.activate(pytz.timezone("Asia/Jerusalem"))

The problem is:
I have a case in which I'm getting from the user "start_time" and "end_time" fields containting datetimes in the user's LOCAL timezone which gets through the UnicodeJSONRenderer to a ModelSerializer and then saved to the DB. However, they are saved as if they were in UTC.

At this point I would expect the serializer (or parser) to treat the datetime input from the user as datetime that needs to be converted from "Asia/Jerusalem" to UTC before saving to the DB since I performed timezone.activate(pytz.timezone("Asia/Jerusalem")).

The same goes when the data is parsed back in the response through JSONParser.
While expecting DateTime fields in the returned JSON to be in the timezone activated in the middleware, they are returned as UTC.

How can I easily achieve that in DRF?

回答1:

I had the same problem and solved it by adding new type of field:

class DateTimeTzAwareField(serializers.DateTimeField):

    def to_native(self, value):
        value = timezone.localtime(value)
        return super(DateTimeTzAwareField, self).to_native(value)

and now you can use it in ModelSerializer:

class XSerializer(serializers.ModelSerializer):
    start = DateTimeTzAwareField()
    end = DateTimeTzAwareField()

    class Meta:
        model = XModel
        fields = (
             'id',
             'start',
             'end',
        )


回答2:

The answer by @yakxxx seems to be the best solution. I am posting it again after updating it to work with newer versions of restframework

inside my directory (common/serializers.py)

from rest_framework import serializers
from django.utils import timezone

class DateTimeFieldWihTZ(serializers.DateTimeField):

    def to_representation(self, value):
        value = timezone.localtime(value)
        return super(DateTimeFieldWihTZ, self).to_representation(value)

Inside my application

from common.serializers import DateTimeFieldWihTZ

class MyObjectSerializer(serializers.ModelSerializer):

    start = DateTimeFieldWihTZ(format='%d %b %Y %I:%M %p')
    end = DateTimeFieldWihTZ(format='%d %b %Y %I:%M %p')


回答3:

Since Django REST Framework v3.8.0(released in May, 2018), you don't need a custom DateTimeField any more.

In previous versions, Django REST Framework only convert native datetime to timezone aware datetime when parsing the date(DateTimeField.to_internal_value()), but do not convert when rendering the datatime field(DateTimeField.to_representation()). This is fixed in DRF v3.8.0.

You may need to change the following settings:

  1. USE_TZ must be True
  2. set TIME_ZONE to specify a default timezone
  3. set REST_FRAMEWORK.DATETIME_FORMAT to the format that fits your frontend code.