From a60b89a3a23925a437c63802da0329d79f879821 Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Wed, 29 Oct 2025 07:39:02 +0000 Subject: [PATCH 1/5] chore: use zoneinfo instead of pytz --- lms/djangoapps/branding/tests/test_page.py | 12 ++++---- .../bulk_email/tests/test_models.py | 6 ++-- lms/djangoapps/ccx/api/v0/views.py | 4 +-- lms/djangoapps/ccx/models.py | 7 ++--- .../ccx/tests/test_ccx_modulestore.py | 6 ++-- .../tests/test_field_override_performance.py | 4 +-- lms/djangoapps/ccx/tests/test_models.py | 18 +++++------ lms/djangoapps/ccx/tests/test_overrides.py | 22 +++++++------- lms/djangoapps/ccx/tests/test_views.py | 18 +++++------ lms/djangoapps/ccx/tests/utils.py | 6 ++-- lms/djangoapps/ccx/utils.py | 4 +-- lms/djangoapps/ccx/views.py | 3 +- lms/djangoapps/certificates/api.py | 4 +-- lms/djangoapps/certificates/tests/test_api.py | 22 +++++++------- .../certificates/tests/test_utils.py | 4 +-- lms/djangoapps/certificates/tests/tests.py | 4 +-- lms/djangoapps/certificates/utils.py | 4 +-- .../commerce/api/v0/tests/test_views.py | 4 +-- lms/djangoapps/commerce/api/v1/serializers.py | 4 +-- .../commerce/api/v1/tests/test_views.py | 16 +++++----- .../course_blocks/transformers/start_date.py | 4 +-- .../tests/test_load_override_data.py | 6 ++-- .../tests/test_goal_reminder_email.py | 18 +++++------ .../course_home_api/progress/serializers.py | 4 +-- .../progress/tests/test_views.py | 4 +-- lms/djangoapps/courseware/access_utils.py | 4 +-- lms/djangoapps/courseware/course_tools.py | 4 +-- lms/djangoapps/courseware/courses.py | 14 ++++----- lms/djangoapps/courseware/date_summary.py | 4 +-- .../commands/tests/test_dump_course.py | 6 ++-- lms/djangoapps/courseware/masquerade.py | 4 +-- lms/djangoapps/courseware/tests/test_about.py | 4 +-- .../courseware/tests/test_access.py | 20 ++++++------- .../courseware/tests/test_block_render.py | 6 ++-- .../courseware/tests/test_course_tools.py | 4 +-- .../courseware/tests/test_courses.py | 6 ++-- .../courseware/tests/test_date_summary.py | 8 ++--- .../courseware/tests/test_masquerade.py | 6 ++-- .../tests/test_self_paced_overrides.py | 6 ++-- .../tests/test_submitting_problems.py | 4 +-- .../tests/test_user_state_client.py | 14 ++++----- .../tests/test_view_authentication.py | 12 ++++---- lms/djangoapps/courseware/tests/test_views.py | 14 ++++----- lms/djangoapps/courseware/utils.py | 4 +-- lms/djangoapps/courseware/views/views.py | 4 +-- .../django_comment_client/tests/test_utils.py | 6 ++-- .../discussion/django_comment_client/utils.py | 8 ++--- lms/djangoapps/discussion/rest_api/api.py | 4 +-- .../discussion/rest_api/tests/test_api_v2.py | 22 +++++++------- .../discussion/rest_api/tests/test_utils.py | 10 +++---- .../discussion/rest_api/tests/test_views.py | 4 +-- .../rest_api/tests/test_views_v2.py | 24 +++++++-------- .../discussion/rest_api/tests/utils.py | 4 +-- lms/djangoapps/discussion/rest_api/utils.py | 4 +-- lms/djangoapps/experiments/flags.py | 8 ++--- .../experiments/tests/test_flags.py | 6 ++-- lms/djangoapps/grades/api.py | 4 +-- .../grades/rest_api/v1/tests/mixins.py | 4 +-- .../rest_api/v1/tests/test_gradebook_views.py | 8 ++--- .../v1/tests/test_grading_policy_view.py | 6 ++-- .../grades/tests/integration/test_problems.py | 4 +-- lms/djangoapps/grades/tests/test_models.py | 6 ++-- lms/djangoapps/grades/tests/test_services.py | 4 +-- lms/djangoapps/grades/tests/test_signals.py | 6 ++-- lms/djangoapps/grades/tests/test_tasks.py | 10 +++---- .../grades/tests/test_transformer.py | 8 ++--- lms/djangoapps/grades/tests/utils.py | 6 ++-- lms/djangoapps/instructor/enrollment.py | 4 +-- lms/djangoapps/instructor/tests/test_api.py | 25 ++++++++-------- lms/djangoapps/instructor/tests/test_tools.py | 30 +++++++++---------- lms/djangoapps/instructor/tests/utils.py | 4 +-- .../tests/views/test_instructor_dashboard.py | 8 ++--- lms/djangoapps/instructor/views/api.py | 6 ++-- .../instructor/views/instructor_dashboard.py | 4 +-- lms/djangoapps/instructor/views/tools.py | 4 +-- lms/djangoapps/instructor_task/api.py | 4 +-- .../commands/tests/test_fail_old_tasks.py | 4 +-- .../rest_api/v1/tests/test_views.py | 4 +-- .../instructor_task/rest_api/v1/views.py | 6 ++-- .../tasks_helper/enrollments.py | 8 ++--- .../instructor_task/tasks_helper/grades.py | 8 ++--- .../instructor_task/tasks_helper/misc.py | 14 ++++----- .../instructor_task/tests/test_api.py | 4 +-- .../tests/test_tasks_helper.py | 12 ++++---- .../tests/test_course_info_views.py | 6 ++-- .../mobile_api/tests/test_middleware.py | 10 +++---- lms/djangoapps/mobile_api/tests/test_model.py | 18 +++++------ lms/djangoapps/mobile_api/testutils.py | 6 ++-- lms/djangoapps/mobile_api/users/tests.py | 14 ++++----- lms/djangoapps/mobile_api/users/views.py | 4 +-- .../program_enrollments/rest_api/v1/utils.py | 4 +-- lms/djangoapps/support/tests/test_views.py | 12 ++++---- lms/djangoapps/teams/models.py | 8 ++--- lms/djangoapps/teams/tests/factories.py | 4 +-- lms/djangoapps/teams/tests/test_models.py | 4 +-- lms/djangoapps/teams/tests/test_views.py | 6 ++-- .../verify_student/tests/test_services.py | 22 +++++++------- lms/templates/courseware/progress.html | 4 +-- lms/templates/dashboard.html | 4 +-- .../instructor_dashboard_2/special_exams.html | 1 - 100 files changed, 397 insertions(+), 401 deletions(-) diff --git a/lms/djangoapps/branding/tests/test_page.py b/lms/djangoapps/branding/tests/test_page.py index 46f10bd6923a..683ef62c3f23 100644 --- a/lms/djangoapps/branding/tests/test_page.py +++ b/lms/djangoapps/branding/tests/test_page.py @@ -13,7 +13,6 @@ from django.test.utils import override_settings from django.urls import reverse from milestones.tests.utils import MilestonesTestCaseMixin -from pytz import UTC from common.djangoapps.edxmako.shortcuts import render_to_response from common.djangoapps.util.milestones_helpers import set_prerequisite_courses @@ -23,6 +22,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order from xmodule.course_block import CATALOG_VISIBILITY_ABOUT, CATALOG_VISIBILITY_NONE +from zoneinfo import ZoneInfo FEATURES_WITH_STARTDATE = settings.FEATURES.copy() FEATURES_WITH_STARTDATE['DISABLE_START_DATES'] = False @@ -48,7 +48,7 @@ def setUp(self): self.factory = RequestFactory() self.course = CourseFactory.create( days_early_for_beta=5, - enrollment_start=datetime.datetime.now(UTC) + datetime.timedelta(days=3), + enrollment_start=datetime.datetime.now(ZoneInfo("UTC")) + datetime.timedelta(days=3), user_id=self.user.id, ) @@ -181,8 +181,8 @@ def setUp(self): number='1000', display_name='Starting later, Announced later', metadata={ - 'start': datetime.datetime.now(UTC) + datetime.timedelta(days=4), - 'announcement': datetime.datetime.now(UTC) + datetime.timedelta(days=3), + 'start': datetime.datetime.now(ZoneInfo("UTC")) + datetime.timedelta(days=4), + 'announcement': datetime.datetime.now(ZoneInfo("UTC")) + datetime.timedelta(days=3), }, emit_signals=True, ) @@ -191,8 +191,8 @@ def setUp(self): number='1001', display_name='Starting earlier, Announced earlier', metadata={ - 'start': datetime.datetime.now(UTC) + datetime.timedelta(days=2), - 'announcement': datetime.datetime.now(UTC) + datetime.timedelta(days=1), + 'start': datetime.datetime.now(ZoneInfo("UTC")) + datetime.timedelta(days=2), + 'announcement': datetime.datetime.now(ZoneInfo("UTC")) + datetime.timedelta(days=1), }, emit_signals=True, ) diff --git a/lms/djangoapps/bulk_email/tests/test_models.py b/lms/djangoapps/bulk_email/tests/test_models.py index 43062492e842..805b27dbdd15 100644 --- a/lms/djangoapps/bulk_email/tests/test_models.py +++ b/lms/djangoapps/bulk_email/tests/test_models.py @@ -12,7 +12,6 @@ from django.test import TestCase from django.test.utils import override_settings from opaque_keys.edx.keys import CourseKey -from pytz import UTC from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory, StaffFactory @@ -35,6 +34,7 @@ from openedx.core.djangoapps.course_groups.models import CourseCohort from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo @ddt.ddt @@ -84,8 +84,8 @@ def test_bad_to_option(self): CourseEmail.create(course_id, sender, to_option, subject, html_message) @ddt.data( - datetime.datetime(1999, 1, 1, tzinfo=UTC), - datetime.datetime(datetime.MAXYEAR, 1, 1, tzinfo=UTC), + datetime.datetime(1999, 1, 1, tzinfo=ZoneInfo("UTC")), + datetime.datetime(datetime.MAXYEAR, 1, 1, tzinfo=ZoneInfo("UTC")), ) def test_track_target(self, expiration_datetime): """ diff --git a/lms/djangoapps/ccx/api/v0/views.py b/lms/djangoapps/ccx/api/v0/views.py index 8ca15e065006..4afcf04dc9f4 100644 --- a/lms/djangoapps/ccx/api/v0/views.py +++ b/lms/djangoapps/ccx/api/v0/views.py @@ -5,7 +5,6 @@ import json import logging -import pytz from ccx_keys.locator import CCXLocator from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.db import transaction @@ -30,6 +29,7 @@ from openedx.core.lib.api import authentication, permissions from openedx.core.lib.courses import get_course_by_id from xmodule.modulestore.django import SignalHandler # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo from .paginators import CCXAPIPagination from .serializers import CCXCourseSerializer @@ -465,7 +465,7 @@ def post(self, request): ccx_course_object.save() # Make sure start/due are overridden for entire course - start = TODAY().replace(tzinfo=pytz.UTC) + start = TODAY().replace(tzinfo=ZoneInfo("UTC")) override_field_for_ccx(ccx_course_object, master_course_object, 'start', start) override_field_for_ccx(ccx_course_object, master_course_object, 'due', None) diff --git a/lms/djangoapps/ccx/models.py b/lms/djangoapps/ccx/models.py index 21f0136f0709..e3b69d71bf07 100644 --- a/lms/djangoapps/ccx/models.py +++ b/lms/djangoapps/ccx/models.py @@ -12,10 +12,10 @@ from django.db import models from lazy import lazy from opaque_keys.edx.django.models import CourseKeyField, UsageKeyField -from pytz import utc from xmodule.error_block import ErrorBlock from xmodule.modulestore.django import modulestore +from zoneinfo import ZoneInfo log = logging.getLogger("edx.ccx") @@ -76,14 +76,13 @@ def max_student_enrollments_allowed(self): def has_started(self): """Return True if the CCX start date is in the past""" - return datetime.now(utc) > self.start + return datetime.now(ZoneInfo("UTC")) > self.start def has_ended(self): """Return True if the CCX due date is set and is in the past""" if self.due is None: return False - - return datetime.now(utc) > self.due + return datetime.now(ZoneInfo("UTC")) > self.due @property def structure(self): diff --git a/lms/djangoapps/ccx/tests/test_ccx_modulestore.py b/lms/djangoapps/ccx/tests/test_ccx_modulestore.py index f5e98a942cde..04be2f0499b1 100644 --- a/lms/djangoapps/ccx/tests/test_ccx_modulestore.py +++ b/lms/djangoapps/ccx/tests/test_ccx_modulestore.py @@ -7,7 +7,6 @@ from collections import deque from itertools import chain -import pytz from ccx_keys.locator import CCXLocator from six.moves import zip_longest from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase @@ -15,6 +14,7 @@ from common.djangoapps.student.tests.factories import AdminFactory, UserFactory from lms.djangoapps.ccx.models import CustomCourseForEdX +from zoneinfo import ZoneInfo class TestCCXModulestoreWrapper(SharedModuleStoreTestCase): @@ -24,8 +24,8 @@ class TestCCXModulestoreWrapper(SharedModuleStoreTestCase): def setUpClass(cls): super().setUpClass() cls.course = CourseFactory.create() - start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=pytz.UTC) - due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=pytz.UTC) + start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=ZoneInfo("UTC")) + due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=ZoneInfo("UTC")) # Create a course outline cls.chapters = chapters = [ BlockFactory.create(start=start, parent=cls.course) for _ in range(2) diff --git a/lms/djangoapps/ccx/tests/test_field_override_performance.py b/lms/djangoapps/ccx/tests/test_field_override_performance.py index a7128495eb59..03e9673e0309 100644 --- a/lms/djangoapps/ccx/tests/test_field_override_performance.py +++ b/lms/djangoapps/ccx/tests/test_field_override_performance.py @@ -18,7 +18,6 @@ from django.test.utils import override_settings from edx_django_utils.cache import RequestCache from opaque_keys.edx.keys import CourseKey -from pytz import UTC from xblock.core import XBlock from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls, check_sum_of_calls @@ -33,6 +32,7 @@ from openedx.core.djangoapps.content.block_structure.api import get_course_in_cache from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES from openedx.features.content_type_gating.models import ContentTypeGatingConfig +from zoneinfo import ZoneInfo QUERY_COUNT_TABLE_IGNORELIST = WAFFLE_TABLES @@ -117,7 +117,7 @@ def setup_course(self, size, enable_ccx, view_as_ccx): self.course = CourseFactory.create( graded=True, - start=datetime.now(UTC), + start=datetime.now(ZoneInfo("UTC")), grading_policy=grading_policy, enable_ccx=enable_ccx, ) diff --git a/lms/djangoapps/ccx/tests/test_models.py b/lms/djangoapps/ccx/tests/test_models.py index 11bd7e43d312..0a21520f518d 100644 --- a/lms/djangoapps/ccx/tests/test_models.py +++ b/lms/djangoapps/ccx/tests/test_models.py @@ -7,12 +7,12 @@ from datetime import datetime, timedelta import ddt -from pytz import utc from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls from common.djangoapps.student.roles import CourseCcxCoachRole from common.djangoapps.student.tests.factories import AdminFactory +from zoneinfo import ZoneInfo from ..overrides import override_field_for_ccx from .factories import CcxFactory @@ -59,7 +59,7 @@ def test_ccx_start_is_correct(self): For this reason we test the difference between and make sure it is less than one second. """ - expected = datetime.now(utc) + expected = datetime.now(ZoneInfo("UTC")) self.set_ccx_override('start', expected) actual = self.ccx.start diff = expected - actual @@ -67,7 +67,7 @@ def test_ccx_start_is_correct(self): def test_ccx_start_caching(self): """verify that caching the start property works to limit queries""" - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) self.set_ccx_override('start', now) with check_mongo_calls(2): # these statements are used entirely to demonstrate the @@ -84,7 +84,7 @@ def test_ccx_due_without_override(self): def test_ccx_due_is_correct(self): """verify that the due datetime for a ccx is correctly retrieved""" - expected = datetime.now(utc) + expected = datetime.now(ZoneInfo("UTC")) self.set_ccx_override('due', expected) actual = self.ccx.due diff = expected - actual @@ -92,7 +92,7 @@ def test_ccx_due_is_correct(self): def test_ccx_due_caching(self): """verify that caching the due property works to limit queries""" - expected = datetime.now(utc) + expected = datetime.now(ZoneInfo("UTC")) self.set_ccx_override('due', expected) with check_mongo_calls(2): # these statements are used entirely to demonstrate the @@ -104,7 +104,7 @@ def test_ccx_due_caching(self): def test_ccx_has_started(self): """verify that a ccx marked as starting yesterday has started""" - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) delta = timedelta(1) then = now - delta self.set_ccx_override('start', then) @@ -112,7 +112,7 @@ def test_ccx_has_started(self): def test_ccx_has_not_started(self): """verify that a ccx marked as starting tomorrow has not started""" - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) delta = timedelta(1) then = now + delta self.set_ccx_override('start', then) @@ -120,7 +120,7 @@ def test_ccx_has_not_started(self): def test_ccx_has_ended(self): """verify that a ccx that has a due date in the past has ended""" - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) delta = timedelta(1) then = now - delta self.set_ccx_override('due', then) @@ -129,7 +129,7 @@ def test_ccx_has_ended(self): def test_ccx_has_not_ended(self): """verify that a ccx that has a due date in the future has not eneded """ - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) delta = timedelta(1) then = now + delta self.set_ccx_override('due', then) diff --git a/lms/djangoapps/ccx/tests/test_overrides.py b/lms/djangoapps/ccx/tests/test_overrides.py index c6822f9ab76f..a2191eaed5c2 100644 --- a/lms/djangoapps/ccx/tests/test_overrides.py +++ b/lms/djangoapps/ccx/tests/test_overrides.py @@ -6,7 +6,6 @@ import datetime from unittest import mock -import pytz from ccx_keys.locator import CCXLocator from django.test.utils import override_settings from edx_django_utils.cache import RequestCache @@ -21,6 +20,7 @@ from lms.djangoapps.courseware.tests.test_field_overrides import inject_field_overrides from lms.djangoapps.courseware.testutils import FieldOverrideTestMixin from openedx.core.lib.courses import get_course_by_id +from zoneinfo import ZoneInfo @override_settings( @@ -41,8 +41,8 @@ def setUpClass(cls): cls.course.enable_ccx = True # Create a course outline - start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=pytz.UTC) - due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=pytz.UTC) + start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=ZoneInfo("UTC")) + due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=ZoneInfo("UTC")) chapters = [BlockFactory.create(start=start, parent=cls.course) for _ in range(2)] sequentials = flatten([ @@ -91,7 +91,7 @@ def test_override_start(self): """ Test that overriding start date on a chapter works. """ - ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) + ccx_start = datetime.datetime(2014, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) assert chapter.start == ccx_start @@ -100,7 +100,7 @@ def test_override_num_queries_new_field(self): """ Test that for creating new field executed only create query """ - ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) + ccx_start = datetime.datetime(2014, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] # One outer SAVEPOINT/RELEASE SAVEPOINT pair around everything caused by the # transaction.atomic decorator wrapping override_field_for_ccx. @@ -114,8 +114,8 @@ def test_override_num_queries_update_existing_field(self): """ Test that overriding existing field executed create, fetch and update queries. """ - ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) - new_ccx_start = datetime.datetime(2015, 12, 25, 00, 00, tzinfo=pytz.UTC) + ccx_start = datetime.datetime(2014, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) + new_ccx_start = datetime.datetime(2015, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) with self.assertNumQueries(3): @@ -125,7 +125,7 @@ def test_override_num_queries_field_value_not_changed(self): """ Test that if value of field does not changed no query execute. """ - ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) + ccx_start = datetime.datetime(2014, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) with self.assertNumQueries(2): # 2 savepoints @@ -135,7 +135,7 @@ def test_overriden_field_access_produces_no_extra_queries(self): """ Test no extra queries when accessing an overriden field more than once. """ - ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) + ccx_start = datetime.datetime(2014, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] # One outer SAVEPOINT/RELEASE SAVEPOINT pair around everything caused by the # transaction.atomic decorator wrapping override_field_for_ccx. @@ -149,7 +149,7 @@ def test_override_is_inherited(self): """ Test that sequentials inherit overridden start date from chapter. """ - ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) + ccx_start = datetime.datetime(2014, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) assert chapter.get_children()[0].start == ccx_start @@ -161,7 +161,7 @@ def test_override_is_inherited_even_if_set_in_mooc(self): (verticals) even if a due date is set explicitly on grandchildren in the mooc. """ - ccx_due = datetime.datetime(2015, 1, 1, 00, 00, tzinfo=pytz.UTC) + ccx_due = datetime.datetime(2015, 1, 1, 0, 0, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] chapter.display_name = 'itsme!' override_field_for_ccx(self.ccx, chapter, 'due', ccx_due) diff --git a/lms/djangoapps/ccx/tests/test_views.py b/lms/djangoapps/ccx/tests/test_views.py index e993d5dc0b7b..f3e157a290e6 100644 --- a/lms/djangoapps/ccx/tests/test_views.py +++ b/lms/djangoapps/ccx/tests/test_views.py @@ -18,7 +18,6 @@ from django.utils.translation import gettext as _ from edx_django_utils.cache import RequestCache from opaque_keys.edx.keys import CourseKey -from pytz import UTC from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase @@ -46,6 +45,7 @@ from openedx.core.djangoapps.django_comment_common.models import FORUM_ROLE_ADMINISTRATOR from openedx.core.djangoapps.django_comment_common.utils import are_permissions_roles_seeded from openedx.core.lib.courses import get_course_by_id +from zoneinfo import ZoneInfo def intercept_renderer(path, context): @@ -176,8 +176,8 @@ def setUpClass(cls): Set up tests """ super().setUpClass() - start = datetime.datetime(2016, 7, 1, 0, 0, tzinfo=UTC) - due = datetime.datetime(2016, 7, 8, 0, 0, tzinfo=UTC) + start = datetime.datetime(2016, 7, 1, 0, 0, tzinfo=ZoneInfo("UTC")) + due = datetime.datetime(2016, 7, 8, 0, 0, tzinfo=ZoneInfo("UTC")) cls.course = course = CourseFactory.create(enable_ccx=True, start=start) chapter = BlockFactory.create(start=start, parent=course, category='chapter') @@ -466,7 +466,7 @@ def test_edit_schedule(self, today): """ Get CCX schedule, modify it, save it. """ - today.return_value = datetime.datetime(2014, 11, 25, tzinfo=UTC) + today.return_value = datetime.datetime(2014, 11, 25, tzinfo=ZoneInfo("UTC")) self.make_coach() ccx = self.make_ccx() url = reverse( @@ -805,10 +805,10 @@ def setUp(self): # Create a course outline self.mooc_start = start = datetime.datetime( - 2010, 5, 12, 2, 42, tzinfo=UTC + 2010, 5, 12, 2, 42, tzinfo=ZoneInfo("UTC") ) self.mooc_due = due = datetime.datetime( - 2010, 7, 7, 0, 0, tzinfo=UTC + 2010, 7, 7, 0, 0, tzinfo=ZoneInfo("UTC") ) self.chapters = [ @@ -890,7 +890,7 @@ def test_get_ccx_schedule(self, today): Hides nodes at a different depth and checks that these nodes are not in the schedule. """ - today.return_value = datetime.datetime(2014, 11, 25, tzinfo=UTC) + today.return_value = datetime.datetime(2014, 11, 25, tzinfo=ZoneInfo("UTC")) self.make_coach() ccx = self.make_ccx() url = reverse( @@ -950,7 +950,7 @@ def setUpClass(cls): # Create a course outline cls.mooc_start = start = datetime.datetime( - 2010, 5, 12, 2, 42, tzinfo=UTC + 2010, 5, 12, 2, 42, tzinfo=ZoneInfo("UTC") ) chapter = BlockFactory.create( start=start, parent=course, category='sequential' @@ -1206,7 +1206,7 @@ def setUp(self): # Create a CCX course and enroll the user in it. self.ccx = CcxFactory(course_id=self.split_course.id, coach=self.coach) - last_week = datetime.datetime.now(UTC) - datetime.timedelta(days=7) + last_week = datetime.datetime.now(ZoneInfo("UTC")) - datetime.timedelta(days=7) override_field_for_ccx(self.ccx, self.split_course, 'start', last_week) # Required by self.ccx.has_started(). self.ccx_course_key = CCXLocator.from_course_locator(self.split_course.id, self.ccx.id) CourseEnrollment.enroll(self.student, self.ccx_course_key) diff --git a/lms/djangoapps/ccx/tests/utils.py b/lms/djangoapps/ccx/tests/utils.py index 4f8c34e14c2d..d02ca9647c62 100644 --- a/lms/djangoapps/ccx/tests/utils.py +++ b/lms/djangoapps/ccx/tests/utils.py @@ -5,7 +5,6 @@ import datetime -import pytz from django.conf import settings from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase @@ -16,6 +15,7 @@ from lms.djangoapps.ccx.overrides import override_field_for_ccx from lms.djangoapps.ccx.tests.factories import CcxFactory from openedx.core.djangoapps.ace_common.tests.mixins import EmailTemplateTagMixin +from zoneinfo import ZoneInfo class CcxTestCase(EmailTemplateTagMixin, SharedModuleStoreTestCase): @@ -31,10 +31,10 @@ def setUpClass(cls): # Create a course outline cls.mooc_start = start = datetime.datetime( - 2010, 5, 12, 2, 42, tzinfo=pytz.UTC + 2010, 5, 12, 2, 42, tzinfo=ZoneInfo("UTC") ) cls.mooc_due = due = datetime.datetime( - 2010, 7, 7, 0, 0, tzinfo=pytz.UTC + 2010, 7, 7, 0, 0, tzinfo=ZoneInfo("UTC") ) cls.chapters = [ diff --git a/lms/djangoapps/ccx/utils.py b/lms/djangoapps/ccx/utils.py index 28ecbba34947..ab350acc6d8f 100644 --- a/lms/djangoapps/ccx/utils.py +++ b/lms/djangoapps/ccx/utils.py @@ -10,7 +10,6 @@ from contextlib import contextmanager from smtplib import SMTPException -import pytz from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.core.exceptions import ValidationError from django.core.validators import validate_email @@ -28,6 +27,7 @@ from lms.djangoapps.instructor.views.tools import get_student_from_identifier from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.lib.courses import get_course_by_id +from zoneinfo import ZoneInfo log = logging.getLogger("edx.ccx") @@ -148,7 +148,7 @@ def parse_date(datestring): hour, minute = list(map(int, time.split(':'))) if validate_date(year, month, day, hour, minute): return datetime.datetime( - year, month, day, hour, minute, tzinfo=pytz.UTC) + year, month, day, hour, minute, tzinfo=ZoneInfo("UTC")) return None diff --git a/lms/djangoapps/ccx/views.py b/lms/djangoapps/ccx/views.py index 7c6a75aaf6d4..0f61870139ed 100644 --- a/lms/djangoapps/ccx/views.py +++ b/lms/djangoapps/ccx/views.py @@ -8,7 +8,6 @@ import logging from copy import deepcopy -import pytz from ccx_keys.locator import CCXLocator from django.conf import settings from django.contrib import messages @@ -190,7 +189,7 @@ def create_ccx(request, course, ccx=None): ccx.save() # Make sure start/due are overridden for entire course - start = TODAY().replace(tzinfo=pytz.UTC) + start = TODAY().replace(tzinfo=ZoneInfo("UTC")) override_field_for_ccx(ccx, course, 'start', start) override_field_for_ccx(ccx, course, 'due', None) diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py index 02b254ffad6c..1e1d97dff4bc 100644 --- a/lms/djangoapps/certificates/api.py +++ b/lms/djangoapps/certificates/api.py @@ -17,7 +17,6 @@ from opaque_keys.edx.django.models import CourseKeyField from opaque_keys.edx.keys import CourseKey from organizations.api import get_course_organization_id -from pytz import UTC from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.api import is_user_enrolled_in_course @@ -48,6 +47,7 @@ from openedx.core.djangoapps.content.course_overviews.api import get_course_overview_or_none from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo log = logging.getLogger("edx.certificate") User = get_user_model() @@ -887,7 +887,7 @@ def display_date_for_certificate(course, certificate): except ObjectDoesNotExist: pass - if _course_uses_available_date(course) and course.certificate_available_date < datetime.now(UTC): + if _course_uses_available_date(course) and course.certificate_available_date < datetime.now(ZoneInfo("UTC")): return course.certificate_available_date # It is possible for a self-paced course run to end up configured with a display behavior of "END" even though it # shouldn't be a valid option. We must check if the course is instructor-paced here to ensure that we are selecting diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py index ca12c989660d..cd1bcbc1be3a 100644 --- a/lms/djangoapps/certificates/tests/test_api.py +++ b/lms/djangoapps/certificates/tests/test_api.py @@ -7,7 +7,6 @@ from unittest.mock import patch import ddt -import pytz from config_models.models import cache from django.conf import settings from django.test import RequestFactory, TestCase @@ -70,6 +69,7 @@ from xmodule.data import CertificatesDisplayBehaviors from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory +from zoneinfo import ZoneInfo CAN_GENERATE_METHOD = "lms.djangoapps.certificates.generation_handler._can_generate_regular_certificate" BETA_TESTER_METHOD = "lms.djangoapps.certificates.api.access.is_beta_tester" @@ -120,9 +120,9 @@ def setUp(self): org="edx", number="verified", display_name="Verified Course", - end=datetime.now(pytz.UTC), + end=datetime.now(ZoneInfo("UTC")), self_paced=False, - certificate_available_date=datetime.now(pytz.UTC) - timedelta(days=2), + certificate_available_date=datetime.now(ZoneInfo("UTC")) - timedelta(days=2), ) GeneratedCertificateFactory.create( @@ -233,7 +233,7 @@ def test_cert_api_return( """ Test 'downloadable status' """ - cert_avail_date = datetime.now(pytz.UTC) + cert_avail_delta + cert_avail_date = datetime.now(ZoneInfo("UTC")) + cert_avail_delta self.course.self_paced = self_paced self.course.certificate_available_date = cert_avail_date self.course.certificates_display_behavior = certificates_display_behavior @@ -1046,8 +1046,8 @@ def __init__(self, user=None, course_id=None, mode=None, status=None): self.course_id = course_id self.mode = mode self.status = status - self.created_date = datetime.now(pytz.UTC) - self.modified_date = datetime.now(pytz.UTC) + self.created_date = datetime.now(ZoneInfo("UTC")) + self.modified_date = datetime.now(ZoneInfo("UTC")) self.date_override = None def is_valid(self): @@ -1059,7 +1059,7 @@ def is_valid(self): class MockCertificateDateOverride: def __init__(self, date=None): - self.date = date or datetime.now(pytz.UTC) + self.date = date or datetime.now(ZoneInfo("UTC")) @contextmanager @@ -1080,8 +1080,8 @@ class CertificatesApiTestCase(TestCase): def setUp(self): super().setUp() self.course = CourseOverviewFactory.create( - start=datetime(2017, 1, 1, tzinfo=pytz.UTC), - end=datetime(2017, 1, 31, tzinfo=pytz.UTC), + start=datetime(2017, 1, 1, tzinfo=ZoneInfo("UTC")), + end=datetime(2017, 1, 31, tzinfo=ZoneInfo("UTC")), certificate_available_date=None, ) self.user = UserFactory.create() @@ -1126,14 +1126,14 @@ def test_available_vs_display_date(self, feature_enabled, is_self_paced, uses_av assert self.certificate.modified_date == display_date_for_certificate(self.course, self.certificate) # With an available date set in the past, both return the available date (if configured) - self.course.certificate_available_date = datetime(2017, 2, 1, tzinfo=pytz.UTC) + self.course.certificate_available_date = datetime(2017, 2, 1, tzinfo=ZoneInfo("UTC")) self.course.certificates_display_behavior = CertificatesDisplayBehaviors.END_WITH_DATE maybe_avail = self.course.certificate_available_date if uses_avail_date else self.certificate.modified_date assert maybe_avail == available_date_for_certificate(self.course, self.certificate) assert maybe_avail == display_date_for_certificate(self.course, self.certificate) # With a future available date, they each return a different date - self.course.certificate_available_date = datetime.max.replace(tzinfo=pytz.UTC) + self.course.certificate_available_date = datetime.max.replace(tzinfo=ZoneInfo("UTC")) maybe_avail = self.course.certificate_available_date if uses_avail_date else self.certificate.modified_date assert maybe_avail == available_date_for_certificate(self.course, self.certificate) assert self.certificate.modified_date == display_date_for_certificate(self.course, self.certificate) diff --git a/lms/djangoapps/certificates/tests/test_utils.py b/lms/djangoapps/certificates/tests/test_utils.py index 298e624fdd8e..56b4c280d4ef 100644 --- a/lms/djangoapps/certificates/tests/test_utils.py +++ b/lms/djangoapps/certificates/tests/test_utils.py @@ -6,13 +6,13 @@ import ddt from django.test import TestCase -from pytz import utc from lms.djangoapps.certificates.utils import has_html_certificates_enabled, should_certificate_be_visible from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo -_TODAY = datetime.now(utc) +_TODAY = datetime.now(ZoneInfo("UTC")) _LAST_MONTH = _TODAY - timedelta(days=30) _LAST_WEEK = _TODAY - timedelta(days=7) _NEXT_WEEK = _TODAY + timedelta(days=7) diff --git a/lms/djangoapps/certificates/tests/tests.py b/lms/djangoapps/certificates/tests/tests.py index a6b6362792b8..a49c5438f043 100644 --- a/lms/djangoapps/certificates/tests/tests.py +++ b/lms/djangoapps/certificates/tests/tests.py @@ -9,7 +9,6 @@ from ddt import data, ddt, unpack from django.conf import settings from milestones.tests.utils import MilestonesTestCaseMixin -from pytz import UTC from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import UserFactory @@ -22,6 +21,7 @@ from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo @ddt @@ -33,7 +33,7 @@ class CertificatesModelTest(ModuleStoreTestCase, MilestonesTestCaseMixin): def setUp(self): super().setUp() - today = datetime.now(UTC) + today = datetime.now(ZoneInfo("UTC")) self.instructor_paced_course = CourseFactory.create( org='edx', number='instructor', display_name='Instructor Paced Course', start=today - timedelta(days=30), diff --git a/lms/djangoapps/certificates/utils.py b/lms/djangoapps/certificates/utils.py index 7ff2a4c97b27..3479c551fe28 100644 --- a/lms/djangoapps/certificates/utils.py +++ b/lms/djangoapps/certificates/utils.py @@ -8,7 +8,6 @@ from django.urls import reverse from eventtracking import tracker from opaque_keys.edx.keys import CourseKey -from pytz import utc from common.djangoapps.student import models_api as student_api from lms.djangoapps.certificates.data import CertificateStatuses @@ -16,6 +15,7 @@ from openedx.core.djangoapps.content.course_overviews.api import get_course_overview_or_none from openedx.features.name_affirmation_api.utils import get_name_affirmation_service from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo log = logging.getLogger(__name__) @@ -160,7 +160,7 @@ def should_certificate_be_visible( past_available_date = ( certificates_display_behavior == CertificatesDisplayBehaviors.END_WITH_DATE and certificate_available_date - and certificate_available_date < datetime.now(utc) + and certificate_available_date < datetime.now(ZoneInfo("UTC")) ) ended_without_available_date = ( certificates_display_behavior == CertificatesDisplayBehaviors.END diff --git a/lms/djangoapps/commerce/api/v0/tests/test_views.py b/lms/djangoapps/commerce/api/v0/tests/test_views.py index ef9327fe6f43..44fbc4f18985 100644 --- a/lms/djangoapps/commerce/api/v0/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v0/tests/test_views.py @@ -8,7 +8,6 @@ from uuid import uuid4 import ddt -import pytz from django.conf import settings from django.test import TestCase from django.test.utils import override_settings @@ -24,6 +23,7 @@ from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo from ....constants import Messages from ....tests.mocks import mock_basket_order @@ -272,7 +272,7 @@ def test_closed_course(self): """ Verifies that the view returns HTTP 406 when a course is closed. """ - self.course.enrollment_end = datetime.now(pytz.UTC) - timedelta(days=1) + self.course.enrollment_end = datetime.now(ZoneInfo("UTC")) - timedelta(days=1) modulestore().update_item(self.course, self.user.id) assert self._post_to_view().status_code == 406 diff --git a/lms/djangoapps/commerce/api/v1/serializers.py b/lms/djangoapps/commerce/api/v1/serializers.py index 8d7fb25cb463..a2aa6de2f565 100644 --- a/lms/djangoapps/commerce/api/v1/serializers.py +++ b/lms/djangoapps/commerce/api/v1/serializers.py @@ -3,7 +3,6 @@ from datetime import datetime -import pytz from django.utils.translation import gettext as _ from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey @@ -11,6 +10,7 @@ from common.djangoapps.course_modes.models import CourseMode from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo from .models import UNDEFINED, Course @@ -95,7 +95,7 @@ def validate(self, attrs): if expires: # If we don't already have an upgrade_deadline value, use datetime.max so that we can actually # complete the comparison. - upgrade_deadline = min(expires, upgrade_deadline or datetime.max.replace(tzinfo=pytz.utc)) + upgrade_deadline = min(expires, upgrade_deadline or datetime.max.replace(tzinfo=ZoneInfo("UTC"))) # In cases where upgrade_deadline is None (e.g. the verified professional mode), allow a verification # deadline to be set anyway. diff --git a/lms/djangoapps/commerce/api/v1/tests/test_views.py b/lms/djangoapps/commerce/api/v1/tests/test_views.py index 71b9a4810f19..120e219c4be2 100644 --- a/lms/djangoapps/commerce/api/v1/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v1/tests/test_views.py @@ -6,7 +6,6 @@ from datetime import datetime, timedelta import ddt -import pytz from django.conf import settings from django.contrib.auth.models import Permission from django.test import TestCase @@ -19,6 +18,7 @@ from lms.djangoapps.verify_student.models import VerificationDeadline from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo from ....tests.mocks import mock_order_endpoint from ....tests.test_views import UserMixin @@ -176,7 +176,7 @@ def test_update(self): assert VerificationDeadline.deadline_for_course(self.course.id) is None # Generate the expected data - now = datetime.now(pytz.utc) + now = datetime.now(ZoneInfo("UTC")) verification_deadline = now + timedelta(days=1) expiration_datetime = now response, expected = self._get_update_response_and_expected_data(expiration_datetime, verification_deadline) @@ -195,8 +195,8 @@ def test_update_invalid_dates(self): """ Verify the API does not allow the verification deadline to be set before the course mode upgrade deadlines. """ - expiration_datetime = datetime.now(pytz.utc) - verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc) + expiration_datetime = datetime.now(ZoneInfo("UTC")) + verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=ZoneInfo("UTC")) response, __ = self._get_update_response_and_expected_data(expiration_datetime, verification_deadline) assert response.status_code == 400 @@ -212,7 +212,7 @@ def test_update_verification_deadline_without_expiring_modes(self): This accounts for the verified professional mode, which requires verification but should never expire. """ - verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc) + verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=ZoneInfo("UTC")) response, __ = self._get_update_response_and_expected_data(None, verification_deadline) assert response.status_code == 200 @@ -222,7 +222,7 @@ def test_update_remove_verification_deadline(self): """ Verify that verification deadlines can be removed through the API. """ - verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc) + verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=ZoneInfo("UTC")) response, __ = self._get_update_response_and_expected_data(None, verification_deadline) assert VerificationDeadline.deadline_for_course(self.course.id) == verification_deadline @@ -247,7 +247,7 @@ def test_update_verification_deadline_left_alone(self): When the course's verification deadline is set and an update request doesn't include it, we should take no action on it. """ - verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc) + verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=ZoneInfo("UTC")) response, __ = self._get_update_response_and_expected_data(None, verification_deadline) assert VerificationDeadline.deadline_for_course(self.course.id) == verification_deadline @@ -273,7 +273,7 @@ def test_remove_upgrade_deadline(self): Verify that course mode upgrade deadlines can be removed through the API. """ # First create a deadline - upgrade_deadline = datetime.now(pytz.utc) + timedelta(days=1) + upgrade_deadline = datetime.now(ZoneInfo("UTC")) + timedelta(days=1) response, __ = self._get_update_response_and_expected_data(upgrade_deadline, None) assert response.status_code == 200 verified_mode = CourseMode.verified_mode_for_course(self.course.id) diff --git a/lms/djangoapps/course_blocks/transformers/start_date.py b/lms/djangoapps/course_blocks/transformers/start_date.py index 13ba3b7470c2..9ab30de0bd62 100644 --- a/lms/djangoapps/course_blocks/transformers/start_date.py +++ b/lms/djangoapps/course_blocks/transformers/start_date.py @@ -4,7 +4,6 @@ from datetime import datetime -from pytz import UTC from lms.djangoapps.courseware.access_utils import check_start_date from openedx.core.djangoapps.content.block_structure.transformer import ( @@ -12,6 +11,7 @@ FilteringTransformerMixin ) from xmodule.course_metadata_utils import DEFAULT_START_DATE # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo from .utils import collect_merged_date_field @@ -94,7 +94,7 @@ def transform_block_filters(self, usage_info, block_structure): if usage_info.has_staff_access or usage_info.allow_start_dates_in_future: return [block_structure.create_universal_filter()] - now = datetime.now(UTC) + now = datetime.now(ZoneInfo("UTC")) def _removal_condition(block_key): return not check_start_date( diff --git a/lms/djangoapps/course_blocks/transformers/tests/test_load_override_data.py b/lms/djangoapps/course_blocks/transformers/tests/test_load_override_data.py index d0fef5065b43..f7b794c4ca25 100644 --- a/lms/djangoapps/course_blocks/transformers/tests/test_load_override_data.py +++ b/lms/djangoapps/course_blocks/transformers/tests/test_load_override_data.py @@ -6,7 +6,6 @@ import datetime import ddt -import pytz from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase from xmodule.modulestore.tests.factories import ToyCourseFactory @@ -15,14 +14,15 @@ from lms.djangoapps.course_blocks.transformers.load_override_data import REQUESTED_FIELDS, OverrideDataTransformer from lms.djangoapps.courseware.student_field_overrides import get_override_for_user, override_field_for_user from openedx.core.djangoapps.content.block_structure.factory import BlockStructureFactory +from zoneinfo import ZoneInfo expected_overrides = { 'start': datetime.datetime( - 2017, 1, 20, 2, 42, tzinfo=pytz.UTC + 2017, 1, 20, 2, 42, tzinfo=ZoneInfo("UTC") ), 'display_name': "Section", 'due': datetime.datetime( - 2017, 2, 20, 2, 42, tzinfo=pytz.UTC + 2017, 2, 20, 2, 42, tzinfo=ZoneInfo("UTC") ) } diff --git a/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py b/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py index 46d46832a6d9..d6fa6bec6057 100644 --- a/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py +++ b/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py @@ -5,7 +5,6 @@ from botocore.exceptions import NoCredentialsError from django.contrib.sites.models import Site from edx_ace import Recipient, Message -from pytz import UTC from unittest import mock # lint-amnesty, pylint: disable=wrong-import-order import ddt @@ -29,6 +28,7 @@ from openedx.core.djangolib.testing.utils import skip_unless_lms from openedx.core.lib.celery.task_utils import emulate_http_request from openedx.features.course_experience import ENABLE_COURSE_GOALS, ENABLE_SES_FOR_GOALREMINDER +from zoneinfo import ZoneInfo # Some constants just for clarity of tests (assuming week starts on a Monday, as March 2021 used below does) MONDAY = 0 @@ -55,8 +55,8 @@ def make_valid_goal(self, **kwargs): """Creates a goal that will cause an email to be sent as the goal is valid but has been missed""" kwargs.setdefault('days_per_week', 6) kwargs.setdefault('subscribed_to_reminders', True) - kwargs.setdefault('overview__start', datetime(2021, 1, 1, tzinfo=UTC)) - kwargs.setdefault('overview__end', datetime(2021, 4, 1, tzinfo=UTC)) # Have it end in the future + kwargs.setdefault('overview__start', datetime(2021, 1, 1, tzinfo=ZoneInfo("UTC"))) + kwargs.setdefault('overview__end', datetime(2021, 4, 1, tzinfo=ZoneInfo("UTC"))) # Have it end in the future goal = CourseGoalFactory(**kwargs) with freeze_time('2021-02-01 10:00:00'): # Create enrollment before March @@ -114,7 +114,7 @@ def test_will_send_on_right_day(self, days_per_week, days_of_activity, current_d """Verify that via the normal conditions, we either send or not based on the days of activity""" goal = self.make_valid_goal(days_per_week=days_per_week) for day in range(days_of_activity): - UserActivityFactory(user=goal.user, course_key=goal.course_key, date=datetime(2021, 3, day + 1, tzinfo=UTC)) + UserActivityFactory(user=goal.user, course_key=goal.course_key, date=datetime(2021, 3, day + 1, tzinfo=ZoneInfo("UTC"))) self.call_command(day=current_day, expect_sent=expect_sent) @@ -150,12 +150,12 @@ def test_inactive_enrollment(self): def test_recent_enrollment(self): self.make_valid_goal() - CourseEnrollment.objects.update(created=datetime(2021, 3, 1, tzinfo=UTC)) + CourseEnrollment.objects.update(created=datetime(2021, 3, 1, tzinfo=ZoneInfo("UTC"))) self.call_command(expect_sent=False) @ddt.data( - (datetime(2021, 3, 8, tzinfo=UTC), True), - (datetime(2021, 3, 7, tzinfo=UTC), False), + (datetime(2021, 3, 8, tzinfo=ZoneInfo("UTC")), True), + (datetime(2021, 3, 7, tzinfo=ZoneInfo("UTC")), False), ) @ddt.unpack @mock.patch('lms.djangoapps.course_goals.management.commands.goal_reminder_email.get_user_course_expiration_date') @@ -183,8 +183,8 @@ def test_no_days_per_week(self): self.call_command(expect_sent=False) @ddt.data( - datetime(2021, 2, 1, tzinfo=UTC), # very over and done with - datetime(2021, 3, 7, tzinfo=UTC), # ending this Sunday + datetime(2021, 2, 1, tzinfo=ZoneInfo("UTC")), # very over and done with + datetime(2021, 3, 7, tzinfo=ZoneInfo("UTC")), # ending this Sunday ) def test_old_course(self, end): self.make_valid_goal(overview__end=end) diff --git a/lms/djangoapps/course_home_api/progress/serializers.py b/lms/djangoapps/course_home_api/progress/serializers.py index c48660a41c6a..1d42a540f4e4 100644 --- a/lms/djangoapps/course_home_api/progress/serializers.py +++ b/lms/djangoapps/course_home_api/progress/serializers.py @@ -5,9 +5,9 @@ from rest_framework import serializers from rest_framework.reverse import reverse -from pytz import UTC from lms.djangoapps.course_home_api.serializers import ReadOnlySerializer, VerifiedModeSerializer +from zoneinfo import ZoneInfo class CourseGradeSerializer(ReadOnlySerializer): @@ -69,7 +69,7 @@ def get_url(self, subsection): """ hide_url_date = subsection.end if subsection.self_paced else subsection.due if (not self.context['staff_access'] and subsection.hide_after_due and hide_url_date - and datetime.now(UTC) > hide_url_date): + and datetime.now(ZoneInfo("UTC")) > hide_url_date): return None relative_path = reverse('jump_to', args=[self.context['course_key'], subsection.location]) diff --git a/lms/djangoapps/course_home_api/progress/tests/test_views.py b/lms/djangoapps/course_home_api/progress/tests/test_views.py index 8012e11675f1..b116a8816aac 100644 --- a/lms/djangoapps/course_home_api/progress/tests/test_views.py +++ b/lms/djangoapps/course_home_api/progress/tests/test_views.py @@ -10,7 +10,6 @@ from django.urls import reverse from django.utils.timezone import now from edx_toggles.toggles.testutils import override_waffle_flag -from pytz import UTC from xmodule.modulestore.tests.factories import BlockFactory from common.djangoapps.course_modes.models import CourseMode @@ -34,6 +33,7 @@ from openedx.features.content_type_gating.helpers import CONTENT_GATING_PARTITION_ID, CONTENT_TYPE_GATE_GROUP_IDS from openedx.features.content_type_gating.models import ContentTypeGatingConfig from openedx.features.course_duration_limits.models import CourseDurationLimitConfig +from zoneinfo import ZoneInfo @ddt.ddt @@ -133,7 +133,7 @@ def test_end(self): self.update_course(self.course, self.user.id) response = self.client.get(self.url) assert response.status_code == 200 - end = dateutil.parser.parse(response.json()['end']).replace(tzinfo=UTC) + end = dateutil.parser.parse(response.json()['end']).replace(tzinfo=ZoneInfo("UTC")) assert end.date() == future.date() def test_user_has_passing_grade(self): diff --git a/lms/djangoapps/courseware/access_utils.py b/lms/djangoapps/courseware/access_utils.py index 23ec992f8189..82752045f670 100644 --- a/lms/djangoapps/courseware/access_utils.py +++ b/lms/djangoapps/courseware/access_utils.py @@ -9,7 +9,6 @@ from crum import get_current_request from django.conf import settings from enterprise.models import EnterpriseCourseEnrollment, EnterpriseCustomerUser -from pytz import UTC from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.roles import CourseBetaTesterRole @@ -25,6 +24,7 @@ from lms.djangoapps.courseware.masquerade import get_course_masquerade, is_masquerading_as_student from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, COURSE_PRE_START_ACCESS_FLAG from xmodule.course_block import COURSE_VISIBILITY_PUBLIC # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo DEBUG_ACCESS = False log = getLogger(__name__) @@ -141,7 +141,7 @@ def check_start_date(user, days_early_for_beta, start, course_key, display_error return ACCESS_GRANTED if now is None: - now = datetime.now(UTC) + now = datetime.now(ZoneInfo("UTC")) effective_start = adjust_start_date(user, days_early_for_beta, start, course_key) should_grant_access = now > effective_start diff --git a/lms/djangoapps/courseware/course_tools.py b/lms/djangoapps/courseware/course_tools.py index c0e37c76e068..e2888d5d962c 100644 --- a/lms/djangoapps/courseware/course_tools.py +++ b/lms/djangoapps/courseware/course_tools.py @@ -5,7 +5,6 @@ import datetime -import pytz from django.conf import settings from django.urls import reverse from django.utils.translation import gettext as _ @@ -15,6 +14,7 @@ from lms.djangoapps.courseware.utils import _use_new_financial_assistance_flow from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.features.course_experience.course_tools import CourseTool +from zoneinfo import ZoneInfo class FinancialAssistanceTool(CourseTool): @@ -33,7 +33,7 @@ def is_enabled(cls, request, course_key): """ Show this link for active courses where financial assistance is available, unless upgrade deadline has passed """ - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(ZoneInfo("UTC")) feature_flags = None try: course_overview = CourseOverview.objects.get(id=course_key) diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index 2c46248456f2..155fa16863fc 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -9,7 +9,6 @@ from datetime import datetime import six -import pytz from crum import get_current_request from dateutil.parser import parse as parse_date from django.conf import settings @@ -68,6 +67,7 @@ from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order from xmodule.x_module import STUDENT_VIEW # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo log = logging.getLogger(__name__) @@ -319,7 +319,7 @@ def course_open_for_self_enrollment(course_key): # Check the enrollment start and end dates. course_details = get_course_enrollment_details(str(course_key)) - now = datetime.now().replace(tzinfo=pytz.UTC) + now = datetime.now().replace(tzinfo=ZoneInfo("UTC")) start = course_details['enrollment_start'] end = course_details['enrollment_end'] @@ -533,7 +533,7 @@ def date_block_key_fn(block): If the block's date is None, return the maximum datetime in order to force it to the end of the list of displayed blocks. """ - return block.date or datetime.max.replace(tzinfo=pytz.UTC) + return block.date or datetime.max.replace(tzinfo=ZoneInfo("UTC")) def _get_absolute_url(request, url_path): @@ -634,7 +634,7 @@ def get_course_assignments(course_key, user, include_access=False, include_witho course_usage_key = store.make_course_usage_key(course_key) block_data = get_course_blocks(user, course_usage_key, allow_start_dates_in_future=True, include_completion=True) - now = datetime.now(pytz.UTC) + now = datetime.now(ZoneInfo("UTC")) assignments = [] for section_key in block_data.get_children(course_usage_key): # lint-amnesty, pylint: disable=too-many-nested-blocks for subsection_key in block_data.get_children(section_key): @@ -755,9 +755,9 @@ def _ora_assessment_to_assignment( else: assessment_start, assessment_due = None, None if assessment.get('start'): - assessment_start = parse_date(assessment.get('start')).replace(tzinfo=pytz.UTC) + assessment_start = parse_date(assessment.get('start')).replace(tzinfo=ZoneInfo("UTC")) if assessment.get('due'): - assessment_due = parse_date(assessment.get('due')).replace(tzinfo=pytz.UTC) + assessment_due = parse_date(assessment.get('due')).replace(tzinfo=ZoneInfo("UTC")) extra_info = _( "This Open Response Assessment's due dates are set by your instructor and can't be shifted." ) @@ -781,7 +781,7 @@ def _ora_assessment_to_assignment( assessment_type = assessment_name title = f"{block_title} ({assessment_type})" url = '' - now = datetime.now(pytz.UTC) + now = datetime.now(ZoneInfo("UTC")) assignment_released = not assessment_start or assessment_start < now if assignment_released: url = reverse('jump_to', args=[block_key.course_key, ora_block]) diff --git a/lms/djangoapps/courseware/date_summary.py b/lms/djangoapps/courseware/date_summary.py index e6bd5ef70597..3c741a2cadc9 100644 --- a/lms/djangoapps/courseware/date_summary.py +++ b/lms/djangoapps/courseware/date_summary.py @@ -15,7 +15,6 @@ from django.utils.translation import gettext as _ from django.utils.translation import gettext_lazy from lazy import lazy -from pytz import utc from common.djangoapps.course_modes.models import CourseMode from lms.djangoapps.certificates.api import get_active_web_certificate, can_show_certificate_available_date_field @@ -26,6 +25,7 @@ from openedx.features.course_duration_limits.access import get_user_course_expiration_date from openedx.features.course_experience import RELATIVE_DATES_FLAG from common.djangoapps.student.models import CourseEnrollment +from zoneinfo import ZoneInfo from .context_processor import user_timezone_locale_prefs @@ -42,7 +42,7 @@ def current_time(self): Returns a consistent current time. """ if self._current_time is None: - self._current_time = datetime.datetime.now(utc) + self._current_time = datetime.datetime.now(ZoneInfo("UTC")) return self._current_time @property diff --git a/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py b/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py index 97a20deac993..7c94f2e1441d 100644 --- a/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py +++ b/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py @@ -7,7 +7,6 @@ from io import StringIO import factory -import pytz from django.conf import settings from django.core.management import call_command @@ -19,11 +18,12 @@ ) from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory from xmodule.modulestore.xml_importer import import_course_from_xml +from zoneinfo import ZoneInfo DATA_DIR = settings.COMMON_TEST_DATA_ROOT XML_COURSE_DIRS = ['simple'] -TEST_COURSE_START = datetime.datetime(2012, 7, 1, tzinfo=pytz.UTC) -TEST_COURSE_END = datetime.datetime(2012, 12, 31, tzinfo=pytz.UTC) +TEST_COURSE_START = datetime.datetime(2012, 7, 1, tzinfo=ZoneInfo("UTC")) +TEST_COURSE_END = datetime.datetime(2012, 12, 31, tzinfo=ZoneInfo("UTC")) class CommandsTestBase(SharedModuleStoreTestCase): diff --git a/lms/djangoapps/courseware/masquerade.py b/lms/djangoapps/courseware/masquerade.py index 5ce266c463d6..c7102d3bfa8e 100644 --- a/lms/djangoapps/courseware/masquerade.py +++ b/lms/djangoapps/courseware/masquerade.py @@ -15,7 +15,6 @@ from django.utils.translation import gettext as _ from django.views import View from opaque_keys.edx.keys import CourseKey -from pytz import utc from web_fragments.fragment import Fragment from xblock.runtime import KeyValueStore @@ -32,6 +31,7 @@ from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID # lint-amnesty, pylint: disable=wrong-import-order from xmodule.partitions.partitions import NoSuchUserPartitionGroupError # lint-amnesty, pylint: disable=wrong-import-order from xmodule.partitions.partitions_service import get_all_partitions_for_course # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo log = logging.getLogger(__name__) @@ -387,7 +387,7 @@ def check_content_start_date_for_masquerade_user(course_key, user, request, cour Add a warning message if the masquerade user would not have access to this content due to the content start date being in the future. """ - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) most_future_date = course_start if chapter_start and section_start: most_future_date = max(course_start, chapter_start, section_start) diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py index bd0c1854ab76..f49c6338f3c7 100644 --- a/lms/djangoapps/courseware/tests/test_about.py +++ b/lms/djangoapps/courseware/tests/test_about.py @@ -8,7 +8,6 @@ from unittest.mock import patch import ddt -import pytz from django.conf import settings from django.test.utils import override_settings from django.urls import reverse @@ -33,6 +32,7 @@ from openedx.core.djangoapps.models.course_details import CourseDetails from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, course_home_url from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML +from zoneinfo import ZoneInfo from .helpers import LoginEnrollmentTestCase @@ -358,7 +358,7 @@ def setUp(self): self.course = CourseFactory.create(metadata={"invitation_only": False}) # Setup enrollment period to be in future - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(ZoneInfo("UTC")) tomorrow = now + datetime.timedelta(days=1) nextday = tomorrow + datetime.timedelta(days=1) diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py index 5b910565e4ae..632739dc6eeb 100644 --- a/lms/djangoapps/courseware/tests/test_access.py +++ b/lms/djangoapps/courseware/tests/test_access.py @@ -9,7 +9,6 @@ from unittest.mock import Mock, patch import pytest import ddt -import pytz from ccx_keys.locator import CCXLocator from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user @@ -67,6 +66,7 @@ EnterpriseCustomerFactory ) from crum import set_current_request +from zoneinfo import ZoneInfo QUERY_COUNT_TABLE_IGNORELIST = WAFFLE_TABLES @@ -175,8 +175,8 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes TOMORROW = 'tomorrow' YESTERDAY = 'yesterday' DATES = { - TOMORROW: datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1), - YESTERDAY: datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1), + TOMORROW: datetime.datetime.now(ZoneInfo("UTC")) + datetime.timedelta(days=1), + YESTERDAY: datetime.datetime.now(ZoneInfo("UTC")) - datetime.timedelta(days=1), None: None, } @@ -466,8 +466,8 @@ def test__has_access_to_block_with_start_date(self, start, expected_error_type): self.verify_access(mock_unit, expected_access, expected_error_type) def test__has_access_course_can_enroll(self): - yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1) - tomorrow = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) + yesterday = datetime.datetime.now(ZoneInfo("UTC")) - datetime.timedelta(days=1) + tomorrow = datetime.datetime.now(ZoneInfo("UTC")) + datetime.timedelta(days=1) # Non-staff can enroll if authenticated and specifically allowed for that course # even outside the open enrollment period @@ -507,8 +507,8 @@ def test__has_access_course_can_enroll(self): @override_settings(FEATURES={**settings.FEATURES, 'DISABLE_ALLOWED_ENROLLMENT_IF_ENROLLMENT_CLOSED': True}) def test__has_access_course_with_disable_allowed_enrollment_flag(self): - yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1) - tomorrow = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) + yesterday = datetime.datetime.now(ZoneInfo("UTC")) - datetime.timedelta(days=1) + tomorrow = datetime.datetime.now(ZoneInfo("UTC")) + datetime.timedelta(days=1) # Non-staff user invited to course, cannot enroll outside the open enrollment period # if DISABLE_ALLOWED_ENROLLMENT_IF_ENROLLMENT_CLOSED is True @@ -586,8 +586,8 @@ def test_old_mongo_is_invite_only(self, old_mongo): self.assertEqual(access._has_access_course(user, 'enroll', course).has_access, not old_mongo) def _mock_course_with_invitation(self, invitation, deprecated=False): - yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1) - tomorrow = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) + yesterday = datetime.datetime.now(ZoneInfo("UTC")) - datetime.timedelta(days=1) + tomorrow = datetime.datetime.now(ZoneInfo("UTC")) + datetime.timedelta(days=1) return Mock( enrollment_start=yesterday, enrollment_end=tomorrow, id=CourseLocator('edX', 'test', '2012_Fall', deprecated=deprecated), enrollment_domain='', @@ -733,7 +733,7 @@ class CourseOverviewAccessTestCase(ModuleStoreTestCase): def setUp(self): super().setUp() - today = datetime.datetime.now(pytz.UTC) + today = datetime.datetime.now(ZoneInfo("UTC")) last_week = today - datetime.timedelta(days=7) next_week = today + datetime.timedelta(days=7) diff --git a/lms/djangoapps/courseware/tests/test_block_render.py b/lms/djangoapps/courseware/tests/test_block_render.py index 182f8d0c03ab..37bad1cfb734 100644 --- a/lms/djangoapps/courseware/tests/test_block_render.py +++ b/lms/djangoapps/courseware/tests/test_block_render.py @@ -10,7 +10,6 @@ import pytest import ddt -import pytz from bson import ObjectId from completion.waffle import ENABLE_COMPLETION_TRACKING_SWITCH # lint-amnesty, pylint: disable=wrong-import-order from completion.models import BlockCompletion # lint-amnesty, pylint: disable=wrong-import-order @@ -97,6 +96,7 @@ from common.djangoapps.student.models import CourseEnrollment, anonymous_id_for_user from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory from common.djangoapps.xblock_django.models import XBlockConfiguration +from zoneinfo import ZoneInfo TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT @@ -2161,7 +2161,7 @@ def test_xblock_runtime_publish_delete(self): @patch('lms.djangoapps.grades.signals.handlers.PROBLEM_RAW_SCORE_CHANGED.send') def test_score_change_signal(self, send_mock): """Test that a Django signal is generated when a score changes""" - with freeze_time(datetime.now().replace(tzinfo=pytz.UTC)): + with freeze_time(datetime.now().replace(tzinfo=ZoneInfo("UTC"))): self.set_block_grade_using_publish(self.grade_dict) expected_signal_kwargs = { 'sender': None, @@ -2172,7 +2172,7 @@ def test_score_change_signal(self, send_mock): 'course_id': str(self.course.id), 'usage_id': str(self.problem.location), 'only_if_higher': None, - 'modified': datetime.now().replace(tzinfo=pytz.UTC), + 'modified': datetime.now().replace(tzinfo=ZoneInfo("UTC")), 'score_db_table': 'csm', 'score_deleted': None, 'grader_response': None diff --git a/lms/djangoapps/courseware/tests/test_course_tools.py b/lms/djangoapps/courseware/tests/test_course_tools.py index e1d3391e2991..e63ed051d9d5 100644 --- a/lms/djangoapps/courseware/tests/test_course_tools.py +++ b/lms/djangoapps/courseware/tests/test_course_tools.py @@ -7,7 +7,6 @@ from unittest.mock import patch import crum -import pytz from django.test import RequestFactory from common.djangoapps.course_modes.models import CourseMode @@ -18,6 +17,7 @@ from common.djangoapps.student.tests.factories import CourseEnrollmentFactory from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo class FinancialAssistanceToolTest(SharedModuleStoreTestCase): @@ -27,7 +27,7 @@ class FinancialAssistanceToolTest(SharedModuleStoreTestCase): @classmethod def setUpClass(cls): super().setUpClass() - cls.now = datetime.datetime.now(pytz.UTC) + cls.now = datetime.datetime.now(ZoneInfo("UTC")) cls.course = CourseFactory.create( org='edX', diff --git a/lms/djangoapps/courseware/tests/test_courses.py b/lms/djangoapps/courseware/tests/test_courses.py index 72465b299ade..144607832626 100644 --- a/lms/djangoapps/courseware/tests/test_courses.py +++ b/lms/djangoapps/courseware/tests/test_courses.py @@ -9,7 +9,6 @@ from unittest import mock import pytest import ddt -import pytz from completion.models import BlockCompletion from completion.test_utils import CompletionWaffleTestMixin from crum import set_current_request @@ -45,6 +44,7 @@ from openedx.core.djangolib.testing.utils import get_mock_request from openedx.core.lib.courses import course_image_url from common.djangoapps.student.tests.factories import UserFactory +from zoneinfo import ZoneInfo CMS_BASE_TEST = 'testcms' TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT @@ -313,7 +313,7 @@ def test_get_course_about_section_render(self): class CourseEnrollmentOpenTests(ModuleStoreTestCase): # lint-amnesty, pylint: disable=missing-class-docstring def setUp(self): super().setUp() - self.now = datetime.datetime.now().replace(tzinfo=pytz.UTC) + self.now = datetime.datetime.now().replace(tzinfo=ZoneInfo("UTC")) def test_course_enrollment_open(self): start = self.now - datetime.timedelta(days=1) @@ -486,7 +486,7 @@ def test_completion_does_not_treat_unreleased_as_complete(self): @ddt.ddt class TestGetCourseAssignmentsORA(CompletionWaffleTestMixin, ModuleStoreTestCase): """ Tests for ora-related behavior in get_course_assignments """ - TODAY = datetime.datetime(2023, 8, 2, 12, 23, 45, tzinfo=pytz.UTC) + TODAY = datetime.datetime(2023, 8, 2, 12, 23, 45, tzinfo=ZoneInfo("UTC")) def setUp(self): super().setUp() diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py index 1762d37bdee0..176aabe0b4c2 100644 --- a/lms/djangoapps/courseware/tests/test_date_summary.py +++ b/lms/djangoapps/courseware/tests/test_date_summary.py @@ -11,7 +11,6 @@ from django.test import RequestFactory from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch from freezegun import freeze_time -from pytz import utc from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory @@ -44,6 +43,7 @@ from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory from openedx.features.course_duration_limits.models import CourseDurationLimitConfig from openedx.features.course_experience import RELATIVE_DATES_FLAG +from zoneinfo import ZoneInfo @ddt.ddt @@ -129,7 +129,7 @@ def test_enabled_block_types_with_assignments(self): # pylint: disable=too-many user = create_user() request = self.make_request(user) CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED) - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) assignment_title_html = [''] with self.store.bulk_operations(course.id): section = BlockFactory.create(category='chapter', parent_location=course.location) @@ -318,7 +318,7 @@ def test_dates_with_openassessments(self, rubric_assessments, date_block_count): user = create_user() request = self.make_request(user) CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED) - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) chapter = BlockFactory.create( parent=course, @@ -350,7 +350,7 @@ def test_enabled_block_types_with_expired_course(self): self.make_request(user) # These two lines are to trigger the course expired block to be rendered CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.AUDIT) - 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"))) expected_blocks = ( TodaysDate, CourseEndDate, CourseExpiredDate, VerifiedUpgradeDeadlineDate diff --git a/lms/djangoapps/courseware/tests/test_masquerade.py b/lms/djangoapps/courseware/tests/test_masquerade.py index 0f0eca4dc2c7..53b2752d4306 100644 --- a/lms/djangoapps/courseware/tests/test_masquerade.py +++ b/lms/djangoapps/courseware/tests/test_masquerade.py @@ -13,7 +13,6 @@ from django.conf import settings from django.test import TestCase, RequestFactory from django.urls import reverse -from pytz import UTC from xblock.runtime import DictKeyValueStore from xmodule.capa.tests.response_xml_factory import OptionResponseXMLFactory @@ -38,6 +37,7 @@ from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory # lint-amnesty, pylint: disable=wrong-import-order from xmodule.partitions.partitions import Group, UserPartition # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase, MasqueradeMixin): @@ -48,7 +48,7 @@ class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase, Mas @classmethod def setUpClass(cls): super().setUpClass() - cls.course = CourseFactory.create(number='masquerade-test', metadata={'start': datetime.now(UTC)}) + cls.course = CourseFactory.create(number='masquerade-test', metadata={'start': datetime.now(ZoneInfo("UTC"))}) cls.info_page = BlockFactory.create( category="course_info", parent_location=cls.course.location, data="OOGIE BLOOGIE", display_name="updates" @@ -510,7 +510,7 @@ class SetupMasqueradeTests(SharedModuleStoreTestCase, ): def setUp(self): super().setUp() - self.course = CourseFactory.create(number='setup-masquerade-test', metadata={'start': datetime.now(UTC)}) + self.course = CourseFactory.create(number='setup-masquerade-test', metadata={'start': datetime.now(ZoneInfo("UTC"))}) self.request = RequestFactory().request() self.staff = StaffFactory(course_key=self.course.id) self.student = UserFactory() diff --git a/lms/djangoapps/courseware/tests/test_self_paced_overrides.py b/lms/djangoapps/courseware/tests/test_self_paced_overrides.py index fef054a4c966..31aedcff89f3 100644 --- a/lms/djangoapps/courseware/tests/test_self_paced_overrides.py +++ b/lms/djangoapps/courseware/tests/test_self_paced_overrides.py @@ -3,7 +3,6 @@ import datetime from unittest.mock import patch -import pytz from django.test.utils import override_settings from common.djangoapps.student.tests.factories import BetaTesterFactory @@ -13,6 +12,7 @@ from openedx.core.djangoapps.discussions.utils import get_accessible_discussion_xblocks from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo @override_settings( @@ -31,7 +31,7 @@ def setUp(self): super().setUp() self.non_staff_user, __ = self.create_non_staff_user() - self.now = datetime.datetime.now(pytz.UTC).replace(microsecond=0) + self.now = datetime.datetime.now(ZoneInfo("UTC")).replace(microsecond=0) self.future = self.now + datetime.timedelta(days=30) def tearDown(self): @@ -88,7 +88,7 @@ def test_course_access_to_beta_users(self): """ Test that beta testers can access `self_paced` course prior to start date. """ - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(ZoneInfo("UTC")) one_month_from_now = now + datetime.timedelta(days=30) course_options = { 'days_early_for_beta': 100, diff --git a/lms/djangoapps/courseware/tests/test_submitting_problems.py b/lms/djangoapps/courseware/tests/test_submitting_problems.py index 4d04204078de..56ef752dbfe4 100644 --- a/lms/djangoapps/courseware/tests/test_submitting_problems.py +++ b/lms/djangoapps/courseware/tests/test_submitting_problems.py @@ -12,7 +12,6 @@ from unittest.mock import patch import ddt -import pytz from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.db import connections @@ -42,6 +41,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory # lint-amnesty, pylint: disable=wrong-import-order from xmodule.partitions.partitions import Group, UserPartition # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo class ProblemSubmissionTestMixin(TestCase): @@ -214,7 +214,7 @@ def add_graded_section_to_course(self, name, section_format='Homework', late=Fal metadata={ 'graded': True, 'format': section_format, - 'due': datetime(2013, 5, 20, 23, 30, tzinfo=pytz.utc), + 'due': datetime(2013, 5, 20, 23, 30, tzinfo=ZoneInfo("UTC")), }, ) elif reset: diff --git a/lms/djangoapps/courseware/tests/test_user_state_client.py b/lms/djangoapps/courseware/tests/test_user_state_client.py index 690a64b8b8c5..70b8724b9a6f 100644 --- a/lms/djangoapps/courseware/tests/test_user_state_client.py +++ b/lms/djangoapps/courseware/tests/test_user_state_client.py @@ -3,7 +3,6 @@ defined in edx_user_state_client. """ -import pytz from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator from xblock.fields import Scope from datetime import datetime @@ -18,6 +17,7 @@ XBlockUserState ) from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo class _UserStateClientTestUtils(TestCase): @@ -339,9 +339,9 @@ def test_delete_many_last_field(self): self.assertCountEqual(self.get_many(user=0, blocks=[0, 1]), []) def test_get_mod_date(self): - start_time = datetime.now(pytz.utc) + start_time = datetime.now(ZoneInfo("UTC")) self.set_many(user=0, block_to_state={0: {'a': 'b'}, 1: {'b': 'c'}}) - end_time = datetime.now(pytz.utc) + end_time = datetime.now(ZoneInfo("UTC")) mod_dates = self.get(user=0, block=0) @@ -350,15 +350,15 @@ def test_get_mod_date(self): self.assertLess(mod_dates.updated, end_time) def test_get_many_mod_date(self): - start_time = datetime.now(pytz.utc) + start_time = datetime.now(ZoneInfo("UTC")) self.set_many( user=0, block_to_state={0: {'a': 'b'}, 1: {'a': 'd'}}) - mid_time = datetime.now(pytz.utc) + mid_time = datetime.now(ZoneInfo("UTC")) self.set_many( user=0, block_to_state={1: {'a': 'c'}}) - end_time = datetime.now(pytz.utc) + end_time = datetime.now(ZoneInfo("UTC")) mod_dates = list(self.get_many( user=0, @@ -607,7 +607,7 @@ def _add_state(self, username, block_key, scope, state): Add the specified state to the state history of this block. """ history_list = self._history.setdefault((username, block_key, scope), []) - history_list.insert(0, XBlockUserState(username, block_key, state, datetime.now(pytz.utc), scope)) + history_list.insert(0, XBlockUserState(username, block_key, state, datetime.now(ZoneInfo("UTC")), scope)) def get_many(self, username, block_keys, scope=Scope.user_state, fields=None): for key in block_keys: diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index f9668853b244..35c325b07782 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -6,7 +6,6 @@ import datetime from unittest.mock import patch -import pytz from django.urls import reverse from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase @@ -22,6 +21,7 @@ from lms.djangoapps.courseware.tests.helpers import CourseAccessTestMixin, LoginEnrollmentTestCase from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory +from zoneinfo import ZoneInfo class TestViewAuth(EnterpriseTestConsentRequired, ModuleStoreTestCase, LoginEnrollmentTestCase): @@ -253,7 +253,7 @@ def test_dark_launch_enrolled_student(self): """ # Make courses start in the future - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(ZoneInfo("UTC")) tomorrow = now + datetime.timedelta(days=1) self.course.start = tomorrow self.test_course.start = tomorrow @@ -278,7 +278,7 @@ def test_dark_launch_instructor(self): Make sure that before course start instructors can access the page for their course. """ - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(ZoneInfo("UTC")) tomorrow = now + datetime.timedelta(days=1) self.course.start = tomorrow self.test_course.start = tomorrow @@ -301,7 +301,7 @@ def test_dark_launch_global_staff(self): Make sure that before course start staff can access course pages. """ - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(ZoneInfo("UTC")) tomorrow = now + datetime.timedelta(days=1) self.course.start = tomorrow @@ -323,7 +323,7 @@ def test_enrollment_period(self): Check that enrollment periods work. """ # Make courses start in the future - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(ZoneInfo("UTC")) tomorrow = now + datetime.timedelta(days=1) nextday = tomorrow + datetime.timedelta(days=1) yesterday = now - datetime.timedelta(days=1) @@ -361,7 +361,7 @@ class TestBetatesterAccess(ModuleStoreTestCase, CourseAccessTestMixin): def setUp(self): super().setUp() - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(ZoneInfo("UTC")) tomorrow = now + datetime.timedelta(days=1) self.course = CourseFactory(days_early_for_beta=2, start=tomorrow) diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 2c3ece3133a5..57bd08e58883 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -26,7 +26,6 @@ from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch from freezegun import freeze_time from opaque_keys.edx.keys import CourseKey, UsageKey -from pytz import UTC from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel from rest_framework import status from rest_framework.test import APIClient @@ -113,6 +112,7 @@ from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired from openedx.features.enterprise_support.api import add_enterprise_customer_to_session from enterprise.api.v1.serializers import EnterpriseCustomerSerializer +from zoneinfo import ZoneInfo QUERY_COUNT_TABLE_IGNORELIST = WAFFLE_TABLES @@ -292,7 +292,7 @@ def setUp(self): self.course_key = self.course.id # Set profile country to Åland Islands to check Unicode characters does not raise error self.user = UserFactory(username='dummy', profile__country='AX') - self.date = datetime(2013, 1, 22, tzinfo=UTC) + self.date = datetime(2013, 1, 22, tzinfo=ZoneInfo("UTC")) self.enrollment = CourseEnrollment.enroll(self.user, self.course_key) self.enrollment.created = self.date self.enrollment.save() @@ -374,7 +374,7 @@ class ViewsTestCase(BaseViewsTestCase): """ YESTERDAY = 'yesterday' DATES = { - YESTERDAY: datetime.now(UTC) - timedelta(days=1), + YESTERDAY: datetime.now(ZoneInfo("UTC")) - timedelta(days=1), None: None, } @@ -1013,7 +1013,7 @@ def create_course(self, **options): self.course = CourseFactory.create( # pylint: disable=attribute-defined-outside-init start=datetime(2013, 9, 16, 7, 17, 28), end=datetime.now(), - certificate_available_date=datetime.now(UTC), + certificate_available_date=datetime.now(ZoneInfo("UTC")), certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE, **options, ) @@ -1613,7 +1613,7 @@ class ProgressPageShowCorrectnessTests(ProgressPageBaseTests): Tests that verify that the progress page works correctly when displaying subsections where correctness is hidden. """ # Constants used in the test data - NOW = datetime.now(UTC) + NOW = datetime.now(ZoneInfo("UTC")) DAY_DELTA = timedelta(days=1) YESTERDAY = 'yesterday' TODAY = 'today' @@ -2702,7 +2702,7 @@ def test_is_course_open_for_learner( * A mock request session to pre-cache the enterprise customer data. """ staff_user = AdminFactory() - start_date = datetime.now(UTC) + timedelta(days=start_date_modifier) + start_date = datetime.now(ZoneInfo("UTC")) + timedelta(days=start_date_modifier) course = CourseFactory.create(start=start_date) request = RequestFactory().get('/') request.user = staff_user @@ -2824,7 +2824,7 @@ def setUp(self): self.course_key = self.course.id self.user = UserFactory(username='staff_user', profile__country='AX', is_staff=True) - self.date = datetime(2013, 1, 22, tzinfo=UTC) + self.date = datetime(2013, 1, 22, tzinfo=ZoneInfo("UTC")) self.enrollment = CourseEnrollment.enroll(self.user, self.course_key) self.enrollment.created = self.date self.enrollment.save() diff --git a/lms/djangoapps/courseware/utils.py b/lms/djangoapps/courseware/utils.py index 5409c89f636b..3b39b1b5188a 100644 --- a/lms/djangoapps/courseware/utils.py +++ b/lms/djangoapps/courseware/utils.py @@ -9,7 +9,6 @@ from django.http import HttpResponse, HttpResponseBadRequest from edx_rest_api_client.client import OAuthAPIClient from oauth2_provider.models import Application -from pytz import utc # lint-amnesty, pylint: disable=wrong-import-order from rest_framework import status from xmodule.partitions.partitions import \ ENROLLMENT_TRACK_PARTITION_ID # lint-amnesty, pylint: disable=wrong-import-order @@ -25,6 +24,7 @@ ) from lms.djangoapps.courseware.models import FinancialAssistanceConfiguration from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel +from zoneinfo import ZoneInfo log = logging.getLogger(__name__) @@ -107,7 +107,7 @@ def can_show_verified_upgrade(user, enrollment, course=None): if upgrade_deadline is None: return False - if datetime.datetime.now(utc).date() > upgrade_deadline.date(): + if datetime.datetime.now(ZoneInfo("UTC")).date() > upgrade_deadline.date(): return False # Show the summary if user enrollment is in which allow user to upsell diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 2c89800d82b6..8dbeaf777949 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -42,7 +42,6 @@ from opaque_keys.edx.keys import CourseKey, UsageKey from openedx_filters.learning.filters import CourseAboutRenderStarted, RenderXBlockStarted from requests.exceptions import ConnectionError, Timeout # pylint: disable=redefined-builtin -from pytz import UTC from rest_framework import status from rest_framework.decorators import api_view, throttle_classes from rest_framework.response import Response @@ -153,6 +152,7 @@ from openedx.features.course_experience.utils import dates_banner_should_display from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML from openedx.features.enterprise_support.api import data_sharing_consent_required +from zoneinfo import ZoneInfo from ..block_render import get_block, get_block_by_usage_id, get_block_for_descriptor from ..tabs import _get_dynamic_tabs @@ -2316,7 +2316,7 @@ def get_financial_aid_courses(user, course_id=None): enrollment.course_overview and \ enrollment.course_overview.eligible_for_financial_aid and \ CourseMode.objects.filter( - Q(_expiration_datetime__isnull=True) | Q(_expiration_datetime__gt=datetime.now(UTC)), + Q(_expiration_datetime__isnull=True) | Q(_expiration_datetime__gt=datetime.now(ZoneInfo("UTC"))), course_id=enrollment.course_id, mode_slug=CourseMode.VERIFIED).exists(): # This is a workaround to set course_id before disabling the field in case of new financial assistance flow. diff --git a/lms/djangoapps/discussion/django_comment_client/tests/test_utils.py b/lms/djangoapps/discussion/django_comment_client/tests/test_utils.py index 742eb23bf5dc..dac7a105da90 100644 --- a/lms/djangoapps/discussion/django_comment_client/tests/test_utils.py +++ b/lms/djangoapps/discussion/django_comment_client/tests/test_utils.py @@ -13,7 +13,6 @@ from django.urls import reverse from edx_django_utils.cache import RequestCache from opaque_keys.edx.keys import CourseKey -from pytz import UTC import lms.djangoapps.discussion.django_comment_client.utils as utils from common.djangoapps.course_modes.models import CourseMode @@ -50,6 +49,7 @@ from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory, ToyCourseFactory +from zoneinfo import ZoneInfo class DictionaryTestCase(TestCase): @@ -367,14 +367,14 @@ def setUp(self): # This test needs to use a course that has already started -- # discussion topics only show up if the course has already started, # and the default start date for courses is Jan 1, 2030. - start=datetime.datetime(2012, 2, 3, tzinfo=UTC) + start=datetime.datetime(2012, 2, 3, tzinfo=ZoneInfo("UTC")) ) # Courses get a default discussion topic on creation, so remove it self.course.discussion_topics = {} self.discussion_num = 0 self.instructor = InstructorFactory(course_key=self.course.id) self.maxDiff = None # pylint: disable=invalid-name - self.later = datetime.datetime(2050, 1, 1, tzinfo=UTC) + self.later = datetime.datetime(2050, 1, 1, tzinfo=ZoneInfo("UTC")) def create_discussion(self, discussion_category, discussion_target, **kwargs): self.discussion_num += 1 diff --git a/lms/djangoapps/discussion/django_comment_client/utils.py b/lms/djangoapps/discussion/django_comment_client/utils.py index e26b748270e3..96dc3a2a03ca 100644 --- a/lms/djangoapps/discussion/django_comment_client/utils.py +++ b/lms/djangoapps/discussion/django_comment_client/utils.py @@ -16,7 +16,6 @@ from django.urls import reverse from django.utils.deprecation import MiddlewareMixin from opaque_keys.edx.keys import CourseKey, UsageKey, i4xEncoder -from pytz import UTC from common.djangoapps.student.models import get_user_by_username_or_email from common.djangoapps.student.roles import GlobalStaff @@ -50,6 +49,7 @@ from xmodule.modulestore.django import modulestore from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID from xmodule.partitions.partitions_service import PartitionService +from zoneinfo import ZoneInfo log = logging.getLogger(__name__) @@ -281,7 +281,7 @@ def _filter_unstarted_categories(category_map, course): Returns a subset of categories from the provided map which have not yet met the start date Includes information about category children, subcategories (different), and entries """ - now = datetime.now(UTC) + now = datetime.now(ZoneInfo("UTC")) result_map = {} @@ -397,7 +397,7 @@ def get_discussion_category_map(course, user, divided_only_if_explicit=False, ex sort_key = xblock.sort_key category = " / ".join([x.strip() for x in xblock.discussion_category.split("/")]) # Handle case where xblock.start is None - entry_start_date = xblock.start if xblock.start else datetime.max.replace(tzinfo=UTC) + entry_start_date = xblock.start if xblock.start else datetime.max.replace(tzinfo=ZoneInfo("UTC")) unexpanded_category_map[category].append({"title": title, "id": discussion_id, "sort_key": sort_key, @@ -464,7 +464,7 @@ def get_discussion_category_map(course, user, divided_only_if_explicit=False, ex category_map['entries'][topic] = { "id": entry["id"], "sort_key": entry.get("sort_key", topic), - "start_date": datetime.now(UTC), + "start_date": datetime.now(ZoneInfo("UTC")), "is_divided": ( discussion_division_enabled and entry["id"] in divided_discussion_ids ) diff --git a/lms/djangoapps/discussion/rest_api/api.py b/lms/djangoapps/discussion/rest_api/api.py index b87852c16cfa..72320ed2a93f 100644 --- a/lms/djangoapps/discussion/rest_api/api.py +++ b/lms/djangoapps/discussion/rest_api/api.py @@ -11,7 +11,6 @@ from enum import Enum from typing import Dict, Iterable, List, Literal, Optional, Set, Tuple from urllib.parse import urlencode, urlunparse -from pytz import UTC from django.conf import settings from django.contrib.auth import get_user_model @@ -84,6 +83,7 @@ from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.tabs import CourseTabList +from zoneinfo import ZoneInfo from ..django_comment_client.base.views import ( track_comment_created_event, @@ -419,7 +419,7 @@ def get_courseware_topics( courseware_topics = [] existing_topic_ids = set() - now = datetime.now(UTC) + now = datetime.now(ZoneInfo("UTC")) discussion_xblocks = get_accessible_discussion_xblocks(course, request.user) xblocks_by_category = defaultdict(list) diff --git a/lms/djangoapps/discussion/rest_api/tests/test_api_v2.py b/lms/djangoapps/discussion/rest_api/tests/test_api_v2.py index 900d52017c5e..9a722c4a5f9f 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_api_v2.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_api_v2.py @@ -22,7 +22,6 @@ from django.test.client import RequestFactory from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import CourseLocator -from pytz import UTC from rest_framework.exceptions import PermissionDenied from xmodule.modulestore import ModuleStoreEnum @@ -96,6 +95,7 @@ Role, ) from openedx.core.lib.exceptions import CourseNotFoundError, PageNotFoundError +from zoneinfo import ZoneInfo User = get_user_model() @@ -166,8 +166,8 @@ def _set_course_discussion_blackout(course, user_id): user_id: User id of user enrolled in the course """ course.discussion_blackouts = [ - datetime.now(UTC) - timedelta(days=3), - datetime.now(UTC) + timedelta(days=3), + datetime.now(ZoneInfo("UTC")) - timedelta(days=3), + datetime.now(ZoneInfo("UTC")) + timedelta(days=3), ] configuration = DiscussionsConfiguration.get(course.id) configuration.posting_restrictions = PostingRestriction.SCHEDULED @@ -4056,20 +4056,20 @@ def setUp(self) -> None: parent_location=self.course.location, category="chapter", display_name="Week 1", - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=ZoneInfo("UTC")), ) self.sequential = BlockFactory.create( parent_location=self.chapter.location, category="sequential", display_name="Lesson 1", - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=ZoneInfo("UTC")), ) self.verticals = [ BlockFactory.create( parent_location=self.sequential.location, category="vertical", display_name=f"vertical-{idx}", - start=datetime(2015, 4, 1, tzinfo=UTC), + start=datetime(2015, 4, 1, tzinfo=ZoneInfo("UTC")), ) for idx in range(10) ] @@ -4246,7 +4246,7 @@ def setUp(self): org="x", course="y", run="z", - start=datetime.now(UTC), + start=datetime.now(ZoneInfo("UTC")), discussion_topics={"Test Topic": {"id": "non-courseware-topic-id"}}, user_partitions=[self.partition], cohort_config={"cohorted": True}, @@ -4504,7 +4504,7 @@ def test_access_control(self): "courseware-5", "Second", "Future Start Date", - start=datetime.now(UTC) + timedelta(days=1) + start=datetime.now(ZoneInfo("UTC")) + timedelta(days=1) ) self.make_discussion_xblock("courseware-4", "Second", "Staff Only", visible_to_staff_only=True) @@ -4582,13 +4582,13 @@ def test_un_released_discussion_topic(self): "courseware-2", "First", "Released", - start=datetime.now(UTC) - timedelta(days=1) + start=datetime.now(ZoneInfo("UTC")) - timedelta(days=1) ) self.make_discussion_xblock( "courseware-3", "First", "Future release", - start=datetime.now(UTC) + timedelta(days=1) + start=datetime.now(ZoneInfo("UTC")) + timedelta(days=1) ) self.request.user = staff @@ -4761,7 +4761,7 @@ def test_blackout(self): # A variety of formats is accepted self.course.discussion_blackouts = [ ["2015-06-09T00:00:00Z", "6-10-15"], - [1433980800000, datetime(2015, 6, 12, tzinfo=UTC)], + [1433980800000, datetime(2015, 6, 12, tzinfo=ZoneInfo("UTC"))], ] self.update_course(self.course, self.user.id) result = get_course(self.request, self.course.id) diff --git a/lms/djangoapps/discussion/rest_api/tests/test_utils.py b/lms/djangoapps/discussion/rest_api/tests/test_utils.py index c6af74d83dc3..d1afa8dbf03a 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_utils.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_utils.py @@ -6,7 +6,6 @@ from datetime import datetime, timedelta import ddt -from pytz import UTC from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory @@ -25,6 +24,7 @@ from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration, PostingRestriction from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory +from zoneinfo import ZoneInfo @ddt.ddt @@ -38,8 +38,8 @@ def setUp(self): super().setUp() # lint-amnesty, pylint: disable=super-with-arguments self.course = CourseFactory.create() - self.course.discussion_blackouts = [datetime.now(UTC) - timedelta(days=3), - datetime.now(UTC) + timedelta(days=3)] + self.course.discussion_blackouts = [datetime.now(ZoneInfo("UTC")) - timedelta(days=3), + datetime.now(ZoneInfo("UTC")) + timedelta(days=3)] configuration = DiscussionsConfiguration.get(self.course.id) configuration.posting_restrictions = PostingRestriction.SCHEDULED configuration.save() @@ -196,7 +196,7 @@ def _get_date_ranges(self): Returns: list: List of date range tuples. """ - now = datetime.now(UTC) + now = datetime.now(ZoneInfo("UTC")) date_ranges = [ (now - timedelta(days=14), now + timedelta(days=23)), ] @@ -247,7 +247,7 @@ def test_posting_scheduled_future(self): Assertion: Posting should be allowed. """ - now = datetime.now(UTC) + now = datetime.now(ZoneInfo("UTC")) date_ranges = [ (now + timedelta(days=6), now + timedelta(days=23)), ] diff --git a/lms/djangoapps/discussion/rest_api/tests/test_views.py b/lms/djangoapps/discussion/rest_api/tests/test_views.py index 619f5c2e7d7b..db5e9cc10afd 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_views.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_views.py @@ -9,7 +9,6 @@ import ddt from django.urls import reverse -from pytz import UTC from rest_framework import status from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase @@ -26,6 +25,7 @@ make_minimal_cs_comment, make_minimal_cs_thread, ) +from zoneinfo import ZoneInfo @ddt.ddt @@ -59,7 +59,7 @@ def setUp(self): self.other_user = UserFactory.create(password=self.TEST_PASSWORD) self.register_get_user_response(self.other_user) - self.course = CourseFactory.create(org="a", course="b", run="c", start=datetime.now(UTC)) + self.course = CourseFactory.create(org="a", course="b", run="c", start=datetime.now(ZoneInfo("UTC"))) CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) self.url = self.build_url(self.user.username, self.course.id) diff --git a/lms/djangoapps/discussion/rest_api/tests/test_views_v2.py b/lms/djangoapps/discussion/rest_api/tests/test_views_v2.py index 10251224ad7d..8a50ce928517 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_views_v2.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_views_v2.py @@ -19,7 +19,6 @@ from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag from opaque_keys.edx.keys import CourseKey -from pytz import UTC from rest_framework import status from rest_framework.parsers import JSONParser from rest_framework.test import APIClient, APITestCase @@ -69,6 +68,7 @@ from openedx.core.djangoapps.django_comment_common.utils import seed_permissions_roles from openedx.core.djangoapps.discussions.config.waffle import ENABLE_NEW_STRUCTURE_DISCUSSIONS from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_storage +from zoneinfo import ZoneInfo class DiscussionAPIViewTestMixin(ForumMockUtilsMixin, UrlResetMixin): @@ -91,7 +91,7 @@ def setUp(self): org="x", course="y", run="z", - start=datetime.now(UTC), + start=datetime.now(ZoneInfo("UTC")), discussion_topics={"Test Topic": {"id": "test_topic"}}, ) self.password = "Password1234" @@ -1227,7 +1227,7 @@ def create_course(self, blocks_count, module_store, topics): org="a", course="b", run="c", - start=datetime.now(UTC), + start=datetime.now(ZoneInfo("UTC")), default_store=module_store, discussion_topics=topics ) @@ -1375,19 +1375,19 @@ def test_new_course_structure_response(self): parent_location=self.course.location, category='chapter', display_name="Week 1", - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=ZoneInfo("UTC")), ) sequential = BlockFactory.create( parent_location=chapter.location, category='sequential', display_name="Lesson 1", - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=ZoneInfo("UTC")), ) BlockFactory.create( parent_location=sequential.location, category='vertical', display_name='vertical', - start=datetime(2015, 4, 1, tzinfo=UTC), + start=datetime(2015, 4, 1, tzinfo=ZoneInfo("UTC")), ) DiscussionsConfiguration.objects.create( context_key=self.course.id, @@ -1436,20 +1436,20 @@ def setUp(self) -> None: parent_location=self.course.location, category='chapter', display_name="Week 1", - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=ZoneInfo("UTC")), ) self.sequential = BlockFactory.create( parent_location=self.chapter.location, category='sequential', display_name="Lesson 1", - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=ZoneInfo("UTC")), ) self.verticals = [ BlockFactory.create( parent_location=self.sequential.location, category='vertical', display_name='vertical', - start=datetime(2015, 4, 1, tzinfo=UTC), + start=datetime(2015, 4, 1, tzinfo=ZoneInfo("UTC")), ) ] course_key = self.course.id @@ -2043,7 +2043,7 @@ def setUp(self): ), } self.user = UserFactory.create(password=self.TEST_PASSWORD) - self.course = CourseFactory.create(org='a', course='b', run='c', start=datetime.now(UTC)) + self.course = CourseFactory.create(org='a', course='b', run='c', start=datetime.now(ZoneInfo("UTC"))) self.url = reverse("upload_file", kwargs={"course_id": str(self.course.id)}) @classmethod @@ -2195,7 +2195,7 @@ def setUp(self): org="x", course="y", run="z", - start=datetime.now(UTC), + start=datetime.now(ZoneInfo("UTC")), discussion_topics={"Test Topic": {"id": "test_topic"}} ) self.path = reverse('discussion_course_settings', kwargs={'course_id': str(self.course.id)}) @@ -2494,7 +2494,7 @@ def setUp(self): org="x", course="y", run="z", - start=datetime.now(UTC), + start=datetime.now(ZoneInfo("UTC")), ) self.password = self.TEST_PASSWORD self.user = UserFactory(username='staff', password=self.password, is_staff=True) diff --git a/lms/djangoapps/discussion/rest_api/tests/utils.py b/lms/djangoapps/discussion/rest_api/tests/utils.py index 37512c3573ee..9d1565e49e1d 100644 --- a/lms/djangoapps/discussion/rest_api/tests/utils.py +++ b/lms/djangoapps/discussion/rest_api/tests/utils.py @@ -12,13 +12,13 @@ import httpretty from PIL import Image -from pytz import UTC from lms.djangoapps.discussion.django_comment_client.tests.mixins import MockForumApiMixin from openedx.core.djangoapps.django_comment_common.comment_client.utils import CommentClientRequestError from openedx.core.djangoapps.profile_images.images import create_profile_images from openedx.core.djangoapps.profile_images.tests.helpers import make_image_file from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_names, set_has_profile_image +from zoneinfo import ZoneInfo def _get_thread_callback(thread_data): @@ -917,7 +917,7 @@ class ProfileImageTestMixin: Mixin with utility methods for user profile image """ - TEST_PROFILE_IMAGE_UPLOADED_AT = datetime(2002, 1, 9, 15, 43, 1, tzinfo=UTC) + TEST_PROFILE_IMAGE_UPLOADED_AT = datetime(2002, 1, 9, 15, 43, 1, tzinfo=ZoneInfo("UTC")) def create_profile_image(self, user, storage): """ diff --git a/lms/djangoapps/discussion/rest_api/utils.py b/lms/djangoapps/discussion/rest_api/utils.py index 0f02a0dcdcf2..0a8925c11d9a 100644 --- a/lms/djangoapps/discussion/rest_api/utils.py +++ b/lms/djangoapps/discussion/rest_api/utils.py @@ -11,7 +11,6 @@ from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.core.paginator import Paginator from django.db.models.functions import Length -from pytz import UTC from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole from common.djangoapps.student.models import CourseAccessRole @@ -28,6 +27,7 @@ FORUM_ROLE_STUDENT, Role ) +from zoneinfo import ZoneInfo from ..django_comment_client.utils import get_user_role_names log = logging.getLogger(__name__) @@ -384,7 +384,7 @@ def is_posting_allowed(posting_restrictions: str, blackout_schedules: List): Returns: bool: True if posting is allowed, False otherwise. """ - now = datetime.now(UTC) + now = datetime.now(ZoneInfo("UTC")) if posting_restrictions == PostingRestriction.DISABLED: return True elif posting_restrictions == PostingRestriction.SCHEDULED: diff --git a/lms/djangoapps/experiments/flags.py b/lms/djangoapps/experiments/flags.py index 3b45bcee5b5c..4c280b23290e 100644 --- a/lms/djangoapps/experiments/flags.py +++ b/lms/djangoapps/experiments/flags.py @@ -6,13 +6,13 @@ import logging import dateutil -import pytz from crum import get_current_request from edx_django_utils.cache import RequestCache from common.djangoapps.track import segment from lms.djangoapps.experiments.stable_bucketing import stable_bucketing_hash_group from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from zoneinfo import ZoneInfo log = logging.getLogger(__name__) @@ -121,7 +121,7 @@ def _is_enrollment_inside_date_bounds(self, experiment_values, user, course_key) if not enrollment_start and not enrollment_end: return True # early exit just to avoid any further lookups - now = datetime.datetime.now(pytz.utc) + now = datetime.datetime.now(ZoneInfo("UTC")) enrollment = CourseEnrollment.get_enrollment(user, course_key) # If the user isn't enrolled, act like they would enroll right now (this keeps the pre-enroll and post-enroll @@ -131,7 +131,7 @@ def _is_enrollment_inside_date_bounds(self, experiment_values, user, course_key) # Enrollment must be after any enrollment_start date, if specified if enrollment_start: try: - start_date = dateutil.parser.parse(enrollment_start).replace(tzinfo=pytz.UTC) + start_date = dateutil.parser.parse(enrollment_start).replace(tzinfo=ZoneInfo("UTC")) except ValueError: log.exception('Could not parse enrollment start date for experiment %d', self.experiment_id) return False @@ -141,7 +141,7 @@ def _is_enrollment_inside_date_bounds(self, experiment_values, user, course_key) # Enrollment must be before any enrollment_end date, if specified if enrollment_end: try: - end_date = dateutil.parser.parse(enrollment_end).replace(tzinfo=pytz.UTC) + end_date = dateutil.parser.parse(enrollment_end).replace(tzinfo=ZoneInfo("UTC")) except ValueError: log.exception('Could not parse enrollment end date for experiment %d', self.experiment_id) return False diff --git a/lms/djangoapps/experiments/tests/test_flags.py b/lms/djangoapps/experiments/tests/test_flags.py index 006efa17770d..1ecbceb9a3cf 100644 --- a/lms/djangoapps/experiments/tests/test_flags.py +++ b/lms/djangoapps/experiments/tests/test_flags.py @@ -5,7 +5,6 @@ from unittest.mock import patch import ddt -import pytz from crum import set_current_request from dateutil import parser from django.test.client import RequestFactory @@ -21,6 +20,7 @@ from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo @ddt.ddt @@ -75,7 +75,7 @@ def test_not_enabled(self): def test_enrollment_start(self, experiment_start, enrollment_created, expected_bucket): if enrollment_created: enrollment = CourseEnrollmentFactory(user=self.user, course_id='a/b/c') - enrollment.created = parser.parse(enrollment_created).replace(tzinfo=pytz.UTC) + enrollment.created = parser.parse(enrollment_created).replace(tzinfo=ZoneInfo("UTC")) enrollment.save() if experiment_start: ExperimentKeyValueFactory(experiment_id=0, key='enrollment_start', value=experiment_start) @@ -93,7 +93,7 @@ def test_enrollment_start(self, experiment_start, enrollment_created, expected_b def test_enrollment_end(self, experiment_end, enrollment_created, expected_bucket): if enrollment_created: enrollment = CourseEnrollmentFactory(user=self.user, course_id='a/b/c') - enrollment.created = parser.parse(enrollment_created).replace(tzinfo=pytz.UTC) + enrollment.created = parser.parse(enrollment_created).replace(tzinfo=ZoneInfo("UTC")) enrollment.save() if experiment_end: ExperimentKeyValueFactory(experiment_id=0, key='enrollment_end', value=experiment_end) diff --git a/lms/djangoapps/grades/api.py b/lms/djangoapps/grades/api.py index bb1b43fa7ff8..462d07ffb3ec 100644 --- a/lms/djangoapps/grades/api.py +++ b/lms/djangoapps/grades/api.py @@ -6,7 +6,6 @@ from datetime import datetime -import pytz from django.core.exceptions import ObjectDoesNotExist from opaque_keys.edx.keys import CourseKey, UsageKey @@ -27,6 +26,7 @@ from lms.djangoapps.grades.tasks import compute_all_grades_for_course as task_compute_all_grades_for_course from lms.djangoapps.grades.util_services import GradesUtilService from lms.djangoapps.utils import _get_key +from zoneinfo import ZoneInfo def graded_subsections_for_course_id(course_id): @@ -126,7 +126,7 @@ def undo_override_subsection_grade(user_id, course_key_or_id, usage_key_or_id, f course_id=str(course_key), usage_id=str(usage_key), only_if_higher=False, - modified=datetime.now().replace(tzinfo=pytz.UTC), # Not used when score_deleted=True + modified=datetime.now().replace(tzinfo=ZoneInfo("UTC")), # Not used when score_deleted=True score_deleted=True, score_db_table=constants.ScoreDatabaseTableEnum.overrides ) diff --git a/lms/djangoapps/grades/rest_api/v1/tests/mixins.py b/lms/djangoapps/grades/rest_api/v1/tests/mixins.py index 1d3a6a0b4551..32bcb50b2a06 100644 --- a/lms/djangoapps/grades/rest_api/v1/tests/mixins.py +++ b/lms/djangoapps/grades/rest_api/v1/tests/mixins.py @@ -5,7 +5,6 @@ from datetime import datetime -from pytz import UTC from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory @@ -13,6 +12,7 @@ from common.djangoapps.student.tests.factories import GlobalStaffFactory from lms.djangoapps.program_enrollments.tests.factories import ProgramCourseEnrollmentFactory, ProgramEnrollmentFactory from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory +from zoneinfo import ZoneInfo class GradeViewTestMixin(SharedModuleStoreTestCase): @@ -57,7 +57,7 @@ class GradeViewTestMixin(SharedModuleStoreTestCase): @classmethod def setUpClass(cls): super().setUpClass() - cls.date = datetime(2013, 1, 22, tzinfo=UTC) + cls.date = datetime(2013, 1, 22, tzinfo=ZoneInfo("UTC")) cls.course = cls._create_test_course_with_default_grading_policy( display_name='test course', run="Testing_course" ) diff --git a/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py b/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py index 42dd9139ee21..7e1458cadede 100644 --- a/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py +++ b/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py @@ -16,7 +16,6 @@ from edx_toggles.toggles.testutils import override_waffle_flag from freezegun import freeze_time from opaque_keys.edx.locator import BlockUsageLocator -from pytz import UTC from rest_framework import status from rest_framework.test import APITestCase from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase @@ -52,6 +51,7 @@ from lms.djangoapps.grades.subsection_grade import ReadSubsectionGrade from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory +from zoneinfo import ZoneInfo # pylint: disable=unused-variable @@ -1025,7 +1025,7 @@ def test_filter_enrollment_mode(self, login_method, num_enrollments, num_filtere _ = CourseEnrollmentFactory( course_id=self.course.id, user=verified_student, - created=datetime(2013, 1, 1, tzinfo=UTC), + created=datetime(2013, 1, 1, tzinfo=ZoneInfo("UTC")), mode=CourseMode.VERIFIED, ) with override_waffle_flag(self.waffle_flag, active=True): @@ -1890,7 +1890,7 @@ def setUpClass(cls): "earned_graded": 6.0, "possible_graded": 8.0, "visible_blocks": cls.block_records, - "first_attempted": datetime(2000, 1, 1, 12, 30, 45, tzinfo=UTC), + "first_attempted": datetime(2000, 1, 1, 12, 30, 45, tzinfo=ZoneInfo("UTC")), } cls.grade = PersistentSubsectionGrade.update_or_create_grade(**cls.params) @@ -2225,7 +2225,7 @@ def test_get_override_for_unreleased_block(self): parent_location=self.chapter_1.location, category='sequential', graded=True, - start=datetime(2999, 1, 1, tzinfo=UTC), # arbitrary future date + start=datetime(2999, 1, 1, tzinfo=ZoneInfo("UTC")), # arbitrary future date display_name='Unreleased Section', ) diff --git a/lms/djangoapps/grades/rest_api/v1/tests/test_grading_policy_view.py b/lms/djangoapps/grades/rest_api/v1/tests/test_grading_policy_view.py index aa5f526332e5..6d26f9742fbd 100644 --- a/lms/djangoapps/grades/rest_api/v1/tests/test_grading_policy_view.py +++ b/lms/djangoapps/grades/rest_api/v1/tests/test_grading_policy_view.py @@ -7,7 +7,6 @@ import ddt from django.urls import reverse -from pytz import UTC from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory @@ -16,6 +15,7 @@ from common.djangoapps.student.tests.factories import StaffFactory from common.djangoapps.student.tests.factories import UserFactory from openedx.core.djangoapps.oauth_dispatch.tests.factories import AccessTokenFactory, ApplicationFactory +from zoneinfo import ZoneInfo @ddt.ddt @@ -149,8 +149,8 @@ def test_course_keys(self, modulestore_type): The view should be addressable by course-keys from both module stores. """ course = CourseFactory.create( - start=datetime(2014, 6, 16, 14, 30, tzinfo=UTC), - end=datetime(2015, 1, 16, tzinfo=UTC), + start=datetime(2014, 6, 16, 14, 30, tzinfo=ZoneInfo("UTC")), + end=datetime(2015, 1, 16, tzinfo=ZoneInfo("UTC")), org="MTD", default_store=modulestore_type, ) diff --git a/lms/djangoapps/grades/tests/integration/test_problems.py b/lms/djangoapps/grades/tests/integration/test_problems.py index b4a2d06c9f45..5f0814c525f8 100644 --- a/lms/djangoapps/grades/tests/integration/test_problems.py +++ b/lms/djangoapps/grades/tests/integration/test_problems.py @@ -3,7 +3,6 @@ import itertools import ddt -import pytz from crum import set_current_request from xmodule.graders import ProblemScore from xmodule.modulestore import ModuleStoreEnum @@ -19,6 +18,7 @@ from lms.djangoapps.course_blocks.api import get_course_blocks from lms.djangoapps.courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin from openedx.core.djangolib.testing.utils import get_mock_request +from zoneinfo import ZoneInfo from ...subsection_grade_factory import SubsectionGradeFactory from ..utils import answer_problem, mock_get_submissions_score @@ -109,7 +109,7 @@ class TestVariedMetadata(ProblemSubmissionTestMixin, ModuleStoreTestCase): default_problem_metadata = { 'graded': True, 'weight': 2.5, - 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=pytz.utc), + 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=ZoneInfo("UTC")), } def setUp(self): diff --git a/lms/djangoapps/grades/tests/test_models.py b/lms/djangoapps/grades/tests/test_models.py index acdb20c5f1a7..6ee90bdfb91c 100644 --- a/lms/djangoapps/grades/tests/test_models.py +++ b/lms/djangoapps/grades/tests/test_models.py @@ -12,7 +12,6 @@ import ddt import pytest -import pytz from django.db.utils import IntegrityError from django.test import TestCase from django.utils.timezone import now @@ -32,6 +31,7 @@ PersistentSubsectionGradeOverride, VisibleBlocks ) +from zoneinfo import ZoneInfo class BlockRecordListTestCase(TestCase): @@ -220,7 +220,7 @@ def setUp(self): "earned_graded": 6.0, "possible_graded": 8.0, "visible_blocks": self.block_records, - "first_attempted": datetime(2000, 1, 1, 12, 30, 45, tzinfo=pytz.UTC), + "first_attempted": datetime(2000, 1, 1, 12, 30, 45, tzinfo=ZoneInfo("UTC")), } self.user = UserFactory(id=self.params['user_id']) @@ -389,7 +389,7 @@ def setUp(self): minute=53, second=24, microsecond=354741, - tzinfo=pytz.UTC, + tzinfo=ZoneInfo("UTC"), ), "percent_grade": 77.7, "letter_grade": "Great job", diff --git a/lms/djangoapps/grades/tests/test_services.py b/lms/djangoapps/grades/tests/test_services.py index 07a77ff13f13..3c6db72d9518 100644 --- a/lms/djangoapps/grades/tests/test_services.py +++ b/lms/djangoapps/grades/tests/test_services.py @@ -7,7 +7,6 @@ from unittest.mock import call, patch import ddt -import pytz from freezegun import freeze_time from common.djangoapps.student.tests.factories import UserFactory @@ -16,6 +15,7 @@ from lms.djangoapps.grades.services import GradesService from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo from ..constants import ScoreDatabaseTableEnum @@ -256,7 +256,7 @@ def test_undo_override_subsection_grade(self): course_id=str(self.course.id), usage_id=str(self.subsection.location), only_if_higher=False, - modified=datetime.now().replace(tzinfo=pytz.UTC), + modified=datetime.now().replace(tzinfo=ZoneInfo("UTC")), score_deleted=True, score_db_table=ScoreDatabaseTableEnum.overrides ) diff --git a/lms/djangoapps/grades/tests/test_signals.py b/lms/djangoapps/grades/tests/test_signals.py index 72a20d406482..c708dc4084a8 100644 --- a/lms/djangoapps/grades/tests/test_signals.py +++ b/lms/djangoapps/grades/tests/test_signals.py @@ -9,7 +9,6 @@ import ddt import pytest -import pytz from django.test import TestCase from django.test.utils import override_settings from opaque_keys.edx.locator import CourseLocator @@ -21,6 +20,7 @@ from lms.djangoapps.grades.models import PersistentCourseGrade from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from zoneinfo import ZoneInfo from ..constants import ScoreDatabaseTableEnum from ..signals.handlers import ( @@ -36,7 +36,7 @@ UUID_REGEX = re.compile('{hex}{{8}}-{hex}{{4}}-{hex}{{4}}-{hex}{{4}}-{hex}{{12}}'.format(hex='[0-9a-f]')) -FROZEN_NOW_DATETIME = datetime.now().replace(tzinfo=pytz.UTC) +FROZEN_NOW_DATETIME = datetime.now().replace(tzinfo=ZoneInfo("UTC")) FROZEN_NOW_TIMESTAMP = to_timestamp(FROZEN_NOW_DATETIME) SUBMISSIONS_SCORE_SET_HANDLER = 'submissions_score_set_handler' @@ -412,7 +412,7 @@ def test_segment_event_on_course_grade_passed_first_time(self, signal_mock, segm minute=53, second=24, microsecond=354741, - tzinfo=pytz.UTC, + tzinfo=ZoneInfo("UTC"), ), "percent_grade": 77.7, "letter_grade": "Great job", diff --git a/lms/djangoapps/grades/tests/test_tasks.py b/lms/djangoapps/grades/tests/test_tasks.py index 8f32991272e2..5823c2f1ecd3 100644 --- a/lms/djangoapps/grades/tests/test_tasks.py +++ b/lms/djangoapps/grades/tests/test_tasks.py @@ -10,7 +10,6 @@ from unittest.mock import MagicMock, patch import ddt -import pytz from django.db.utils import IntegrityError from django.utils import timezone from edx_toggles.toggles.testutils import override_waffle_flag @@ -39,6 +38,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory, check_mongo_calls from xmodule.partitions.partitions import USER_PARTITION_SCHEME_NAMESPACE, Group, UserPartition +from zoneinfo import ZoneInfo from .utils import mock_get_score @@ -66,7 +66,7 @@ def set_up_course(self, create_multiple_subsections=False, course_end=None): seq2 = BlockFactory.create(parent=self.chapter, category='sequential') BlockFactory.create(parent=seq2, category='problem') - self.frozen_now_datetime = datetime.now().replace(tzinfo=pytz.UTC) + self.frozen_now_datetime = datetime.now().replace(tzinfo=ZoneInfo("UTC")) self.frozen_now_timestamp = to_timestamp(self.frozen_now_datetime) self.problem_weighted_score_changed_kwargs = OrderedDict([ @@ -297,7 +297,7 @@ def test_retry_on_integrity_error(self, mock_update, mock_retry): def test_retry_when_db_not_updated(self, score_db_table, mock_log, mock_retry): self.set_up_course() self.recalculate_subsection_grade_kwargs['score_db_table'] = score_db_table - modified_datetime = datetime.utcnow().replace(tzinfo=pytz.UTC) - timedelta(days=1) + modified_datetime = datetime.utcnow().replace(tzinfo=ZoneInfo("UTC")) - timedelta(days=1) if score_db_table == ScoreDatabaseTableEnum.submissions: with patch('lms.djangoapps.grades.tasks.sub_api.get_score') as mock_sub_score: mock_sub_score.return_value = { @@ -384,7 +384,7 @@ def test_no_log_known_error(self, mock_update, mock_retry, mock_log): def _apply_recalculate_subsection_grade( self, mock_score=MagicMock( - modified=datetime.utcnow().replace(tzinfo=pytz.UTC) + timedelta(days=1), + modified=datetime.utcnow().replace(tzinfo=ZoneInfo("UTC")) + timedelta(days=1), grade=1.0, max_grade=2.0, ) @@ -638,7 +638,7 @@ def test_recalculate_subsection_grade_v3(self, freeze_flag_value, end_date_adjus CourseEnrollment.enroll(user, self.course.id) with override_waffle_flag(self.freeze_grade_flag, active=freeze_flag_value): - modified_datetime = datetime.utcnow().replace(tzinfo=pytz.UTC) - timedelta(days=1) # lint-amnesty, pylint: disable=unused-variable + modified_datetime = datetime.utcnow().replace(tzinfo=ZoneInfo("UTC")) - timedelta(days=1) # lint-amnesty, pylint: disable=unused-variable with patch('lms.djangoapps.grades.tasks._has_db_updated_with_new_score') as mock_has_db_updated: result = recalculate_subsection_grade_v3.apply_async(kwargs=self.recalculate_subsection_grade_kwargs) self._assert_for_freeze_grade_flag( diff --git a/lms/djangoapps/grades/tests/test_transformer.py b/lms/djangoapps/grades/tests/test_transformer.py index 7ef13e5b5b7c..6c634a0acda0 100644 --- a/lms/djangoapps/grades/tests/test_transformer.py +++ b/lms/djangoapps/grades/tests/test_transformer.py @@ -8,7 +8,6 @@ from copy import deepcopy import ddt -import pytz from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import check_mongo_calls_range @@ -18,6 +17,7 @@ from lms.djangoapps.course_blocks.api import get_course_blocks from lms.djangoapps.course_blocks.transformers.tests.helpers import CourseStructureTestCase from openedx.core.djangoapps.content.block_structure.api import clear_course_from_cache +from zoneinfo import ZoneInfo from ..transformer import GradesTransformer @@ -35,7 +35,7 @@ class GradesTransformerTestCase(CourseStructureTestCase): problem_metadata = { 'graded': True, 'weight': 1, - 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=pytz.utc), + 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=ZoneInfo("UTC")), } def setUp(self): @@ -280,7 +280,7 @@ def test_collecting_staff_only_problem(self): problem_metadata = { 'graded': True, 'weight': 1, - 'due': datetime.datetime(2016, 10, 16, 0, 4, 0, tzinfo=pytz.utc), + 'due': datetime.datetime(2016, 10, 16, 0, 4, 0, tzinfo=ZoneInfo("UTC")), 'visible_to_staff_only': True, } @@ -449,7 +449,7 @@ def test_modulestore_performance(self, store_type, max_mongo_calls, min_mongo_ca 'metadata': { 'graded': True, 'weight': 1, - 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=pytz.utc), + 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=ZoneInfo("UTC")), }, '#type': 'problem', '#ref': f'problem_{problem_number}', diff --git a/lms/djangoapps/grades/tests/utils.py b/lms/djangoapps/grades/tests/utils.py index 9111820379b4..068832f6e14c 100644 --- a/lms/djangoapps/grades/tests/utils.py +++ b/lms/djangoapps/grades/tests/utils.py @@ -7,11 +7,11 @@ from datetime import datetime from unittest.mock import MagicMock, patch -import pytz from lms.djangoapps.courseware.model_data import FieldDataCache from lms.djangoapps.courseware.block_render import get_block from xmodule.graders import ProblemScore # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo @contextmanager @@ -34,7 +34,7 @@ def mock_passing_grade(letter_grade='Pass', percent=0.75, last_updated=None): @contextmanager -def mock_get_score(earned=0, possible=1, first_attempted=datetime(2000, 1, 1, 0, 0, 0, tzinfo=pytz.UTC)): +def mock_get_score(earned=0, possible=1, first_attempted=datetime(2000, 1, 1, 0, 0, 0, tzinfo=ZoneInfo("UTC"))): """ Mocks the get_score function to return a valid grade. """ @@ -52,7 +52,7 @@ def mock_get_score(earned=0, possible=1, first_attempted=datetime(2000, 1, 1, 0, @contextmanager -def mock_get_submissions_score(earned=0, possible=1, first_attempted=datetime(2000, 1, 1, 0, 0, 0, tzinfo=pytz.UTC)): +def mock_get_submissions_score(earned=0, possible=1, first_attempted=datetime(2000, 1, 1, 0, 0, 0, tzinfo=ZoneInfo("UTC"))): """ Mocks the _get_submissions_score function to return the specified values """ diff --git a/lms/djangoapps/instructor/enrollment.py b/lms/djangoapps/instructor/enrollment.py index 85eb5bac1ce8..bd825e3a9964 100644 --- a/lms/djangoapps/instructor/enrollment.py +++ b/lms/djangoapps/instructor/enrollment.py @@ -9,7 +9,6 @@ from contextlib import ExitStack, contextmanager from datetime import datetime -import pytz from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.template.loader import render_to_string @@ -53,6 +52,7 @@ from openedx.core.djangoapps.user_api.models import UserPreference from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo log = logging.getLogger(__name__) @@ -456,7 +456,7 @@ def _fire_score_changed_for_block( usage_id=str(module_state_key), score_deleted=True, only_if_higher=False, - modified=datetime.now().replace(tzinfo=pytz.UTC), + modified=datetime.now().replace(tzinfo=ZoneInfo("UTC")), score_db_table=grades_constants.ScoreDatabaseTableEnum.courseware_student_module, ) diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index af8408ba8fde..fdedc540b033 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -13,7 +13,6 @@ import dateutil import ddt import pytest -import pytz from botocore.exceptions import ClientError from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user @@ -29,7 +28,6 @@ from edx_when.api import get_dates_for_course, get_overrides_for_user, set_date_for_block from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import UsageKey -from pytz import UTC from testfixtures import LogCapture from common.djangoapps.course_modes.models import CourseMode @@ -110,6 +108,7 @@ SharedModuleStoreTestCase ) from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory +from zoneinfo import ZoneInfo from .test_tools import msk_from_problem_urlname @@ -376,14 +375,14 @@ def setUpClass(cls): category='chapter', display_name="Chapter", publish_item=True, - start=datetime.datetime(2018, 3, 10, tzinfo=UTC), + start=datetime.datetime(2018, 3, 10, tzinfo=ZoneInfo("UTC")), ) cls.sequential = BlockFactory.create( parent=cls.chapter, category='sequential', display_name="Lesson", publish_item=True, - start=datetime.datetime(2018, 3, 10, tzinfo=UTC), + start=datetime.datetime(2018, 3, 10, tzinfo=ZoneInfo("UTC")), metadata={'graded': True, 'format': 'Homework'}, ) cls.vertical = BlockFactory.create( @@ -391,7 +390,7 @@ def setUpClass(cls): category='vertical', display_name='Subsection', publish_item=True, - start=datetime.datetime(2018, 3, 10, tzinfo=UTC), + start=datetime.datetime(2018, 3, 10, tzinfo=ZoneInfo("UTC")), ) cls.problem = BlockFactory.create( category="problem", @@ -3813,7 +3812,7 @@ def test_send_email_with_schedule(self, mock_task_api): """ schedule = "2099-05-02T14:00:00.000Z" self.full_test_message['schedule'] = schedule - expected_schedule = dateutil.parser.parse(schedule).replace(tzinfo=pytz.utc) + expected_schedule = dateutil.parser.parse(schedule).replace(tzinfo=ZoneInfo("UTC")) url = reverse('send_email', kwargs={'course_id': str(self.course.id)}) response = self.client.post(url, self.full_test_message) @@ -4270,7 +4269,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase): def setUpClass(cls): super().setUpClass() cls.course = CourseFactory.create() - cls.due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=UTC) + cls.due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=ZoneInfo("UTC")) with cls.store.bulk_operations(cls.course.id, emit_signals=False): cls.week1 = BlockFactory.create(due=cls.due) @@ -4349,7 +4348,7 @@ def setUp(self): def test_change_due_date(self): url = reverse('change_due_date', kwargs={'course_id': str(self.course.id)}) - due_date = datetime.datetime(2013, 12, 30, tzinfo=UTC) + due_date = datetime.datetime(2013, 12, 30, tzinfo=ZoneInfo("UTC")) response = self.client.post(url, { 'student': self.user1.username, 'url': str(self.week1.location), @@ -4362,7 +4361,7 @@ def test_change_due_date(self): def test_change_due_date_with_reason(self): url = reverse('change_due_date', kwargs={'course_id': str(self.course.id)}) - due_date = datetime.datetime(2013, 12, 30, tzinfo=UTC) + due_date = datetime.datetime(2013, 12, 30, tzinfo=ZoneInfo("UTC")) response = self.client.post(url, { 'student': self.user1.username, 'url': str(self.week1.location), @@ -4450,12 +4449,12 @@ def test_reset_date(self): def test_reset_date_only_in_edx_when(self): # Start with a unit that only has a date in edx-when assert get_date_for_block(self.course, self.week3, self.user1) is None - original_due = datetime.datetime(2010, 4, 1, tzinfo=UTC) + original_due = datetime.datetime(2010, 4, 1, tzinfo=ZoneInfo("UTC")) set_date_for_block(self.course.id, self.week3.location, 'due', original_due) assert get_date_for_block(self.course, self.week3, self.user1) == original_due # set override, confirm it took - override = datetime.datetime(2010, 7, 1, tzinfo=UTC) + override = datetime.datetime(2010, 7, 1, tzinfo=ZoneInfo("UTC")) set_date_for_block(self.course.id, self.week3.location, 'due', override, user=self.user1) assert get_date_for_block(self.course, self.week3, self.user1) == override @@ -4507,7 +4506,7 @@ def setUp(self): super().setUp() self.course = CourseFactory.create() - self.due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=UTC) + self.due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=ZoneInfo("UTC")) with self.store.bulk_operations(self.course.id, emit_signals=False): self.week1 = BlockFactory.create(due=self.due) @@ -4592,7 +4591,7 @@ def test_reset_extension_to_deleted_date(self): 'due_datetime': '12/30/2013 00:00' }) assert response.status_code == 200, response.content - assert datetime.datetime(2013, 12, 30, 0, 0, tzinfo=UTC) ==\ + assert datetime.datetime(2013, 12, 30, 0, 0, tzinfo=ZoneInfo("UTC")) ==\ get_extended_due(self.course, self.week1, self.user1) self.week1.due = None diff --git a/lms/djangoapps/instructor/tests/test_tools.py b/lms/djangoapps/instructor/tests/test_tools.py index b9f754dbfff1..85845fc93906 100644 --- a/lms/djangoapps/instructor/tests/test_tools.py +++ b/lms/djangoapps/instructor/tests/test_tools.py @@ -16,7 +16,6 @@ from edx_when.api import get_dates_for_course, set_dates_for_course from edx_when.field_data import DateLookupFieldData from opaque_keys.edx.keys import CourseKey -from pytz import UTC from xmodule.fields import Date from xmodule.modulestore.tests.django_utils import ( TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase, SharedModuleStoreTestCase, @@ -25,6 +24,7 @@ from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory from openedx.core.djangoapps.course_date_signals import handlers +from zoneinfo import ZoneInfo from ..views import tools @@ -91,7 +91,7 @@ class TestParseDatetime(unittest.TestCase): Test date parsing. """ def test_parse_no_error(self): - assert tools.parse_datetime('5/12/2010 2:42') == datetime.datetime(2010, 5, 12, 2, 42, tzinfo=UTC) + assert tools.parse_datetime('5/12/2010 2:42') == datetime.datetime(2010, 5, 12, 2, 42, tzinfo=ZoneInfo("UTC")) def test_parse_error(self): with pytest.raises(tools.DashboardError): @@ -149,7 +149,7 @@ def setUp(self): week2 = BlockFactory.create(parent=course) child = BlockFactory.create(parent=week1) - due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=UTC) + due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=ZoneInfo("UTC")) set_dates_for_course(course.id, [ (week1.location, {'due': due}), (week2.location, {'due': due}), @@ -212,7 +212,7 @@ def setUp(self): """ super().setUp() - self.due = due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=UTC) + self.due = due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=ZoneInfo("UTC")) course = CourseFactory.create() week1 = BlockFactory.create(due=due, parent=course) week2 = BlockFactory.create(due=due, parent=course) @@ -249,7 +249,7 @@ def _clear_field_data_cache(self): def test_set_due_date_extension(self): # First, extend the leaf assignment date - extended_hw = datetime.datetime(2013, 10, 25, 0, 0, tzinfo=UTC) + extended_hw = datetime.datetime(2013, 10, 25, 0, 0, tzinfo=ZoneInfo("UTC")) tools.set_due_date_extension(self.course, self.assignment, self.user, extended_hw) self._clear_field_data_cache() assert self.week1.due == self.due @@ -257,7 +257,7 @@ def test_set_due_date_extension(self): assert self.assignment.due == extended_hw # Now, extend the whole section that the assignment was in. Both it and all under it should change - extended_week = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=UTC) + extended_week = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) tools.set_due_date_extension(self.course, self.week1, self.user, extended_week) self._clear_field_data_cache() assert self.week1.due == extended_week @@ -265,17 +265,17 @@ def test_set_due_date_extension(self): assert self.assignment.due == extended_week def test_set_due_date_extension_invalid_date(self): - extended = datetime.datetime(2009, 1, 1, 0, 0, tzinfo=UTC) + extended = datetime.datetime(2009, 1, 1, 0, 0, tzinfo=ZoneInfo("UTC")) with pytest.raises(tools.DashboardError), self.allow_transaction_exception(): tools.set_due_date_extension(self.course, self.week1, self.user, extended) def test_set_due_date_extension_no_date(self): - extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=UTC) + extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) with pytest.raises(tools.DashboardError), self.allow_transaction_exception(): tools.set_due_date_extension(self.course, self.week3, self.user, extended) def test_reset_due_date_extension(self): - extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=UTC) + extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) tools.set_due_date_extension(self.course, self.week1, self.user, extended) tools.set_due_date_extension(self.course, self.week1, self.user, None) assert self.week1.due == self.due @@ -286,7 +286,7 @@ def test_reset_due_date_extension_with_no_enrollment(self): for a block given the user is not enrolled in the course. """ user = UserFactory.create() - extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=UTC) + extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) with pytest.raises(tools.DashboardError): tools.set_due_date_extension(self.course, self.week3, user, extended) @@ -295,7 +295,7 @@ def test_set_due_date_extension_cache_invalidation(self, mock_method: MagicMock) """ Tests that the course dates are reloaded once they are overridden. """ - extended_hw = datetime.datetime(2013, 10, 25, 0, 0, tzinfo=UTC) + extended_hw = datetime.datetime(2013, 10, 25, 0, 0, tzinfo=ZoneInfo("UTC")) tools.set_due_date_extension(self.course, self.assignment, self.user, extended_hw) assert mock_method.call_count == 3 @@ -308,7 +308,7 @@ def test_set_due_date_extension_cache_invalidation_with_version(self, mock_metho a unified experience across the platform. """ self.course.course_version = 'test_version' - extended_hw = datetime.datetime(2013, 10, 25, 0, 0, tzinfo=UTC) + extended_hw = datetime.datetime(2013, 10, 25, 0, 0, tzinfo=ZoneInfo("UTC")) tools.set_due_date_extension(self.course, self.assignment, self.user, extended_hw) assert mock_method.call_count == 3 @@ -328,7 +328,7 @@ def setUp(self): """ super().setUp() - due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=UTC) + due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=ZoneInfo("UTC")) course = CourseFactory.create() week1 = BlockFactory.create(due=due, parent=course) week2 = BlockFactory.create(due=due, parent=course) @@ -352,7 +352,7 @@ def setUp(self): handlers.extract_dates(None, course.id) def test_dump_module_extensions(self): - extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=UTC) + extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) tools.set_due_date_extension(self.course, self.week1, self.user1, extended) tools.set_due_date_extension(self.course, self.week1, self.user2, @@ -379,7 +379,7 @@ def test_dump_module_extensions(self): ) def test_dump_student_extensions(self): - extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=UTC) + extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) tools.set_due_date_extension(self.course, self.week1, self.user1, extended) tools.set_due_date_extension(self.course, self.week2, self.user1, diff --git a/lms/djangoapps/instructor/tests/utils.py b/lms/djangoapps/instructor/tests/utils.py index 64911f87d897..0671423d6aae 100644 --- a/lms/djangoapps/instructor/tests/utils.py +++ b/lms/djangoapps/instructor/tests/utils.py @@ -7,7 +7,7 @@ import json import random -from pytz import UTC +from zoneinfo import ZoneInfo class FakeInfo: @@ -65,7 +65,7 @@ def __init__(self, email_id): day = random.randint(1, 28) hour = random.randint(0, 23) minute = random.randint(0, 59) - self.created = datetime.datetime(year, month, day, hour, minute, tzinfo=UTC) + self.created = datetime.datetime(year, month, day, hour, minute, tzinfo=ZoneInfo("UTC")) self.targets = FakeTargetGroup() diff --git a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py index e48098b9ef80..15081947a7c1 100644 --- a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py +++ b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py @@ -13,7 +13,6 @@ from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag from pyquery import PyQuery as pq -from pytz import UTC from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory, check_mongo_calls @@ -39,6 +38,7 @@ OVERRIDE_DISCUSSION_LEGACY_SETTINGS_FLAG ) from openedx.core.djangoapps.site_configuration.models import SiteConfiguration +from zoneinfo import ZoneInfo def intercept_renderer(path, context): @@ -652,14 +652,14 @@ def test_spoc_gradebook_mongo_calls(self): category='chapter', display_name="Chapter", publish_item=True, - start=datetime.datetime(2015, 3, 1, tzinfo=UTC), + start=datetime.datetime(2015, 3, 1, tzinfo=ZoneInfo("UTC")), ) sequential = BlockFactory.create( parent=chapter, category='sequential', display_name="Lesson", publish_item=True, - start=datetime.datetime(2015, 3, 1, tzinfo=UTC), + start=datetime.datetime(2015, 3, 1, tzinfo=ZoneInfo("UTC")), metadata={'graded': True, 'format': 'Homework'}, ) vertical = BlockFactory.create( @@ -667,7 +667,7 @@ def test_spoc_gradebook_mongo_calls(self): category='vertical', display_name='Subsection', publish_item=True, - start=datetime.datetime(2015, 4, 1, tzinfo=UTC), + start=datetime.datetime(2015, 4, 1, tzinfo=ZoneInfo("UTC")), ) for i in range(10): problem = BlockFactory.create( diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index eae657ed19e7..dd249b58ad0f 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -15,7 +15,6 @@ import re import dateutil -import pytz import edx_api_doc_tools as apidocs from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user @@ -135,6 +134,7 @@ from openedx.core.lib.courses import get_course_by_id from openedx.core.lib.api.serializers import CourseKeyField from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url +from zoneinfo import ZoneInfo from .tools import ( DashboardError, dump_block_extensions, @@ -3044,8 +3044,8 @@ def post(self, request, course_id): # convert the schedule from a string to a datetime, then check if its a # valid future date and time, dateutil # will throw a ValueError if the schedule is no good. - schedule_dt = dateutil.parser.parse(schedule).replace(tzinfo=pytz.utc) - if schedule_dt < datetime.datetime.now(pytz.utc): + schedule_dt = dateutil.parser.parse(schedule).replace(tzinfo=ZoneInfo("UTC")) + if schedule_dt < datetime.datetime.now(ZoneInfo("UTC")): raise ValueError("the requested schedule is in the past") except ValueError as value_error: error_message = ( diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 3c47a67b632a..9f4f7393d0ca 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -6,7 +6,6 @@ from functools import reduce import markupsafe -import pytz from django.conf import settings from django.contrib.auth.decorators import login_required from django.http import Http404, HttpResponseRedirect, HttpResponseServerError @@ -61,6 +60,7 @@ from openedx.core.lib.courses import get_course_by_id from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.tabs import CourseTab # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo from .. import permissions from ..toggles import data_download_v2_is_enabled @@ -431,7 +431,7 @@ def set_course_mode_price(request, course_id): CourseModesArchive.objects.create( course_id=course_id, mode_slug='honor', mode_display_name='Honor Code Certificate', min_price=course_honor_mode[0].min_price, currency=course_honor_mode[0].currency, - expiration_datetime=datetime.datetime.now(pytz.utc), expiration_date=datetime.date.today() + expiration_datetime=datetime.datetime.now(ZoneInfo("UTC")), expiration_date=datetime.date.today() ) course_honor_mode.update( min_price=course_price, diff --git a/lms/djangoapps/instructor/views/tools.py b/lms/djangoapps/instructor/views/tools.py index 5a62f705f530..0bdb19ca649a 100644 --- a/lms/djangoapps/instructor/views/tools.py +++ b/lms/djangoapps/instructor/views/tools.py @@ -11,10 +11,10 @@ from django.http import HttpResponseBadRequest from django.utils.translation import gettext as _ from edx_when import api -from pytz import UTC from common.djangoapps.student.models import CourseEnrollment, get_user_by_username_or_email from openedx.core.djangoapps.schedules.models import Schedule +from zoneinfo import ZoneInfo class DashboardError(Exception): @@ -88,7 +88,7 @@ def parse_datetime(datestr): UTC. """ try: - return dateutil.parser.parse(datestr).replace(tzinfo=UTC) + return dateutil.parser.parse(datestr).replace(tzinfo=ZoneInfo("UTC")) except ValueError: raise DashboardError(_("Unable to parse date: ") + datestr) # lint-amnesty, pylint: disable=raise-missing-from diff --git a/lms/djangoapps/instructor_task/api.py b/lms/djangoapps/instructor_task/api.py index 6474efc1d374..c89bfceb4a48 100644 --- a/lms/djangoapps/instructor_task/api.py +++ b/lms/djangoapps/instructor_task/api.py @@ -11,7 +11,6 @@ import logging from collections import Counter -import pytz from celery.states import READY_STATES from common.djangoapps.util import milestones_helpers @@ -52,6 +51,7 @@ generate_anonymous_ids_for_course ) from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo log = logging.getLogger(__name__) @@ -594,7 +594,7 @@ def process_scheduled_instructor_tasks(): Utility function that retrieves tasks whose schedules have elapsed and should be processed. Only retrieves instructor tasks that are in the `SCHEDULED` state. Then submits these tasks for processing by Celery. """ - now = datetime.datetime.now(pytz.utc) + now = datetime.datetime.now(ZoneInfo("UTC")) due_schedules = InstructorTaskSchedule.objects.filter(task__task_state=SCHEDULED).filter(task_due__lte=now) log.info(f"Retrieved {due_schedules.count()} scheduled instructor tasks due for execution") for schedule in due_schedules: diff --git a/lms/djangoapps/instructor_task/management/commands/tests/test_fail_old_tasks.py b/lms/djangoapps/instructor_task/management/commands/tests/test_fail_old_tasks.py index 28e611eae64f..fc37085ed94d 100644 --- a/lms/djangoapps/instructor_task/management/commands/tests/test_fail_old_tasks.py +++ b/lms/djangoapps/instructor_task/management/commands/tests/test_fail_old_tasks.py @@ -6,7 +6,6 @@ from datetime import datetime import pytest import ddt -import pytz from celery.states import FAILURE from django.core.management import call_command from django.core.management.base import CommandError @@ -14,6 +13,7 @@ from lms.djangoapps.instructor_task.models import PROGRESS, QUEUING, InstructorTask from lms.djangoapps.instructor_task.tests.factories import InstructorTaskFactory from lms.djangoapps.instructor_task.tests.test_base import InstructorTaskTestCase +from zoneinfo import ZoneInfo @ddt.ddt @@ -51,7 +51,7 @@ def update_task_created(self, created_date): Override each task's "created" date """ for task in self.tasks: - task.created = datetime.strptime(created_date, "%Y-%m-%d").replace(tzinfo=pytz.UTC) + task.created = datetime.strptime(created_date, "%Y-%m-%d").replace(tzinfo=ZoneInfo("UTC")) task.save() def get_tasks(self): diff --git a/lms/djangoapps/instructor_task/rest_api/v1/tests/test_views.py b/lms/djangoapps/instructor_task/rest_api/v1/tests/test_views.py index bb338f0bf480..82c93ccb9890 100644 --- a/lms/djangoapps/instructor_task/rest_api/v1/tests/test_views.py +++ b/lms/djangoapps/instructor_task/rest_api/v1/tests/test_views.py @@ -4,7 +4,6 @@ import datetime import json from uuid import uuid4 -import pytz from celery.states import REVOKED import ddt @@ -25,6 +24,7 @@ from lms.djangoapps.instructor_task.models import InstructorTask, InstructorTaskSchedule, PROGRESS, SCHEDULED from lms.djangoapps.instructor_task.tests.factories import InstructorTaskFactory, InstructorTaskScheduleFactory from openedx.core.lib.html_to_text import html_to_text +from zoneinfo import ZoneInfo User = get_user_model() @@ -227,7 +227,7 @@ def test_update_schedule_new_date(self): self._create_scheduled_course_emails_for_course(self.course1.id, self.instructor_course1, SCHEDULED, 1) task = InstructorTask.objects.get(course_id=self.course1.id) task_schedule = InstructorTaskSchedule.objects.get(task=task) - schedule_datetime = datetime.datetime(3000, 6, 1, 17, 15, 0, tzinfo=pytz.utc) + schedule_datetime = datetime.datetime(3000, 6, 1, 17, 15, 0, tzinfo=ZoneInfo("UTC")) data = { "schedule": schedule_datetime.strftime('%Y-%m-%dT%H:%M:%SZ'), "browser_timezone": "UTC" diff --git a/lms/djangoapps/instructor_task/rest_api/v1/views.py b/lms/djangoapps/instructor_task/rest_api/v1/views.py index 812b88e11da3..2cee4ed74312 100644 --- a/lms/djangoapps/instructor_task/rest_api/v1/views.py +++ b/lms/djangoapps/instructor_task/rest_api/v1/views.py @@ -4,7 +4,6 @@ import datetime import json import logging -import pytz import dateutil from celery.states import REVOKED @@ -18,6 +17,7 @@ from lms.djangoapps.instructor_task.rest_api.v1.exceptions import TaskUpdateException from lms.djangoapps.instructor_task.rest_api.v1.serializers import ScheduledBulkEmailSerializer from lms.djangoapps.instructor_task.rest_api.v1.permissions import CanViewOrModifyScheduledBulkCourseEmailTasks +from zoneinfo import ZoneInfo log = logging.getLogger(__name__) @@ -117,7 +117,7 @@ def patch(self, request, *args, **kwargs): with transaction.atomic(): if schedule: - schedule_dt = dateutil.parser.parse(schedule).replace(tzinfo=pytz.utc) + schedule_dt = dateutil.parser.parse(schedule).replace(tzinfo=ZoneInfo("UTC")) self._verify_valid_schedule(schedule_id, schedule_dt) task_schedule.task_due = schedule_dt task_schedule.save() @@ -149,7 +149,7 @@ def _verify_valid_schedule(self, schedule_id, schedule): Verifies that the updated schedule data for the task is valid. We check to make sure that the date or time requested is not in the past. """ - now = datetime.datetime.now(pytz.utc) + now = datetime.datetime.now(ZoneInfo("UTC")) if schedule < now: raise TaskUpdateException( f"Cannot update instructor task schedule '{schedule_id}', the updated schedule occurs in the past" diff --git a/lms/djangoapps/instructor_task/tasks_helper/enrollments.py b/lms/djangoapps/instructor_task/tasks_helper/enrollments.py index 468786323b79..c3b95e120a64 100644 --- a/lms/djangoapps/instructor_task/tasks_helper/enrollments.py +++ b/lms/djangoapps/instructor_task/tasks_helper/enrollments.py @@ -6,7 +6,6 @@ import logging from datetime import datetime from time import time -from pytz import UTC from lms.djangoapps.instructor_analytics.basic import ( enrolled_students_features, list_inactive_enrolled_students, @@ -14,6 +13,7 @@ ) from lms.djangoapps.instructor_analytics.csvs import format_dictlist from common.djangoapps.student.models import CourseEnrollment # lint-amnesty, pylint: disable=unused-import +from zoneinfo import ZoneInfo from .runner import TaskProgress from .utils import upload_csv_to_report_store # lint-amnesty, pylint: disable=unused-import @@ -29,7 +29,7 @@ def upload_may_enroll_csv(_xblock_instance_args, _entry_id, course_id, task_inpu yet, and store using a `ReportStore`. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(ZoneInfo("UTC")) num_reports = 1 task_progress = TaskProgress(action_name, num_reports, start_time) current_step = {'step': 'Calculating info about students who may enroll'} @@ -61,7 +61,7 @@ def upload_inactive_enrolled_students_info_csv(_xblock_instance_args, _entry_id, activated their account yet, and store using a `ReportStore`. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(ZoneInfo("UTC")) num_reports = 1 task_progress = TaskProgress(action_name, num_reports, start_time) current_step = {'step': 'Calculating info about students who are enrolled and their account is inactive'} @@ -93,7 +93,7 @@ def upload_students_csv(_xblock_instance_args, _entry_id, course_id, task_input, `ReportStore`. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(ZoneInfo("UTC")) enrolled_students = CourseEnrollment.objects.users_enrolled_in(course_id) task_progress = TaskProgress(action_name, enrolled_students.count(), start_time) diff --git a/lms/djangoapps/instructor_task/tasks_helper/grades.py b/lms/djangoapps/instructor_task/tasks_helper/grades.py index 5358af370897..bfb12ff99cc8 100644 --- a/lms/djangoapps/instructor_task/tasks_helper/grades.py +++ b/lms/djangoapps/instructor_task/tasks_helper/grades.py @@ -16,7 +16,6 @@ from django.contrib.auth import get_user_model from lazy import lazy from opaque_keys.edx.keys import UsageKey -from pytz import UTC from six.moves import zip_longest from common.djangoapps.course_modes.models import CourseMode @@ -46,6 +45,7 @@ from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.partitions.partitions_service import PartitionService # lint-amnesty, pylint: disable=wrong-import-order from xmodule.split_test_block import get_split_user_partitions # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo from .runner import TaskProgress from .utils import upload_csv_to_report_store, upload_csv_file_to_report_store @@ -301,7 +301,7 @@ def _upload(self, success_headers, success_rows, error_headers, error_rows): """ Creates and uploads a CSV for the given headers and rows. """ - date = datetime.now(UTC) + date = datetime.now(ZoneInfo("UTC")) upload_csv_to_report_store( [success_headers] + success_rows, self.context.upload_filename, @@ -389,7 +389,7 @@ def upload_temp_files(self, success_file, error_file, has_errors): """ Uploads success and error csv files to report store """ - date = datetime.now(UTC) + date = datetime.now(ZoneInfo("UTC")) success_file.seek(0) upload_csv_file_to_report_store( @@ -966,7 +966,7 @@ def generate(cls, _xblock_instance_args, _entry_id, course_id, task_input, actio all student answers to a given problem, and store using a `ReportStore`. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(ZoneInfo("UTC")) num_reports = 1 task_progress = TaskProgress(action_name, num_reports, start_time) current_step = {'step': 'Calculating students answers to problem'} diff --git a/lms/djangoapps/instructor_task/tasks_helper/misc.py b/lms/djangoapps/instructor_task/tasks_helper/misc.py index 85a5d4e1361e..7b72d7dd7f5b 100644 --- a/lms/djangoapps/instructor_task/tasks_helper/misc.py +++ b/lms/djangoapps/instructor_task/tasks_helper/misc.py @@ -17,7 +17,6 @@ from django.core.exceptions import ValidationError from django.core.files.storage import DefaultStorage from openassessment.data import OraAggregateData, OraDownloadData -from pytz import UTC from common.djangoapps.student.models import unique_id_for_user, anonymous_id_for_user from lms.djangoapps.instructor_analytics.basic import get_proctored_exam_results @@ -25,6 +24,7 @@ from lms.djangoapps.survey.models import SurveyAnswer from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort from openedx.core.djangoapps.course_groups.models import CourseUserGroup +from zoneinfo import ZoneInfo from .runner import TaskProgress from .utils import ( @@ -44,7 +44,7 @@ def upload_course_survey_report(_xblock_instance_args, _entry_id, course_id, _ta For a given `course_id`, generate a html report containing the survey results for a course. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(ZoneInfo("UTC")) num_reports = 1 task_progress = TaskProgress(action_name, num_reports, start_time) @@ -103,7 +103,7 @@ def upload_proctored_exam_results_report(_xblock_instance_args, _entry_id, cours information about proctored exam results, and store using a `ReportStore`. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(ZoneInfo("UTC")) num_reports = 1 task_progress = TaskProgress(action_name, num_reports, start_time) current_step = {'step': 'Calculating info about proctored exam results in a course'} @@ -171,7 +171,7 @@ def cohort_students_and_upload(_xblock_instance_args, _entry_id, course_id, task using a `ReportStore`. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(ZoneInfo("UTC")) # Iterate through rows to get total assignments for task progress with DefaultStorage().open(task_input['file_name']) as f: @@ -305,7 +305,7 @@ def _upload_ora2_data_common( """ Common code for uploading data or summary csv report. """ - start_date = datetime.now(UTC) + start_date = datetime.now(ZoneInfo("UTC")) start_time = time() num_attempted = 1 @@ -411,7 +411,7 @@ def upload_ora2_submission_files( """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(ZoneInfo("UTC")) num_attempted = 1 num_total = 1 @@ -511,7 +511,7 @@ def _log_and_update_progress(step): TASK_LOG.info('%s, Task type: %s, Starting task execution', task_info_string, action_name) start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(ZoneInfo("UTC")) students = User.objects.filter( courseenrollment__course_id=course_id, diff --git a/lms/djangoapps/instructor_task/tests/test_api.py b/lms/djangoapps/instructor_task/tests/test_api.py index 4b84fbff7f70..fc08c04007d7 100644 --- a/lms/djangoapps/instructor_task/tests/test_api.py +++ b/lms/djangoapps/instructor_task/tests/test_api.py @@ -8,7 +8,6 @@ from uuid import uuid4 import pytest -import pytz import ddt from testfixtures import LogCapture from celery.states import FAILURE, SUCCESS @@ -61,6 +60,7 @@ InstructorTaskTestCase, TestReportMixin ) +from zoneinfo import ZoneInfo LOG_PATH = 'lms.djangoapps.instructor_task.api' @@ -465,7 +465,7 @@ def test_submit_bulk_course_email_with_schedule(self, mock_submit_task, mock_sch A test to determine if the right helper function is being called when a scheduled task is being processed. """ email_id = self._define_course_email() - schedule = datetime.datetime(2030, 8, 15, 8, 15, 12, 0, pytz.utc) + schedule = datetime.datetime(2030, 8, 15, 8, 15, 12, 0, ZoneInfo("UTC")) submit_bulk_course_email( self.create_task_request(self.instructor), self.course.id, diff --git a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py index 4144dd95680f..c6e1950339ea 100644 --- a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py +++ b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py @@ -22,7 +22,6 @@ from django.test.utils import override_settings from edx_django_utils.cache import RequestCache from freezegun import freeze_time -from pytz import UTC import openedx.core.djangoapps.user_api.course_tag.api as course_tag_api import openedx.core.djangoapps.content.block_structure.api as bs_api @@ -74,6 +73,7 @@ from xmodule.partitions.partitions import Group, UserPartition # lint-amnesty, pylint: disable=wrong-import-order # noinspection PyUnresolvedReferences from xmodule.tests.helpers import override_descriptor_system # pylint: disable=unused-import +from zoneinfo import ZoneInfo from ..models import ReportStore from ..tasks_helper.utils import UPDATE_STATUS_FAILED, UPDATE_STATUS_SUCCEEDED @@ -1806,8 +1806,8 @@ def create_course(self): """ Creates a course with various subsections for testing """ - in_the_past = datetime.now(UTC) - timedelta(days=5) - in_the_future = datetime.now(UTC) + timedelta(days=5) + in_the_past = datetime.now(ZoneInfo("UTC")) - timedelta(days=5) + in_the_future = datetime.now(ZoneInfo("UTC")) + timedelta(days=5) self.course = CourseFactory.create( grading_policy={ "GRADER": [ @@ -1997,7 +1997,7 @@ class TestGradeReportEnrollmentAndCertificateInfo(TestReportMixin, InstructorTas def setUp(self): super().setUp() - today = datetime.now(UTC) + today = datetime.now(ZoneInfo("UTC")) course_factory_kwargs = { 'start': today - timedelta(days=30), 'end': today - timedelta(days=2), @@ -2655,7 +2655,7 @@ def test_report_stores_results(self): return_val = upload_ora2_data(None, None, self.course.id, None, 'generated') - timestamp_str = datetime.now(UTC).strftime('%Y-%m-%d-%H%M') + timestamp_str = datetime.now(ZoneInfo("UTC")).strftime('%Y-%m-%d-%H%M') key = self.course.id filename = f'{key.org}_{key.course}_{key.run}_ORA_data_{timestamp_str}.csv' @@ -2708,7 +2708,7 @@ def test_summary_report_stores_results(self): ) as mock_store_rows: return_val = upload_ora2_summary(None, None, self.course.id, None, 'generated') - timestamp_str = datetime.now(UTC).strftime('%Y-%m-%d-%H%M') + timestamp_str = datetime.now(ZoneInfo("UTC")).strftime('%Y-%m-%d-%H%M') key = self.course.id filename = f'{key.org}_{key.course}_{key.run}_ORA_summary_{timestamp_str}.csv' diff --git a/lms/djangoapps/mobile_api/tests/test_course_info_views.py b/lms/djangoapps/mobile_api/tests/test_course_info_views.py index efb3f7d9fdbb..96d56d166761 100644 --- a/lms/djangoapps/mobile_api/tests/test_course_info_views.py +++ b/lms/djangoapps/mobile_api/tests/test_course_info_views.py @@ -12,7 +12,6 @@ from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag from milestones.tests.utils import MilestonesTestCaseMixin -from pytz import utc from rest_framework import status from common.djangoapps.student.tests.factories import UserFactory # pylint: disable=unused-import @@ -30,6 +29,7 @@ SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.xml_importer import import_course_from_xml # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo User = get_user_model() @@ -536,7 +536,7 @@ def test_course_not_started(self, mock_certificate_downloadable_status): 'is_downloadable': True, 'download_url': certificate_url, } - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) course_not_started = CourseFactory.create( mobile_available=True, static_asset_path="needed_for_split", @@ -563,7 +563,7 @@ def test_course_closed(self, mock_certificate_downloadable_status): 'is_downloadable': True, 'download_url': certificate_url, } - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) course_closed = CourseFactory.create( mobile_available=True, static_asset_path="needed_for_split", diff --git a/lms/djangoapps/mobile_api/tests/test_middleware.py b/lms/djangoapps/mobile_api/tests/test_middleware.py index 925138c6e4dd..711db699e116 100644 --- a/lms/djangoapps/mobile_api/tests/test_middleware.py +++ b/lms/djangoapps/mobile_api/tests/test_middleware.py @@ -9,11 +9,11 @@ import ddt from django.core.cache import caches from django.http import HttpRequest, HttpResponse -from pytz import UTC from lms.djangoapps.mobile_api.middleware import AppVersionUpgrade from lms.djangoapps.mobile_api.models import AppVersionConfig from openedx.core.djangolib.testing.utils import CacheIsolationTestCase +from zoneinfo import ZoneInfo @ddt.ddt @@ -35,13 +35,13 @@ def set_app_version_config(self): AppVersionConfig( platform="iOS", version="2.2.2", - expire_at=datetime(2014, 1, 1, tzinfo=UTC), + expire_at=datetime(2014, 1, 1, tzinfo=ZoneInfo("UTC")), enabled=True ).save() AppVersionConfig( platform="iOS", version="4.4.4", - expire_at=datetime(9000, 1, 1, tzinfo=UTC), + expire_at=datetime(9000, 1, 1, tzinfo=ZoneInfo("UTC")), enabled=True ).save() AppVersionConfig(platform="iOS", version="6.6.6", expire_at=None, enabled=True).save() @@ -50,13 +50,13 @@ def set_app_version_config(self): AppVersionConfig( platform="Android", version="2.2.2", - expire_at=datetime(2014, 1, 1, tzinfo=UTC), + expire_at=datetime(2014, 1, 1, tzinfo=ZoneInfo("UTC")), enabled=True ).save() AppVersionConfig( platform="Android", version="4.4.4", - expire_at=datetime(5000, 1, 1, tzinfo=UTC), + expire_at=datetime(5000, 1, 1, tzinfo=ZoneInfo("UTC")), enabled=True ).save() AppVersionConfig(platform="Android", version="8.8.8", expire_at=None, enabled=True).save() diff --git a/lms/djangoapps/mobile_api/tests/test_model.py b/lms/djangoapps/mobile_api/tests/test_model.py index 6c2b324817d0..9a612a432afc 100644 --- a/lms/djangoapps/mobile_api/tests/test_model.py +++ b/lms/djangoapps/mobile_api/tests/test_model.py @@ -7,9 +7,9 @@ import ddt from django.test import TestCase -from pytz import UTC from lms.djangoapps.mobile_api.models import AppVersionConfig, MobileApiConfig, MobileConfig +from zoneinfo import ZoneInfo @ddt.ddt @@ -24,19 +24,19 @@ def set_app_version_config(self): AppVersionConfig( platform="ios", version="2.2.2", - expire_at=datetime(2014, 1, 1, tzinfo=UTC), + expire_at=datetime(2014, 1, 1, tzinfo=ZoneInfo("UTC")), enabled=True ).save() AppVersionConfig( platform="ios", version="4.1.1", - expire_at=datetime(5000, 1, 1, tzinfo=UTC), + expire_at=datetime(5000, 1, 1, tzinfo=ZoneInfo("UTC")), enabled=False ).save() AppVersionConfig( platform="ios", version="4.4.4", - expire_at=datetime(9000, 1, 1, tzinfo=UTC), + expire_at=datetime(9000, 1, 1, tzinfo=ZoneInfo("UTC")), enabled=True ).save() AppVersionConfig(platform="ios", version="6.6.6", expire_at=None, enabled=True).save() @@ -46,13 +46,13 @@ def set_app_version_config(self): AppVersionConfig( platform="android", version="2.2.2", - expire_at=datetime(2014, 1, 1, tzinfo=UTC), + expire_at=datetime(2014, 1, 1, tzinfo=ZoneInfo("UTC")), enabled=True ).save() AppVersionConfig( platform="android", version="4.4.4", - expire_at=datetime(9000, 1, 1, tzinfo=UTC), + expire_at=datetime(9000, 1, 1, tzinfo=ZoneInfo("UTC")), enabled=True ).save() AppVersionConfig(platform="android", version="8.8.8", expire_at=None, enabled=True).save() @@ -75,10 +75,10 @@ def test_latest_version(self, platform, latest_version): assert latest_version == AppVersionConfig.latest_version(platform) @ddt.data( - ('ios', '3.3.3', datetime(9000, 1, 1, tzinfo=UTC)), - ('ios', '4.4.4', datetime(9000, 1, 1, tzinfo=UTC)), + ('ios', '3.3.3', datetime(9000, 1, 1, tzinfo=ZoneInfo("UTC"))), + ('ios', '4.4.4', datetime(9000, 1, 1, tzinfo=ZoneInfo("UTC"))), ('ios', '6.6.6', None), - ("android", '4.4.4', datetime(9000, 1, 1, tzinfo=UTC)), + ("android", '4.4.4', datetime(9000, 1, 1, tzinfo=ZoneInfo("UTC"))), ('android', '8.8.8', None) ) @ddt.unpack diff --git a/lms/djangoapps/mobile_api/testutils.py b/lms/djangoapps/mobile_api/testutils.py index f74d4b45a5fd..f1939689f696 100644 --- a/lms/djangoapps/mobile_api/testutils.py +++ b/lms/djangoapps/mobile_api/testutils.py @@ -16,7 +16,6 @@ from unittest.mock import patch import ddt -import pytz from django.conf import settings from django.urls import reverse from django.utils import timezone @@ -32,6 +31,7 @@ from lms.djangoapps.mobile_api.models import IgnoreMobileAvailableFlagConfig from lms.djangoapps.mobile_api.tests.test_milestones import MobileAPIMilestonesMixin from lms.djangoapps.mobile_api.utils import API_V1 +from zoneinfo import ZoneInfo class MobileAPITestCase(ModuleStoreTestCase, APITestCase): @@ -46,8 +46,8 @@ def setUp(self): self.course = CourseFactory.create( mobile_available=True, static_asset_path="needed_for_split", - end=datetime.datetime.now(pytz.UTC), - certificate_available_date=datetime.datetime.now(pytz.UTC) + end=datetime.datetime.now(ZoneInfo("UTC")), + certificate_available_date=datetime.datetime.now(ZoneInfo("UTC")) ) self.user = UserFactory.create() self.password = self.TEST_PASSWORD diff --git a/lms/djangoapps/mobile_api/users/tests.py b/lms/djangoapps/mobile_api/users/tests.py index 7c4b3e437de5..18395f86ab4e 100644 --- a/lms/djangoapps/mobile_api/users/tests.py +++ b/lms/djangoapps/mobile_api/users/tests.py @@ -8,7 +8,6 @@ from urllib.parse import parse_qs import ddt -import pytz from completion.models import BlockCompletion from completion.test_utils import CompletionWaffleTestMixin, submit_completions_for_testing from django.conf import settings @@ -45,6 +44,7 @@ from openedx.features.course_experience.tests.views.helpers import add_course_mode from xmodule.course_block import DEFAULT_START_DATE # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo from .. import errors from .serializers import CourseEnrollmentSerializer, CourseEnrollmentSerializerv05 @@ -108,8 +108,8 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest ALLOW_ACCESS_TO_UNRELEASED_COURSE = True ALLOW_ACCESS_TO_MILESTONE_COURSE = True ALLOW_ACCESS_TO_NON_VISIBLE_COURSE = True - NEXT_WEEK = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=7) - LAST_WEEK = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=7) + NEXT_WEEK = datetime.datetime.now(ZoneInfo("UTC")) + datetime.timedelta(days=7) + LAST_WEEK = datetime.datetime.now(ZoneInfo("UTC")) - datetime.timedelta(days=7) THREE_YEARS_AGO = now() - datetime.timedelta(days=(365 * 3)) ADVERTISED_START = "Spring 2016" ENABLED_SIGNALS = ['course_published'] @@ -1467,7 +1467,7 @@ def test_user_have_active_and_inactive_enrollments_and_no_completions(self) -> N old_course = CourseFactory.create(org="edx", mobile_available=True) self.enroll(old_course.id) old_enrollment = CourseEnrollment.objects.filter(user=self.user, course=old_course.course_id).first() - old_enrollment.created = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=31) + old_enrollment.created = datetime.datetime.now(ZoneInfo("UTC")) - datetime.timedelta(days=31) old_enrollment.save() response = self.api_response(api_version=API_V1) @@ -1495,7 +1495,7 @@ def test_different_enrollment_dates(self, enrolled_days_ago: int, recently_activ course = CourseFactory.create(org="edx", mobile_available=True, run='1001') self.enroll(course.id) enrollment = CourseEnrollment.objects.filter(user=self.user, course=course.course_id).first() - enrollment.created = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=enrolled_days_ago) + enrollment.created = datetime.datetime.now(ZoneInfo("UTC")) - datetime.timedelta(days=enrolled_days_ago) enrollment.save() response = self.api_response(api_version=API_V1) @@ -1529,7 +1529,7 @@ def test_different_completion_dates(self, completed_days_ago: int, recently_acti self.enroll(course.id) enrollment = CourseEnrollment.objects.filter(user=self.user, course=course.course_id).first() # make enrollment older 30 days ago - enrollment.created = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=50) + enrollment.created = datetime.datetime.now(ZoneInfo("UTC")) - datetime.timedelta(days=50) enrollment.save() completion = BlockCompletion.objects.create( user=self.user, @@ -1538,7 +1538,7 @@ def test_different_completion_dates(self, completed_days_ago: int, recently_acti block_key=section.location, completion=0.5, ) - completion.created = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=completed_days_ago) + completion.created = datetime.datetime.now(ZoneInfo("UTC")) - datetime.timedelta(days=completed_days_ago) completion.save() response = self.api_response(api_version=API_V1) diff --git a/lms/djangoapps/mobile_api/users/views.py b/lms/djangoapps/mobile_api/users/views.py index 324db83a374c..53a013116c0c 100644 --- a/lms/djangoapps/mobile_api/users/views.py +++ b/lms/djangoapps/mobile_api/users/views.py @@ -8,7 +8,6 @@ from functools import cached_property from typing import Dict, List, Optional, Set -import pytz from completion.exceptions import UnavailableCompletionData from completion.models import BlockCompletion from completion.utilities import get_key_to_last_completed_block @@ -44,6 +43,7 @@ from openedx.features.course_duration_limits.access import check_course_expired from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo from .. import errors from ..decorators import mobile_course_access, mobile_view @@ -603,7 +603,7 @@ def get(self, request, *args, **kwargs) -> Response: """ Gets user's enrollments status. """ - active_status_date = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=30) + active_status_date = datetime.datetime.now(ZoneInfo("UTC")) - datetime.timedelta(days=30) username = kwargs.get('username') course_ids_where_user_has_completions = self._get_course_ids_where_user_has_completions( username, diff --git a/lms/djangoapps/program_enrollments/rest_api/v1/utils.py b/lms/djangoapps/program_enrollments/rest_api/v1/utils.py index e72cb76ce87b..013822c34b5c 100644 --- a/lms/djangoapps/program_enrollments/rest_api/v1/utils.py +++ b/lms/djangoapps/program_enrollments/rest_api/v1/utils.py @@ -7,7 +7,6 @@ from django.core.exceptions import PermissionDenied from django.utils.functional import cached_property from opaque_keys.edx.keys import CourseKey -from pytz import UTC from rest_framework import status from rest_framework.pagination import CursorPagination @@ -21,6 +20,7 @@ from lms.djangoapps.program_enrollments.constants import ProgramEnrollmentStatuses from openedx.core.djangoapps.catalog.utils import course_run_keys_for_program, get_programs, is_course_run_in_program from openedx.core.lib.api.view_utils import verify_course_exists +from zoneinfo import ZoneInfo from .constants import CourseRunProgressStatuses @@ -362,7 +362,7 @@ def get_course_run_status(course_overview, certificate_info): else: return CourseRunProgressStatuses.UPCOMING elif course_overview.pacing == 'self': - thirty_days_ago = datetime.now(UTC) - timedelta(30) + thirty_days_ago = datetime.now(ZoneInfo("UTC")) - timedelta(30) certificate_completed = is_certificate_passing and ( certificate_creation_date <= thirty_days_ago ) diff --git a/lms/djangoapps/support/tests/test_views.py b/lms/djangoapps/support/tests/test_views.py index df713daf0b98..47c5a200bdc3 100644 --- a/lms/djangoapps/support/tests/test_views.py +++ b/lms/djangoapps/support/tests/test_views.py @@ -27,7 +27,6 @@ from oauth2_provider.models import AccessToken, RefreshToken from opaque_keys.edx.locator import BlockUsageLocator from organizations.tests.factories import OrganizationFactory -from pytz import UTC from rest_framework import status from social_django.models import UserSocialAuth from xmodule.modulestore.tests.django_utils import ( @@ -69,6 +68,7 @@ EnterpriseCourseEnrollmentFactory, EnterpriseCustomerUserFactory ) +from zoneinfo import ZoneInfo try: from consent.models import DataSharingConsent @@ -316,7 +316,7 @@ def setUp(self): self.verification_deadline = VerificationDeadline( course_key=self.course.id, - deadline=datetime.now(UTC) + timedelta(days=365) + deadline=datetime.now(ZoneInfo("UTC")) + timedelta(days=365) ) self.verification_deadline.save() @@ -746,15 +746,15 @@ def assert_update_enrollment(self, search_string_type, new_mode): def set_course_end_date_and_expiry(self): """ Set the course-end date and expire its verified mode.""" - self.course.start = datetime(year=1970, month=1, day=1, tzinfo=UTC) - self.course.end = datetime(year=1970, month=1, day=10, tzinfo=UTC) + self.course.start = datetime(year=1970, month=1, day=1, tzinfo=ZoneInfo("UTC")) + self.course.end = datetime(year=1970, month=1, day=10, tzinfo=ZoneInfo("UTC")) # change verified mode expiry. verified_mode = CourseMode.objects.get( course_id=self.course.id, mode_slug=CourseMode.VERIFIED ) - verified_mode.expiration_datetime = datetime(year=1970, month=1, day=9, tzinfo=UTC) + verified_mode.expiration_datetime = datetime(year=1970, month=1, day=9, tzinfo=ZoneInfo("UTC")) verified_mode.save() @@ -2111,7 +2111,7 @@ def setUp(self): """ super().setUp() SupportStaffRole().add_users(self.user) - self.now = datetime.now().replace(tzinfo=UTC) + self.now = datetime.now().replace(tzinfo=ZoneInfo("UTC")) self.course = CourseFactory.create( start=self.now - timedelta(days=90), end=self.now + timedelta(days=90), diff --git a/lms/djangoapps/teams/models.py b/lms/djangoapps/teams/models.py index d58fe9801d0e..e44531c4f0f6 100644 --- a/lms/djangoapps/teams/models.py +++ b/lms/djangoapps/teams/models.py @@ -6,7 +6,6 @@ from datetime import datetime from uuid import uuid4 -import pytz from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.core.exceptions import ObjectDoesNotExist from django.db import models @@ -34,6 +33,7 @@ thread_unfollowed, thread_voted ) +from zoneinfo import ZoneInfo from .errors import ( AddToIncompatibleTeamError, @@ -101,7 +101,7 @@ def handle_activity(user, post, original_author_id=None): def utc_now(): - return datetime.utcnow().replace(tzinfo=pytz.utc) + return datetime.utcnow().replace(tzinfo=ZoneInfo("UTC")) class CourseTeam(models.Model): @@ -286,7 +286,7 @@ def save(self, *args, **kwargs): # lint-amnesty, pylint: disable=arguments-diff if self.pk is None: should_reset_team_size = True if not self.last_activity_at: - self.last_activity_at = datetime.utcnow().replace(tzinfo=pytz.utc) + self.last_activity_at = datetime.utcnow().replace(tzinfo=ZoneInfo("UTC")) super().save(*args, **kwargs) if should_reset_team_size: self.team.reset_team_size() @@ -347,7 +347,7 @@ def update_last_activity(cls, user, discussion_topic_id): # information. except ObjectDoesNotExist: return - now = datetime.utcnow().replace(tzinfo=pytz.utc) + now = datetime.utcnow().replace(tzinfo=ZoneInfo("UTC")) membership.last_activity_at = now membership.team.last_activity_at = now membership.team.save() diff --git a/lms/djangoapps/teams/tests/factories.py b/lms/djangoapps/teams/tests/factories.py index 178d926183ee..bef5230bc3e9 100644 --- a/lms/djangoapps/teams/tests/factories.py +++ b/lms/djangoapps/teams/tests/factories.py @@ -7,12 +7,12 @@ from uuid import uuid4 import factory -import pytz from factory.django import DjangoModelFactory from lms.djangoapps.teams.models import CourseTeam, CourseTeamMembership +from zoneinfo import ZoneInfo -LAST_ACTIVITY_AT = datetime(2015, 8, 15, 0, 0, 0, tzinfo=pytz.utc) +LAST_ACTIVITY_AT = datetime(2015, 8, 15, 0, 0, 0, tzinfo=ZoneInfo("UTC")) class CourseTeamFactory(DjangoModelFactory): diff --git a/lms/djangoapps/teams/tests/test_models.py b/lms/djangoapps/teams/tests/test_models.py index 60190076004c..e43bd2009999 100644 --- a/lms/djangoapps/teams/tests/test_models.py +++ b/lms/djangoapps/teams/tests/test_models.py @@ -10,7 +10,6 @@ import ddt import pytest -import pytz from opaque_keys.edx.keys import CourseKey from common.djangoapps.course_modes.models import CourseMode @@ -34,6 +33,7 @@ from openedx.core.lib.teams_config import TeamsConfig from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo COURSE_KEY1 = CourseKey.from_string('edx/history/1') COURSE_KEY2 = CourseKey.from_string('edx/math/1') @@ -292,7 +292,7 @@ def assert_last_activity_updated(self, should_update): if should_update: assert team.last_activity_at > team_last_activity assert team_membership.last_activity_at > team_membership_last_activity - now = datetime.utcnow().replace(tzinfo=pytz.utc) + now = datetime.utcnow().replace(tzinfo=ZoneInfo("UTC")) assert now > team.last_activity_at assert now > team_membership.last_activity_at self.assert_event_emitted( diff --git a/lms/djangoapps/teams/tests/test_views.py b/lms/djangoapps/teams/tests/test_views.py index 6eaa1bdb680e..33e090ddc543 100644 --- a/lms/djangoapps/teams/tests/test_views.py +++ b/lms/djangoapps/teams/tests/test_views.py @@ -10,7 +10,6 @@ from uuid import UUID import ddt -import pytz from dateutil import parser from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user @@ -33,6 +32,7 @@ from openedx.core.lib.teams_config import TeamsConfig from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo from .factories import CourseTeamFactory, LAST_ACTIVITY_AT from ..models import CourseTeamMembership from ..search_indexes import CourseTeam, CourseTeamIndexer, course_team_post_save_callback @@ -862,7 +862,7 @@ def test_order_by(self, field, status, names): dispatch_uid='teams.signals.course_team_post_save_callback' ): solar_team = self.test_team_name_id_map['Sólar team'] - solar_team.last_activity_at = datetime.utcnow().replace(tzinfo=pytz.utc) + solar_team.last_activity_at = datetime.utcnow().replace(tzinfo=ZoneInfo("UTC")) solar_team.save() data = {'order_by': field} if field else {} @@ -1292,7 +1292,7 @@ def test_full_student_creator(self): del team['membership'] # verify that it's been set to a time today. - assert parser.parse(team['last_activity_at']).date() == datetime.utcnow().replace(tzinfo=pytz.utc).date() + assert parser.parse(team['last_activity_at']).date() == datetime.utcnow().replace(tzinfo=ZoneInfo("UTC")).date() del team['last_activity_at'] # Verify that the creating user gets added to the team. diff --git a/lms/djangoapps/verify_student/tests/test_services.py b/lms/djangoapps/verify_student/tests/test_services.py index 4bfa8c27d5b3..55091d2d0a56 100644 --- a/lms/djangoapps/verify_student/tests/test_services.py +++ b/lms/djangoapps/verify_student/tests/test_services.py @@ -13,7 +13,6 @@ from django.utils.translation import gettext as _ from freezegun import freeze_time from openedx_filters import PipelineStep -from pytz import utc from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.verify_student.models import ( @@ -27,6 +26,7 @@ from xmodule.modulestore.tests.django_utils import \ ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo FAKE_SETTINGS = { "DAYS_GOOD_FOR": 365, @@ -204,10 +204,10 @@ def test_get_expiration_datetime(self): user_a = UserFactory.create() SSOVerification.objects.create( - user=user_a, status='approved', expiration_date=datetime(2021, 11, 12, 0, 0, tzinfo=timezone.utc) + user=user_a, status='approved', expiration_date=datetime(2021, 11, 12, 0, 0, tzinfo=ZoneInfo("UTC")) ) newer_record = SSOVerification.objects.create( - user=user_a, status='approved', expiration_date=datetime(2022, 1, 12, 0, 0, tzinfo=timezone.utc) + user=user_a, status='approved', expiration_date=datetime(2022, 1, 12, 0, 0, tzinfo=ZoneInfo("UTC")) ) expiration_datetime = IDVerificationService.get_expiration_datetime(user_a, ['approved']) @@ -221,10 +221,10 @@ def test_get_expiration_datetime_mixed_models(self): user = UserFactory.create() SoftwareSecurePhotoVerification.objects.create( - user=user, status='approved', expiration_date=datetime(2021, 11, 12, 0, 0, tzinfo=timezone.utc) + user=user, status='approved', expiration_date=datetime(2021, 11, 12, 0, 0, tzinfo=ZoneInfo("UTC")) ) newest = VerificationAttempt.objects.create( - user=user, status='approved', expiration_datetime=datetime(2022, 1, 12, 0, 0, tzinfo=timezone.utc) + user=user, status='approved', expiration_datetime=datetime(2022, 1, 12, 0, 0, tzinfo=ZoneInfo("UTC")) ) expiration_datetime = IDVerificationService.get_expiration_datetime(user, ['approved']) @@ -257,7 +257,7 @@ def test_approved_software_secure_verification(self): SoftwareSecurePhotoVerification.objects.create(user=self.user, status='approved') status = IDVerificationService.user_status(self.user) expected_status = {'status': 'approved', 'error': '', 'should_display': True, 'verification_expiry': '', - 'status_date': datetime.now(utc)} + 'status_date': datetime.now(ZoneInfo("UTC"))} self.assertDictEqual(status, expected_status) def test_denied_software_secure_verification(self): @@ -280,7 +280,7 @@ def test_approved_verification_attempt_verification(self): VerificationAttempt.objects.create(user=self.user, status='approved') status = IDVerificationService.user_status(self.user) expected_status = {'status': 'approved', 'error': '', 'should_display': True, 'verification_expiry': '', - 'status_date': datetime.now(utc)} + 'status_date': datetime.now(ZoneInfo("UTC"))} self.assertDictEqual(status, expected_status) def test_denied_verification_attempt_verification(self): @@ -303,7 +303,7 @@ def test_approved_sso_verification(self): SSOVerification.objects.create(user=self.user, status='approved') status = IDVerificationService.user_status(self.user) expected_status = {'status': 'approved', 'error': '', 'should_display': False, 'verification_expiry': '', - 'status_date': datetime.now(utc)} + 'status_date': datetime.now(ZoneInfo("UTC"))} self.assertDictEqual(status, expected_status) def test_denied_sso_verification(self): @@ -324,7 +324,7 @@ def test_manual_verification(self): ManualVerification.objects.create(user=self.user, status='approved') status = IDVerificationService.user_status(self.user) expected_status = {'status': 'approved', 'error': '', 'should_display': False, 'verification_expiry': '', - 'status_date': datetime.now(utc)} + 'status_date': datetime.now(ZoneInfo("UTC"))} self.assertDictEqual(status, expected_status) @ddt.idata(itertools.product( @@ -336,13 +336,13 @@ def test_expiring_software_secure_verification(self, value): with freeze_time('2015-07-11') as frozen_datetime: # create approved photo verification for the user verification_model.objects.create(user=self.user, status='approved') - expiring_datetime = datetime.now(utc) + expiring_datetime = datetime.now(ZoneInfo("UTC")) frozen_datetime.move_to('2015-07-14') # create another according to status passed in. verification_model.objects.create(user=self.user, status=new_status) status_date = expiring_datetime if new_status == 'approved': - status_date = datetime.now(utc) + status_date = datetime.now(ZoneInfo("UTC")) expected_status = {'status': 'approved', 'error': '', 'should_display': True, 'verification_expiry': '', 'status_date': status_date} status = IDVerificationService.user_status(self.user) diff --git a/lms/templates/courseware/progress.html b/lms/templates/courseware/progress.html index 3ee4044fcbbf..8a85e092172c 100644 --- a/lms/templates/courseware/progress.html +++ b/lms/templates/courseware/progress.html @@ -9,7 +9,7 @@ from django.urls import reverse from urllib.parse import quote_plus from django.utils.translation import gettext as _ -from pytz import UTC +from zoneinfo import ZoneInfo from common.djangoapps.course_modes.models import CourseMode from lms.djangoapps.certificates.data import CertificateStatuses @@ -171,7 +171,7 @@

