如何在 Django Forms 中使用时区

How to use timezones in Django Forms

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 转换为我的时区,完美,但是当我提交这个表单时,日期在DB 已从 UTC 更改为温尼伯时间。这是为什么?

我曾尝试在 form_valid 函数中将提交的时间转换为 UTC,但这似乎不是正确的方法。我在这里错过了什么? 我只想将所有时间存储为 UTC,但将它们显示在 forms/pages.

中的用户时区

编辑

当我从 getform_valid 中删除 timezone.activate(pytz.timezone(self.request.user.t_zone)) 时,UTC 保留在数据库中,这很棒。但是现在窗体上显示的时间是默认的TIME_ZONE中的settings.py。我只需要它在用户的时区....

编辑 2

我也试过补充:

{% load tz %}

{% timezone "America/Winnipeg" %}
    {{form}}
{% endtimezone %}

它在表单上正确显示了时间,但是当表单提交时,它会再次从数据库中的 UTC 时间中删除 1 小时。

如果我将模板更改为:

{% load tz %}

{% timezone "Europe/Paris" %}
    {{form}}
{% endtimezone %}

时间将以巴黎当地时间显示。当我提交表格时,它会以 UTC+2 将巴黎时间写入数据库。所以,总结一下:

这里发生了什么!?

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

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

最常放置 activate() 的地方是中间件(如 this example from the documentation), since that ensures that it comes before any view processing. Alternatively, if using generic class-based views like you are, you could put it in dispatch().