pyupgrade on verify_students app (#26648)
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
Helper Methods
|
||||
"""
|
||||
import six
|
||||
|
||||
|
||||
def _get_key(key_or_id, key_cls):
|
||||
@@ -11,6 +10,6 @@ def _get_key(key_or_id, key_cls):
|
||||
"""
|
||||
return (
|
||||
key_cls.from_string(key_or_id)
|
||||
if isinstance(key_or_id, six.string_types)
|
||||
if isinstance(key_or_id, str)
|
||||
else key_or_id
|
||||
)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Admin site configurations for verify_student.
|
||||
"""
|
||||
@@ -7,8 +6,11 @@ Admin site configurations for verify_student.
|
||||
from django.contrib import admin
|
||||
|
||||
from lms.djangoapps.verify_student.models import (
|
||||
ManualVerification, SoftwareSecurePhotoVerification, SSOVerification,
|
||||
SSPVerificationRetryConfig)
|
||||
ManualVerification,
|
||||
SoftwareSecurePhotoVerification,
|
||||
SSOVerification,
|
||||
SSPVerificationRetryConfig
|
||||
)
|
||||
|
||||
|
||||
@admin.register(SoftwareSecurePhotoVerification)
|
||||
|
||||
@@ -12,12 +12,11 @@ of SSO IDV records.
|
||||
"""
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from social_django.models import UserSocialAuth
|
||||
|
||||
from common.djangoapps.third_party_auth.api.utils import filter_user_social_auth_queryset_by_provider
|
||||
from lms.djangoapps.verify_student.models import SSOVerification
|
||||
from common.djangoapps.third_party_auth.provider import Registry
|
||||
from lms.djangoapps.verify_student.models import SSOVerification
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -50,7 +49,7 @@ class Command(BaseCommand):
|
||||
try:
|
||||
provider = Registry.get(provider_slug)
|
||||
except ValueError as e: # lint-amnesty, pylint: disable=unused-variable
|
||||
raise CommandError('provider slug {slug} does not exist'.format(slug=provider_slug)) # lint-amnesty, pylint: disable=raise-missing-from
|
||||
raise CommandError(f'provider slug {provider_slug} does not exist') # lint-amnesty, pylint: disable=raise-missing-from
|
||||
|
||||
query_set = UserSocialAuth.objects.select_related('user__profile')
|
||||
query_set = filter_user_social_auth_queryset_by_provider(query_set, provider)
|
||||
|
||||
@@ -41,18 +41,18 @@ class Command(BaseCommand):
|
||||
|
||||
if email_ids_file:
|
||||
if not os.path.exists(email_ids_file):
|
||||
raise CommandError(u'Pass the correct absolute path to email ids file as --email-ids-file argument.')
|
||||
raise CommandError('Pass the correct absolute path to email ids file as --email-ids-file argument.')
|
||||
|
||||
total_emails, failed_emails = self._generate_manual_verification_from_file(email_ids_file)
|
||||
|
||||
if failed_emails:
|
||||
log.error(u'Completed manual verification. {} of {} failed.'.format(
|
||||
log.error('Completed manual verification. {} of {} failed.'.format(
|
||||
len(failed_emails),
|
||||
total_emails
|
||||
))
|
||||
log.error(u'Failed emails:{}'.format(pformat(failed_emails)))
|
||||
log.error('Failed emails:{}'.format(pformat(failed_emails)))
|
||||
else:
|
||||
log.info(u'Successfully generated manual verification for {} emails.'.format(total_emails))
|
||||
log.info(f'Successfully generated manual verification for {total_emails} emails.')
|
||||
|
||||
def _generate_manual_verification_from_file(self, email_ids_file):
|
||||
"""
|
||||
@@ -67,10 +67,10 @@ class Command(BaseCommand):
|
||||
"""
|
||||
failed_emails = []
|
||||
|
||||
with open(email_ids_file, 'r') as file_handler:
|
||||
with open(email_ids_file) as file_handler:
|
||||
email_ids = file_handler.readlines()
|
||||
total_emails = len(email_ids)
|
||||
log.info(u'Creating manual verification for {} emails.'.format(total_emails))
|
||||
log.info(f'Creating manual verification for {total_emails} emails.')
|
||||
for email_id in email_ids:
|
||||
try:
|
||||
email_id = email_id.strip()
|
||||
@@ -83,6 +83,6 @@ class Command(BaseCommand):
|
||||
)
|
||||
except User.DoesNotExist:
|
||||
failed_emails.append(email_id)
|
||||
err_msg = u'Tried to verify email {}, but user not found'
|
||||
err_msg = 'Tried to verify email {}, but user not found'
|
||||
log.error(err_msg.format(email_id))
|
||||
return total_emails, failed_emails
|
||||
|
||||
@@ -12,8 +12,8 @@ from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import F
|
||||
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from common.djangoapps.util.query import use_read_replica_if_available
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ Django admin commands related to verify_student
|
||||
|
||||
|
||||
import logging
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from django.core.management.base import CommandError # lint-amnesty, pylint: disable=unused-import
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, SSPVerificationRetryConfig
|
||||
|
||||
@@ -64,20 +65,20 @@ class Command(BaseCommand):
|
||||
attempts_to_retry = SoftwareSecurePhotoVerification.objects.filter(
|
||||
receipt_id__in=options['verification_ids']
|
||||
)
|
||||
log.info(u"Fetching retry verification ids from config model")
|
||||
log.info("Fetching retry verification ids from config model")
|
||||
force_must_retry = True
|
||||
else:
|
||||
attempts_to_retry = SoftwareSecurePhotoVerification.objects.filter(status='must_retry')
|
||||
force_must_retry = False
|
||||
|
||||
log.info(u"Attempting to retry {0} failed PhotoVerification submissions".format(len(attempts_to_retry)))
|
||||
log.info("Attempting to retry {} failed PhotoVerification submissions".format(len(attempts_to_retry)))
|
||||
for index, attempt in enumerate(attempts_to_retry):
|
||||
log.info(u"Retrying submission #{0} (ID: {1}, User: {2})".format(index, attempt.id, attempt.user))
|
||||
log.info(f"Retrying submission #{index} (ID: {attempt.id}, User: {attempt.user})")
|
||||
|
||||
# Set the attempts status to 'must_retry' so that we can re-submit it
|
||||
if force_must_retry:
|
||||
attempt.status = 'must_retry'
|
||||
|
||||
attempt.submit(copy_id_photo_from=attempt.copy_id_photo_from)
|
||||
log.info(u"Retry result: {0}".format(attempt.status))
|
||||
log.info(f"Retry result: {attempt.status}")
|
||||
log.info("Done resubmitting failed photo verifications")
|
||||
|
||||
@@ -7,19 +7,19 @@ import logging
|
||||
import time
|
||||
from datetime import timedelta
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from django.conf import settings # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user, wrong-import-order
|
||||
from django.contrib.sites.models import Site # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from django.core.management.base import BaseCommand, CommandError # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from django.db.models import Q # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db.models import Q
|
||||
from django.urls import reverse # lint-amnesty, pylint: disable=unused-import, wrong-import-order
|
||||
from django.utils.timezone import now # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from edx_ace import ace # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from edx_ace.recipient import Recipient # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from django.utils.timezone import now
|
||||
from edx_ace import ace
|
||||
from edx_ace.recipient import Recipient
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.util.query import use_read_replica_if_available
|
||||
|
||||
from lms.djangoapps.verify_student.message_types import VerificationExpiry
|
||||
from lms.djangoapps.verify_student.models import ManualVerification, SoftwareSecurePhotoVerification, SSOVerification
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService # lint-amnesty, pylint: disable=unused-import
|
||||
@@ -91,8 +91,8 @@ class Command(BaseCommand):
|
||||
dry_run = options['dry_run']
|
||||
|
||||
if default_emails <= 0:
|
||||
raise CommandError(u'DEFAULT_EMAILS must be a positive integer. If you do not wish to send emails '
|
||||
u'use --dry-run flag instead.')
|
||||
raise CommandError('DEFAULT_EMAILS must be a positive integer. If you do not wish to send emails '
|
||||
'use --dry-run flag instead.')
|
||||
|
||||
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
|
||||
@@ -121,11 +121,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(), now().date()))
|
||||
logger.info("No approved expired entries found in SoftwareSecurePhotoVerification for the "
|
||||
"date range {} - {}".format(start_date.date(), now().date()))
|
||||
return
|
||||
|
||||
logger.info(u"For the date range {} - {}, total Software Secure Photo verification filtered are {}"
|
||||
logger.info("For the date range {} - {}, total Software Secure Photo verification filtered are {}"
|
||||
.format(start_date.date(), now().date(), total_verification))
|
||||
|
||||
batch_verifications = []
|
||||
@@ -191,8 +191,8 @@ class Command(BaseCommand):
|
||||
"""
|
||||
if email_config['dry_run']:
|
||||
logger.info(
|
||||
u"This was a dry run, no email was sent. For the actual run email would have been sent "
|
||||
u"to {} learner(s)".format(len(batch_verifications))
|
||||
"This was a dry run, no email was sent. For the actual run email would have been sent "
|
||||
"to {} learner(s)".format(len(batch_verifications))
|
||||
)
|
||||
return True
|
||||
|
||||
@@ -200,7 +200,7 @@ class Command(BaseCommand):
|
||||
message_context = get_base_template_context(site)
|
||||
message_context.update({
|
||||
'platform_name': settings.PLATFORM_NAME,
|
||||
'lms_verification_link': '{}/id-verification'.format(settings.ACCOUNT_MICROFRONTEND_URL),
|
||||
'lms_verification_link': f'{settings.ACCOUNT_MICROFRONTEND_URL}/id-verification',
|
||||
'help_center_link': settings.ID_VERIFICATION_SUPPORT_LINK
|
||||
})
|
||||
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
Tests for management command backfill_sso_verifications_for_old_account_links
|
||||
"""
|
||||
|
||||
from mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import CommandError
|
||||
|
||||
from common.djangoapps.third_party_auth.tests.testutil import TestCase
|
||||
from lms.djangoapps.program_enrollments.management.commands.tests.utils import UserSocialAuthFactory
|
||||
from lms.djangoapps.verify_student.models import SSOVerification
|
||||
from lms.djangoapps.verify_student.tests.factories import SSOVerificationFactory
|
||||
from common.djangoapps.third_party_auth.tests.testutil import TestCase
|
||||
|
||||
|
||||
class TestBackfillSSOVerificationsCommand(TestCase):
|
||||
@@ -20,7 +21,7 @@ class TestBackfillSSOVerificationsCommand(TestCase):
|
||||
slug = 'test'
|
||||
|
||||
def setUp(self):
|
||||
super(TestBackfillSSOVerificationsCommand, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.enable_saml()
|
||||
self.provider = self.configure_saml_provider(
|
||||
name="Test",
|
||||
|
||||
@@ -7,15 +7,15 @@ Tests for django admin commands in the verify_student module
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
import six
|
||||
from django.core.management import CommandError, call_command
|
||||
from django.test import TestCase
|
||||
from testfixtures import LogCapture
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from lms.djangoapps.verify_student.models import ManualVerification
|
||||
from lms.djangoapps.verify_student.utils import earliest_allowed_verification_date
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
|
||||
LOGGER_NAME = 'lms.djangoapps.verify_student.management.commands.manual_verifications'
|
||||
|
||||
@@ -27,11 +27,11 @@ class TestVerifyStudentCommand(TestCase):
|
||||
tmp_file_path = os.path.join(tempfile.gettempdir(), 'tmp-emails.txt')
|
||||
|
||||
def setUp(self):
|
||||
super(TestVerifyStudentCommand, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.user1 = UserFactory.create()
|
||||
self.user2 = UserFactory.create()
|
||||
self.user3 = UserFactory.create()
|
||||
self.invalid_email = six.text_type('unknown@unknown.com')
|
||||
self.invalid_email = 'unknown@unknown.com'
|
||||
|
||||
self.create_email_ids_file(
|
||||
self.tmp_file_path,
|
||||
@@ -87,15 +87,15 @@ class TestVerifyStudentCommand(TestCase):
|
||||
expected_log = (
|
||||
(LOGGER_NAME,
|
||||
'INFO',
|
||||
u'Creating manual verification for 4 emails.'
|
||||
'Creating manual verification for 4 emails.'
|
||||
),
|
||||
(LOGGER_NAME,
|
||||
'ERROR',
|
||||
u'Tried to verify email unknown@unknown.com, but user not found'
|
||||
'Tried to verify email unknown@unknown.com, but user not found'
|
||||
),
|
||||
(LOGGER_NAME,
|
||||
'ERROR',
|
||||
u'Completed manual verification. 1 of 4 failed.'
|
||||
'Completed manual verification. 1 of 4 failed.'
|
||||
),
|
||||
(LOGGER_NAME,
|
||||
'ERROR',
|
||||
|
||||
@@ -4,17 +4,17 @@ Tests for django admin command `populate_expiry_date` in the verify_student modu
|
||||
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
from mock import patch
|
||||
from testfixtures import LogCapture
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from common.test.utils import MockS3BotoMixin
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.tests.test_models import FAKE_SETTINGS, mock_software_secure_post
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
|
||||
LOGGER_NAME = 'lms.djangoapps.verify_student.management.commands.populate_expiry_date'
|
||||
|
||||
|
||||
@@ -4,18 +4,18 @@ Tests for django admin command `send_verification_expiry_email` in the verify_st
|
||||
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core import mail
|
||||
from django.core.management import call_command, CommandError
|
||||
from django.core.management import CommandError, call_command
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.utils.timezone import now
|
||||
from mock import patch
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from testfixtures import LogCapture # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from testfixtures import LogCapture
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from common.test.utils import MockS3BotoMixin
|
||||
from lms.djangoapps.verify_student.models import ManualVerification, SoftwareSecurePhotoVerification, SSOVerification
|
||||
from lms.djangoapps.verify_student.tests.test_models import FAKE_SETTINGS, mock_software_secure_post
|
||||
@@ -30,7 +30,7 @@ class TestSendVerificationExpiryEmail(MockS3BotoMixin, TestCase):
|
||||
|
||||
def setUp(self):
|
||||
""" Initial set up for tests """
|
||||
super(TestSendVerificationExpiryEmail, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
Site.objects.create(domain='edx.org', name='edx.org')
|
||||
self.resend_days = settings.VERIFICATION_EXPIRY_EMAIL['RESEND_DAYS']
|
||||
self.days = settings.VERIFICATION_EXPIRY_EMAIL['DAYS_RANGE']
|
||||
@@ -184,8 +184,8 @@ class TestSendVerificationExpiryEmail(MockS3BotoMixin, TestCase):
|
||||
call_command('send_verification_expiry_email')
|
||||
logger.check(
|
||||
(LOGGER_NAME,
|
||||
'INFO', u"No approved expired entries found in SoftwareSecurePhotoVerification for the "
|
||||
u"date range {} - {}".format(start_date.date(), now().date()))
|
||||
'INFO', "No approved expired entries found in SoftwareSecurePhotoVerification for the "
|
||||
"date range {} - {}".format(start_date.date(), now().date()))
|
||||
)
|
||||
|
||||
def test_dry_run_flag(self):
|
||||
@@ -206,13 +206,13 @@ class TestSendVerificationExpiryEmail(MockS3BotoMixin, TestCase):
|
||||
logger.check(
|
||||
(LOGGER_NAME,
|
||||
'INFO',
|
||||
u"For the date range {} - {}, total Software Secure Photo verification filtered are {}"
|
||||
"For the date range {} - {}, total Software Secure Photo verification filtered are {}"
|
||||
.format(start_date.date(), now().date(), count)
|
||||
),
|
||||
(LOGGER_NAME,
|
||||
'INFO',
|
||||
u"This was a dry run, no email was sent. For the actual run email would have been sent "
|
||||
u"to {} learner(s)".format(count)
|
||||
"This was a dry run, no email was sent. For the actual run email would have been sent "
|
||||
"to {} learner(s)".format(count)
|
||||
))
|
||||
assert len(mail.outbox) == 0
|
||||
|
||||
@@ -269,8 +269,8 @@ class TestSendVerificationExpiryEmail(MockS3BotoMixin, TestCase):
|
||||
|
||||
@override_settings(VERIFICATION_EXPIRY_EMAIL={'RESEND_DAYS': 15, 'DAYS_RANGE': 1, 'DEFAULT_EMAILS': 0})
|
||||
def test_command_error(self):
|
||||
err_string = u"DEFAULT_EMAILS must be a positive integer. If you do not wish to send " \
|
||||
u"emails use --dry-run flag instead."
|
||||
err_string = "DEFAULT_EMAILS must be a positive integer. If you do not wish to send " \
|
||||
"emails use --dry-run flag instead."
|
||||
with self.assertRaisesRegex(CommandError, err_string):
|
||||
call_command('send_verification_expiry_email')
|
||||
|
||||
|
||||
@@ -4,18 +4,18 @@ Tests for django admin command `update_expiration_date` in the verify_student mo
|
||||
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
from django.utils.timezone import now
|
||||
from mock import patch
|
||||
from testfixtures import LogCapture
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from common.test.utils import MockS3BotoMixin
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.tests.test_models import FAKE_SETTINGS, mock_software_secure_post
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
|
||||
LOGGER_NAME = 'lms.djangoapps.verify_student.management.commands.update_expiration_date'
|
||||
|
||||
|
||||
@@ -4,11 +4,14 @@ Tests for django admin commands in the verify_student module
|
||||
Lots of imports from verify_student's model tests, since they cover similar ground
|
||||
"""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from mock import patch
|
||||
from testfixtures import LogCapture
|
||||
|
||||
from common.djangoapps.student.tests.factories import \
|
||||
UserFactory # lint-amnesty, pylint: disable=import-error, unused-import, useless-suppression
|
||||
from common.test.utils import MockS3BotoMixin
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, SSPVerificationRetryConfig
|
||||
from lms.djangoapps.verify_student.tests import TestVerificationBase
|
||||
@@ -17,7 +20,6 @@ from lms.djangoapps.verify_student.tests.test_models import (
|
||||
mock_software_secure_post,
|
||||
mock_software_secure_post_error
|
||||
)
|
||||
from common.djangoapps.student.tests.factories import UserFactory # lint-amnesty, pylint: disable=import-error, unused-import, useless-suppression
|
||||
|
||||
LOGGER_NAME = 'retry_photo_verification'
|
||||
|
||||
@@ -77,7 +79,7 @@ class TestVerifyStudentCommand(MockS3BotoMixin, TestVerificationBase):
|
||||
log.check_present(
|
||||
(
|
||||
LOGGER_NAME, 'INFO',
|
||||
'Attempting to retry {0} failed PhotoVerification submissions'.format(1)
|
||||
'Attempting to retry {} failed PhotoVerification submissions'.format(1)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ from django.conf import settings # lint-amnesty, pylint: disable=unused-import
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import F
|
||||
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from common.djangoapps.util.query import use_read_replica_if_available
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class VerificationExpiry(BaseMessageType): # lint-amnesty, pylint: disable=miss
|
||||
Name = 'verificationexpiry'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(VerificationExpiry, self).__init__(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.options['transactional'] = True
|
||||
|
||||
@@ -22,7 +22,7 @@ class VerificationApproved(BaseMessageType):
|
||||
Name = 'verificationapproved'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(VerificationApproved, self).__init__(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().__init__(*args, **kwargs)
|
||||
self.options['transactional'] = True
|
||||
|
||||
|
||||
@@ -34,5 +34,5 @@ class VerificationSubmitted(BaseMessageType):
|
||||
Name = 'verificationsubmitted'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(VerificationSubmitted, self).__init__(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().__init__(*args, **kwargs)
|
||||
self.options['transactional'] = True
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import model_utils.fields
|
||||
@@ -75,7 +72,7 @@ class Migration(migrations.Migration):
|
||||
name='SoftwareSecurePhotoVerification',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('status', model_utils.fields.StatusField(default=u'created', max_length=100, verbose_name='status', no_check_for_status=True, choices=[(u'created', u'created'), (u'ready', u'ready'), (u'submitted', u'submitted'), (u'must_retry', u'must_retry'), (u'approved', u'approved'), (u'denied', u'denied')])),
|
||||
('status', model_utils.fields.StatusField(default='created', max_length=100, verbose_name='status', no_check_for_status=True, choices=[('created', 'created'), ('ready', 'ready'), ('submitted', 'submitted'), ('must_retry', 'must_retry'), ('approved', 'approved'), ('denied', 'denied')])),
|
||||
('status_changed', model_utils.fields.MonitorField(default=django.utils.timezone.now, verbose_name='status changed', monitor='status')),
|
||||
('name', models.CharField(max_length=255, blank=True)),
|
||||
('face_image_url', models.URLField(max_length=255, blank=True)),
|
||||
@@ -124,7 +121,7 @@ class Migration(migrations.Migration):
|
||||
name='VerificationStatus',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('status', models.CharField(db_index=True, max_length=32, choices=[(u'submitted', u'submitted'), (u'approved', u'approved'), (u'denied', u'denied'), (u'error', u'error')])),
|
||||
('status', models.CharField(db_index=True, max_length=32, choices=[('submitted', 'submitted'), ('approved', 'approved'), ('denied', 'denied'), ('error', 'error')])),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
('response', models.TextField(null=True, blank=True)),
|
||||
('error', models.TextField(null=True, blank=True)),
|
||||
@@ -149,10 +146,10 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='verificationcheckpoint',
|
||||
unique_together=set([('course_id', 'checkpoint_location')]),
|
||||
unique_together={('course_id', 'checkpoint_location')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='skippedreverification',
|
||||
unique_together=set([('user', 'course_id')]),
|
||||
unique_together={('user', 'course_id')},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@@ -21,7 +18,7 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='skippedreverification',
|
||||
unique_together=set([]),
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='skippedreverification',
|
||||
@@ -33,7 +30,7 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='verificationcheckpoint',
|
||||
unique_together=set([]),
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='verificationcheckpoint',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-11 15:20
|
||||
|
||||
|
||||
@@ -21,7 +20,7 @@ class Migration(migrations.Migration):
|
||||
name='SSOVerification',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('status', model_utils.fields.StatusField(choices=[(u'created', u'created'), (u'ready', u'ready'), (u'submitted', u'submitted'), (u'must_retry', u'must_retry'), (u'approved', u'approved'), (u'denied', u'denied')], default=u'created', max_length=100, no_check_for_status=True, verbose_name='status')),
|
||||
('status', model_utils.fields.StatusField(choices=[('created', 'created'), ('ready', 'ready'), ('submitted', 'submitted'), ('must_retry', 'must_retry'), ('approved', 'approved'), ('denied', 'denied')], default='created', max_length=100, no_check_for_status=True, verbose_name='status')),
|
||||
('status_changed', model_utils.fields.MonitorField(default=django.utils.timezone.now, monitor='status', verbose_name='status changed')),
|
||||
('name', models.CharField(blank=True, max_length=255)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
@@ -30,16 +29,16 @@ class Migration(migrations.Migration):
|
||||
'identity_provider_type',
|
||||
models.CharField(
|
||||
choices=[
|
||||
(u'common.djangoapps.third_party_auth.models.OAuth2ProviderConfig', u'OAuth2 Provider'),
|
||||
(u'common.djangoapps.third_party_auth.models.SAMLProviderConfig', u'SAML Provider'),
|
||||
(u'common.djangoapps.third_party_auth.models.LTIProviderConfig', u'LTI Provider'),
|
||||
('common.djangoapps.third_party_auth.models.OAuth2ProviderConfig', 'OAuth2 Provider'),
|
||||
('common.djangoapps.third_party_auth.models.SAMLProviderConfig', 'SAML Provider'),
|
||||
('common.djangoapps.third_party_auth.models.LTIProviderConfig', 'LTI Provider'),
|
||||
],
|
||||
default=u'common.djangoapps.third_party_auth.models.SAMLProviderConfig',
|
||||
help_text=u'Specifies which type of Identity Provider this verification originated from.',
|
||||
default='common.djangoapps.third_party_auth.models.SAMLProviderConfig',
|
||||
help_text='Specifies which type of Identity Provider this verification originated from.',
|
||||
max_length=100,
|
||||
),
|
||||
),
|
||||
('identity_provider_slug', models.SlugField(default=u'default', help_text=u'The slug uniquely identifying the Identity Provider this verification originated from.', max_length=30)),
|
||||
('identity_provider_slug', models.SlugField(default='default', help_text='The slug uniquely identifying the Identity Provider this verification originated from.', max_length=30)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-12 15:22
|
||||
|
||||
|
||||
@@ -22,7 +21,7 @@ class Migration(migrations.Migration):
|
||||
name='IDVerificationAggregate',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('status', model_utils.fields.StatusField(choices=[(u'created', u'created'), (u'ready', u'ready'), (u'submitted', u'submitted'), (u'must_retry', u'must_retry'), (u'approved', u'approved'), (u'denied', u'denied')], default=u'created', max_length=100, no_check_for_status=True, verbose_name='status')),
|
||||
('status', model_utils.fields.StatusField(choices=[('created', 'created'), ('ready', 'ready'), ('submitted', 'submitted'), ('must_retry', 'must_retry'), ('approved', 'approved'), ('denied', 'denied')], default='created', max_length=100, no_check_for_status=True, verbose_name='status')),
|
||||
('status_changed', model_utils.fields.MonitorField(default=django.utils.timezone.now, monitor='status', verbose_name='status changed')),
|
||||
('name', models.CharField(blank=True, max_length=255)),
|
||||
('object_id', models.PositiveIntegerField()),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-11 19:15
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-27 16:27
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.13 on 2018-06-07 10:51
|
||||
|
||||
|
||||
@@ -21,12 +20,12 @@ class Migration(migrations.Migration):
|
||||
name='ManualVerification',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('status', model_utils.fields.StatusField(choices=[(u'created', u'created'), (u'ready', u'ready'), (u'submitted', u'submitted'), (u'must_retry', u'must_retry'), (u'approved', u'approved'), (u'denied', u'denied')], default=u'created', max_length=100, no_check_for_status=True, verbose_name='status')),
|
||||
('status', model_utils.fields.StatusField(choices=[('created', 'created'), ('ready', 'ready'), ('submitted', 'submitted'), ('must_retry', 'must_retry'), ('approved', 'approved'), ('denied', 'denied')], default='created', max_length=100, no_check_for_status=True, verbose_name='status')),
|
||||
('status_changed', model_utils.fields.MonitorField(default=django.utils.timezone.now, monitor='status', verbose_name='status changed')),
|
||||
('name', models.CharField(blank=True, max_length=255)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
||||
('reason', models.CharField(blank=True, help_text=u'Specifies the reason for manual verification of the user.', max_length=255)),
|
||||
('reason', models.CharField(blank=True, help_text='Specifies the reason for manual verification of the user.', max_length=255)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.16 on 2019-01-10 09:19
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.26 on 2019-12-10 11:19
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Models for Student Identity Verification
|
||||
|
||||
@@ -20,7 +19,6 @@ from datetime import timedelta
|
||||
from email.utils import formatdate
|
||||
|
||||
import requests
|
||||
import six
|
||||
from config_models.models import ConfigurationModel
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
|
||||
@@ -36,28 +34,24 @@ from model_utils.models import StatusModel, TimeStampedModel
|
||||
from opaque_keys.edx.django.models import CourseKeyField
|
||||
|
||||
from lms.djangoapps.verify_student.ssencrypt import (
|
||||
encrypt_and_encode,
|
||||
decode_and_decrypt,
|
||||
encrypt_and_encode,
|
||||
generate_signed_message,
|
||||
random_aes_key,
|
||||
rsa_encrypt,
|
||||
rsa_decrypt
|
||||
rsa_decrypt,
|
||||
rsa_encrypt
|
||||
)
|
||||
from openedx.core.djangoapps.signals.signals import LEARNER_NOW_VERIFIED
|
||||
from openedx.core.storage import get_storage
|
||||
|
||||
from .utils import (
|
||||
auto_verify_for_testing_enabled,
|
||||
earliest_allowed_verification_date,
|
||||
submit_request_to_ss
|
||||
)
|
||||
from .utils import auto_verify_for_testing_enabled, earliest_allowed_verification_date, submit_request_to_ss
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def generateUUID(): # pylint: disable=invalid-name
|
||||
""" Utility function; generates UUIDs """
|
||||
return six.text_type(uuid.uuid4())
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
class VerificationException(Exception):
|
||||
@@ -87,7 +81,7 @@ def status_before_must_be(*valid_start_statuses):
|
||||
def with_status_check(obj, *args, **kwargs):
|
||||
if obj.status not in valid_start_statuses:
|
||||
exception_msg = (
|
||||
u"Error calling {} {}: status is '{}', must be one of: {}"
|
||||
"Error calling {} {}: status is '{}', must be one of: {}"
|
||||
).format(func, obj, obj.status, valid_start_statuses)
|
||||
raise VerificationException(exception_msg)
|
||||
return func(obj, *args, **kwargs)
|
||||
@@ -107,7 +101,7 @@ class IDVerificationAttempt(StatusModel):
|
||||
.. pii_types: name
|
||||
.. pii_retirement: retained
|
||||
"""
|
||||
STATUS = Choices(u'created', u'ready', u'submitted', u'must_retry', u'approved', u'denied')
|
||||
STATUS = Choices('created', 'ready', 'submitted', 'must_retry', 'approved', 'denied')
|
||||
user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
|
||||
|
||||
# They can change their name later on, so we want to copy the value here so
|
||||
@@ -130,7 +124,7 @@ class IDVerificationAttempt(StatusModel):
|
||||
default=expiration_default
|
||||
)
|
||||
|
||||
class Meta(object):
|
||||
class Meta:
|
||||
app_label = "verify_student"
|
||||
abstract = True
|
||||
ordering = ['-created_at']
|
||||
@@ -179,11 +173,11 @@ class ManualVerification(IDVerificationAttempt):
|
||||
max_length=255,
|
||||
blank=True,
|
||||
help_text=(
|
||||
u'Specifies the reason for manual verification of the user.'
|
||||
'Specifies the reason for manual verification of the user.'
|
||||
)
|
||||
)
|
||||
|
||||
class Meta(object):
|
||||
class Meta:
|
||||
app_label = 'verify_student'
|
||||
|
||||
def __str__(self):
|
||||
@@ -209,13 +203,13 @@ class SSOVerification(IDVerificationAttempt):
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
OAUTH2 = u'common.djangoapps.third_party_auth.models.OAuth2ProviderConfig'
|
||||
SAML = u'common.djangoapps.third_party_auth.models.SAMLProviderConfig'
|
||||
LTI = u'common.djangoapps.third_party_auth.models.LTIProviderConfig'
|
||||
OAUTH2 = 'common.djangoapps.third_party_auth.models.OAuth2ProviderConfig'
|
||||
SAML = 'common.djangoapps.third_party_auth.models.SAMLProviderConfig'
|
||||
LTI = 'common.djangoapps.third_party_auth.models.LTIProviderConfig'
|
||||
IDENTITY_PROVIDER_TYPE_CHOICES = (
|
||||
(OAUTH2, u'OAuth2 Provider'),
|
||||
(SAML, u'SAML Provider'),
|
||||
(LTI, u'LTI Provider'),
|
||||
(OAUTH2, 'OAuth2 Provider'),
|
||||
(SAML, 'SAML Provider'),
|
||||
(LTI, 'LTI Provider'),
|
||||
)
|
||||
|
||||
identity_provider_type = models.CharField(
|
||||
@@ -224,17 +218,17 @@ class SSOVerification(IDVerificationAttempt):
|
||||
choices=IDENTITY_PROVIDER_TYPE_CHOICES,
|
||||
default=SAML,
|
||||
help_text=(
|
||||
u'Specifies which type of Identity Provider this verification originated from.'
|
||||
'Specifies which type of Identity Provider this verification originated from.'
|
||||
)
|
||||
)
|
||||
|
||||
identity_provider_slug = models.SlugField(
|
||||
max_length=30, db_index=True, default=u'default',
|
||||
max_length=30, db_index=True, default='default',
|
||||
help_text=(
|
||||
u'The slug uniquely identifying the Identity Provider this verification originated from.'
|
||||
'The slug uniquely identifying the Identity Provider this verification originated from.'
|
||||
))
|
||||
|
||||
class Meta(object):
|
||||
class Meta:
|
||||
app_label = "verify_student"
|
||||
|
||||
def __str__(self):
|
||||
@@ -251,7 +245,7 @@ class SSOVerification(IDVerificationAttempt):
|
||||
"""
|
||||
Send a signal indicating that this verification was approved.
|
||||
"""
|
||||
log.info(u"Verification for user '{user_id}' approved by '{reviewer}' SSO.".format(
|
||||
log.info("Verification for user '{user_id}' approved by '{reviewer}' SSO.".format(
|
||||
user_id=self.user, reviewer=approved_by
|
||||
))
|
||||
|
||||
@@ -261,7 +255,7 @@ class SSOVerification(IDVerificationAttempt):
|
||||
user=self.user
|
||||
)
|
||||
|
||||
message = u'LEARNER_NOW_VERIFIED signal fired for {user} from SSOVerification'
|
||||
message = 'LEARNER_NOW_VERIFIED signal fired for {user} from SSOVerification'
|
||||
log.info(message.format(user=self.user.username))
|
||||
|
||||
|
||||
@@ -357,7 +351,7 @@ class PhotoVerification(IDVerificationAttempt):
|
||||
# capturing it so that we can later query for the common problems.
|
||||
error_code = models.CharField(blank=True, max_length=50)
|
||||
|
||||
class Meta(object):
|
||||
class Meta:
|
||||
app_label = "verify_student"
|
||||
abstract = True
|
||||
ordering = ['-created_at']
|
||||
@@ -446,7 +440,7 @@ class PhotoVerification(IDVerificationAttempt):
|
||||
if self.status == self.STATUS.approved:
|
||||
return
|
||||
|
||||
log.info(u"Verification for user '{user_id}' approved by '{reviewer}'.".format(
|
||||
log.info("Verification for user '{user_id}' approved by '{reviewer}'.".format(
|
||||
user_id=self.user, reviewer=user_id
|
||||
))
|
||||
self.error_msg = "" # reset, in case this attempt was denied before
|
||||
@@ -464,7 +458,7 @@ class PhotoVerification(IDVerificationAttempt):
|
||||
user=self.user
|
||||
)
|
||||
|
||||
message = u'LEARNER_NOW_VERIFIED signal fired for {user} from PhotoVerification'
|
||||
message = 'LEARNER_NOW_VERIFIED signal fired for {user} from PhotoVerification'
|
||||
log.info(message.format(user=self.user.username))
|
||||
|
||||
@status_before_must_be("ready", "must_retry")
|
||||
@@ -548,7 +542,7 @@ class PhotoVerification(IDVerificationAttempt):
|
||||
lets you amend the error message in case there were additional
|
||||
details to be made.
|
||||
"""
|
||||
log.info(u"Verification for user '{user_id}' denied by '{reviewer}'.".format(
|
||||
log.info("Verification for user '{user_id}' denied by '{reviewer}'.".format(
|
||||
user_id=self.user, reviewer=reviewing_user
|
||||
))
|
||||
self.error_msg = error_msg
|
||||
@@ -658,7 +652,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
"""Use expiry_date for older entries if it still exists."""
|
||||
if self.expiry_date:
|
||||
return self.expiry_date
|
||||
return super(SoftwareSecurePhotoVerification, self).expiration_datetime # lint-amnesty, pylint: disable=super-with-arguments
|
||||
return super().expiration_datetime
|
||||
|
||||
@classmethod
|
||||
def get_initial_verification(cls, user, earliest_allowed_date=None):
|
||||
@@ -725,10 +719,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
|
||||
aes_key_str = settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["FACE_IMAGE_AES_KEY"]
|
||||
|
||||
if six.PY3:
|
||||
aes_key = codecs.decode(aes_key_str, "hex")
|
||||
else:
|
||||
aes_key = aes_key_str.decode("hex")
|
||||
aes_key = codecs.decode(aes_key_str, "hex")
|
||||
|
||||
encrypted_data = encrypt_and_encode(img_data, aes_key)
|
||||
|
||||
@@ -767,10 +758,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
self._save_image_to_storage(path, encrypted_data)
|
||||
|
||||
# Update our record fields
|
||||
if six.PY3:
|
||||
self.photo_id_key = codecs.encode(rsa_encrypted_aes_key, 'base64').decode('utf-8')
|
||||
else:
|
||||
self.photo_id_key = rsa_encrypted_aes_key.encode('base64')
|
||||
self.photo_id_key = codecs.encode(rsa_encrypted_aes_key, 'base64').decode('utf-8')
|
||||
|
||||
self.save()
|
||||
|
||||
@@ -796,15 +784,12 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
aes_key_str = settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["FACE_IMAGE_AES_KEY"]
|
||||
|
||||
try:
|
||||
if six.PY3:
|
||||
aes_key = codecs.decode(aes_key_str, "hex")
|
||||
else:
|
||||
aes_key = aes_key_str.decode("hex")
|
||||
aes_key = codecs.decode(aes_key_str, "hex")
|
||||
|
||||
img_bytes = decode_and_decrypt(byte_img_data, aes_key)
|
||||
return img_bytes
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
log.exception(u'Failed to decrypt face image due to an exception: %s', e)
|
||||
log.exception('Failed to decrypt face image due to an exception: %s', e)
|
||||
return None
|
||||
|
||||
@status_before_must_be("must_retry", "submitted", "approved", "denied")
|
||||
@@ -828,7 +813,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
img_bytes = decode_and_decrypt(byte_img_data, decrypted_aes_key)
|
||||
return img_bytes
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
log.exception(u'Failed to decrypt photo id image due to an exception: %s', e)
|
||||
log.exception('Failed to decrypt photo id image due to an exception: %s', e)
|
||||
return None
|
||||
|
||||
@status_before_must_be("must_retry", "ready", "submitted")
|
||||
@@ -894,7 +879,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
message_groups = json.loads(self.error_msg)
|
||||
|
||||
for message_group in message_groups:
|
||||
messages = messages.union(set(*six.itervalues(message_group)))
|
||||
messages = messages.union(set(*message_group.values()))
|
||||
|
||||
for message in messages:
|
||||
parsed_error = error_map.get(message)
|
||||
@@ -902,9 +887,9 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
if parsed_error:
|
||||
parsed_errors.append(parsed_error)
|
||||
else:
|
||||
log.debug(u'Ignoring photo verification error message: %s', message)
|
||||
log.debug('Ignoring photo verification error message: %s', message)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
log.exception(u'Failed to parse error message for SoftwareSecurePhotoVerification %d', self.pk)
|
||||
log.exception('Failed to parse error message for SoftwareSecurePhotoVerification %d', self.pk)
|
||||
|
||||
return parsed_errors
|
||||
|
||||
@@ -1042,7 +1027,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
headers, body = self.create_request()
|
||||
|
||||
header_txt = "\n".join(
|
||||
u"{}: {}".format(h, v) for h, v in sorted(headers.items())
|
||||
f"{h}: {v}" for h, v in sorted(headers.items())
|
||||
)
|
||||
body_txt = json.dumps(body, indent=2, sort_keys=True, ensure_ascii=False)
|
||||
|
||||
@@ -1106,19 +1091,19 @@ class VerificationDeadline(TimeStampedModel):
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
class Meta(object):
|
||||
class Meta:
|
||||
app_label = "verify_student"
|
||||
|
||||
course_key = CourseKeyField(
|
||||
max_length=255,
|
||||
db_index=True,
|
||||
unique=True,
|
||||
help_text=ugettext_lazy(u"The course for which this deadline applies"),
|
||||
help_text=ugettext_lazy("The course for which this deadline applies"),
|
||||
)
|
||||
|
||||
deadline = models.DateTimeField(
|
||||
help_text=ugettext_lazy(
|
||||
u"The datetime after which users are no longer allowed "
|
||||
"The datetime after which users are no longer allowed "
|
||||
"to submit photos for verification."
|
||||
)
|
||||
)
|
||||
@@ -1204,7 +1189,7 @@ class SSPVerificationRetryConfig(ConfigurationModel): # pylint: disable=model-m
|
||||
to retry_failed_photo_verifications management command
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
class Meta:
|
||||
app_label = 'verify_student'
|
||||
verbose_name = 'sspv retry student argument'
|
||||
|
||||
@@ -1215,4 +1200,4 @@ class SSPVerificationRetryConfig(ConfigurationModel): # pylint: disable=model-m
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return six.text_type(self.arguments)
|
||||
return str(self.arguments)
|
||||
|
||||
@@ -13,9 +13,9 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.student.models import User
|
||||
from lms.djangoapps.verify_student.utils import is_verification_expiring_soon
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from common.djangoapps.student.models import User
|
||||
|
||||
from .models import ManualVerification, SoftwareSecurePhotoVerification, SSOVerification
|
||||
from .utils import most_recent_verification
|
||||
@@ -23,7 +23,7 @@ from .utils import most_recent_verification
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XBlockVerificationService(object):
|
||||
class XBlockVerificationService:
|
||||
"""
|
||||
Learner verification XBlock service.
|
||||
"""
|
||||
@@ -52,7 +52,7 @@ class XBlockVerificationService(object):
|
||||
return IDVerificationService.get_verify_location()
|
||||
|
||||
|
||||
class IDVerificationService(object):
|
||||
class IDVerificationService:
|
||||
"""
|
||||
Learner verification service interface for callers within edx-platform.
|
||||
"""
|
||||
@@ -212,7 +212,7 @@ class IDVerificationService(object):
|
||||
if attempt.expiration_datetime < now() and attempt.status == 'approved':
|
||||
if user_status['should_display']:
|
||||
user_status['status'] = 'expired'
|
||||
user_status['error'] = _(u"Your {platform_name} verification has expired.").format(
|
||||
user_status['error'] = _("Your {platform_name} verification has expired.").format(
|
||||
platform_name=configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
|
||||
)
|
||||
else:
|
||||
@@ -262,7 +262,7 @@ class IDVerificationService(object):
|
||||
Returns a string:
|
||||
Returns URL for IDV on Account Microfrontend
|
||||
"""
|
||||
location = '{}/id-verification'.format(settings.ACCOUNT_MICROFRONTEND_URL)
|
||||
location = f'{settings.ACCOUNT_MICROFRONTEND_URL}/id-verification'
|
||||
if course_id:
|
||||
location += '?course_id={}'.format(quote(str(course_id)))
|
||||
return location
|
||||
|
||||
@@ -33,7 +33,6 @@ from cryptography.hazmat.primitives.ciphers.algorithms import AES
|
||||
from cryptography.hazmat.primitives.ciphers.modes import CBC
|
||||
from cryptography.hazmat.primitives.hashes import SHA1
|
||||
from cryptography.hazmat.primitives.padding import PKCS7
|
||||
from six import text_type
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -84,10 +83,7 @@ def generate_aes_iv(key):
|
||||
Return the initialization vector Software Secure expects for a given AES
|
||||
key (they hash it a couple of times and take a substring).
|
||||
"""
|
||||
if six.PY3:
|
||||
return md5(key + md5(key).hexdigest().encode('utf-8')).hexdigest()[:AES_BLOCK_SIZE_BYTES].encode('utf-8')
|
||||
else:
|
||||
return md5(key + md5(key).hexdigest()).hexdigest()[:AES_BLOCK_SIZE_BYTES]
|
||||
return md5(key + md5(key).hexdigest().encode('utf-8')).hexdigest()[:AES_BLOCK_SIZE_BYTES].encode('utf-8')
|
||||
|
||||
|
||||
def random_aes_key():
|
||||
@@ -114,9 +110,9 @@ def rsa_encrypt(data, rsa_pub_key_bytes):
|
||||
"""
|
||||
`rsa_pub_key_bytes` is a byte sequence with the public key
|
||||
"""
|
||||
if isinstance(data, text_type):
|
||||
if isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
if isinstance(rsa_pub_key_bytes, text_type):
|
||||
if isinstance(rsa_pub_key_bytes, str):
|
||||
rsa_pub_key_bytes = rsa_pub_key_bytes.encode('utf-8')
|
||||
if rsa_pub_key_bytes.startswith(b'-----'):
|
||||
key = serialization.load_pem_public_key(rsa_pub_key_bytes, backend=default_backend())
|
||||
@@ -131,9 +127,9 @@ def rsa_decrypt(data, rsa_priv_key_bytes):
|
||||
"""
|
||||
When given some `data` and an RSA private key, decrypt the data
|
||||
"""
|
||||
if isinstance(data, text_type):
|
||||
if isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
if isinstance(rsa_priv_key_bytes, text_type):
|
||||
if isinstance(rsa_priv_key_bytes, str):
|
||||
rsa_priv_key_bytes = rsa_priv_key_bytes.encode('utf-8')
|
||||
if rsa_priv_key_bytes.startswith(b'-----'):
|
||||
key = serialization.load_pem_private_key(rsa_priv_key_bytes, password=None, backend=default_backend())
|
||||
@@ -157,12 +153,12 @@ def has_valid_signature(method, headers_dict, body_dict, access_key, secret_key)
|
||||
|
||||
if post_access_key != access_key:
|
||||
log.error("Posted access key does not match ours")
|
||||
log.debug(u"Their access: %s; Our access: %s", post_access_key, access_key)
|
||||
log.debug("Their access: %s; Our access: %s", post_access_key, access_key)
|
||||
return False
|
||||
|
||||
if post_signature != expected_signature:
|
||||
log.error("Posted signature does not match expected")
|
||||
log.debug(u"Their sig: %s; Expected: %s", post_signature, expected_signature)
|
||||
log.debug("Their sig: %s; Expected: %s", post_signature, expected_signature)
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -177,7 +173,7 @@ def generate_signed_message(method, headers_dict, body_dict, access_key, secret_
|
||||
# hmac needs a byte string for it's starting key, can't be unicode.
|
||||
hashed = hmac.new(secret_key.encode('utf-8'), message.encode('utf-8'), sha256)
|
||||
signature = binascii.b2a_base64(hashed.digest()).rstrip(b'\n').decode('utf-8')
|
||||
authorization_header = u"SSI {}:{}".format(access_key, signature)
|
||||
authorization_header = f"SSI {access_key}:{signature}"
|
||||
|
||||
message += '\n'
|
||||
return message, signature, authorization_header
|
||||
@@ -221,14 +217,14 @@ def body_string(body_dict, prefix=""):
|
||||
if isinstance(value, (list, tuple)):
|
||||
for i, arr in enumerate(value):
|
||||
if isinstance(arr, dict):
|
||||
body_list.append(body_string(arr, u"{}.{}.".format(key, i)))
|
||||
body_list.append(body_string(arr, f"{key}.{i}."))
|
||||
else:
|
||||
body_list.append(u"{}.{}:{}\n".format(key, i, arr))
|
||||
body_list.append(f"{key}.{i}:{arr}\n")
|
||||
elif isinstance(value, dict):
|
||||
body_list.append(body_string(value, key + ":"))
|
||||
else:
|
||||
if value is None:
|
||||
value = "null"
|
||||
body_list.append(u"{}{}:{}\n".format(prefix, key, value))
|
||||
body_list.append(f"{prefix}{key}:{value}\n")
|
||||
|
||||
return "".join(body_list) # Note that trailing \n's are important
|
||||
|
||||
@@ -91,7 +91,7 @@ def send_verification_status_email(context):
|
||||
msg.content_subtype = 'html'
|
||||
msg.send(fail_silently=False)
|
||||
except SMTPException:
|
||||
log.warning(u"Failure in sending verification status e-mail to %s", dest_addr)
|
||||
log.warning("Failure in sending verification status e-mail to %s", dest_addr)
|
||||
|
||||
|
||||
@shared_task(
|
||||
|
||||
@@ -8,8 +8,8 @@ from django.db import DEFAULT_DB_ALIAS
|
||||
from django.test import TestCase
|
||||
from django.utils.timezone import now
|
||||
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
|
||||
|
||||
class TestVerificationBase(TestCase):
|
||||
@@ -85,7 +85,7 @@ class TestVerificationBase(TestCase):
|
||||
if not user:
|
||||
user = UserFactory.create()
|
||||
attempt = SoftwareSecurePhotoVerification(user=user)
|
||||
user.profile.name = u"Rust\u01B4"
|
||||
user.profile.name = "Rust\u01B4"
|
||||
|
||||
attempt.upload_face_image("Just pretend this is image data")
|
||||
attempt.upload_photo_id_image("Hey, we're a photo ID")
|
||||
|
||||
@@ -9,14 +9,14 @@ from django.conf import settings # lint-amnesty, pylint: disable=unused-import
|
||||
from django.utils.timezone import now # lint-amnesty, pylint: disable=unused-import
|
||||
from factory.django import DjangoModelFactory
|
||||
|
||||
from lms.djangoapps.verify_student.models import SSOVerification, SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, SSOVerification
|
||||
|
||||
|
||||
class SoftwareSecurePhotoVerificationFactory(DjangoModelFactory):
|
||||
"""
|
||||
Factory for SoftwareSecurePhotoVerification
|
||||
"""
|
||||
class Meta(object):
|
||||
class Meta:
|
||||
model = SoftwareSecurePhotoVerification
|
||||
|
||||
status = 'approved'
|
||||
|
||||
@@ -36,7 +36,7 @@ class SoftwareSecureFakeView(View):
|
||||
access_key = settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_ACCESS_KEY"]
|
||||
context = {
|
||||
'receipt_id': None,
|
||||
'authorization_code': u'SIS {}:0000'.format(access_key),
|
||||
'authorization_code': f'SIS {access_key}:0000',
|
||||
'results_callback': reverse('verify_student_results_callback')
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@ Tests for the fake software secure response.
|
||||
"""
|
||||
|
||||
|
||||
from django.test import TestCase
|
||||
from mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from common.djangoapps.util.testing import UrlResetMixin
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
|
||||
|
||||
class SoftwareSecureFakeViewTest(UrlResetMixin, TestCase):
|
||||
@@ -21,7 +22,7 @@ class SoftwareSecureFakeViewTest(UrlResetMixin, TestCase):
|
||||
def setUp(self, **kwargs):
|
||||
enable_software_secure_fake = kwargs.get('enable_software_secure_fake', False)
|
||||
with patch.dict('django.conf.settings.FEATURES', {'ENABLE_SOFTWARE_SECURE_FAKE': enable_software_secure_fake}):
|
||||
super(SoftwareSecureFakeViewTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
self.user = UserFactory.create(username="test", password="test")
|
||||
self.attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
|
||||
@@ -35,7 +36,7 @@ class SoftwareSecureFakeViewDisabledTest(SoftwareSecureFakeViewTest):
|
||||
"""
|
||||
|
||||
def setUp(self): # lint-amnesty, pylint: disable=arguments-differ
|
||||
super(SoftwareSecureFakeViewDisabledTest, self).setUp(enable_software_secure_fake=False) # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp(enable_software_secure_fake=False)
|
||||
|
||||
def test_get_method_without_enable_feature_flag(self):
|
||||
"""
|
||||
@@ -56,7 +57,7 @@ class SoftwareSecureFakeViewEnabledTest(SoftwareSecureFakeViewTest):
|
||||
"""
|
||||
|
||||
def setUp(self): # lint-amnesty, pylint: disable=arguments-differ
|
||||
super(SoftwareSecureFakeViewEnabledTest, self).setUp(enable_software_secure_fake=True) # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp(enable_software_secure_fake=True)
|
||||
|
||||
def test_get_method_without_logged_in_user(self):
|
||||
"""
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Integration tests of the payment flow, including course mode selection.
|
||||
"""
|
||||
|
||||
import six
|
||||
from django.urls import reverse
|
||||
|
||||
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
|
||||
@@ -23,7 +22,7 @@ class TestProfEdVerification(ModuleStoreTestCase):
|
||||
MIN_PRICE = 1438
|
||||
|
||||
def setUp(self):
|
||||
super(TestProfEdVerification, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
self.user = UserFactory.create(username="rusty", password="test")
|
||||
self.client.login(username="rusty", password="test")
|
||||
@@ -38,7 +37,7 @@ class TestProfEdVerification(ModuleStoreTestCase):
|
||||
self.urls = {
|
||||
'course_modes_choose': reverse(
|
||||
'course_modes_choose',
|
||||
args=[six.text_type(self.course_key)]
|
||||
args=[str(self.course_key)]
|
||||
),
|
||||
|
||||
'verify_student_start_flow': IDVerificationService.get_verify_location(self.course_key),
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
import base64
|
||||
from datetime import datetime, timedelta
|
||||
from unittest import mock
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import ddt
|
||||
import mock
|
||||
import requests.exceptions
|
||||
import simplejson as json
|
||||
from django.conf import settings
|
||||
from django.utils.timezone import now
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
from six.moves import range
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from common.test.utils import MockS3BotoMixin
|
||||
from lms.djangoapps.verify_student.models import (
|
||||
ManualVerification,
|
||||
@@ -21,7 +22,6 @@ from lms.djangoapps.verify_student.models import (
|
||||
SSOVerification,
|
||||
VerificationException
|
||||
)
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from lms.djangoapps.verify_student.tests import TestVerificationBase
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
|
||||
@@ -175,15 +175,15 @@ class TestPhotoVerification(TestVerificationBase, MockS3BotoMixin, ModuleStoreTe
|
||||
was when you submitted it.
|
||||
"""
|
||||
user = UserFactory.create()
|
||||
user.profile.name = u"Jack \u01B4" # gratuious non-ASCII char to test encodings
|
||||
user.profile.name = "Jack \u01B4" # gratuious non-ASCII char to test encodings
|
||||
|
||||
attempt = SoftwareSecurePhotoVerification(user=user)
|
||||
user.profile.name = u"Clyde \u01B4"
|
||||
user.profile.name = "Clyde \u01B4"
|
||||
attempt.mark_ready()
|
||||
|
||||
user.profile.name = u"Rusty \u01B4"
|
||||
user.profile.name = "Rusty \u01B4"
|
||||
|
||||
assert u'Clyde ƴ' == attempt.name
|
||||
assert 'Clyde ƴ' == attempt.name
|
||||
|
||||
def test_submissions(self):
|
||||
"""Test that we set our status correctly after a submission."""
|
||||
@@ -222,7 +222,7 @@ class TestPhotoVerification(TestVerificationBase, MockS3BotoMixin, ModuleStoreTe
|
||||
@ddt.data(
|
||||
'Not Provided',
|
||||
'{"IdReasons": ["Not provided"]}',
|
||||
u'[{"ïḋṚëäṡöṅṡ": ["Ⓝⓞⓣ ⓟⓡⓞⓥⓘⓓⓔⓓ "]}]',
|
||||
'[{"ïḋṚëäṡöṅṡ": ["Ⓝⓞⓣ ⓟⓡⓞⓥⓘⓓⓔⓓ "]}]',
|
||||
)
|
||||
def test_parse_error_msg_failure(self, msg):
|
||||
user = UserFactory.create()
|
||||
@@ -286,7 +286,7 @@ class TestPhotoVerification(TestVerificationBase, MockS3BotoMixin, ModuleStoreTe
|
||||
Retire user with record(s) in table
|
||||
"""
|
||||
user = UserFactory.create()
|
||||
user.profile.name = u"Enrique"
|
||||
user.profile.name = "Enrique"
|
||||
attempt = SoftwareSecurePhotoVerification(user=user)
|
||||
|
||||
# Populate Record
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests for the service classes in verify_student.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
import ddt
|
||||
from django.conf import settings
|
||||
@@ -11,7 +11,6 @@ from django.test import TestCase
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext as _
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
from pytz import utc
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
@@ -161,7 +160,7 @@ class TestIDVerificationService(ModuleStoreTestCase):
|
||||
Test for the path to the IDV flow with no course key given
|
||||
"""
|
||||
path = IDVerificationService.get_verify_location()
|
||||
expected_path = '{}/id-verification'.format(settings.ACCOUNT_MICROFRONTEND_URL)
|
||||
expected_path = f'{settings.ACCOUNT_MICROFRONTEND_URL}/id-verification'
|
||||
assert path == expected_path
|
||||
|
||||
def test_get_verify_location_from_course_id(self):
|
||||
@@ -170,7 +169,7 @@ class TestIDVerificationService(ModuleStoreTestCase):
|
||||
"""
|
||||
course = CourseFactory.create(org='Robot', number='999', display_name='Test Course')
|
||||
path = IDVerificationService.get_verify_location(course.id)
|
||||
expected_path = '{}/id-verification'.format(settings.ACCOUNT_MICROFRONTEND_URL)
|
||||
expected_path = f'{settings.ACCOUNT_MICROFRONTEND_URL}/id-verification'
|
||||
assert path == (expected_path + '?course_id=Robot/999/Test_Course')
|
||||
|
||||
def test_get_verify_location_from_string(self):
|
||||
@@ -178,7 +177,7 @@ class TestIDVerificationService(ModuleStoreTestCase):
|
||||
Test for the path to the IDV flow with a course key string
|
||||
"""
|
||||
path = IDVerificationService.get_verify_location('course-v1:edX+DemoX+Demo_Course')
|
||||
expected_path = '{}/id-verification'.format(settings.ACCOUNT_MICROFRONTEND_URL)
|
||||
expected_path = f'{settings.ACCOUNT_MICROFRONTEND_URL}/id-verification'
|
||||
assert path == (expected_path + '?course_id=course-v1%3AedX%2BDemoX%2BDemo_Course')
|
||||
|
||||
|
||||
@@ -192,7 +191,7 @@ class TestIDVerificationServiceUserStatus(TestCase):
|
||||
we just put everything inside of a frozen time
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestIDVerificationServiceUserStatus, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.user = UserFactory.create()
|
||||
|
||||
def test_no_verification(self):
|
||||
@@ -290,7 +289,7 @@ class TestIDVerificationServiceUserStatus(TestCase):
|
||||
frozen_datetime.move_to('2016-07-11')
|
||||
expected_status = {
|
||||
'status': 'expired',
|
||||
'error': _(u"Your {platform_name} verification has expired.").format(
|
||||
'error': _("Your {platform_name} verification has expired.").format(
|
||||
platform_name=configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)
|
||||
),
|
||||
'should_display': True,
|
||||
|
||||
@@ -7,11 +7,11 @@ from datetime import timedelta
|
||||
|
||||
from django.utils.timezone import now
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline
|
||||
from lms.djangoapps.verify_student.signals import _listen_for_course_publish, _listen_for_lms_retire
|
||||
from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory
|
||||
from openedx.core.djangoapps.user_api.accounts.tests.retirement_helpers import fake_completed_retirement
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
@@ -22,7 +22,7 @@ class VerificationDeadlineSignalTest(ModuleStoreTestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(VerificationDeadlineSignalTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.end = now().replace(microsecond=0) + timedelta(days=7)
|
||||
self.course = CourseFactory.create(end=self.end)
|
||||
VerificationDeadline.objects.all().delete()
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# lint-amnesty, pylint: disable=missing-module-docstring
|
||||
# Lots of patching to stub in our own settings, and HTTP posting
|
||||
from unittest import mock
|
||||
from unittest.mock import patch
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from django.conf import settings
|
||||
from mock import patch
|
||||
|
||||
from common.test.utils import MockS3BotoMixin
|
||||
from lms.djangoapps.verify_student.tests import TestVerificationBase
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests for verify_student utility functions.
|
||||
"""
|
||||
@@ -6,21 +5,21 @@ Tests for verify_student utility functions.
|
||||
|
||||
import unittest
|
||||
from datetime import timedelta
|
||||
from unittest import mock
|
||||
from unittest.mock import patch
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from mock import patch
|
||||
from pytest import mark
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from lms.djangoapps.verify_student.models import ManualVerification, SoftwareSecurePhotoVerification, SSOVerification
|
||||
from lms.djangoapps.verify_student.utils import (
|
||||
most_recent_verification,
|
||||
submit_request_to_ss,
|
||||
verification_for_datetime
|
||||
)
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
|
||||
FAKE_SETTINGS = {
|
||||
"DAYS_GOOD_FOR": 10,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Tests of verify_student views.
|
||||
"""
|
||||
|
||||
from datetime import timedelta
|
||||
from uuid import uuid4
|
||||
|
||||
import base64
|
||||
import codecs
|
||||
import urllib
|
||||
from datetime import timedelta
|
||||
from unittest import mock
|
||||
from unittest.mock import Mock, patch
|
||||
from uuid import uuid4
|
||||
|
||||
import ddt
|
||||
import httpretty
|
||||
import mock
|
||||
import simplejson as json
|
||||
import six
|
||||
from bs4 import BeautifulSoup
|
||||
from django.conf import settings
|
||||
from django.core import mail
|
||||
@@ -21,29 +21,27 @@ from django.test.client import Client, RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from mock import Mock, patch
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from six.moves import zip
|
||||
from waffle.testutils import override_switch
|
||||
|
||||
from common.test.utils import MockS3BotoMixin, XssTestMixin
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserFactory
|
||||
from common.djangoapps.util.testing import UrlResetMixin
|
||||
from common.test.utils import MockS3BotoMixin, XssTestMixin
|
||||
from lms.djangoapps.commerce.models import CommerceConfiguration
|
||||
from lms.djangoapps.commerce.tests import TEST_API_URL, TEST_PAYMENT_DATA, TEST_PUBLIC_URL_ROOT
|
||||
from lms.djangoapps.commerce.tests.mocks import mock_payment_processors
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from lms.djangoapps.verify_student.views import PayAndVerifyView, checkout_with_ecommerce_service, render_to_response
|
||||
from lms.djangoapps.verify_student.ssencrypt import encrypt_and_encode, rsa_encrypt
|
||||
from lms.djangoapps.verify_student.tests import TestVerificationBase
|
||||
from lms.djangoapps.verify_student.views import PayAndVerifyView, checkout_with_ecommerce_service, render_to_response
|
||||
from openedx.core.djangoapps.embargo.test_utils import restrict_course
|
||||
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
|
||||
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserFactory
|
||||
from common.djangoapps.util.testing import UrlResetMixin
|
||||
from lms.djangoapps.verify_student.tests import TestVerificationBase
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
@@ -104,7 +102,7 @@ def _mock_payment_processors():
|
||||
"""
|
||||
httpretty.register_uri(
|
||||
httpretty.GET,
|
||||
"{}/payment/processors/".format(TEST_API_URL),
|
||||
f"{TEST_API_URL}/payment/processors/",
|
||||
body=json.dumps(['foo', 'bar']),
|
||||
content_type="application/json",
|
||||
)
|
||||
@@ -117,7 +115,7 @@ class StartView(TestCase):
|
||||
"""
|
||||
|
||||
def start_url(self, course_id=""):
|
||||
return "/verify_student/{0}".format(six.moves.urllib.parse.quote(course_id))
|
||||
return "/verify_student/{}".format(urllib.parse.quote(course_id))
|
||||
|
||||
def test_start_new_verification(self):
|
||||
"""
|
||||
@@ -151,7 +149,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin, Tes
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
|
||||
def setUp(self):
|
||||
super(TestPayAndVerifyView, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
|
||||
result = self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
assert result, 'Could not log in'
|
||||
@@ -193,7 +191,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin, Tes
|
||||
configuration = CommerceConfiguration.objects.create(checkout_on_ecommerce_service=True)
|
||||
checkout_page = configuration.basket_checkout_page
|
||||
checkout_page += "?utm_source=test"
|
||||
httpretty.register_uri(httpretty.GET, "{}{}".format(TEST_PUBLIC_URL_ROOT, checkout_page))
|
||||
httpretty.register_uri(httpretty.GET, f"{TEST_PUBLIC_URL_ROOT}{checkout_page}")
|
||||
|
||||
course = self._create_course('verified', sku=sku)
|
||||
self._enroll(course.id)
|
||||
@@ -202,7 +200,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin, Tes
|
||||
url_with_utm = 'http://www.example.com/basket/add/?utm_source=test&sku=TESTSKU'
|
||||
with mock.patch.object(EcommerceService, 'get_checkout_page_url', return_value=url_with_utm):
|
||||
response = self._get_page('verify_student_start_flow', course.id, expected_status_code=302)
|
||||
expected_page = '{}{}&sku={}'.format(TEST_PUBLIC_URL_ROOT, checkout_page, sku)
|
||||
expected_page = f'{TEST_PUBLIC_URL_ROOT}{checkout_page}&sku={sku}'
|
||||
self.assertRedirects(response, expected_page, fetch_redirect_response=False)
|
||||
|
||||
@ddt.data(
|
||||
@@ -588,8 +586,8 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin, Tes
|
||||
course = self._create_course("verified")
|
||||
response = self._get_page(url_name, course.id, expected_status_code=302)
|
||||
|
||||
original_url = reverse(url_name, kwargs={'course_id': six.text_type(course.id)})
|
||||
login_url = u"{login_url}?next={original_url}".format(
|
||||
original_url = reverse(url_name, kwargs={'course_id': str(course.id)})
|
||||
login_url = "{login_url}?next={original_url}".format(
|
||||
login_url=reverse('signin_user'),
|
||||
original_url=original_url
|
||||
)
|
||||
@@ -688,7 +686,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin, Tes
|
||||
# Expect that the expiration date is set
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
data = self._get_page_data(response)
|
||||
assert data['verification_deadline'] == six.text_type(deadline)
|
||||
assert data['verification_deadline'] == str(deadline)
|
||||
|
||||
def test_course_mode_expired(self):
|
||||
deadline = now() + timedelta(days=-360)
|
||||
@@ -756,7 +754,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin, Tes
|
||||
|
||||
# Check that the verification deadline (rather than the upgrade deadline) is displayed
|
||||
if verification_deadline is not None:
|
||||
assert data['verification_deadline'] == six.text_type(verification_deadline)
|
||||
assert data['verification_deadline'] == str(verification_deadline)
|
||||
else:
|
||||
assert data['verification_deadline'] == ''
|
||||
|
||||
@@ -887,7 +885,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin, Tes
|
||||
"""Set the contribution amount pre-filled in a session var. """
|
||||
session = self.client.session
|
||||
session["donation_for_course"] = {
|
||||
six.text_type(course_id): amount
|
||||
str(course_id): amount
|
||||
}
|
||||
session.save()
|
||||
|
||||
@@ -895,7 +893,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin, Tes
|
||||
@override_settings(ECOMMERCE_API_URL=TEST_API_URL)
|
||||
def _get_page(self, url_name, course_key, expected_status_code=200, skip_first_step=False, assert_headers=False):
|
||||
"""Retrieve one of the verification pages. """
|
||||
url = reverse(url_name, kwargs={"course_id": six.text_type(course_key)})
|
||||
url = reverse(url_name, kwargs={"course_id": str(course_key)})
|
||||
|
||||
if skip_first_step:
|
||||
url += "?skip-first-step=1"
|
||||
@@ -930,11 +928,11 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin, Tes
|
||||
def _assert_requirements_displayed(self, response, requirements):
|
||||
"""Check that requirements are displayed on the page. """
|
||||
response_dict = self._get_page_data(response)
|
||||
for req, displayed in six.iteritems(response_dict['requirements']):
|
||||
for req, displayed in response_dict['requirements'].items():
|
||||
if req in requirements:
|
||||
assert displayed, "Expected '{req}' requirement to be displayed".format(req=req)
|
||||
assert displayed, f"Expected '{req}' requirement to be displayed"
|
||||
else:
|
||||
assert not displayed, "Expected '{req}' requirement to be displayed".format(req=req)
|
||||
assert not displayed, f"Expected '{req}' requirement to be hidden"
|
||||
|
||||
def _assert_course_details(self, response, course_key, display_name, url):
|
||||
"""Check the course information on the page. """
|
||||
@@ -986,7 +984,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin, Tes
|
||||
|
||||
def _assert_redirects_to_start_flow(self, response, course_id):
|
||||
"""Check that the page redirects to the start of the payment/verification flow. """
|
||||
url = reverse('verify_student_start_flow', kwargs={'course_id': six.text_type(course_id)})
|
||||
url = reverse('verify_student_start_flow', kwargs={'course_id': str(course_id)})
|
||||
with mock_payment_processors():
|
||||
self.assertRedirects(response, url)
|
||||
|
||||
@@ -997,14 +995,14 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin, Tes
|
||||
|
||||
def _assert_redirects_to_upgrade(self, response, course_id):
|
||||
"""Check that the page redirects to the "upgrade" part of the flow. """
|
||||
url = reverse('verify_student_upgrade_and_verify', kwargs={'course_id': six.text_type(course_id)})
|
||||
url = reverse('verify_student_upgrade_and_verify', kwargs={'course_id': str(course_id)})
|
||||
with mock_payment_processors():
|
||||
self.assertRedirects(response, url)
|
||||
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_course_upgrade_page_with_unicode_and_special_values_in_display_name(self, payment_flow):
|
||||
"""Check the course information on the page. """
|
||||
mode_display_name = u"Introduction à l'astrophysique"
|
||||
mode_display_name = "Introduction à l'astrophysique"
|
||||
course = CourseFactory.create(display_name=mode_display_name)
|
||||
for course_mode in [CourseMode.DEFAULT_MODE_SLUG, "verified"]:
|
||||
min_price = (self.MIN_PRICE if course_mode != CourseMode.DEFAULT_MODE_SLUG else 0)
|
||||
@@ -1037,7 +1035,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin, Tes
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
class CheckoutTestMixin(object):
|
||||
class CheckoutTestMixin:
|
||||
"""
|
||||
Mixin implementing test methods that should behave identically regardless
|
||||
of which backend is used (currently only the ecommerce service). Subclasses
|
||||
@@ -1052,7 +1050,7 @@ class CheckoutTestMixin(object):
|
||||
|
||||
def setUp(self):
|
||||
""" Create a user and course. """
|
||||
super(CheckoutTestMixin, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
self.user = UserFactory.create(username="test", password="test")
|
||||
self.course = CourseFactory.create()
|
||||
@@ -1094,7 +1092,7 @@ class CheckoutTestMixin(object):
|
||||
def test_create_order(self, patched_create_order):
|
||||
# Create an order
|
||||
params = {
|
||||
'course_id': six.text_type(self.course.id),
|
||||
'course_id': str(self.course.id),
|
||||
'contribution': 100,
|
||||
}
|
||||
self._assert_checked_out(params, patched_create_order, self.course.id, 'verified')
|
||||
@@ -1104,7 +1102,7 @@ class CheckoutTestMixin(object):
|
||||
course = CourseFactory.create()
|
||||
CourseModeFactory.create(mode_slug="professional", course_id=course.id, min_price=10, sku=self.make_sku())
|
||||
# Create an order for a prof ed course
|
||||
params = {'course_id': six.text_type(course.id)}
|
||||
params = {'course_id': str(course.id)}
|
||||
self._assert_checked_out(params, patched_create_order, course.id, 'professional')
|
||||
|
||||
def test_create_order_no_id_professional(self, patched_create_order):
|
||||
@@ -1112,7 +1110,7 @@ class CheckoutTestMixin(object):
|
||||
course = CourseFactory.create()
|
||||
CourseModeFactory.create(mode_slug="no-id-professional", course_id=course.id, min_price=10, sku=self.make_sku())
|
||||
# Create an order for a prof ed course
|
||||
params = {'course_id': six.text_type(course.id)}
|
||||
params = {'course_id': str(course.id)}
|
||||
self._assert_checked_out(params, patched_create_order, course.id, 'no-id-professional')
|
||||
|
||||
def test_create_order_for_multiple_paid_modes(self, patched_create_order):
|
||||
@@ -1121,14 +1119,14 @@ class CheckoutTestMixin(object):
|
||||
CourseModeFactory.create(mode_slug="no-id-professional", course_id=course.id, min_price=10, sku=self.make_sku())
|
||||
CourseModeFactory.create(mode_slug="professional", course_id=course.id, min_price=10, sku=self.make_sku())
|
||||
# Create an order for a prof ed course
|
||||
params = {'course_id': six.text_type(course.id)}
|
||||
params = {'course_id': str(course.id)}
|
||||
# TODO jsa - is this the intended behavior?
|
||||
self._assert_checked_out(params, patched_create_order, course.id, 'no-id-professional')
|
||||
|
||||
def test_create_order_bad_donation_amount(self, patched_create_order):
|
||||
# Create an order
|
||||
params = {
|
||||
'course_id': six.text_type(self.course.id),
|
||||
'course_id': str(self.course.id),
|
||||
'contribution': '99.9'
|
||||
}
|
||||
self._assert_checked_out(params, patched_create_order, None, None, expected_status_code=400)
|
||||
@@ -1136,7 +1134,7 @@ class CheckoutTestMixin(object):
|
||||
def test_create_order_good_donation_amount(self, patched_create_order):
|
||||
# Create an order
|
||||
params = {
|
||||
'course_id': six.text_type(self.course.id),
|
||||
'course_id': str(self.course.id),
|
||||
'contribution': '100.0'
|
||||
}
|
||||
self._assert_checked_out(params, patched_create_order, self.course.id, 'verified')
|
||||
@@ -1149,7 +1147,7 @@ class CheckoutTestMixin(object):
|
||||
expected_payment_data['payment_form_data'].update({'foo': 'bar'})
|
||||
patched_create_order.return_value = expected_payment_data
|
||||
# there is no 'processor' parameter in the post payload, so the response should only contain payment form data.
|
||||
params = {'course_id': six.text_type(self.course.id), 'contribution': 100}
|
||||
params = {'course_id': str(self.course.id), 'contribution': 100}
|
||||
response = self.client.post(reverse('verify_student_create_order'), params)
|
||||
assert response.status_code == 200
|
||||
assert patched_create_order.called
|
||||
@@ -1174,7 +1172,7 @@ class TestCreateOrderEcommerceService(CheckoutTestMixin, ModuleStoreTestCase):
|
||||
|
||||
def make_sku(self):
|
||||
""" Checkout is handled by the ecommerce service when the course mode's sku is nonempty. """
|
||||
return six.text_type(uuid4().hex)
|
||||
return str(uuid4().hex)
|
||||
|
||||
def _get_checkout_args(self, patched_create_order):
|
||||
""" Assuming patched_create_order was called, return a mapping containing the call arguments."""
|
||||
@@ -1200,7 +1198,7 @@ class TestCheckoutWithEcommerceService(ModuleStoreTestCase):
|
||||
# mock out the payment processors endpoint
|
||||
httpretty.register_uri(
|
||||
httpretty.POST,
|
||||
"{}/baskets/".format(TEST_API_URL),
|
||||
f"{TEST_API_URL}/baskets/",
|
||||
body=json.dumps({'payment_data': expected_payment_data}),
|
||||
content_type="application/json",
|
||||
)
|
||||
@@ -1233,10 +1231,10 @@ class TestSubmitPhotosForVerification(MockS3BotoMixin, TestVerificationBase):
|
||||
USERNAME = "test_user"
|
||||
PASSWORD = "test_password"
|
||||
IMAGE_DATA = "abcd,1234"
|
||||
FULL_NAME = u"Ḟüḷḷ Ṅäṁë"
|
||||
FULL_NAME = "Ḟüḷḷ Ṅäṁë"
|
||||
|
||||
def setUp(self):
|
||||
super(TestSubmitPhotosForVerification, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
|
||||
result = self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
assert result, 'Could not log in'
|
||||
@@ -1447,7 +1445,7 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase, TestVerification
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestPhotoVerificationResultsCallback, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
self.course = CourseFactory.create(org='Robot', number='999', display_name='Test Course')
|
||||
self.course_id = self.course.id
|
||||
@@ -1591,7 +1589,7 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase, TestVerification
|
||||
)
|
||||
attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=self.receipt_id)
|
||||
old_verification = SoftwareSecurePhotoVerification.objects.get(pk=verification.pk)
|
||||
assert attempt.status == u'approved'
|
||||
assert attempt.status == 'approved'
|
||||
assert attempt.expiration_datetime.date() == expiration_datetime.date()
|
||||
assert old_verification.expiry_email_date is None
|
||||
assert response.content.decode('utf-8') == 'OK!'
|
||||
@@ -1626,7 +1624,7 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase, TestVerification
|
||||
)
|
||||
|
||||
attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=self.receipt_id)
|
||||
assert attempt.status == u'approved'
|
||||
assert attempt.status == 'approved'
|
||||
assert attempt.expiration_datetime.date() == expiration_datetime.date()
|
||||
assert response.content.decode('utf-8') == 'OK!'
|
||||
self._assert_verification_approved_email(expiration_datetime.date())
|
||||
@@ -1656,9 +1654,9 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase, TestVerification
|
||||
HTTP_DATE='testdate'
|
||||
)
|
||||
attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=self.receipt_id)
|
||||
assert attempt.status == u'denied'
|
||||
assert attempt.error_code == u"Your photo doesn't meet standards."
|
||||
assert attempt.error_msg == u'[{"photoIdReasons": ["Not provided"]}]'
|
||||
assert attempt.status == 'denied'
|
||||
assert attempt.error_code == "Your photo doesn't meet standards."
|
||||
assert attempt.error_msg == '[{"photoIdReasons": ["Not provided"]}]'
|
||||
assert response.content.decode('utf-8') == 'OK!'
|
||||
self._assert_verification_denied_email()
|
||||
|
||||
@@ -1683,9 +1681,9 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase, TestVerification
|
||||
HTTP_DATE='testdate'
|
||||
)
|
||||
attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=self.receipt_id)
|
||||
assert attempt.status == u'must_retry'
|
||||
assert attempt.error_code == u'You must retry the verification.'
|
||||
assert attempt.error_msg == u'"Memory overflow"'
|
||||
assert attempt.status == 'must_retry'
|
||||
assert attempt.error_code == 'You must retry the verification.'
|
||||
assert attempt.error_msg == '"Memory overflow"'
|
||||
assert response.content.decode('utf-8') == 'OK!'
|
||||
|
||||
@patch(
|
||||
@@ -1725,7 +1723,7 @@ class TestReverifyView(TestVerificationBase):
|
||||
PASSWORD = "detachment-2702"
|
||||
|
||||
def setUp(self):
|
||||
super(TestReverifyView, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
|
||||
success = self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
assert success, 'Could not log in'
|
||||
@@ -1823,7 +1821,7 @@ class TestPhotoURLView(TestVerificationBase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestPhotoURLView, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
self.user = AdminFactory()
|
||||
login_success = self.client.login(username=self.user.username, password='test')
|
||||
@@ -1836,7 +1834,7 @@ class TestPhotoURLView(TestVerificationBase):
|
||||
self.receipt_id = self.attempt.receipt_id
|
||||
|
||||
def test_photo_url_view_returns_data(self):
|
||||
url = reverse('verification_photo_urls', kwargs={'receipt_id': six.text_type(self.receipt_id)})
|
||||
url = reverse('verification_photo_urls', kwargs={'receipt_id': str(self.receipt_id)})
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response.data['EdX-ID'] == self.receipt_id
|
||||
@@ -1847,7 +1845,7 @@ class TestPhotoURLView(TestVerificationBase):
|
||||
|
||||
def test_photo_url_view_returns_404_if_invalid_receipt_id(self):
|
||||
url = reverse('verification_photo_urls',
|
||||
kwargs={'receipt_id': six.text_type('00000000-0000-0000-0000-000000000000')})
|
||||
kwargs={'receipt_id': '00000000-0000-0000-0000-000000000000'})
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == 404
|
||||
|
||||
@@ -1855,7 +1853,7 @@ class TestPhotoURLView(TestVerificationBase):
|
||||
self.user = UserFactory()
|
||||
login_success = self.client.login(username=self.user.username, password='test')
|
||||
assert login_success
|
||||
url = reverse('verification_photo_urls', kwargs={'receipt_id': six.text_type(self.receipt_id)})
|
||||
url = reverse('verification_photo_urls', kwargs={'receipt_id': str(self.receipt_id)})
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == 403
|
||||
|
||||
@@ -1927,7 +1925,7 @@ class TestDecodeImageViews(MockS3BotoMixin, TestVerificationBase):
|
||||
url_name = 'verification_decrypt_face_image'
|
||||
if img_type == 'photo_id':
|
||||
url_name = 'verification_decrypt_photo_id_image'
|
||||
url = reverse(url_name, kwargs={'receipt_id': six.text_type(receipt_id)})
|
||||
url = reverse(url_name, kwargs={'receipt_id': str(receipt_id)})
|
||||
|
||||
response = self.client.get(url)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ urlpatterns = [
|
||||
# most likely after enrolling in a course and selecting
|
||||
# a "verified" track.
|
||||
url(
|
||||
r'^start-flow/{course}/$'.format(course=settings.COURSE_ID_PATTERN),
|
||||
fr'^start-flow/{settings.COURSE_ID_PATTERN}/$',
|
||||
# Pylint seems to dislike the as_view() method because as_view() is
|
||||
# decorated with `classonlymethod` instead of `classmethod`.
|
||||
views.PayAndVerifyView.as_view(),
|
||||
@@ -27,7 +27,7 @@ urlpatterns = [
|
||||
|
||||
# This is for A/B testing.
|
||||
url(
|
||||
r'^begin-flow/{course}/$'.format(course=settings.COURSE_ID_PATTERN),
|
||||
fr'^begin-flow/{settings.COURSE_ID_PATTERN}/$',
|
||||
views.PayAndVerifyView.as_view(),
|
||||
name="verify_student_begin_flow",
|
||||
kwargs={
|
||||
@@ -39,7 +39,7 @@ urlpatterns = [
|
||||
# This is the same as the "start verification" flow,
|
||||
# except with slight messaging changes.
|
||||
url(
|
||||
r'^upgrade/{course}/$'.format(course=settings.COURSE_ID_PATTERN),
|
||||
fr'^upgrade/{settings.COURSE_ID_PATTERN}/$',
|
||||
views.PayAndVerifyView.as_view(),
|
||||
name="verify_student_upgrade_and_verify",
|
||||
kwargs={
|
||||
@@ -54,7 +54,7 @@ urlpatterns = [
|
||||
# Note that if the user has already verified, this will redirect
|
||||
# to the dashboard.
|
||||
url(
|
||||
r'^verify-now/{course}/$'.format(course=settings.COURSE_ID_PATTERN),
|
||||
fr'^verify-now/{settings.COURSE_ID_PATTERN}/$',
|
||||
views.PayAndVerifyView.as_view(),
|
||||
name="verify_student_verify_now",
|
||||
kwargs={
|
||||
@@ -102,19 +102,19 @@ urlpatterns = [
|
||||
),
|
||||
|
||||
url(
|
||||
r'^photo-urls/{receipt_id}$'.format(receipt_id=IDV_RECEIPT_ID_PATTERN),
|
||||
fr'^photo-urls/{IDV_RECEIPT_ID_PATTERN}$',
|
||||
views.PhotoUrlsView.as_view(),
|
||||
name="verification_photo_urls"
|
||||
),
|
||||
|
||||
url(
|
||||
r'^decrypt-idv-images/face/{receipt_id}$'.format(receipt_id=IDV_RECEIPT_ID_PATTERN),
|
||||
fr'^decrypt-idv-images/face/{IDV_RECEIPT_ID_PATTERN}$',
|
||||
views.DecryptFaceImageView.as_view(),
|
||||
name="verification_decrypt_face_image"
|
||||
),
|
||||
|
||||
url(
|
||||
r'^decrypt-idv-images/photo-id/{receipt_id}$'.format(receipt_id=IDV_RECEIPT_ID_PATTERN),
|
||||
fr'^decrypt-idv-images/photo-id/{IDV_RECEIPT_ID_PATTERN}$',
|
||||
views.DecryptPhotoIDImageView.as_view(),
|
||||
name="verification_decrypt_photo_id_image"
|
||||
),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Common Utilities for the verify_student application.
|
||||
"""
|
||||
@@ -9,8 +8,6 @@ import logging
|
||||
from django.conf import settings
|
||||
from django.utils.timezone import now
|
||||
|
||||
from six import text_type
|
||||
|
||||
from lms.djangoapps.verify_student.tasks import send_request_to_ss_for_user
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -29,7 +26,7 @@ def submit_request_to_ss(user_verification, copy_id_photo_from):
|
||||
)
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
log.error(
|
||||
"Software Secure submit request %r failed, result: %s", user_verification.user.username, text_type(error)
|
||||
"Software Secure submit request %r failed, result: %s", user_verification.user.username, str(error)
|
||||
)
|
||||
user_verification.mark_must_retry()
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import datetime
|
||||
import decimal
|
||||
import json
|
||||
import logging
|
||||
import urllib
|
||||
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
@@ -30,6 +30,11 @@ from rest_framework.views import APIView
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_response
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.track import segment
|
||||
from common.djangoapps.util.db import outer_atomic
|
||||
from common.djangoapps.util.json_request import JsonResponse
|
||||
from common.djangoapps.util.views import require_global_staff
|
||||
from lms.djangoapps.commerce.utils import EcommerceService, is_account_activation_requirement_disabled
|
||||
from lms.djangoapps.verify_student.emails import send_verification_approved_email, send_verification_confirmation_email
|
||||
from lms.djangoapps.verify_student.image import InvalidImageData, decode_image_data
|
||||
@@ -44,11 +49,6 @@ from openedx.core.djangoapps.user_api.accounts import NAME_MIN_LENGTH
|
||||
from openedx.core.djangoapps.user_api.accounts.api import update_account_settings
|
||||
from openedx.core.djangoapps.user_api.errors import AccountValidationError, UserNotFound
|
||||
from openedx.core.lib.log_utils import audit_log
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.track import segment
|
||||
from common.djangoapps.util.db import outer_atomic
|
||||
from common.djangoapps.util.json_request import JsonResponse
|
||||
from common.djangoapps.util.views import require_global_staff
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from .services import IDVerificationService
|
||||
@@ -235,7 +235,7 @@ class PayAndVerifyView(View):
|
||||
|
||||
# Verify that the course exists
|
||||
if course is None:
|
||||
log.warning(u"Could not find course with ID %s.", course_id)
|
||||
log.warning("Could not find course with ID %s.", course_id)
|
||||
raise Http404
|
||||
|
||||
# Check whether the user has access to this course
|
||||
@@ -262,7 +262,7 @@ class PayAndVerifyView(View):
|
||||
verification_deadline = VerificationDeadline.deadline_for_course(course.id)
|
||||
response = self._response_if_deadline_passed(course, self.VERIFICATION_DEADLINE, verification_deadline)
|
||||
if response is not None:
|
||||
log.info(u"Verification deadline for '%s' has passed.", course.id)
|
||||
log.info("Verification deadline for '%s' has passed.", course.id)
|
||||
return response
|
||||
|
||||
# Retrieve the relevant course mode for the payment/verification flow.
|
||||
@@ -280,19 +280,19 @@ class PayAndVerifyView(View):
|
||||
if relevant_course_mode is not None:
|
||||
if CourseMode.is_verified_mode(relevant_course_mode):
|
||||
log.info(
|
||||
u"Entering payment and verification flow for user '%s', course '%s', with current step '%s'.",
|
||||
"Entering payment and verification flow for user '%s', course '%s', with current step '%s'.",
|
||||
request.user.id, course_id, current_step
|
||||
)
|
||||
else:
|
||||
log.info(
|
||||
u"Entering payment flow for user '%s', course '%s', with current step '%s'",
|
||||
"Entering payment flow for user '%s', course '%s', with current step '%s'",
|
||||
request.user.id, course_id, current_step
|
||||
)
|
||||
else:
|
||||
# Otherwise, there has never been a verified/paid mode,
|
||||
# so return a page not found response.
|
||||
log.warning(
|
||||
u"No paid/verified course mode found for course '%s' for verification/payment flow request",
|
||||
"No paid/verified course mode found for course '%s' for verification/payment flow request",
|
||||
course_id
|
||||
)
|
||||
raise Http404
|
||||
@@ -309,7 +309,7 @@ class PayAndVerifyView(View):
|
||||
upgrade_deadline = relevant_course_mode.expiration_datetime
|
||||
response = self._response_if_deadline_passed(course, self.UPGRADE_DEADLINE, upgrade_deadline)
|
||||
if response is not None:
|
||||
log.info(u"Upgrade deadline for '%s' has passed.", course.id)
|
||||
log.info("Upgrade deadline for '%s' has passed.", course.id)
|
||||
return response
|
||||
|
||||
# Check whether the user has verified, paid, and enrolled.
|
||||
@@ -374,7 +374,7 @@ class PayAndVerifyView(View):
|
||||
if not course.start or course.start < now():
|
||||
courseware_url = reverse(
|
||||
'course_root',
|
||||
kwargs={'course_id': six.text_type(course_key)}
|
||||
kwargs={'course_id': str(course_key)}
|
||||
)
|
||||
|
||||
full_name = (
|
||||
@@ -387,7 +387,7 @@ class PayAndVerifyView(View):
|
||||
# use that amount to pre-fill the price selection form.
|
||||
contribution_amount = request.session.get(
|
||||
'donation_for_course', {}
|
||||
).get(six.text_type(course_key), '')
|
||||
).get(str(course_key), '')
|
||||
|
||||
# Remember whether the user is upgrading
|
||||
# so we can fire an analytics event upon payment.
|
||||
@@ -403,7 +403,7 @@ class PayAndVerifyView(View):
|
||||
context = {
|
||||
'contribution_amount': contribution_amount,
|
||||
'course': course,
|
||||
'course_key': six.text_type(course_key),
|
||||
'course_key': str(course_key),
|
||||
'checkpoint_location': request.GET.get('checkpoint'),
|
||||
'course_mode': relevant_course_mode,
|
||||
'courseware_url': courseware_url,
|
||||
@@ -431,10 +431,10 @@ class PayAndVerifyView(View):
|
||||
# utm_params is [(u'utm_content', u'course-v1:IDBx IDB20.1x 1T2017'),...
|
||||
utm_params = [item for item in self.request.GET.items() if 'utm_' in item[0]]
|
||||
# utm_params is utm_content=course-v1%3AIDBx+IDB20.1x+1T2017&...
|
||||
utm_params = six.moves.urllib.parse.urlencode(utm_params, True)
|
||||
utm_params = urllib.parse.urlencode(utm_params, True)
|
||||
# utm_params is utm_content=course-v1:IDBx+IDB20.1x+1T2017&...
|
||||
# (course-keys do not have url encoding)
|
||||
utm_params = six.moves.urllib.parse.unquote(utm_params)
|
||||
utm_params = urllib.parse.unquote(utm_params)
|
||||
if utm_params:
|
||||
if '?' in url:
|
||||
url = url + '&' + utm_params
|
||||
@@ -478,7 +478,7 @@ class PayAndVerifyView(View):
|
||||
|
||||
"""
|
||||
url = None
|
||||
course_kwargs = {'course_id': six.text_type(course_key)}
|
||||
course_kwargs = {'course_id': str(course_key)}
|
||||
|
||||
if already_verified and already_paid:
|
||||
# If they've already paid and verified, there's nothing else to do,
|
||||
@@ -498,7 +498,7 @@ class PayAndVerifyView(View):
|
||||
if is_enrolled:
|
||||
if already_paid:
|
||||
# If the student has paid, but not verified, redirect to the verification flow.
|
||||
url = IDVerificationService.get_verify_location(six.text_type(course_key))
|
||||
url = IDVerificationService.get_verify_location(str(course_key))
|
||||
else:
|
||||
url = reverse('verify_student_start_flow', kwargs=course_kwargs)
|
||||
|
||||
@@ -582,11 +582,11 @@ class PayAndVerifyView(View):
|
||||
else:
|
||||
# The "make payment" step doubles as an intro step,
|
||||
# so if we're showing the payment step, hide the intro step.
|
||||
remove_steps |= set([self.INTRO_STEP])
|
||||
remove_steps |= {self.INTRO_STEP}
|
||||
return [
|
||||
{
|
||||
'name': step,
|
||||
'title': six.text_type(self.STEP_TITLES[step]),
|
||||
'title': str(self.STEP_TITLES[step]),
|
||||
}
|
||||
for step in display_steps
|
||||
if step not in remove_steps
|
||||
@@ -618,9 +618,9 @@ class PayAndVerifyView(View):
|
||||
if is_account_activation_requirement_disabled():
|
||||
all_requirements.pop(self.ACCOUNT_ACTIVATION_REQ)
|
||||
|
||||
display_steps = set(step['name'] for step in display_steps)
|
||||
display_steps = {step['name'] for step in display_steps}
|
||||
|
||||
for step, step_requirements in six.iteritems(self.STEP_REQUIREMENTS):
|
||||
for step, step_requirements in self.STEP_REQUIREMENTS.items():
|
||||
if step in display_steps:
|
||||
for requirement in step_requirements:
|
||||
all_requirements[requirement] = True
|
||||
@@ -700,7 +700,7 @@ class PayAndVerifyView(View):
|
||||
|
||||
"""
|
||||
if deadline_name not in [self.VERIFICATION_DEADLINE, self.UPGRADE_DEADLINE]:
|
||||
log.error(u"Invalid deadline name %s. Skipping check for whether the deadline passed.", deadline_name)
|
||||
log.error("Invalid deadline name %s. Skipping check for whether the deadline passed.", deadline_name)
|
||||
return None
|
||||
|
||||
deadline_passed = (
|
||||
@@ -718,7 +718,7 @@ class PayAndVerifyView(View):
|
||||
|
||||
def checkout_with_ecommerce_service(user, course_key, course_mode, processor):
|
||||
""" Create a new basket and trigger immediate checkout, using the E-Commerce API. """
|
||||
course_id = six.text_type(course_key)
|
||||
course_id = str(course_key)
|
||||
try:
|
||||
api = ecommerce_api_client(user)
|
||||
# Make an API call to create the order and retrieve the results
|
||||
@@ -732,7 +732,7 @@ def checkout_with_ecommerce_service(user, course_key, course_mode, processor):
|
||||
return result.get('payment_data')
|
||||
except SlumberBaseException:
|
||||
params = {'username': user.username, 'mode': course_mode.slug, 'course_id': course_id}
|
||||
log.exception(u'Failed to create order for %(username)s %(mode)s mode of %(course_id)s', params)
|
||||
log.exception('Failed to create order for %(username)s %(mode)s mode of %(course_id)s', params)
|
||||
raise
|
||||
finally:
|
||||
audit_log(
|
||||
@@ -755,7 +755,7 @@ def create_order(request):
|
||||
course_id = request.POST['course_id']
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
donation_for_course = request.session.get('donation_for_course', {})
|
||||
contribution = request.POST.get("contribution", donation_for_course.get(six.text_type(course_id), 0))
|
||||
contribution = request.POST.get("contribution", donation_for_course.get(str(course_id), 0))
|
||||
try:
|
||||
amount = decimal.Decimal(contribution).quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN)
|
||||
except decimal.InvalidOperation:
|
||||
@@ -768,7 +768,7 @@ def create_order(request):
|
||||
try:
|
||||
current_mode = CourseMode.objects.get(sku=sku)
|
||||
except CourseMode.DoesNotExist:
|
||||
log.exception(u'Failed to find CourseMode with SKU [%s].', sku)
|
||||
log.exception('Failed to find CourseMode with SKU [%s].', sku)
|
||||
|
||||
if not current_mode:
|
||||
# Check if there are more than 1 paid(mode with min_price>0 e.g verified/professional/no-id-professional) modes
|
||||
@@ -776,12 +776,12 @@ def create_order(request):
|
||||
paid_modes = CourseMode.paid_modes_for_course(course_id)
|
||||
if paid_modes:
|
||||
if len(paid_modes) > 1:
|
||||
log.warning(u"Multiple paid course modes found for course '%s' for create order request", course_id)
|
||||
log.warning("Multiple paid course modes found for course '%s' for create order request", course_id)
|
||||
current_mode = paid_modes[0]
|
||||
|
||||
# Make sure this course has a paid mode
|
||||
if not current_mode:
|
||||
log.warning(u"Create order requested for course '%s' without a paid mode.", course_id)
|
||||
log.warning("Create order requested for course '%s' without a paid mode.", course_id)
|
||||
return HttpResponseBadRequest(_("This course doesn't support paid certificates"))
|
||||
|
||||
if CourseMode.is_professional_mode(current_mode):
|
||||
@@ -814,7 +814,7 @@ class SubmitPhotosView(View):
|
||||
|
||||
@method_decorator(transaction.non_atomic_requests)
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super(SubmitPhotosView, self).dispatch(request, *args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
@method_decorator(login_required)
|
||||
@method_decorator(outer_atomic(read_committed=True))
|
||||
@@ -902,7 +902,7 @@ class SubmitPhotosView(View):
|
||||
if "photo_id_image" not in params and not has_initial_verification:
|
||||
log.error(
|
||||
(
|
||||
u"User %s does not have an initial verification attempt "
|
||||
"User %s does not have an initial verification attempt "
|
||||
"and no photo ID image data was provided. "
|
||||
"This most likely means that the JavaScript client is not "
|
||||
"correctly constructing the request to submit photos."
|
||||
@@ -915,7 +915,7 @@ class SubmitPhotosView(View):
|
||||
# The face image is always required.
|
||||
if "face_image" not in params:
|
||||
msg = _("Missing required parameter face_image")
|
||||
log.error((u"User {user_id} missing required parameter face_image").format(user_id=request.user.id))
|
||||
log.error(("User {user_id} missing required parameter face_image").format(user_id=request.user.id))
|
||||
return None, HttpResponseBadRequest(msg)
|
||||
|
||||
return params, None
|
||||
@@ -935,14 +935,14 @@ class SubmitPhotosView(View):
|
||||
try:
|
||||
update_account_settings(request.user, {"name": full_name})
|
||||
except UserNotFound:
|
||||
log.error((u"No profile found for user {user_id}").format(user_id=request.user.id))
|
||||
log.error(("No profile found for user {user_id}").format(user_id=request.user.id))
|
||||
return HttpResponseBadRequest(_("No profile found for user"))
|
||||
except AccountValidationError:
|
||||
msg = _(
|
||||
u"Name must be at least {min_length} character long."
|
||||
"Name must be at least {min_length} character long."
|
||||
).format(min_length=NAME_MIN_LENGTH)
|
||||
log.error(
|
||||
(u"User {user_id} provided an account name less than {min_length} characters").format(
|
||||
("User {user_id} provided an account name less than {min_length} characters").format(
|
||||
user_id=request.user.id,
|
||||
min_length=NAME_MIN_LENGTH
|
||||
)
|
||||
@@ -977,7 +977,7 @@ class SubmitPhotosView(View):
|
||||
|
||||
except InvalidImageData:
|
||||
msg = _("Image data is not valid.")
|
||||
log.error((u"Image data for user {user_id} is not valid").format(user_id=request.user.id))
|
||||
log.error(("Image data for user {user_id} is not valid").format(user_id=request.user.id))
|
||||
return None, None, HttpResponseBadRequest(msg)
|
||||
|
||||
def _submit_attempt(self, user, face_image, photo_id_image=None, initial_verification=None):
|
||||
@@ -1053,12 +1053,12 @@ def results_callback(request): # lint-amnesty, pylint: disable=too-many-stateme
|
||||
try:
|
||||
body_dict = json.loads(body.decode('utf-8'))
|
||||
except ValueError:
|
||||
log.exception(u"Invalid JSON received from Software Secure:\n\n{}\n".format(body))
|
||||
return HttpResponseBadRequest(u"Invalid JSON. Received:\n\n{}".format(body))
|
||||
log.exception(f"Invalid JSON received from Software Secure:\n\n{body}\n")
|
||||
return HttpResponseBadRequest(f"Invalid JSON. Received:\n\n{body}")
|
||||
|
||||
if not isinstance(body_dict, dict):
|
||||
log.error(u"Reply from Software Secure is not a dict:\n\n{}\n".format(body))
|
||||
return HttpResponseBadRequest(u"JSON should be dict. Received:\n\n{}".format(body))
|
||||
log.error(f"Reply from Software Secure is not a dict:\n\n{body}\n")
|
||||
return HttpResponseBadRequest(f"JSON should be dict. Received:\n\n{body}")
|
||||
|
||||
headers = {
|
||||
"Authorization": request.META.get("HTTP_AUTHORIZATION", ""),
|
||||
@@ -1092,8 +1092,8 @@ def results_callback(request): # lint-amnesty, pylint: disable=too-many-stateme
|
||||
try:
|
||||
attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=receipt_id)
|
||||
except SoftwareSecurePhotoVerification.DoesNotExist:
|
||||
log.error(u"Software Secure posted back for receipt_id %s, but not found", receipt_id)
|
||||
return HttpResponseBadRequest(u"edX ID {} not found".format(receipt_id))
|
||||
log.error("Software Secure posted back for receipt_id %s, but not found", receipt_id)
|
||||
return HttpResponseBadRequest(f"edX ID {receipt_id} not found")
|
||||
|
||||
user = attempt.user
|
||||
verification_status_email_vars = {
|
||||
@@ -1106,7 +1106,7 @@ def results_callback(request): # lint-amnesty, pylint: disable=too-many-stateme
|
||||
if attempt.status != 'approved':
|
||||
verification = SoftwareSecurePhotoVerification.objects.filter(status='approved', user_id=attempt.user_id)
|
||||
if verification:
|
||||
log.info(u'Making expiry email date of previous approved verification NULL for {}'.format(attempt.user_id)) # lint-amnesty, pylint: disable=line-too-long
|
||||
log.info(f'Making expiry email date of previous approved verification NULL for {attempt.user_id}') # lint-amnesty, pylint: disable=line-too-long
|
||||
# The updated_at field in sspv model has auto_now set to True, which means any time save() is called on
|
||||
# the model instance, `updated_at` will change. Some of the existing functionality of verification
|
||||
# (showing your verification has expired on dashboard) relies on updated_at.
|
||||
@@ -1115,7 +1115,7 @@ def results_callback(request): # lint-amnesty, pylint: disable=too-many-stateme
|
||||
previous_verification = verification.latest('updated_at')
|
||||
SoftwareSecurePhotoVerification.objects.filter(pk=previous_verification.pk
|
||||
).update(expiry_email_date=None)
|
||||
log.debug(u'Approving verification for {}'.format(receipt_id))
|
||||
log.debug(f'Approving verification for {receipt_id}')
|
||||
attempt.approve()
|
||||
|
||||
expiration_datetime = attempt.expiration_datetime.date()
|
||||
@@ -1123,13 +1123,13 @@ def results_callback(request): # lint-amnesty, pylint: disable=too-many-stateme
|
||||
send_verification_approved_email(context=email_context)
|
||||
|
||||
elif result == "FAIL":
|
||||
log.debug(u"Denying verification for %s", receipt_id)
|
||||
log.debug("Denying verification for %s", receipt_id)
|
||||
attempt.deny(json.dumps(reason), error_code=error_code)
|
||||
reverify_url = '{}/id-verification'.format(settings.ACCOUNT_MICROFRONTEND_URL)
|
||||
reverify_url = f'{settings.ACCOUNT_MICROFRONTEND_URL}/id-verification'
|
||||
verification_status_email_vars['reasons'] = reason
|
||||
verification_status_email_vars['reverify_url'] = reverify_url
|
||||
verification_status_email_vars['faq_url'] = settings.ID_VERIFICATION_SUPPORT_LINK
|
||||
subject = _(u"Your {platform_name} Verification Has Been Denied").format(
|
||||
subject = _("Your {platform_name} Verification Has Been Denied").format(
|
||||
platform_name=settings.PLATFORM_NAME
|
||||
)
|
||||
context = {
|
||||
@@ -1141,13 +1141,13 @@ def results_callback(request): # lint-amnesty, pylint: disable=too-many-stateme
|
||||
send_verification_status_email.delay(context)
|
||||
|
||||
elif result == "SYSTEM FAIL":
|
||||
log.debug(u"System failure for %s -- resetting to must_retry", receipt_id)
|
||||
log.debug("System failure for %s -- resetting to must_retry", receipt_id)
|
||||
attempt.system_error(json.dumps(reason), error_code=error_code)
|
||||
log.error(u"Software Secure callback attempt for %s failed: %s", receipt_id, reason)
|
||||
log.error("Software Secure callback attempt for %s failed: %s", receipt_id, reason)
|
||||
else:
|
||||
log.error(u"Software Secure returned unknown result %s", result)
|
||||
log.error("Software Secure returned unknown result %s", result)
|
||||
return HttpResponseBadRequest(
|
||||
u"Result {} not understood. Known results: PASS, FAIL, SYSTEM FAIL".format(result)
|
||||
f"Result {result} not understood. Known results: PASS, FAIL, SYSTEM FAIL"
|
||||
)
|
||||
|
||||
return HttpResponse("OK!")
|
||||
@@ -1244,7 +1244,7 @@ class PhotoUrlsView(APIView):
|
||||
body.pop('SendResponseTo')
|
||||
return Response(body)
|
||||
|
||||
log.warning(u"Could not find verification with receipt ID %s.", receipt_id)
|
||||
log.warning("Could not find verification with receipt ID %s.", receipt_id)
|
||||
raise Http404
|
||||
|
||||
|
||||
@@ -1267,7 +1267,7 @@ class DecryptFaceImageView(APIView):
|
||||
"""
|
||||
# if this endpoint is not being accessed on stage, raise a 403. Only stage will have an RSA_PRIVATE_KEY
|
||||
if not settings.VERIFY_STUDENT["SOFTWARE_SECURE"].get("RSA_PRIVATE_KEY", None):
|
||||
log.warning(u"Cannot access image decryption outside of staging environment")
|
||||
log.warning("Cannot access image decryption outside of staging environment")
|
||||
return HttpResponseForbidden()
|
||||
|
||||
verification = SoftwareSecurePhotoVerification.get_verification_from_receipt(receipt_id)
|
||||
@@ -1276,7 +1276,7 @@ class DecryptFaceImageView(APIView):
|
||||
if user_photo:
|
||||
return HttpResponse(user_photo, content_type="image/png")
|
||||
|
||||
log.warning(u"Could not decrypt face image for receipt ID %s.", receipt_id)
|
||||
log.warning("Could not decrypt face image for receipt ID %s.", receipt_id)
|
||||
raise Http404
|
||||
|
||||
|
||||
@@ -1299,7 +1299,7 @@ class DecryptPhotoIDImageView(APIView):
|
||||
"""
|
||||
# if this endpoint is not being accessed on stage, raise a 403. Only stage will have an RSA_PRIVATE_KEY
|
||||
if not settings.VERIFY_STUDENT["SOFTWARE_SECURE"].get("RSA_PRIVATE_KEY", None):
|
||||
log.warning(u"Cannot access image decryption outside of staging environment")
|
||||
log.warning("Cannot access image decryption outside of staging environment")
|
||||
return HttpResponseForbidden()
|
||||
|
||||
verification = SoftwareSecurePhotoVerification.get_verification_from_receipt(receipt_id)
|
||||
@@ -1308,5 +1308,5 @@ class DecryptPhotoIDImageView(APIView):
|
||||
if id_photo:
|
||||
return HttpResponse(id_photo, content_type="image/png")
|
||||
|
||||
log.warning(u"Could not decrypt photo ID image for receipt ID %s.", receipt_id)
|
||||
log.warning("Could not decrypt photo ID image for receipt ID %s.", receipt_id)
|
||||
raise Http404
|
||||
|
||||
Reference in New Issue
Block a user