diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a05b78e8837a..fc452f7acde7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,6 +15,7 @@ lms/djangoapps/grades/ lms/djangoapps/instructor/ lms/djangoapps/instructor_task/ lms/djangoapps/mobile_api/ +openedx/core/djangoapps/commerce/ @openedx/2u-infinity openedx/core/djangoapps/credentials @openedx/2U-aperture openedx/core/djangoapps/credit @openedx/2U-aperture openedx/core/djangoapps/enrollments/ @openedx/2U-aperture @@ -22,6 +23,7 @@ openedx/core/djangoapps/heartbeat/ openedx/core/djangoapps/oauth_dispatch openedx/core/djangoapps/user_api/ @openedx/2U-aperture openedx/core/djangoapps/user_authn/ @openedx/2U-vanguards +openedx/core/djangoapps/verified_track_content/ @openedx/2u-infinity openedx/features/course_experience/ xmodule/ @@ -36,16 +38,18 @@ common/djangoapps/track/ lms/djangoapps/certificates/ @openedx/2U-aperture # Discovery -common/djangoapps/course_modes/ +common/djangoapps/course_modes/ @openedx/2U-aperture common/djangoapps/enrollment/ -lms/djangoapps/branding/ @openedx/2U-aperture -lms/djangoapps/commerce/ -lms/djangoapps/experiments/ @openedx/2U-aperture -lms/djangoapps/learner_dashboard/ @openedx/2U-aperture -lms/djangoapps/learner_home/ @openedx/2U-aperture -openedx/features/content_type_gating/ +common/djangoapps/entitlements/ @openedx/2U-aperture +lms/djangoapps/branding/ @openedx/2U-aperture +lms/djangoapps/commerce/ @openedx/2u-infinity +lms/djangoapps/experiments/ @openedx/2u-infinity +lms/djangoapps/gating/ @openedx/2u-infinity +lms/djangoapps/learner_dashboard/ @openedx/2U-aperture +lms/djangoapps/learner_home/ @openedx/2U-aperture +openedx/features/content_type_gating/ @openedx/2u-infinity openedx/features/course_duration_limits/ -openedx/features/discounts/ +openedx/features/discounts/ @openedx/2u-infinity # Ping Axim On-call if someone uses the QuickStart # https://docs.openedx.org/en/latest/developers/quickstarts/first_openedx_pr.html diff --git a/lms/djangoapps/verify_student/management/commands/approve_id_verifications.py b/lms/djangoapps/verify_student/management/commands/approve_id_verifications.py index 3a08ede0aaf6..4c45f415cf63 100644 --- a/lms/djangoapps/verify_student/management/commands/approve_id_verifications.py +++ b/lms/djangoapps/verify_student/management/commands/approve_id_verifications.py @@ -10,9 +10,11 @@ from django.core.management.base import BaseCommand, CommandError +from common.djangoapps.student.models_api import get_name, get_pending_name_change from lms.djangoapps.verify_student.api import send_approval_email from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification from lms.djangoapps.verify_student.utils import earliest_allowed_verification_date +from openedx.features.name_affirmation_api.utils import get_name_affirmation_service log = logging.getLogger(__name__) @@ -149,5 +151,37 @@ def _approve_id_verifications(self, user_ids): for verification in existing_id_verifications: verification.approve(service='idv_verifications command') send_approval_email(verification) + self._approve_verified_name_for_software_secure_verification(verification) return list(failed_user_ids) + + def _approve_verified_name_for_software_secure_verification(self, verification): + """ + This method manually creates a verified name given a SoftwareSecurePhotoVerification object. + """ + + name_affirmation_service = get_name_affirmation_service() + + if name_affirmation_service: + from edx_name_affirmation.exceptions import VerifiedNameDoesNotExist # pylint: disable=import-error + + pending_name_change = get_pending_name_change(verification.user) + if pending_name_change: + full_name = pending_name_change.new_name + else: + full_name = get_name(verification.user.id) + + try: + name_affirmation_service.update_verified_name_status( + verification.user, + 'approved', + verification_attempt_id=verification.id + ) + except VerifiedNameDoesNotExist: + name_affirmation_service.create_verified_name( + verification.user, + verification.name, + full_name, + verification_attempt_id=verification.id, + status='approved', + ) diff --git a/lms/djangoapps/verify_student/management/commands/tests/test_approve_id_verifications.py b/lms/djangoapps/verify_student/management/commands/tests/test_approve_id_verifications.py index e6e580c1d1a6..6eccee194795 100644 --- a/lms/djangoapps/verify_student/management/commands/tests/test_approve_id_verifications.py +++ b/lms/djangoapps/verify_student/management/commands/tests/test_approve_id_verifications.py @@ -6,6 +6,8 @@ import logging import os import tempfile +from unittest import skipUnless +from unittest.mock import MagicMock, patch import pytest from django.core import mail @@ -15,9 +17,12 @@ from common.djangoapps.student.tests.factories import UserFactory, UserProfileFactory from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification +from openedx.features.name_affirmation_api.utils import get_name_affirmation_service LOGGER_NAME = 'lms.djangoapps.verify_student.management.commands.approve_id_verifications' +name_affirmation_service = get_name_affirmation_service() + @ddt.ddt class TestApproveIDVerificationsCommand(TestCase): @@ -158,3 +163,57 @@ def test_invalid_file_path(self): """ with pytest.raises(CommandError): call_command('approve_id_verifications', 'invalid/user_id/file/path') + + @skipUnless(name_affirmation_service is not None, 'Requires Name Affirmation') + @patch('lms.djangoapps.verify_student.management.commands.approve_id_verifications.get_name_affirmation_service') + def test_create_verified_names(self, mock_get_service): + mock_service = MagicMock() + mock_get_service.return_value = mock_service + + verification = SoftwareSecurePhotoVerification.objects.create( + user=self.user1_profile.user, + name=self.user1_profile.name, + status='submitted', + ) + + call_command('approve_id_verifications', self.tmp_file_path) + mock_service.update_verified_name_status.assert_called_with( + self.user1_profile.user, + 'approved', + verification_attempt_id=verification.id, + ) + + @skipUnless(name_affirmation_service is not None, 'Requires Name Affirmation') + @patch('lms.djangoapps.verify_student.management.commands.approve_id_verifications.get_name') + @patch('lms.djangoapps.verify_student.management.commands.approve_id_verifications.get_pending_name_change') + @patch('lms.djangoapps.verify_student.management.commands.approve_id_verifications.get_name_affirmation_service') + @ddt.data( + '', + MagicMock(new_name='test') + ) + def test_create_update_verified_names(self, pending_name, mock_get_service, mock_get_pending, mock_get_name): + from edx_name_affirmation.exceptions import VerifiedNameDoesNotExist # pylint: disable=import-error + + mock_service = MagicMock() + mock_get_service.return_value = mock_service + mock_service.update_verified_name_status.side_effect = VerifiedNameDoesNotExist() + + mock_get_pending.return_value = pending_name + mock_get_name.return_value = self.user1_profile.name + + verification = SoftwareSecurePhotoVerification.objects.create( + user=self.user1_profile.user, + name=self.user1_profile.name, + status='submitted', + ) + + expected_name = 'test' if pending_name else self.user1_profile.name + + call_command('approve_id_verifications', self.tmp_file_path) + mock_service.create_verified_name.assert_called_with( + verification.user, + verification.name, + expected_name, + verification_attempt_id=verification.id, + status='approved', + ) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 996391b2bebb..dd727a4b1801 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -82,7 +82,7 @@ django-storages<1.14.4 # The team that owns this package will manually bump this package rather than having it pulled in automatically. # This is to allow them to better control its deployment and to do it in a process that works better # for them. -edx-enterprise==4.27.0 +edx-enterprise==4.27.2 # Date: 2024-05-09 # This has to be constrained as well because newer versions of edx-i18n-tools need the diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 6393509c8f87..c1ea982854b9 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -469,7 +469,7 @@ edx-drf-extensions==10.4.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.27.0 +edx-enterprise==4.27.2 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 8b5fe8b7ef6a..10a9fbd3db6a 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -743,7 +743,7 @@ edx-drf-extensions==10.4.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.27.0 +edx-enterprise==4.27.2 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 03def852117b..dd4e38a4f0d7 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -549,7 +549,7 @@ edx-drf-extensions==10.4.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.27.0 +edx-enterprise==4.27.2 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 0d6bcaf77dea..1a0c9c9d72ed 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -573,7 +573,7 @@ edx-drf-extensions==10.4.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.27.0 +edx-enterprise==4.27.2 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt