如何在 django DRF 中处理时区而不重复自己太多?

Posted

技术标签:

【中文标题】如何在 django DRF 中处理时区而不重复自己太多?【英文标题】:How to handling timezones in django DRF without repeating myself too much? 【发布时间】:2021-09-25 19:10:49 【问题描述】: 简介:我的项目TIME_ZONE 等于'UTC',而我的用户来自太多时区。因此,当我使用datetimedateTime 字段创建POSTPUT 时,我将这些字段转换为UTCserializer.save() 之前。然后,当用户发出GET 请求时,我将相同的字段转换回用户的时区request.user.timezone
# simplified functions

def localize(usertimzone, date, type):
    """
    type = 'date', 'time', 'datetime'
    """
    from dateutil import parser
    date = parser.parse(date)
    if not date.tzinfo:
        usertimzone = pytz.timezone(usertimzone)
        date = usertimzone.localize(date)
    utc_date = date.astimezone(pytz.utc)
    return utc_date

def normalize(usertimzone, date, type):
    current_user_tz = pytz.timezone(usertimzone)
    date = current_user_tz.localize(date)
    return date
#usages example
    def post(self, request, *args, **kwargs):
        alert_date = request.data.get('alert_date')
        if alert_date:
            request.data['alert_date'] = localize(request.user.timezone, alert_date, 'datetime')
问题:我在太多视图中使用了这些功能,每次创建新视图时都会再次使用它。 目标:我需要在一个函数中找到一种方法来实现这一点,该函数将这种转换推广到所有 dateFieldtimeFielddatetimeField 字段。 我尝试过:创建一个类视图并在其中使用这些函数,然后为每个新应用覆盖该类。但是,每个模型都有不同名称的日期字段,很难创建一个灵活的函数来识别需要本地化或规范化的字段。

注意:我的意思是将时区从 UTC 转换为当前登录用户时区。

【问题讨论】:

【参考方案1】:

如 DRF 中 DateTimeField 的 documentation 中所述,它有一个参数 default_timezone

default_timezone - 代表时区的pytz.timezone。如果 未指定且启用了USE_TZ 设置,默认为 current timezone。 如果 USE_TZ 被禁用,那么 datetime 对象将是幼稚的。

正如它所描述的,只要您设置了USE_TZ,该字段将自动使用当前时区。这当然意味着您必须以某种方式设置 (activate [Django docs]) 当前时区。 Django 的documentation 也有一些示例代码使用会话来存储时区和一个中间件来设置它,尽管您似乎将时区存储在用户对象本身中,因此您可以编写一个使用它的中间件。此外,由于您使用 DRF 进行身份验证,它实际上是在视图层进行身份验证,因此中间件实际上并没有经过身份验证的用户,由于您使用的是rest_framework_simplejwt,您可以使用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

将此中间件添加到settings.pyAuthenticationMiddleware 之后的MIDDLEWARE 列表中,理想情况下最后应该可以工作:

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

虽然上述解决方案一开始看起来不错,但后来转向需要使用解决方法。更好的方法是使用可以设置当前时区的 mixin。我们的 mixin 完成它的任务的一个好点是 ApiView 类的 initial 方法,该方法在实际视图方法(getpost 等)被调用之前调用,所以它适合我们的需要:

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

【讨论】:

我得到了request.user.is_authenticated 总是返回False @alial-karaawi 哦,等等,您使用 DRF 的身份验证吗?这可能需要一些修改。 我正在使用'rest_framework_simplejwt.authentication.JWTAuthentication' @alial-karaawi 检查编辑,使用here描述的解决方法 @alial-karaawi 虽然这是一种解决方法(身份验证代码被调用两次),但也许更好的方法是创建一个 mixin 或一个基类,您的所有视图都将继承自(或装饰器)用于基于函数的视图)。

以上是关于如何在 django DRF 中处理时区而不重复自己太多?的主要内容,如果未能解决你的问题,请参考以下文章

DRF框架中的异常处理程序

如何在 DRF + django-rest-auth 中使用自定义用户模型保存注册时的额外字段

如何在 django 中自定义数据库连接设置的时区?

如何确保保存在数据库中的UTC日期等于Django中指定时区的午夜[重复]

如何在 Django Rest Framework 中处理并行请求?

Django Rest Framework JWT 中的 ValidationError 不使用自定义异常处理程序