如何在 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'
,而我的用户来自太多时区。因此,当我使用date
或time
或dateTime
字段创建POST
或PUT
时,我将这些字段转换为UTC
在serializer.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')
问题:我在太多视图中使用了这些功能,每次创建新视图时都会再次使用它。
目标:我需要在一个函数中找到一种方法来实现这一点,该函数将这种转换推广到所有 dateField
、timeField
和 datetimeField
字段。
我尝试过:创建一个类视图并在其中使用这些函数,然后为每个新应用覆盖该类。但是,每个模型都有不同名称的日期字段,很难创建一个灵活的函数来识别需要本地化或规范化的字段。
注意:我的意思是将时区从 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.py
中AuthenticationMiddleware
之后的MIDDLEWARE
列表中,理想情况下最后应该可以工作:
MIDDLEWARE = [
...
'path.to.TimezoneMiddleware',
]
虽然上述解决方案一开始看起来不错,但后来转向需要使用解决方法。更好的方法是使用可以设置当前时区的 mixin。我们的 mixin 完成它的任务的一个好点是 ApiView
类的 initial
方法,该方法在实际视图方法(get
、post
等)被调用之前调用,所以它适合我们的需要:
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 + django-rest-auth 中使用自定义用户模型保存注册时的额外字段
如何确保保存在数据库中的UTC日期等于Django中指定时区的午夜[重复]