diff --git a/lms/djangoapps/verify_student/management/commands/populate_expiry_date.py b/lms/djangoapps/verify_student/management/commands/populate_expiry_date.py deleted file mode 100644 index 9d2da9f09b..0000000000 --- a/lms/djangoapps/verify_student/management/commands/populate_expiry_date.py +++ /dev/null @@ -1,103 +0,0 @@ -""" -(DEPRECATED) Django admin command to populate expiry_date for -approved verifications in SoftwareSecurePhotoVerification -""" - - -import logging -import time -from datetime import timedelta - -from django.conf import settings -from django.core.management.base import BaseCommand -from django.db.models import F - -from common.djangoapps.util.query import use_read_replica_if_available -from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification - -logger = logging.getLogger(__name__) - - -class Command(BaseCommand): - """ - This command sets the expiry_date for users for which the verification is approved - The task is performed in batches with maximum number of rows to process given in argument `batch_size` - and a sleep time between each batch given by `sleep_time` - Default values: - `batch_size` = 1000 rows - `sleep_time` = 10 seconds - Example usage: - $ ./manage.py lms populate_expiry_date --batch_size=1000 --sleep_time=5 - OR - $ ./manage.py lms populate_expiry_date - """ - help = 'Populate expiry_date for approved verifications' - - def add_arguments(self, parser): - parser.add_argument( - '--batch_size', - action='store', - dest='batch_size', - type=int, - default=1000, - help='Maximum number of database rows to process. ' - 'This helps avoid locking the database while updating large amount of data.') - parser.add_argument( - '--sleep_time', - action='store', - dest='sleep_time', - type=int, - default=10, - help='Sleep time in seconds between update of batches') - - def handle(self, *args, **options): - """ - Handler for the command - It filters approved Software Secure Photo Verification and then for each distinct user it finds the most - recent approved verification and set its expiry_date - """ - batch_size = options['batch_size'] - sleep_time = options['sleep_time'] - - query = SoftwareSecurePhotoVerification.objects.filter(status='approved').order_by() - sspv = use_read_replica_if_available(query) - - if not sspv.count(): - logger.info("No approved entries found in SoftwareSecurePhotoVerification") - return - - distinct_user_ids = set() - update_verification_ids = [] - update_verification_count = 0 - - for verification in sspv: - if verification.user_id not in distinct_user_ids: - distinct_user_ids.add(verification.user_id) - - recent_verification = self.find_recent_verification(sspv, verification.user_id) - if not recent_verification.expiry_date: - update_verification_ids.append(recent_verification.pk) - update_verification_count += 1 - - if update_verification_count == batch_size: - self.bulk_update(update_verification_ids) - update_verification_count = 0 - update_verification_ids = [] - time.sleep(sleep_time) - - if update_verification_ids: - self.bulk_update(update_verification_ids) - - def bulk_update(self, verification_ids): - """ - It updates the expiry_date for all the verification whose ids lie in verification_ids - """ - recent_verification_qs = SoftwareSecurePhotoVerification.objects.filter(pk__in=verification_ids) - recent_verification_qs.update(expiry_date=F('updated_at') + timedelta( - days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])) - - def find_recent_verification(self, model, user_id): - """ - Returns the most recent approved verification for a user - """ - return model.filter(user_id=user_id).latest('updated_at') diff --git a/lms/djangoapps/verify_student/management/commands/send_verification_expiry_email.py b/lms/djangoapps/verify_student/management/commands/send_verification_expiry_email.py index 09f784b3ff..04f75ef424 100644 --- a/lms/djangoapps/verify_student/management/commands/send_verification_expiry_email.py +++ b/lms/djangoapps/verify_student/management/commands/send_verification_expiry_email.py @@ -104,12 +104,6 @@ class Command(BaseCommand): Q(expiration_date__isnull=False) & ( Q(expiration_date__gte=start_date, expiration_date__lt=end_date) | Q(expiry_email_date__lte=date_resend_days_ago) - ) | - # Account for old entries still using `expiry_date` rather than`expiration_date` - # (this will be deprecated) - Q(expiry_date__isnull=False) & ( - Q(expiry_date__gte=start_date, expiry_date__lt=end_date) | - Q(expiry_email_date__lte=date_resend_days_ago) ) ) ).order_by() diff --git a/lms/djangoapps/verify_student/management/commands/tests/test_populate_expiry_date.py b/lms/djangoapps/verify_student/management/commands/tests/test_populate_expiry_date.py deleted file mode 100644 index 30751224e6..0000000000 --- a/lms/djangoapps/verify_student/management/commands/tests/test_populate_expiry_date.py +++ /dev/null @@ -1,115 +0,0 @@ -""" -Tests for django admin command `populate_expiry_date` in the verify_student module -""" - - -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 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 - -LOGGER_NAME = 'lms.djangoapps.verify_student.management.commands.populate_expiry_date' - - -@patch.dict(settings.VERIFY_STUDENT, FAKE_SETTINGS) -@patch('lms.djangoapps.verify_student.models.requests.post', new=mock_software_secure_post) -class TestPopulateExpiryDate(MockS3BotoMixin, TestCase): - """ Tests for django admin command `populate_expiry_date` in the verify_student module """ - - def create_and_submit(self, user): - """ Helper method that lets us create new SoftwareSecurePhotoVerifications """ - attempt = SoftwareSecurePhotoVerification(user=user) - attempt.upload_face_image("Fake Data") - attempt.upload_photo_id_image("More Fake Data") - attempt.mark_ready() - attempt.submit() - return attempt - - def test_expiry_date_already_present(self): - """ - Test that the expiry_date for most recent approved verification is updated only when the - expiry_date is not already present - """ - user = UserFactory.create() - verification = self.create_and_submit(user) - verification.status = 'approved' - verification.expiry_date = verification.updated_at + timedelta(days=10) - verification.save() - - expiry_date = verification.expiry_date - call_command('populate_expiry_date') - - # Check that the expiry_date for approved verification is not changed when it is already present - verification_expiry_date = SoftwareSecurePhotoVerification.objects.get(pk=verification.pk).expiry_date - - assert verification_expiry_date == expiry_date - - def test_recent_approved_verification(self): - """ - Test that the expiry_date for most recent approved verification is updated - A user can have multiple approved Software Secure Photo Verification over the year - Only the most recent is considered for course verification - """ - user = UserFactory.create() - outdated_verification = self.create_and_submit(user) - outdated_verification.status = 'approved' - outdated_verification.save() - - recent_verification = self.create_and_submit(user) - recent_verification.status = 'approved' - recent_verification.save() - - call_command('populate_expiry_date') - - # Check that expiry_date for only one verification is set - assert len(SoftwareSecurePhotoVerification.objects.filter(expiry_date__isnull=False)) == 1 - - # Check that the expiry_date date set for verification is not for the outdated approved verification - expiry_date = SoftwareSecurePhotoVerification.objects.get(pk=outdated_verification.pk).expiry_date - assert expiry_date is None - - def test_approved_verification_expiry_date(self): - """ - Tests that the command correctly updates expiry_date - Criteria : - Verification for which status is approved and expiry_date is null - """ - # Create verification with status : submitted - user = UserFactory.create() - self.create_and_submit(user) - - # Create verification with status : approved - approved_verification = self.create_and_submit(user) - approved_verification.status = 'approved' - approved_verification.save() - - expected_date = approved_verification.updated_at + timedelta( - days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]) - - call_command('populate_expiry_date') - - # Check to make sure we have one verification with expiry_date set and one with null - assert len(SoftwareSecurePhotoVerification.objects.filter(expiry_date__isnull=True)) == 1 - assert len(SoftwareSecurePhotoVerification.objects.filter(expiry_date__isnull=False)) == 1 - - # Confirm that expiry_date set for approved verification is correct - approved_verification = SoftwareSecurePhotoVerification.objects.get(pk=approved_verification.pk) - assert approved_verification.expiry_date == expected_date - - def test_no_approved_verification_found(self): - """ - Test that if no approved verifications are found the management command terminates gracefully - """ - with LogCapture(LOGGER_NAME) as logger: - call_command('populate_expiry_date') - logger.check( - (LOGGER_NAME, 'INFO', "No approved entries found in SoftwareSecurePhotoVerification") - ) diff --git a/lms/djangoapps/verify_student/management/commands/tests/test_update_expiration_date.py b/lms/djangoapps/verify_student/management/commands/tests/test_update_expiration_date.py deleted file mode 100644 index 93ae9dfb5b..0000000000 --- a/lms/djangoapps/verify_student/management/commands/tests/test_update_expiration_date.py +++ /dev/null @@ -1,102 +0,0 @@ -""" -Tests for django admin command `update_expiration_date` in the verify_student module -""" - - -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 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 - -LOGGER_NAME = 'lms.djangoapps.verify_student.management.commands.update_expiration_date' - - -@patch.dict(settings.VERIFY_STUDENT, FAKE_SETTINGS) -@patch('lms.djangoapps.verify_student.models.requests.post', new=mock_software_secure_post) -class TestPopulateExpiryationDate(MockS3BotoMixin, TestCase): - """ Tests for django admin command `update_expiration_date` in the verify_student module """ - - def create_and_submit(self, user): - """ Helper method that lets us create new SoftwareSecurePhotoVerifications """ - attempt = SoftwareSecurePhotoVerification(user=user) - attempt.upload_face_image("Fake Data") - attempt.upload_photo_id_image("More Fake Data") - attempt.mark_ready() - attempt.submit() - attempt.expiry_date = now() + timedelta(days=FAKE_SETTINGS["DAYS_GOOD_FOR"]) - return attempt - - def test_expiration_date_not_changed(self): - """ - Test that the `expiration_date` is updated only when its value is over 365 days higher - than `updated_at` - """ - user = UserFactory.create() - verification = self.create_and_submit(user) - expected_date = verification.expiration_date - call_command('update_expiration_date') - # Check that the `expiration_date` is not changed - expiration_date = SoftwareSecurePhotoVerification.objects.get(pk=verification.pk).expiration_date - assert expiration_date == expected_date - - def test_expiration_date_updated(self): - """ - Test that `expiration_date` is properly updated when its value is over 365 days higher - than `updated_at` - """ - user = UserFactory.create() - verification = self.create_and_submit(user) - verification.status = 'approved' - verification.expiration_date = now() + timedelta(days=FAKE_SETTINGS["DAYS_GOOD_FOR"] + 1) - verification.expiry_date = now() + timedelta(days=FAKE_SETTINGS["DAYS_GOOD_FOR"]) - verification.save() - expected_date = verification.created_at + timedelta( - days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]) - call_command('update_expiration_date') - updated_verification = SoftwareSecurePhotoVerification.objects.get(pk=verification.pk) - # Check that the `expiration_date` is updated - assert updated_verification.expiration_date == expected_date - # Check that the `expiry_date` is set to NULL - assert updated_verification.expiry_date is None - - def test_expiration_date_updated_multiple_records(self): - """ - Test that `expiration_date` is properly updated with multiple records fits the selection - criteria - """ - users = UserFactory.create_batch(10) - for user in users: - verification = self.create_and_submit(user) - verification.status = 'approved' - verification.expiry_date = now() + timedelta(days=3) - verification.expiration_date = '2021-11-12T16:33:15.691Z' - verification.save() - - call_command('update_expiration_date', batch_size=3, sleep_time=1) - updated_verifications = SoftwareSecurePhotoVerification.objects.filter(user__in=users) - for updated_verification in updated_verifications: - expected_date = updated_verification.created_at + timedelta( - days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]) - # Check that the `expiration_date` is updated - assert updated_verification.expiration_date == expected_date - # Check that the `expiry_date` is set to NULL - assert updated_verification.expiry_date is None - - def test_no_approved_verification_found(self): - """ - Test that if no approved verifications are found the management command terminates gracefully - """ - with LogCapture(LOGGER_NAME) as logger: - call_command('update_expiration_date') - logger.check( - (LOGGER_NAME, 'INFO', "No approved entries found in SoftwareSecurePhotoVerification") - ) diff --git a/lms/djangoapps/verify_student/management/commands/update_expiration_date.py b/lms/djangoapps/verify_student/management/commands/update_expiration_date.py deleted file mode 100644 index 24506a6b9e..0000000000 --- a/lms/djangoapps/verify_student/management/commands/update_expiration_date.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -Django admin command to update expiration_date for approved verifications in SoftwareSecurePhotoVerification -""" - - -import logging -import time -from datetime import timedelta # lint-amnesty, pylint: disable=unused-import - -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 common.djangoapps.util.query import use_read_replica_if_available -from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification - -logger = logging.getLogger(__name__) - - -class Command(BaseCommand): - """ - This command updates the `expiration_date` for old entries still dependent on `expiry_date` - The task is performed in batches with maximum number of rows to process given in argument `batch_size` - and a sleep time between each batch given by `sleep_time` - Default values: - `batch_size` = 1000 rows - `sleep_time` = 10 seconds - Example usage: - $ ./manage.py lms update_expiration_date --batch_size=1000 --sleep_time=5 - OR - $ ./manage.py lms update_expiration_date - """ - help = 'Update expiration_date for approved verifications' - - def add_arguments(self, parser): - parser.add_argument( - '--batch_size', - action='store', - dest='batch_size', - type=int, - default=1000, - help='Maximum number of database rows to process. ' - 'This helps avoid locking the database while updating large amount of data.') - parser.add_argument( - '--sleep_time', - action='store', - dest='sleep_time', - type=int, - default=10, - help='Sleep time in seconds between update of batches') - - def handle(self, *args, **options): - """ - Handler for the command - It filters approved Software Secure Photo Verifications and then sets the correct expiration_date - """ - batch_size = options['batch_size'] - sleep_time = options['sleep_time'] - - query = SoftwareSecurePhotoVerification.objects.filter(status='approved').order_by() - sspv = use_read_replica_if_available(query) - - if not sspv.count(): - logger.info("No approved entries found in SoftwareSecurePhotoVerification") - return - - update_verification_ids = [] - update_verification_count = 0 - - for verification in sspv: - # The expiration date should not be higher than 365 days - # past the `updated_at` field, so only update those entries - if verification.expiration_date > ( - verification.updated_at + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]) - ): - update_verification_ids.append(verification.pk) - update_verification_count += 1 - - if update_verification_count == batch_size: - self.bulk_update(update_verification_ids) - update_verification_count = 0 - update_verification_ids = [] - time.sleep(sleep_time) - - if update_verification_ids: - self.bulk_update(update_verification_ids) - - def bulk_update(self, verification_ids): - """ - It updates the expiration_date and sets the expiry_date to NULL for all the - verifications whose ids lie in verification_ids - """ - verification_qs = SoftwareSecurePhotoVerification.objects.filter(pk__in=verification_ids) - verification_qs.update(expiration_date=F('created_at') + timedelta( - days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])) - verification_qs.update(expiry_date=None) diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index c094d873b5..c66af8c0a9 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -647,13 +647,6 @@ class SoftwareSecurePhotoVerification(PhotoVerification): # to notify for expired verification is already sent. expiry_email_date = models.DateTimeField(null=True, blank=True, db_index=True) - @property - def expiration_datetime(self): - """Use expiry_date for older entries if it still exists.""" - if self.expiry_date: - return self.expiry_date - return super().expiration_datetime - @classmethod def get_initial_verification(cls, user, earliest_allowed_date=None): """Get initial verification for a user with the 'photo_id_key'. diff --git a/lms/djangoapps/verify_student/tests/test_models.py b/lms/djangoapps/verify_student/tests/test_models.py index 340a9680ac..268b48bbc1 100644 --- a/lms/djangoapps/verify_student/tests/test_models.py +++ b/lms/djangoapps/verify_student/tests/test_models.py @@ -390,19 +390,6 @@ class TestPhotoVerification(TestVerificationBase, MockS3BotoMixin, ModuleStoreTe assert verification.expiration_datetime == (verification.created_at + timedelta(days=FAKE_SETTINGS['DAYS_GOOD_FOR'])) - def test_deprecated_expiry_date(self): - """ - Test `expiration_datetime` returns `expiry_date` if it is not null. - """ - user = UserFactory.create() - with freeze_time(now()): - verification = SoftwareSecurePhotoVerification(user=user) - # First, assert that expiration_date is set correctly - assert verification.expiration_datetime == (now() + timedelta(days=FAKE_SETTINGS['DAYS_GOOD_FOR'])) - verification.expiry_date = now() + timedelta(days=10) - # Then, assert that expiration_datetime favors expiry_date's value if set - assert verification.expiration_datetime == (now() + timedelta(days=10)) - def test_get_verification_from_receipt(self): result = SoftwareSecurePhotoVerification.get_verification_from_receipt('') assert result is None