如何在 django DRF 中处理时区而不重复太多? [英] How to handling timezones in django DRF without repeating myself too much?
问题描述
- 简介:我的项目
TIME_ZONE
等于'UTC'
,而我的用户来自太多时区.因此,当我使用date
或time
或dateTime
制作POST
或PUT
时字段 我在serializer.save()
之前将这些字段转换为UTC
.然后,当用户发出GET
请求时,我将相同的字段转换回用户的时区,即request.user.timezone
- Intro: My project
TIME_ZONE
is equal to'UTC'
while I have users from too many time zones. So, when I user makePOST
orPUT
withdate
ortime
ordateTime
fields I convert these fields toUTC
beforeserializer.save()
. Then, when a user make aGET
request I convert the same fields back to the timezone of the user which isrequest.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
字段. - 我尝试过:创建一个类视图并在其中使用这些函数,然后为每个新应用覆盖该类.但是,每个模型都有名称不同的日期字段,很难做出一个灵活的函数来识别哪些字段需要本地化或规范化.
- Problem: I used these function in too many views and every time I create a new view I use it again.
- Goal: I need to find a way to do that in one function that generalize this conversion for all
dateField
,timeField
anddatetimeField
fields. - I tried: to make a class view and use these functions inside it then override that class for each new app. But, each model have date fields with different names and it was hard to make a flexible function that recognize which field need to be localized or normalized.
注意:我的意思是标准化将时区从 UTC 转换为当前登录用户时区.
Note: I mean by normalized to convert the timezone from UTC the the current login-in user timezone.
推荐答案
如 DateTimeField 的 ="nofollow noreferrer">文档,它有一个参数 default_timezone
:
As described in the documentation for DateTimeField
in DRF, it has an argument default_timezone
:
default_timezone
- 代表时区的 pytz.timezone
.如果未指定且 USE_TZ
设置已启用,默认为当前时区.如果 USE_TZ
被禁用,那么日期时间对象将是幼稚的.
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 文档]) 当前时区.Django 的文档有一些示例代码使用会话来存储时区和一个中间件来设置它,尽管您似乎将时区存储在用户对象本身中,因此您可以编写一个使用它的中间件.此外,由于您使用 DRF 进行身份验证,它实际上是在视图层上进行身份验证,因此中间件实际上并没有经过身份验证的用户,由于您使用的是 rest_framework_simplejwt
,您可以使用
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
将此中间件添加到 settings.py
中 AuthenticationMiddleware
之后的 MIDDLEWARE
列表中,理想情况下最后应该可以工作:
Add this middleware to the MIDDLEWARE
list in settings.py
somewhere after AuthenticationMiddleware
, ideally at the last should work:
MIDDLEWARE = [
...
'path.to.TimezoneMiddleware',
]
虽然上述解决方案一开始看起来不错,但后来变成需要使用变通方法.更好的方法是使用设置当前时区的 mixin.让我们的 mixin 完成它的任务的一个好点是 ApiView
类的 initial
方法,这个方法在实际的视图方法之前被调用(get
、post
等)被调用,因此它适合我们的需要:
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):
...
这篇关于如何在 django DRF 中处理时区而不重复太多?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!