【问题标题】:Handle Django Util Timezone Ambiguous Time Error without directly modifying the Django Package处理 Django Util Timezone Ambiguous Time Error 而不直接修改 Django 包
【发布时间】:2019-07-13 15:48:21
【问题描述】:

我知道要修复 make_aware 我可以拦截异常并将其提前一个小时,但是问题是 Django 在查询集中发现它们时正在运行 make_aware,我没有在代码,它是否发生在 Django 库代码中,我无法编辑它而不必在代码库的每个版本中包含修改后的版本。有解决办法吗?

请注意:再次,为了清楚起见,我不是手动调用 make_aware,它正在被评估时在查询集中的对象上运行

【问题讨论】:

  • 也许这里的东西会帮助你:stackoverflow.com/questions/21465528/…
  • 是的,我看到了那条评论,但是请注意这是在代码中调用 make_aware 的解释——我不是手动调用它,django 在生成查询集时会自动调用它
  • 我只是添加:现在添加 = timezone.now() 没有帮助

标签: python django timezone ambiguous


【解决方案1】:

我遇到了同样的问题,并且似乎没有在任何地方记录解决方案,但我得到了一个可以在几行代码中运行的解决方案。答案来自自定义 Django DB 后端。我们要做的是扩展现有的 MySQL DB 后端,并替换使 MySQL 日期时间时区感知的代码。

首先,让我描述一下我面临的问题。 2021 年 11 月 6 日,PST 时区发生了 DST 转换,时钟在凌晨 1:59 之后回滚到凌晨 1 点,而不是像正常情况下那样递增到凌晨 2 点。结果,任何带有 2021 年 11 月 6 日凌晨 1 点时间戳的东西对 Django 来说都是模棱两可的,因为它不确定是回滚之前还是之后的凌晨 1 点(即“在正常情况下应该是凌晨 2 点”) .从凌晨 1:00:00 到 1:59:59 的每一分每一秒都会出现此问题。

我深入研究了错误并注意到问题出在以下代码中,该代码在从数据库转换 DateTimeField 值时由 Django ORM 调用:

# in file django.db.backends.mysql.operations

class DatabaseOperations(BaseDatabaseOperations):
    # other code here
    
    def convert_datetimefield_value(self, value, expression, connection):
        if value is not None:
            value = timezone.make_aware(value, self.connection.timezone)
        return value

特别是,问题在于以下行:

value = timezone.make_aware(value, self.connection.timezone)

由于它没有将is_dst 参数传递给timezone.make_aware 调用,因此在某些条件下我们必然会得到AmbiguousTimeError。一种简单的解决方案是将is_dst=True 传递给调用(或False,选择是任意的),但这意味着修改Django 的代码......还是会这样?您不需要修改 Django 的代码,您可以简单地扩展默认的 MySQL DB 后端并用您自己的实现覆盖该功能! (我一开始就已经剧透了,但你还是应该表现得感到惊讶,以获得额外的戏剧效果)。

Django 2、3 和 4 的解决方案

  1. 扩展默认的 Django MySQL DB 后端,并覆盖执行 DateTime 字段转换的代码部分。为此,请在 Django 项目的根目录中创建一个新包,并在其中包含一个名为 base.py 的文件。在该文件中,我们的自定义 Django DB 后端代码将生效。我调用了我的数据库后端illyadbengine,所以我将拥有以下文件夹结构:
my-django-app
   my-django-app
      - settings.py
   
   illyadbengine
      - __init__.py
      - base.py

并将以下代码放入base.py:

"""
A custom MySQL DB engine that solves the ambiguous time error during DST transition, by assuming that it is DST
in case of an error.
"""
from django.db.backends.mysql import base
from django.utils import timezone
from django.db.backends.mysql.operations import DatabaseOperations
from pytz.exceptions import AmbiguousTimeError


class MySQLOperationsWithDSTConflictResolutionOperations(DatabaseOperations):
    def convert_datetimefield_value(self, value, expression, connection):
        try:
            # attempt at performing the default conversion, and only fallback to DST conflict resolution if it fails
            return super().convert_datetimefield_value(value, expression, connection)
        except AmbiguousTimeError:
            if value is not None:
                # NOTE: this can cause an error by 1 hour, since it always assumes DST timezone in case of conflict
                # if a precise time conversion is important for your use case, you should write that custom logic here
                value = timezone.make_aware(value, self.connection.timezone, is_dst=True)
            return value


class DatabaseWrapper(base.DatabaseWrapper):
    ops_class = MySQLOperationsWithDSTConflictResolutionOperations

  1. 在settings.py 中指示您的数据库使用您的自定义引擎。您可以通过多种方式执行此操作,具体取决于您在设置中定义数据库连接的方式。您正在做的是将数据库连接的ENGINE 参数设置为illyadbengine。

因此,如果您为 DB 使用连接字符串,您可以执行以下操作:

import environ
env = environ.Env()

DATABASES = {
    "default": env.db_url("DATABASE_URL"),
}

DATABASES["default"]["ENGINE"] = "illyadbengine"

或者,如果您使用内联 dict 定义连接元素,您将拥有如下内容:

DATABASES = {
    'default': {
        # other settings here
        'ENGINE': 'illyadbengine',
    }
}

请注意,此解决方案假定 DST 时间在冲突期间有效,因此在夏季/冬季时间转换期间,您可能会比实际时间晚一个小时。如果该信息在您的应用程序中至关重要,那么您应该在覆盖 convert_datetimefield_value 的实现中包含该信息的逻辑。

此解决方案适用于 Django 2、Django 3 和 Django 4。

我找到了一些documentation on extending a database engine in Django's 3 Documentation,虽然它在 Django 2 中不存在,但我自己在 Django 2.2 中对其进行了测试,它可以工作。您在 Django 4 中应该没有问题,因为那里也有该文档。

【讨论】:

    猜你喜欢
    • 2013-11-15
    • 1970-01-01
    • 2017-01-03
    • 2020-01-08
    • 2016-01-03
    • 1970-01-01
    • 2015-01-18
    • 2021-01-21
    • 1970-01-01
    相关资源
    最近更新 更多