【问题标题】:How to use timezones in Django Forms如何在 Django 表单中使用时区
【发布时间】: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,但在表单/页面中的用户时区中显示它们。

编辑

当我从getform_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


【解决方案1】:

简单地说:您在form_valid() 中的activate() 调用来得太晚,无法影响表单字段,因此传入的日期时间在转换为@987654326 之前在默认时区(在您的情况下为America/Toronto)进行解释@ 并保存到数据库中。因此出现了明显的时移。

文档并未真正指定何时您需要致电activate()。不过,据推测,它必须在 Django 将请求中的字符串值转换为表单字典中的感知 Python 日期时间之前(或在发送日期时间时反之亦然)。在调用 form_valid() 时,字段值字典已经填充了 Python 日期时间对象。

最常见的放置activate() 的位置是在中间件中(如文档中的this example),因为这样可以确保它在任何视图处理之前出现。或者,如果像您一样使用基于类的通用视图,您可以将其放入 dispatch()

【讨论】:

    猜你喜欢
    • 2015-08-22
    • 2011-07-23
    • 2014-10-17
    • 2012-04-29
    • 1970-01-01
    • 1970-01-01
    • 2019-12-06
    • 2019-12-17
    • 1970-01-01
    相关资源
    最近更新 更多