chore: Replace pytz with zoneinfo for UTC handling - Part 1 (#37523)
First PR to replace pytz with zoneinfo for UTC handling across codebase. This PR migrates all UTC timezone handling from pytz to Python’s standard library zoneinfo. The pytz library is now deprecated, and its documentation recommends using zoneinfo for all new code. This update modernizes our codebase, removes legacy pytz usage, and ensures compatibility with current best practices for timezone management in Python 3.9+. No functional changes to timezone logic - just a direct replacement for UTC handling. https://github.com/openedx/edx-platform/issues/33980
This commit is contained in:
@@ -8,7 +8,7 @@ import ddt
|
||||
from crum import set_current_request
|
||||
from django.test import RequestFactory
|
||||
from django.utils import timezone
|
||||
from pytz import UTC
|
||||
from zoneinfo import ZoneInfo
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
@@ -34,9 +34,12 @@ class TestAccess(ModuleStoreTestCase):
|
||||
def setUp(self):
|
||||
super().setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
|
||||
CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=UTC))
|
||||
CourseDurationLimitConfig.objects.create(
|
||||
enabled=True,
|
||||
enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))
|
||||
)
|
||||
DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True)
|
||||
self.course = CourseOverviewFactory.create(start=datetime(2018, 1, 1, tzinfo=UTC), self_paced=True)
|
||||
self.course = CourseOverviewFactory.create(start=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC")), self_paced=True)
|
||||
|
||||
def assertDateInMessage(self, date, message): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
# First, check that the formatted version is in there
|
||||
@@ -148,7 +151,7 @@ class TestAccess(ModuleStoreTestCase):
|
||||
course_id=enrollment.course.id,
|
||||
mode_slug=CourseMode.AUDIT,
|
||||
)
|
||||
Schedule.objects.update(start_date=datetime(2017, 1, 1, tzinfo=UTC))
|
||||
Schedule.objects.update(start_date=datetime(2017, 1, 1, tzinfo=ZoneInfo("UTC")))
|
||||
|
||||
content_availability_date = max(enrollment.created, enrollment.course.start)
|
||||
access_duration = get_user_course_duration(enrollment.user, enrollment.course)
|
||||
|
||||
@@ -8,7 +8,7 @@ from unittest.mock import Mock
|
||||
|
||||
import ddt
|
||||
import pytest
|
||||
import pytz
|
||||
from zoneinfo import ZoneInfo
|
||||
from django.utils import timezone
|
||||
from edx_django_utils.cache import RequestCache
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
@@ -178,13 +178,18 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase):
|
||||
def test_all_current_course_configs(self):
|
||||
# Set up test objects
|
||||
for global_setting in (True, False, None):
|
||||
CourseDurationLimitConfig.objects.create(enabled=global_setting, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long
|
||||
CourseDurationLimitConfig.objects.create(
|
||||
enabled=global_setting,
|
||||
enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))
|
||||
)
|
||||
for site_setting in (True, False, None):
|
||||
test_site_cfg = SiteConfigurationFactory.create(
|
||||
site_values={'course_org_filter': []}
|
||||
)
|
||||
CourseDurationLimitConfig.objects.create(
|
||||
site=test_site_cfg.site, enabled=site_setting, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)
|
||||
site=test_site_cfg.site,
|
||||
enabled=site_setting,
|
||||
enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))
|
||||
)
|
||||
|
||||
for org_setting in (True, False, None):
|
||||
@@ -193,7 +198,7 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase):
|
||||
test_site_cfg.save()
|
||||
|
||||
CourseDurationLimitConfig.objects.create(
|
||||
org=test_org, enabled=org_setting, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)
|
||||
org=test_org, enabled=org_setting, enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))
|
||||
)
|
||||
|
||||
for course_setting in (True, False, None):
|
||||
@@ -202,7 +207,7 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase):
|
||||
id=CourseLocator(test_org, 'test_course', f'run-{course_setting}')
|
||||
)
|
||||
CourseDurationLimitConfig.objects.create(
|
||||
course=test_course, enabled=course_setting, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC) # lint-amnesty, pylint: disable=line-too-long
|
||||
course=test_course, enabled=course_setting, enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC")) # lint-amnesty, pylint: disable=line-too-long
|
||||
)
|
||||
|
||||
with self.assertNumQueries(4):
|
||||
@@ -216,22 +221,25 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase):
|
||||
# Point-test some of the final configurations
|
||||
assert all_configs[CourseLocator('7-True', 'test_course', 'run-None')] == {
|
||||
'enabled': (True, Provenance.org),
|
||||
'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC),
|
||||
'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=ZoneInfo("UTC")),
|
||||
Provenance.run)
|
||||
}
|
||||
assert all_configs[CourseLocator('7-True', 'test_course', 'run-False')] == {
|
||||
'enabled': (False, Provenance.run),
|
||||
'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC),
|
||||
'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=ZoneInfo("UTC")),
|
||||
Provenance.run)
|
||||
}
|
||||
assert all_configs[CourseLocator('7-None', 'test_course', 'run-None')] == {
|
||||
'enabled': (True, Provenance.site),
|
||||
'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC),
|
||||
'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=ZoneInfo("UTC")),
|
||||
Provenance.run)
|
||||
}
|
||||
|
||||
def test_caching_global(self):
|
||||
global_config = CourseDurationLimitConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC))
|
||||
global_config = CourseDurationLimitConfig(
|
||||
enabled=True,
|
||||
enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))
|
||||
)
|
||||
global_config.save()
|
||||
|
||||
RequestCache.clear_all_namespaces()
|
||||
@@ -257,7 +265,7 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase):
|
||||
|
||||
def test_caching_site(self):
|
||||
site_cfg = SiteConfigurationFactory()
|
||||
site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long
|
||||
site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))) # lint-amnesty, pylint: disable=line-too-long
|
||||
site_config.save()
|
||||
|
||||
RequestCache.clear_all_namespaces()
|
||||
@@ -281,7 +289,10 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase):
|
||||
with self.assertNumQueries(1):
|
||||
assert not CourseDurationLimitConfig.current(site=site_cfg.site).enabled
|
||||
|
||||
global_config = CourseDurationLimitConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC))
|
||||
global_config = CourseDurationLimitConfig(
|
||||
enabled=True,
|
||||
enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))
|
||||
)
|
||||
global_config.save()
|
||||
|
||||
RequestCache.clear_all_namespaces()
|
||||
@@ -295,7 +306,7 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase):
|
||||
site_cfg = SiteConfigurationFactory.create(
|
||||
site_values={'course_org_filter': course.org}
|
||||
)
|
||||
org_config = CourseDurationLimitConfig(org=course.org, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long
|
||||
org_config = CourseDurationLimitConfig(org=course.org, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))) # lint-amnesty, pylint: disable=line-too-long
|
||||
org_config.save()
|
||||
|
||||
RequestCache.clear_all_namespaces()
|
||||
@@ -319,7 +330,10 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase):
|
||||
with self.assertNumQueries(2):
|
||||
assert not CourseDurationLimitConfig.current(org=course.org).enabled
|
||||
|
||||
global_config = CourseDurationLimitConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC))
|
||||
global_config = CourseDurationLimitConfig(
|
||||
enabled=True,
|
||||
enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))
|
||||
)
|
||||
global_config.save()
|
||||
|
||||
RequestCache.clear_all_namespaces()
|
||||
@@ -328,7 +342,7 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase):
|
||||
with self.assertNumQueries(0):
|
||||
assert not CourseDurationLimitConfig.current(org=course.org).enabled
|
||||
|
||||
site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long
|
||||
site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))) # lint-amnesty, pylint: disable=line-too-long
|
||||
site_config.save()
|
||||
|
||||
RequestCache.clear_all_namespaces()
|
||||
@@ -342,7 +356,7 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase):
|
||||
site_cfg = SiteConfigurationFactory.create(
|
||||
site_values={'course_org_filter': course.org}
|
||||
)
|
||||
course_config = CourseDurationLimitConfig(course=course, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long
|
||||
course_config = CourseDurationLimitConfig(course=course, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))) # lint-amnesty, pylint: disable=line-too-long
|
||||
course_config.save()
|
||||
|
||||
RequestCache.clear_all_namespaces()
|
||||
@@ -366,7 +380,10 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase):
|
||||
with self.assertNumQueries(2):
|
||||
assert not CourseDurationLimitConfig.current(course_key=course.id).enabled
|
||||
|
||||
global_config = CourseDurationLimitConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC))
|
||||
global_config = CourseDurationLimitConfig(
|
||||
enabled=True,
|
||||
enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))
|
||||
)
|
||||
global_config.save()
|
||||
|
||||
RequestCache.clear_all_namespaces()
|
||||
@@ -375,7 +392,7 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase):
|
||||
with self.assertNumQueries(0):
|
||||
assert not CourseDurationLimitConfig.current(course_key=course.id).enabled
|
||||
|
||||
site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long
|
||||
site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))) # lint-amnesty, pylint: disable=line-too-long
|
||||
site_config.save()
|
||||
|
||||
RequestCache.clear_all_namespaces()
|
||||
@@ -384,7 +401,7 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase):
|
||||
with self.assertNumQueries(0):
|
||||
assert not CourseDurationLimitConfig.current(course_key=course.id).enabled
|
||||
|
||||
org_config = CourseDurationLimitConfig(org=course.org, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long
|
||||
org_config = CourseDurationLimitConfig(org=course.org, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))) # lint-amnesty, pylint: disable=line-too-long
|
||||
org_config.save()
|
||||
|
||||
RequestCache.clear_all_namespaces()
|
||||
|
||||
Reference in New Issue
Block a user