Fix time zone bug in send_verification_expiry_email (#20350)
This commit is contained in:
@@ -3,7 +3,7 @@ Django admin command to send verification expiry email to learners
|
||||
"""
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
@@ -11,9 +11,9 @@ from django.contrib.sites.models import Site
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Q
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from edx_ace import ace
|
||||
from edx_ace.recipient import Recipient
|
||||
from pytz import UTC
|
||||
from util.query import use_read_replica_if_available
|
||||
from verify_student.message_types import VerificationExpiry
|
||||
|
||||
@@ -92,16 +92,17 @@ class Command(BaseCommand):
|
||||
days = options['days_range']
|
||||
dry_run = options['dry_run']
|
||||
|
||||
end_date = now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
# If email was sent and user did not re-verify then this date will be used as the criteria for resending email
|
||||
date_resend_days_ago = datetime.now(UTC) - timedelta(days=resend_days)
|
||||
date_resend_days_ago = end_date - timedelta(days=resend_days)
|
||||
|
||||
start_date = datetime.now(UTC) - timedelta(days=days)
|
||||
start_date = end_date - timedelta(days=days)
|
||||
|
||||
# Adding an order_by() clause will override the class meta ordering as we don't need ordering here
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(Q(status='approved') &
|
||||
(Q(expiry_date__date__gte=start_date.date(),
|
||||
expiry_date__date__lt=datetime.now(UTC).date()) |
|
||||
Q(expiry_email_date__lt=date_resend_days_ago.date())
|
||||
(Q(expiry_date__gte=start_date,
|
||||
expiry_date__lt=end_date) |
|
||||
Q(expiry_email_date__lt=date_resend_days_ago)
|
||||
)).order_by()
|
||||
|
||||
sspv = use_read_replica_if_available(query)
|
||||
@@ -109,11 +110,11 @@ class Command(BaseCommand):
|
||||
total_verification = sspv.count()
|
||||
if not total_verification:
|
||||
logger.info(u"No approved expired entries found in SoftwareSecurePhotoVerification for the "
|
||||
u"date range {} - {}".format(start_date.date(), datetime.now(UTC).date()))
|
||||
u"date range {} - {}".format(start_date.date(), now().date()))
|
||||
return
|
||||
|
||||
logger.info(u"For the date range {} - {}, total Software Secure Photo verification filtered are {}"
|
||||
.format(start_date.date(), datetime.now(UTC).date(), total_verification))
|
||||
.format(start_date.date(), now().date(), total_verification))
|
||||
|
||||
batch_verifications = []
|
||||
|
||||
@@ -167,4 +168,4 @@ def send_verification_expiry_email(batch_verifications, dry_run=False):
|
||||
)
|
||||
ace.send(msg)
|
||||
verification_qs = SoftwareSecurePhotoVerification.objects.filter(pk=verification.pk)
|
||||
verification_qs.update(expiry_email_date=datetime.now(UTC))
|
||||
verification_qs.update(expiry_email_date=now())
|
||||
|
||||
@@ -13,10 +13,9 @@ import json
|
||||
import logging
|
||||
import os.path
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
from email.utils import formatdate
|
||||
|
||||
import pytz
|
||||
import requests
|
||||
import six
|
||||
from django.conf import settings
|
||||
@@ -27,6 +26,7 @@ from django.urls import reverse
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from model_utils import Choices
|
||||
from model_utils.models import StatusModel, TimeStampedModel
|
||||
@@ -138,7 +138,7 @@ class IDVerificationAttempt(StatusModel):
|
||||
"""
|
||||
return (
|
||||
self.created_at < deadline and
|
||||
self.expiration_datetime > datetime.now(pytz.UTC)
|
||||
self.expiration_datetime > now()
|
||||
)
|
||||
|
||||
|
||||
@@ -573,7 +573,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
status is set to `approved`
|
||||
expiry_date is set to one year from now
|
||||
"""
|
||||
self.expiry_date = datetime.now(pytz.UTC) + timedelta(
|
||||
self.expiry_date = now() + timedelta(
|
||||
days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]
|
||||
)
|
||||
super(SoftwareSecurePhotoVerification, self).approve(user_id, service)
|
||||
@@ -675,7 +675,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
try:
|
||||
response = self.send_request(copy_id_photo_from=copy_id_photo_from)
|
||||
if response.ok:
|
||||
self.submitted_at = datetime.now(pytz.UTC)
|
||||
self.submitted_at = now()
|
||||
self.status = "submitted"
|
||||
self.save()
|
||||
else:
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
|
||||
import boto
|
||||
import ddt
|
||||
import mock
|
||||
import pytz
|
||||
import requests.exceptions
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
from django.utils.timezone import now
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
@@ -114,7 +114,7 @@ class TestVerification(TestCase):
|
||||
# Not active after the expiration date
|
||||
attempt.created_at = attempt.created_at - timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
|
||||
attempt.save()
|
||||
self.assertFalse(attempt.active_at_datetime(datetime.now(pytz.UTC) + timedelta(days=1)))
|
||||
self.assertFalse(attempt.active_at_datetime(now() + timedelta(days=1)))
|
||||
|
||||
|
||||
# Lots of patching to stub in our own settings, and HTTP posting
|
||||
@@ -309,15 +309,15 @@ class TestPhotoVerification(TestVerification, MockS3Mixin, ModuleStoreTestCase):
|
||||
self.assertEqual(second_result, first_result)
|
||||
|
||||
# Test method 'get_initial_verification' returns None after expiration
|
||||
expired_future = datetime.utcnow() + timedelta(days=(FAKE_SETTINGS['DAYS_GOOD_FOR'] + 1))
|
||||
expired_future = now() + timedelta(days=(FAKE_SETTINGS['DAYS_GOOD_FOR'] + 1))
|
||||
with freeze_time(expired_future):
|
||||
third_result = SoftwareSecurePhotoVerification.get_initial_verification(user)
|
||||
self.assertIsNone(third_result)
|
||||
|
||||
# Test method 'get_initial_verification' returns correct attempt after system expiration,
|
||||
# but within earliest allowed override.
|
||||
expired_future = datetime.utcnow() + timedelta(days=(FAKE_SETTINGS['DAYS_GOOD_FOR'] + 1))
|
||||
earliest_allowed = datetime.utcnow() - timedelta(days=1)
|
||||
expired_future = now() + timedelta(days=(FAKE_SETTINGS['DAYS_GOOD_FOR'] + 1))
|
||||
earliest_allowed = now() - timedelta(days=1)
|
||||
with freeze_time(expired_future):
|
||||
fourth_result = SoftwareSecurePhotoVerification.get_initial_verification(user, earliest_allowed)
|
||||
self.assertIsNotNone(fourth_result)
|
||||
@@ -398,8 +398,8 @@ class VerificationDeadlineTest(CacheIsolationTestCase):
|
||||
|
||||
def test_caching(self):
|
||||
deadlines = {
|
||||
CourseKey.from_string("edX/DemoX/Fall"): datetime.now(pytz.UTC),
|
||||
CourseKey.from_string("edX/DemoX/Spring"): datetime.now(pytz.UTC) + timedelta(days=1)
|
||||
CourseKey.from_string("edX/DemoX/Fall"): now(),
|
||||
CourseKey.from_string("edX/DemoX/Spring"): now() + timedelta(days=1)
|
||||
}
|
||||
course_keys = deadlines.keys()
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
Unit tests for the VerificationDeadline signals
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
|
||||
from pytz import UTC
|
||||
from django.utils.timezone import now
|
||||
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline
|
||||
from lms.djangoapps.verify_student.signals import _listen_for_course_publish, _listen_for_lms_retire
|
||||
@@ -22,7 +22,7 @@ class VerificationDeadlineSignalTest(ModuleStoreTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VerificationDeadlineSignalTest, self).setUp()
|
||||
self.end = datetime.now(tz=UTC).replace(microsecond=0) + timedelta(days=7)
|
||||
self.end = now().replace(microsecond=0) + timedelta(days=7)
|
||||
self.course = CourseFactory.create(end=self.end)
|
||||
VerificationDeadline.objects.all().delete()
|
||||
|
||||
@@ -34,7 +34,7 @@ class VerificationDeadlineSignalTest(ModuleStoreTestCase):
|
||||
|
||||
def test_deadline(self):
|
||||
""" Verify deadline is set to course end date by signal when changed. """
|
||||
deadline = datetime.now(tz=UTC) - timedelta(days=7)
|
||||
deadline = now() - timedelta(days=7)
|
||||
VerificationDeadline.set_deadline(self.course.id, deadline)
|
||||
|
||||
_listen_for_course_publish('store', self.course.id)
|
||||
@@ -42,7 +42,7 @@ class VerificationDeadlineSignalTest(ModuleStoreTestCase):
|
||||
|
||||
def test_deadline_explicit(self):
|
||||
""" Verify deadline is unchanged by signal when explicitly set. """
|
||||
deadline = datetime.now(tz=UTC) - timedelta(days=7)
|
||||
deadline = now() - timedelta(days=7)
|
||||
VerificationDeadline.set_deadline(self.course.id, deadline, is_explicit=True)
|
||||
|
||||
_listen_for_course_publish('store', self.course.id)
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
Tests for verify_student utility functions.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
|
||||
import ddt
|
||||
import unittest
|
||||
import pytz
|
||||
from mock import patch
|
||||
from pytest import mark
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, SSOVerification, ManualVerification
|
||||
from lms.djangoapps.verify_student.utils import verification_for_datetime, most_recent_verification
|
||||
from student.tests.factories import UserFactory
|
||||
@@ -30,7 +30,7 @@ class TestVerifyStudentUtils(unittest.TestCase):
|
||||
|
||||
def test_verification_for_datetime(self):
|
||||
user = UserFactory.create()
|
||||
now = datetime.now(pytz.UTC)
|
||||
now = timezone.now()
|
||||
|
||||
# No attempts in the query set, so should return None
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
@@ -72,7 +72,7 @@ class TestVerifyStudentUtils(unittest.TestCase):
|
||||
# Immediately after the expiration date, should not get the attempt
|
||||
attempt.created_at = attempt.created_at - timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
|
||||
attempt.save()
|
||||
after = datetime.now(pytz.UTC) + timedelta(days=1)
|
||||
after = now + timedelta(days=1)
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = verification_for_datetime(after, query)
|
||||
self.assertIs(result, None)
|
||||
|
||||
@@ -5,7 +5,7 @@ Tests of verify_student views.
|
||||
|
||||
import json
|
||||
import urllib
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
from uuid import uuid4
|
||||
|
||||
import boto
|
||||
@@ -13,7 +13,6 @@ import ddt
|
||||
import httpretty
|
||||
import mock
|
||||
import moto
|
||||
import pytz
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from django.conf import settings
|
||||
@@ -22,6 +21,7 @@ from django.urls import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client, RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext as _
|
||||
from mock import Mock, patch
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
@@ -90,7 +90,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
USERNAME = "test_user"
|
||||
PASSWORD = "test_password"
|
||||
|
||||
NOW = datetime.now(pytz.UTC)
|
||||
NOW = now()
|
||||
YESTERDAY = 'yesterday'
|
||||
TOMORROW = 'tomorrow'
|
||||
NEXT_YEAR = 'next_year'
|
||||
@@ -729,7 +729,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_verification_deadline(self, payment_flow):
|
||||
deadline = datetime.now(tz=pytz.UTC) + timedelta(days=360)
|
||||
deadline = now() + timedelta(days=360)
|
||||
course = self._create_course("verified")
|
||||
|
||||
# Set a deadline on the course mode AND on the verification deadline model.
|
||||
@@ -745,7 +745,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
self.assertEqual(data['verification_deadline'], unicode(deadline))
|
||||
|
||||
def test_course_mode_expired(self):
|
||||
deadline = datetime.now(tz=pytz.UTC) + timedelta(days=-360)
|
||||
deadline = now() + timedelta(days=-360)
|
||||
course = self._create_course("verified")
|
||||
|
||||
# Set the upgrade deadline (course mode expiration) and verification deadline
|
||||
@@ -773,13 +773,13 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
# deadline in the future.
|
||||
self._set_deadlines(
|
||||
course.id,
|
||||
upgrade_deadline=datetime.now(tz=pytz.UTC) + timedelta(days=-360),
|
||||
upgrade_deadline=now() + timedelta(days=-360),
|
||||
verification_deadline=verification_deadline,
|
||||
)
|
||||
# Set the upgrade deadline for credit mode in future.
|
||||
self._set_deadlines(
|
||||
course.id,
|
||||
upgrade_deadline=datetime.now(tz=pytz.UTC) + timedelta(days=360),
|
||||
upgrade_deadline=now() + timedelta(days=360),
|
||||
verification_deadline=verification_deadline,
|
||||
mode_slug="credit"
|
||||
)
|
||||
@@ -823,8 +823,8 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
# since it's a bad user experience
|
||||
# to purchase a verified track and then not be able to verify,
|
||||
# but if it happens we need to handle it gracefully.
|
||||
upgrade_deadline_in_future = datetime.now(tz=pytz.UTC) + timedelta(days=360)
|
||||
verification_deadline_in_past = datetime.now(tz=pytz.UTC) + timedelta(days=-360)
|
||||
upgrade_deadline_in_future = now() + timedelta(days=360)
|
||||
verification_deadline_in_past = now() + timedelta(days=-360)
|
||||
self._set_deadlines(
|
||||
course.id,
|
||||
upgrade_deadline=upgrade_deadline_in_future,
|
||||
@@ -910,7 +910,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
|
||||
if status == "expired":
|
||||
days_good_for = settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]
|
||||
attempt.created_at = datetime.now(pytz.UTC) - timedelta(days=(days_good_for + 1))
|
||||
attempt.created_at = now() - timedelta(days=(days_good_for + 1))
|
||||
attempt.save()
|
||||
|
||||
def _set_deadlines(self, course_key, upgrade_deadline=None, verification_deadline=None, mode_slug="verified"):
|
||||
@@ -1764,15 +1764,15 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
|
||||
"""
|
||||
Test for verification passed.
|
||||
"""
|
||||
expiry_date = datetime.now(pytz.UTC) + timedelta(
|
||||
expiry_date = now() + timedelta(
|
||||
days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]
|
||||
)
|
||||
verification = SoftwareSecurePhotoVerification.objects.create(user=self.user)
|
||||
verification.mark_ready()
|
||||
verification.submit()
|
||||
verification.approve()
|
||||
verification.expiry_date = datetime.now(pytz.UTC)
|
||||
verification.expiry_email_date = datetime.now(pytz.UTC)
|
||||
verification.expiry_date = now()
|
||||
verification.expiry_email_date = now()
|
||||
verification.save()
|
||||
|
||||
data = {
|
||||
@@ -1807,7 +1807,7 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
|
||||
"""
|
||||
Test for verification passed if the learner does not have any previous verification
|
||||
"""
|
||||
expiry_date = datetime.now(pytz.UTC) + timedelta(
|
||||
expiry_date = now() + timedelta(
|
||||
days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]
|
||||
)
|
||||
|
||||
@@ -1952,7 +1952,7 @@ class TestReverifyView(TestCase):
|
||||
attempt.approve()
|
||||
|
||||
days_good_for = settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]
|
||||
attempt.created_at = datetime.now(pytz.UTC) - timedelta(days=(days_good_for + 1))
|
||||
attempt.created_at = now() - timedelta(days=(days_good_for + 1))
|
||||
attempt.save()
|
||||
|
||||
# Allow the student to reverify
|
||||
|
||||
@@ -5,9 +5,9 @@ Common Utilities for the verify_student application.
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import pytz
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.timezone import now
|
||||
from sailthru import SailthruClient
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -18,7 +18,7 @@ def is_verification_expiring_soon(expiration_datetime):
|
||||
Returns True if verification is expiring within EXPIRING_SOON_WINDOW.
|
||||
"""
|
||||
if expiration_datetime:
|
||||
if (expiration_datetime - datetime.datetime.now(pytz.UTC)).days <= settings.VERIFY_STUDENT.get(
|
||||
if (expiration_datetime - now()).days <= settings.VERIFY_STUDENT.get(
|
||||
"EXPIRING_SOON_WINDOW"):
|
||||
return True
|
||||
|
||||
@@ -30,7 +30,7 @@ def earliest_allowed_verification_date():
|
||||
Returns the earliest allowed date given the settings
|
||||
"""
|
||||
days_good_for = settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]
|
||||
return datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=days_good_for)
|
||||
return now() - datetime.timedelta(days=days_good_for)
|
||||
|
||||
|
||||
def verification_for_datetime(deadline, candidates):
|
||||
|
||||
@@ -17,6 +17,7 @@ from django.http import Http404, HttpResponse, HttpResponseBadRequest
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
@@ -27,7 +28,6 @@ from eventtracking import tracker
|
||||
from ipware.ip import get_ip
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from pytz import UTC
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from edxmako.shortcuts import render_to_response, render_to_string
|
||||
@@ -376,7 +376,7 @@ class PayAndVerifyView(View):
|
||||
current_step = display_steps[current_step_idx + 1]['name']
|
||||
|
||||
courseware_url = ""
|
||||
if not course.start or course.start < datetime.datetime.today().replace(tzinfo=UTC):
|
||||
if not course.start or course.start < now():
|
||||
courseware_url = reverse(
|
||||
'course_root',
|
||||
kwargs={'course_id': unicode(course_key)}
|
||||
@@ -716,7 +716,7 @@ class PayAndVerifyView(View):
|
||||
|
||||
deadline_passed = (
|
||||
deadline_datetime is not None and
|
||||
deadline_datetime < datetime.datetime.now(UTC)
|
||||
deadline_datetime < now()
|
||||
)
|
||||
if deadline_passed:
|
||||
context = {
|
||||
|
||||
Reference in New Issue
Block a user