[Django]-How to handling timezones in django DRF without repeating myself too much?

5👍

As described in the documentation for DateTimeField in DRF, it has an argument default_timezone:

default_timezone – A pytz.timezone representing the timezone. If
not specified and the USE_TZ setting is enabled, this defaults to
the current
timezone
.
If USE_TZ is disabled, then datetime objects will be naive.

As it also describes as long as you have set USE_TZ the field will automatically use the current timezone. This of course means you would have to set (activate [Django docs]) the current timezone somehow. Django’s documentation also has some sample code that uses sessions for storing the timezone and a middleware to set it, although it seems you store the timezone in the user object itself so you can write a middleware that uses that instead. Also since you use DRF for authentication, it actually does the authentication on the view layer, so the middleware does not really have the authenticated user, Since you are using rest_framework_simplejwt you can use the workaround described in this question:

import pytz

from django.utils import timezone
from rest_framework_simplejwt import authentication


class TimezoneMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        tzname = None
        user = self.get_request_user(request)
        if user:
            tzname = user.timezone
        if tzname:
            timezone.activate(pytz.timezone(tzname))
        else:
            timezone.deactivate()
        return self.get_response(request)
    
    def get_request_user(self, request):
        try:
            return authentication.JWTAuthentication().authenticate(request)[0]
        except:
            return None

Add this middleware to the MIDDLEWARE list in settings.py somewhere after AuthenticationMiddleware, ideally at the last should work:

MIDDLEWARE = [
    ...
    'path.to.TimezoneMiddleware',
]

Although the above solution seemed good at the start, it later turned to needing to use a workaround. A better way would be to use a mixin that would set the current timezone. A good point for our mixin to do it’s task would be the initial method of the ApiView class, this method is called just before the actual view method (get, post, etc.) is called so it suits our needs:

import pytz

from django.utils import timezone


class TimezoneMixin:
    def initial(self, request, *args, **kwargs):
        super().initial(request, *args, **kwargs)
        tzname = None
        if request.user.is_authenticated:
            tzname = request.user.timezone
        if tzname:
            timezone.activate(pytz.timezone(tzname))
        else:
            timezone.deactivate()


class YourView(TimezoneMixin, SomeViewClassFromDRF):
    ...

Leave a comment