【发布时间】:2021-08-08 21:00:05
【问题描述】:
Django 中的时区...
我不知道为什么这么难,但我很难过。 我有一个表单用用户的本地时间覆盖数据库中的 UTC 日期时间。我似乎无法弄清楚是什么原因造成的。
我的 settings.py 时区设置如下:
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'America/Toronto'
USE_I18N = True
USE_L10N = False
USE_TZ = True
我在温尼伯,我的服务器托管在多伦多。我的用户可以在任何地方。
我为每个用户都有一个模型字段t_zone = models.CharField(max_length=50, default = "America/Winnipeg",),用户可以自行更改。
关于这个模型:
class Build(models.Model):
PSScustomer = models.ForeignKey(Customer, on_delete=models.CASCADE)
buildStart = models.DateTimeField(null=True, blank=True)
...
我使用如下视图逻辑在数据库中创建一个新条目:
...
now = timezone.now()
newBuild = Build(author=machine,
PSScustomer = userCustomer,
buildStart = now,
status = "building",
addedBy = (request.user.first_name + ' ' +request.user.last_name),
...
)
newBuild.save()
buildStart 以 UTC 格式保存到数据库中,一切都按预期工作。当我在带有timezone.activate(pytz.timezone(self.request.user.t_zone)) 的视图中更改用户的时区时,它将在他们各自的时区中显示 UTC 时间。
到目前为止一切都很好(我认为)。
以下是事情的发展方向:
当我希望用户在表单中更改buildStart 时,我似乎无法获得将日期以UTC 格式保存到数据库的表单。它将在用户选择的任何时区保存到数据库中。
使用这种形式:
class EditBuild_building(forms.ModelForm):
buildStart = forms.DateTimeField(input_formats = ['%Y-%m-%dT%H:%M'],widget = forms.DateTimeInput(attrs={'type': 'datetime-local','class': 'form-control'},format='%Y-%m-%dT%H:%M'), label = "Build Start Time")
def __init__(self, *args, **kwargs):# for ensuring fields are not left empty
super(EditBuild_building, self).__init__(*args, **kwargs)
self.fields['buildDescrip'].required = True
class Meta:
model = Build
fields = ['buildDescrip', 'buildStart','buildLength'...]
labels = {
'buildDescrip': ('Build Description'),
'buildStart': ('Build Start Time'),
...
}
widgets = {'buildDescrip': forms.TextInput(attrs={'class': 'required'}),
还有这个观点:
class BuildUpdateView_Building(LoginRequiredMixin,UpdateView):
model = Build
form_class = EditBuild_building
template_name = 'build_edit_building.html'
login_url = 'login'
def get(self, request, *args, **kwargs):
proceed = True
try:
instance = Build.objects.get(id = (self.kwargs['pk']))
except:
return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer available it has been deleted, please please return to dashboard</h2>")
if instance.buildActive == False:
proceed = False
if instance.deleted == True:
proceed = False
#all appears to be well, process request
if proceed == True:
form = self.form_class(instance=instance)
timezone.activate(pytz.timezone(self.request.user.t_zone))
customer = self.request.user.PSScustomer
choices = [(item.id, (str(item.first_name) + ' ' + str(item.last_name))) for item in CustomUser.objects.filter(isDevice=False, PSScustomer = customer)]
choices.insert(0, ('', 'Unconfirmed'))
form.fields['buildStrategyBy'].choices = choices
form.fields['buildProgrammedBy'].choices = choices
form.fields['operator'].choices = choices
form.fields['powder'].queryset = Powder.objects.filter(PSScustomer = customer)
context = {}
context['buildID'] = self.kwargs['pk']
context['build'] = Build.objects.get(id = (self.kwargs['pk']))
return render(request, self.template_name, {'form': form, 'context': context})
else:
return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer editable here, or has been deleted, please return to dashboard</h2>")
def form_valid(self, form):
timezone.activate(pytz.timezone(self.request.user.t_zone))
proceed = True
try:
instance = Build.objects.get(id = (self.kwargs['pk']))
except:
return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer available it has been deleted, please please return to dashboard</h2>")
if instance.buildActive == False:
proceed = False
if instance.deleted == True:
proceed = False
#all appears to be well, process request
if proceed == True:
form.instance.editedBy = (self.request.user.first_name)+ " " +(self.request.user.last_name)
form.instance.editedDate = timezone.now()
print('edited date ' + str(form.instance.editedDate))
form.instance.reviewed = True
next = self.request.POST['next'] #grabs prev url from form template
form.save()
build = Build.objects.get(id = self.kwargs['pk'])
if build.buildLength >0:
anticipated_end = build.buildStart + (timedelta(hours = float(build.buildLength)))
print(anticipated_end)
else:
anticipated_end = None
build.anticipatedEnd = anticipated_end
build.save()
build_thres_updater(self.kwargs['pk'])#this is function above, it updates threshold alarm counts on the build
return HttpResponseRedirect(next) #returns to this page after valid form submission
else:
return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer available it has been deleted, please please return to dashboard</h2>")
当我打开此表单时,buildStart 的日期和时间显示在我的温尼伯时区中,因此 Django 从 UTC 转换为我的时区,完美,但是当我提交此表单时,数据库中的日期已更改从 UTC 到温尼伯时间。这是为什么呢?
我尝试在form_valid 函数中将提交的时间转换为UTC,但这似乎不是正确的方法。我在这里想念什么?
我只是想将所有时间存储为 UTC,但在表单/页面中的用户时区中显示它们。
编辑
当我从get 和form_valid 中删除timezone.activate(pytz.timezone(self.request.user.t_zone)) 时,UTC 会保留在数据库中,这很棒。但是表单上显示的时间现在默认为TIME_ZONE in settings.py。我只需要它在用户的时区....
编辑 2
我也尝试添加:
{% load tz %}
{% timezone "America/Winnipeg" %}
{{form}}
{% endtimezone %}
它在表单上正确显示了时间,但是当表单提交时,它将再次从数据库中的 UTC 时间中删除 1 小时。
如果我将模板更改为:
{% load tz %}
{% timezone "Europe/Paris" %}
{{form}}
{% endtimezone %}
时间将以巴黎当地时间显示。当我提交表单时,它将以 UTC+2 将此巴黎时间写入数据库。所以,总结一下:
- 创建的时间记录是温尼伯时间 11:40,它写道 16:40 UTC 到数据库,完美
- 我访问表单模板,时间显示为巴黎当地时间下午 6:40,这也是我所期望的。
- 我提交表单时未更改任何字段。
- 记录已更新为 22:40,即 UTC + 6 小时。
这里发生了什么!?
【问题讨论】:
-
你使用什么数据库? PostgreSQL?如果是这样,PostgreSQL 支持时区,并且日期时间与数据库中的时区一起保存。
-
目前只是在本地使用 SQLite 进行调试。生产版本使用 PostgreSQL。我不认为这会有所作为,但我并不奇怪这会变得更加复杂......
-
如果您使用 SQLite,应该不会有任何问题,因为它不支持时区 Django 应该在数据库中以 UTC 格式存储时间。请参阅数据库设置文档中的此部分:docs.djangoproject.com/en/3.2/ref/settings/#time-zone(注意这与
TIME_ZONE设置不同,它是数据库设置 的一部分)您是否为您的数据库设置设置了此设置? -
不,我没有更改任何默认的数据库设置。我想知道这是否与我呈现/保存表单的方式有关
-
@KevinChristopherHenry,我在我的问题中添加了“activate()”,我希望这是问题所在,但是添加
activate()的效果会阻止表单保存本地时间(我想是朝着正确方向迈出的一步),但它仍会从数据库中的 UTC 时间中删除一小时。所以时间从数据库中正确的 UTC 时间开始,当我查看表单时它显示为本地温尼伯时间,但是当我点击保存时,它所做的只是用 UTC-1 小时覆盖 UTC 时间。我被难住了……
标签: django datetime timezone django-timezone