${ chapter['display_name']}

<% hide_url_date = (section.self_paced and section.end) or section.due - hide_url = not staff_access and section.hide_after_due and hide_url_date and datetime.now(UTC) > hide_url_date + hide_url = not staff_access and section.hide_after_due and hide_url_date and datetime.now(ZoneInfo("UTC")) > hide_url_date earned = section.graded_total.earned total = section.graded_total.possible diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 6149418035ca..86e5e64f5dce 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -3,7 +3,7 @@ <%def name="online_help_token()"><% return "learnerdashboard" %> <%namespace name='static' file='static_content.html'/> <%! -import pytz +from zoneinfo import ZoneInfo from datetime import datetime, timedelta from django.urls import reverse from django.utils.translation import gettext as _ @@ -166,7 +166,7 @@ entitlement = enrollment if isinstance(enrollment, CourseEntitlement) else None entitlement_session = entitlement.enrollment_course_run if entitlement else None entitlement_days_until_expiration = entitlement.get_days_until_expiration() if entitlement else None - entitlement_expiration = datetime.now(tz=pytz.UTC) + timedelta(days=entitlement_days_until_expiration) if (entitlement and entitlement_days_until_expiration < settings.ENTITLEMENT_EXPIRED_ALERT_PERIOD) else None + entitlement_expiration = datetime.now(tz=ZoneInfo("UTC")) + timedelta(days=entitlement_days_until_expiration) if (entitlement and entitlement_days_until_expiration < settings.ENTITLEMENT_EXPIRED_ALERT_PERIOD) else None entitlement_expiration_date = strftime_localized(entitlement_expiration, 'SHORT_DATE') if entitlement and entitlement_expiration else None entitlement_expired_at = strftime_localized(entitlement.expired_at_datetime, 'SHORT_DATE') if entitlement and entitlement.expired_at_datetime else None diff --git a/lms/templates/instructor/instructor_dashboard_2/special_exams.html b/lms/templates/instructor/instructor_dashboard_2/special_exams.html index 2658af0bc70e..b87e954e459f 100644 --- a/lms/templates/instructor/instructor_dashboard_2/special_exams.html +++ b/lms/templates/instructor/instructor_dashboard_2/special_exams.html @@ -2,7 +2,6 @@ <%! from django.utils.translation import gettext as _ from datetime import datetime, timedelta -import pytz %>
% if section_data.get('mfe_view_url'): From b1bb71fa8e7bc04fcb0df188bb5026eac621e023 Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Wed, 29 Oct 2025 08:04:44 +0000 Subject: [PATCH 2/5] fixup! linter fixes --- lms/djangoapps/discussion/rest_api/tests/test_api_v2.py | 4 ++-- lms/djangoapps/discussion/rest_api/tests/test_utils.py | 6 ++++-- lms/djangoapps/grades/tests/utils.py | 8 +++++++- lms/djangoapps/verify_student/tests/test_services.py | 9 +++++---- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lms/djangoapps/discussion/rest_api/tests/test_api_v2.py b/lms/djangoapps/discussion/rest_api/tests/test_api_v2.py index 9a722c4a5f9f..e7db1fb37b7d 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_api_v2.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_api_v2.py @@ -166,8 +166,8 @@ def _set_course_discussion_blackout(course, user_id): user_id: User id of user enrolled in the course """ course.discussion_blackouts = [ - datetime.now(ZoneInfo("UTC")) - timedelta(days=3), - datetime.now(ZoneInfo("UTC")) + timedelta(days=3), + datetime.now(ZoneInfo("UTC")) - timedelta(days=3), + datetime.now(ZoneInfo("UTC")) + timedelta(days=3), ] configuration = DiscussionsConfiguration.get(course.id) configuration.posting_restrictions = PostingRestriction.SCHEDULED diff --git a/lms/djangoapps/discussion/rest_api/tests/test_utils.py b/lms/djangoapps/discussion/rest_api/tests/test_utils.py index d1afa8dbf03a..4aa178b357b9 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_utils.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_utils.py @@ -38,8 +38,10 @@ def setUp(self): super().setUp() # lint-amnesty, pylint: disable=super-with-arguments self.course = CourseFactory.create() - self.course.discussion_blackouts = [datetime.now(ZoneInfo("UTC")) - timedelta(days=3), - datetime.now(ZoneInfo("UTC")) + timedelta(days=3)] + self.course.discussion_blackouts = [ + datetime.now(ZoneInfo("UTC")) - timedelta(days=3), + datetime.now(ZoneInfo("UTC")) + timedelta(days=3) + ] configuration = DiscussionsConfiguration.get(self.course.id) configuration.posting_restrictions = PostingRestriction.SCHEDULED configuration.save() diff --git a/lms/djangoapps/grades/tests/utils.py b/lms/djangoapps/grades/tests/utils.py index 068832f6e14c..867fc24d14a4 100644 --- a/lms/djangoapps/grades/tests/utils.py +++ b/lms/djangoapps/grades/tests/utils.py @@ -52,7 +52,13 @@ def mock_get_score(earned=0, possible=1, first_attempted=datetime(2000, 1, 1, 0, @contextmanager -def mock_get_submissions_score(earned=0, possible=1, first_attempted=datetime(2000, 1, 1, 0, 0, 0, tzinfo=ZoneInfo("UTC"))): +def mock_get_submissions_score( + earned=0, + possible=1, + first_attempted=datetime( + 2000, 1, 1, 0, 0, 0, tzinfo=ZoneInfo("UTC") + ), +): """ Mocks the _get_submissions_score function to return the specified values """ diff --git a/lms/djangoapps/verify_student/tests/test_services.py b/lms/djangoapps/verify_student/tests/test_services.py index 55091d2d0a56..e5d99ca7a06c 100644 --- a/lms/djangoapps/verify_student/tests/test_services.py +++ b/lms/djangoapps/verify_student/tests/test_services.py @@ -13,6 +13,7 @@ from django.utils.translation import gettext as _ from freezegun import freeze_time from openedx_filters import PipelineStep +from pytz import utc from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.verify_student.models import ( @@ -204,10 +205,10 @@ def test_get_expiration_datetime(self): user_a = UserFactory.create() SSOVerification.objects.create( - user=user_a, status='approved', expiration_date=datetime(2021, 11, 12, 0, 0, tzinfo=ZoneInfo("UTC")) + user=user_a, status='approved', expiration_date=datetime(2021, 11, 12, 0, 0, tzinfo=timezone.utc) ) newer_record = SSOVerification.objects.create( - user=user_a, status='approved', expiration_date=datetime(2022, 1, 12, 0, 0, tzinfo=ZoneInfo("UTC")) + user=user_a, status='approved', expiration_date=datetime(2022, 1, 12, 0, 0, tzinfo=timezone.utc) ) expiration_datetime = IDVerificationService.get_expiration_datetime(user_a, ['approved']) @@ -221,10 +222,10 @@ def test_get_expiration_datetime_mixed_models(self): user = UserFactory.create() SoftwareSecurePhotoVerification.objects.create( - user=user, status='approved', expiration_date=datetime(2021, 11, 12, 0, 0, tzinfo=ZoneInfo("UTC")) + user=user, status='approved', expiration_date=datetime(2021, 11, 12, 0, 0, tzinfo=timezone.utc) ) newest = VerificationAttempt.objects.create( - user=user, status='approved', expiration_datetime=datetime(2022, 1, 12, 0, 0, tzinfo=ZoneInfo("UTC")) + user=user, status='approved', expiration_datetime=datetime(2022, 1, 12, 0, 0, tzinfo=timezone.utc) ) expiration_datetime = IDVerificationService.get_expiration_datetime(user, ['approved']) From 1cb99e137d0c9de5118955eaadd187681dc787bd Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Wed, 29 Oct 2025 09:38:12 +0000 Subject: [PATCH 3/5] fixup! unit testcase fix --- .../courseware/tests/test_date_summary.py | 55 +++++++++++++------ .../verify_student/tests/test_services.py | 1 - 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py index 176aabe0b4c2..951837e6a298 100644 --- a/lms/djangoapps/courseware/tests/test_date_summary.py +++ b/lms/djangoapps/courseware/tests/test_date_summary.py @@ -350,7 +350,10 @@ def test_enabled_block_types_with_expired_course(self): self.make_request(user) # These two lines are to trigger the course expired block to be rendered CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.AUDIT) - CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC"))) + CourseDurationLimitConfig.objects.create( + enabled=True, + enabled_as_of=datetime(2018, 1, 1, tzinfo=ZoneInfo("UTC")) + ) expected_blocks = ( TodaysDate, CourseEndDate, CourseExpiredDate, VerifiedUpgradeDeadlineDate @@ -390,7 +393,7 @@ def test_todays_date_block(self): block = TodaysDate(course, user) assert block.is_enabled assert block.is_allowed - assert block.date == datetime.now(utc) + assert block.date == datetime.now(ZoneInfo("UTC")) assert block.title == 'current_datetime' ## Tests Course Start Date @@ -402,19 +405,35 @@ def test_course_start_date(self): @ddt.data( # Instructor-paced course: Use course start date - (False, datetime(2025, 1, 10, tzinfo=utc), datetime(2025, 1, 12, tzinfo=utc), - datetime(2025, 1, 10, tzinfo=utc), 'Course starts'), + (False, datetime(2025, 1, 10, tzinfo=ZoneInfo("UTC")), datetime(2025, 1, 12, tzinfo=ZoneInfo("UTC")), + datetime(2025, 1, 10, tzinfo=ZoneInfo("UTC")), 'Course starts'), # Self-paced course: Enrollment created later than course start - (True, datetime(2025, 1, 10, tzinfo=utc), datetime(2025, 1, 12), datetime(2025, 1, 12, tzinfo=utc), - 'Enrollment Date'), + ( + True, + datetime(2025, 1, 10, tzinfo=ZoneInfo("UTC")), + datetime(2025, 1, 12), + datetime(2025, 1, 12, tzinfo=ZoneInfo("UTC")), + 'Enrollment Date' + ), # Self-paced course: Enrollment created earlier than course start - (True, datetime(2025, 1, 10, tzinfo=utc), datetime(2025, 1, 8), datetime(2025, 1, 10, tzinfo=utc), - 'Course starts'), + ( + True, + datetime(2025, 1, 10, tzinfo=ZoneInfo("UTC")), + datetime(2025, 1, 8), + datetime(2025, 1, 10, tzinfo=ZoneInfo("UTC")), + 'Course starts' + ), # Self-paced course: No enrollment - (True, datetime(2025, 1, 10, tzinfo=utc), None, datetime(2025, 1, 10, tzinfo=utc), 'Course starts'), + ( + True, + datetime(2025, 1, 10, tzinfo=ZoneInfo("UTC")), + None, + datetime(2025, 1, 10, tzinfo=ZoneInfo("UTC")), + 'Course starts' + ), ) @ddt.unpack def test_course_start_date_label(self, self_paced, course_start, enrollment_created, expected_date, expected_title): @@ -465,7 +484,7 @@ def test_course_end_date_self_paced(self, days_till_end): In self-paced courses, the end date will only show up if the learner views the course within 365 days of the course end date. """ - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) course = CourseFactory.create( start=now + timedelta(days=-7), end=now + timedelta(days=days_till_end), self_paced=True) user = create_user() @@ -507,7 +526,7 @@ def test_no_certificate_available_date_for_self_paced(self): course = create_self_paced_course_run() verified_user = create_user() CourseEnrollmentFactory(course_id=course.id, user=verified_user, mode=CourseMode.VERIFIED) - course.certificate_available_date = datetime.now(utc) + timedelta(days=7) + course.certificate_available_date = datetime.now(ZoneInfo("UTC")) + timedelta(days=7) course.save() block = CertificateAvailableDate(course, verified_user) assert block.date is not None @@ -528,7 +547,7 @@ def test_no_certificate_available_date_for_audit_course(self): assert len(all_course_modes) == 1 assert all_course_modes[0].slug == CourseMode.AUDIT - course.certificate_available_date = datetime.now(utc) + timedelta(days=7) + course.certificate_available_date = datetime.now(ZoneInfo("UTC")) + timedelta(days=7) course.save() # Verify Certificate Available Date is not enabled for learner. @@ -543,7 +562,7 @@ def test_certificate_available_date_defined(self): CourseEnrollmentFactory(course_id=course.id, user=audit_user, mode=CourseMode.AUDIT) verified_user = create_user() CourseEnrollmentFactory(course_id=course.id, user=verified_user, mode=CourseMode.VERIFIED) - course.certificate_available_date = datetime.now(utc) + timedelta(days=7) + course.certificate_available_date = datetime.now(ZoneInfo("UTC")) + timedelta(days=7) enable_course_certificates(course) expected_blocks = [ CourseEndDate, CourseStartDate, TodaysDate, VerificationDeadlineDate, CertificateAvailableDate @@ -587,7 +606,7 @@ def test_verification_deadline_date_upcoming(self): block = VerificationDeadlineDate(course, user) assert block.css_class == 'verification-deadline-upcoming' assert block.title == 'Verification Deadline' - assert block.date == (datetime.now(utc) + timedelta(days=14)) + assert block.date == (datetime.now(ZoneInfo("UTC")) + timedelta(days=14)) assert block.description ==\ 'You must successfully complete verification before this date to qualify for a Verified Certificate.' assert block.link_text == 'Verify My Identity' @@ -602,7 +621,7 @@ def test_verification_deadline_date_retry(self): block = VerificationDeadlineDate(course, user) assert block.css_class == 'verification-deadline-retry' assert block.title == 'Verification Deadline' - assert block.date == (datetime.now(utc) + timedelta(days=14)) + assert block.date == (datetime.now(ZoneInfo("UTC")) + timedelta(days=14)) assert block.description ==\ 'You must successfully complete verification before this date to qualify for a Verified Certificate.' assert block.link_text == 'Retry Verification' @@ -617,7 +636,7 @@ def test_verification_deadline_date_denied(self): block = VerificationDeadlineDate(course, user) assert block.css_class == 'verification-deadline-passed' assert block.title == 'Missed Verification Deadline' - assert block.date == (datetime.now(utc) + timedelta(days=- 1)) + assert block.date == (datetime.now(ZoneInfo("UTC")) + timedelta(days=- 1)) assert block.description == "Unfortunately you missed this course's deadline for a successful verification." assert block.link_text == 'Learn More' assert block.link == '' @@ -820,7 +839,7 @@ def create_course_run( days_till_verification_deadline (int): Number of days until the course run's verification deadline. If this value is set to `None` no deadline will be verification deadline will be created. """ - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) course = CourseFactory.create(start=now + timedelta(days=days_till_start)) course.end = None @@ -854,7 +873,7 @@ def create_self_paced_course_run(days_till_start=1, org_id=None): days_till_start (int): Number of days until the course starts. org_id (string): String org id to assign the course to (default: None; use CourseFactory default) """ - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) course = CourseFactory.create(start=now + timedelta(days=days_till_start), self_paced=True, org=org_id if org_id else 'TestedX') diff --git a/lms/djangoapps/verify_student/tests/test_services.py b/lms/djangoapps/verify_student/tests/test_services.py index e5d99ca7a06c..fcb35253abf4 100644 --- a/lms/djangoapps/verify_student/tests/test_services.py +++ b/lms/djangoapps/verify_student/tests/test_services.py @@ -13,7 +13,6 @@ from django.utils.translation import gettext as _ from freezegun import freeze_time from openedx_filters import PipelineStep -from pytz import utc from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.verify_student.models import ( From f2055c75b19be7232b048e92c5ca62ef4beec30e Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Wed, 29 Oct 2025 10:17:34 +0000 Subject: [PATCH 4/5] fixup! linter fixes --- lms/djangoapps/ccx/views.py | 1 + lms/djangoapps/certificates/tests/test_api.py | 4 ++-- .../management/commands/tests/test_goal_reminder_email.py | 6 +++++- lms/djangoapps/courseware/tests/test_masquerade.py | 5 ++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/ccx/views.py b/lms/djangoapps/ccx/views.py index 0f61870139ed..afd72f72006c 100644 --- a/lms/djangoapps/ccx/views.py +++ b/lms/djangoapps/ccx/views.py @@ -53,6 +53,7 @@ from openedx.core.djangoapps.django_comment_common.utils import seed_permissions_roles from openedx.core.lib.courses import get_course_by_id from xmodule.modulestore.django import SignalHandler # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo log = logging.getLogger(__name__) TODAY = datetime.datetime.today # for patching in tests diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py index cd1bcbc1be3a..b9bf9ac2654a 100644 --- a/lms/djangoapps/certificates/tests/test_api.py +++ b/lms/djangoapps/certificates/tests/test_api.py @@ -1162,7 +1162,7 @@ def test_display_date_for_certificate_cdb_end_with_date(self): with configure_waffle_namespace(True): self.course.self_paced = False self.course.certificates_display_behavior = CertificatesDisplayBehaviors.END_WITH_DATE - self.course.certificate_available_date = datetime(2017, 2, 1, tzinfo=pytz.UTC) + self.course.certificate_available_date = datetime(2017, 2, 1, tzinfo=ZoneInfo("UTC")) assert display_date_for_certificate(self.course, self.certificate) == self.course.certificate_available_date def test_display_date_for_certificate_cdb_end(self): @@ -1181,7 +1181,7 @@ def test_display_date_for_certificate_date_override(self): if-and-only-if date override associated with the certificate instance. """ with configure_waffle_namespace(True): - self.certificate.date_override = datetime(2016, 1, 1, tzinfo=pytz.UTC) + self.certificate.date_override = datetime(2016, 1, 1, tzinfo=ZoneInfo("UTC")) assert display_date_for_certificate(self.course, self.certificate) == self.certificate.date_override.date def test_display_date_for_self_paced_course_run(self): diff --git a/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py b/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py index d6fa6bec6057..0cd47f619faa 100644 --- a/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py +++ b/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py @@ -114,7 +114,11 @@ def test_will_send_on_right_day(self, days_per_week, days_of_activity, current_d """Verify that via the normal conditions, we either send or not based on the days of activity""" goal = self.make_valid_goal(days_per_week=days_per_week) for day in range(days_of_activity): - UserActivityFactory(user=goal.user, course_key=goal.course_key, date=datetime(2021, 3, day + 1, tzinfo=ZoneInfo("UTC"))) + UserActivityFactory( + user=goal.user, + course_key=goal.course_key, + date=datetime(2021, 3, day + 1, tzinfo=ZoneInfo("UTC")) + ) self.call_command(day=current_day, expect_sent=expect_sent) diff --git a/lms/djangoapps/courseware/tests/test_masquerade.py b/lms/djangoapps/courseware/tests/test_masquerade.py index 53b2752d4306..d59f4a0c36a8 100644 --- a/lms/djangoapps/courseware/tests/test_masquerade.py +++ b/lms/djangoapps/courseware/tests/test_masquerade.py @@ -510,7 +510,10 @@ class SetupMasqueradeTests(SharedModuleStoreTestCase, ): def setUp(self): super().setUp() - self.course = CourseFactory.create(number='setup-masquerade-test', metadata={'start': datetime.now(ZoneInfo("UTC"))}) + self.course = CourseFactory.create( + number='setup-masquerade-test', + metadata={'start': datetime.now(ZoneInfo("UTC"))} + ) self.request = RequestFactory().request() self.staff = StaffFactory(course_key=self.course.id) self.student = UserFactory() From 42fbcc621094bdf43b8cb1f3c4a0dea15d153ad9 Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Wed, 29 Oct 2025 11:23:59 +0000 Subject: [PATCH 5/5] fixup! code fixes --- lms/djangoapps/ccx/tests/test_overrides.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lms/djangoapps/ccx/tests/test_overrides.py b/lms/djangoapps/ccx/tests/test_overrides.py index a2191eaed5c2..be22be08a000 100644 --- a/lms/djangoapps/ccx/tests/test_overrides.py +++ b/lms/djangoapps/ccx/tests/test_overrides.py @@ -91,7 +91,7 @@ def test_override_start(self): """ Test that overriding start date on a chapter works. """ - ccx_start = datetime.datetime(2014, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) + ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) assert chapter.start == ccx_start @@ -100,7 +100,7 @@ def test_override_num_queries_new_field(self): """ Test that for creating new field executed only create query """ - ccx_start = datetime.datetime(2014, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) + ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] # One outer SAVEPOINT/RELEASE SAVEPOINT pair around everything caused by the # transaction.atomic decorator wrapping override_field_for_ccx. @@ -114,8 +114,8 @@ def test_override_num_queries_update_existing_field(self): """ Test that overriding existing field executed create, fetch and update queries. """ - ccx_start = datetime.datetime(2014, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) - new_ccx_start = datetime.datetime(2015, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) + ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=ZoneInfo("UTC")) + new_ccx_start = datetime.datetime(2015, 12, 25, 00, 00, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) with self.assertNumQueries(3): @@ -125,7 +125,7 @@ def test_override_num_queries_field_value_not_changed(self): """ Test that if value of field does not changed no query execute. """ - ccx_start = datetime.datetime(2014, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) + ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) with self.assertNumQueries(2): # 2 savepoints @@ -135,7 +135,7 @@ def test_overriden_field_access_produces_no_extra_queries(self): """ Test no extra queries when accessing an overriden field more than once. """ - ccx_start = datetime.datetime(2014, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) + ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] # One outer SAVEPOINT/RELEASE SAVEPOINT pair around everything caused by the # transaction.atomic decorator wrapping override_field_for_ccx. @@ -149,7 +149,7 @@ def test_override_is_inherited(self): """ Test that sequentials inherit overridden start date from chapter. """ - ccx_start = datetime.datetime(2014, 12, 25, 0, 0, tzinfo=ZoneInfo("UTC")) + ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) assert chapter.get_children()[0].start == ccx_start @@ -161,7 +161,7 @@ def test_override_is_inherited_even_if_set_in_mooc(self): (verticals) even if a due date is set explicitly on grandchildren in the mooc. """ - ccx_due = datetime.datetime(2015, 1, 1, 0, 0, tzinfo=ZoneInfo("UTC")) + ccx_due = datetime.datetime(2015, 1, 1, 00, 00, tzinfo=ZoneInfo("UTC")) chapter = self.ccx_course.get_children()[0] chapter.display_name = 'itsme!' override_field_for_ccx(self.ccx, chapter, 'due', ccx_due)