如何在不重复太多的情况下处理 django DRF 中的时区?
How to handling timezones in django DRF without repeating myself too much?
- 简介: 我的项目
TIME_ZONE
等于 'UTC'
而我的用户来自多个时区。因此,当我使用 date
或 time
或 dateTime
字段制作 POST
或 PUT
时,我在 [=20 之前将这些字段转换为 UTC
=].然后,当用户发出 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
字段。
- 我试过:创建一个 class 视图并在其中使用这些函数,然后为每个新应用覆盖 class。但是,每个模型都有不同名称的日期字段,很难创建一个灵活的函数来识别哪个字段需要本地化或规范化。
注意:我的意思是通过标准化将时区从 UTC 转换为当前登录用户时区。
如 DRF 中 documentation for DateTimeField
中所述,它有一个参数 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.
正如它还描述的那样,只要您设置了 USE_TZ
该字段将自动使用当前时区。这当然意味着您必须设置 (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
将此中间件添加到 settings.py
的 MIDDLEWARE
列表中 AuthenticationMiddleware
之后的某处,理想情况下最后应该可以工作:
MIDDLEWARE = [
...
'path.to.TimezoneMiddleware',
]
虽然上面的解决方案一开始看起来不错,但后来转向需要使用变通方法。更好的方法是使用可以设置当前时区的混合。 ApiView
class 的 initial
方法是我们 mixin 完成任务的一个好点,这个方法在实际视图方法之前被调用(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):
...
- 简介: 我的项目
TIME_ZONE
等于'UTC'
而我的用户来自多个时区。因此,当我使用date
或time
或dateTime
字段制作POST
或PUT
时,我在 [=20 之前将这些字段转换为UTC
=].然后,当用户发出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
字段。 - 我试过:创建一个 class 视图并在其中使用这些函数,然后为每个新应用覆盖 class。但是,每个模型都有不同名称的日期字段,很难创建一个灵活的函数来识别哪个字段需要本地化或规范化。
注意:我的意思是通过标准化将时区从 UTC 转换为当前登录用户时区。
如 DRF 中 documentation for DateTimeField
中所述,它有一个参数 default_timezone
:
default_timezone
- Apytz.timezone
representing the timezone. If not specified and theUSE_TZ
setting is enabled, this defaults to the current timezone. IfUSE_TZ
is disabled, then datetime objects will be naive.
正如它还描述的那样,只要您设置了 USE_TZ
该字段将自动使用当前时区。这当然意味着您必须设置 (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
将此中间件添加到 settings.py
的 MIDDLEWARE
列表中 AuthenticationMiddleware
之后的某处,理想情况下最后应该可以工作:
MIDDLEWARE = [
...
'path.to.TimezoneMiddleware',
]
虽然上面的解决方案一开始看起来不错,但后来转向需要使用变通方法。更好的方法是使用可以设置当前时区的混合。 ApiView
class 的 initial
方法是我们 mixin 完成任务的一个好点,这个方法在实际视图方法之前被调用(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):
...