Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add idv events to api #35468

Merged
merged 13 commits into from
Sep 17, 2024
42 changes: 41 additions & 1 deletion lms/djangoapps/verify_student/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
from lms.djangoapps.verify_student.emails import send_verification_approved_email
from lms.djangoapps.verify_student.exceptions import VerificationAttemptInvalidStatus
from lms.djangoapps.verify_student.models import VerificationAttempt
from lms.djangoapps.verify_student.signals.signals import (
emit_idv_attempt_approved_event,
emit_idv_attempt_created_event,
emit_idv_attempt_denied_event,
emit_idv_attempt_pending_event,
)
from lms.djangoapps.verify_student.statuses import VerificationAttemptStatus
from lms.djangoapps.verify_student.tasks import send_verification_status_email

Expand Down Expand Up @@ -70,14 +76,22 @@ def create_verification_attempt(user: User, name: str, status: str, expiration_d
expiration_datetime=expiration_datetime,
)

emit_idv_attempt_created_event(
attempt_id=verification_attempt.id,
user=user,
status=status,
name=name,
expiration_date=expiration_datetime,
)

return verification_attempt.id


def update_verification_attempt(
attempt_id: int,
name: Optional[str] = None,
status: Optional[str] = None,
expiration_datetime: Optional[datetime] = None
expiration_datetime: Optional[datetime] = None,
):
"""
Update a verification attempt.
Expand Down Expand Up @@ -125,3 +139,29 @@ def update_verification_attempt(
attempt.expiration_datetime = expiration_datetime

attempt.save()

user = attempt.user
if status == VerificationAttemptStatus.PENDING:
emit_idv_attempt_pending_event(
attempt_id=attempt_id,
user=user,
status=status,
name=name,
expiration_date=expiration_datetime,
)
elif status == VerificationAttemptStatus.APPROVED:
emit_idv_attempt_approved_event(
attempt_id=attempt_id,
user=user,
status=status,
name=name,
expiration_date=expiration_datetime,
)
elif status == VerificationAttemptStatus.DENIED:
emit_idv_attempt_denied_event(
attempt_id=attempt_id,
user=user,
status=status,
name=name,
expiration_date=expiration_datetime,
)
2 changes: 1 addition & 1 deletion lms/djangoapps/verify_student/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ def ready(self):
"""
Connect signal handlers.
"""
from lms.djangoapps.verify_student import signals # pylint: disable=unused-import
from lms.djangoapps.verify_student.signals import signals # pylint: disable=unused-import
from lms.djangoapps.verify_student import tasks # pylint: disable=unused-import
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def _create_attempts(self, num_attempts):
for _ in range(num_attempts):
self.create_upload_and_submit_attempt_for_user()

@patch('lms.djangoapps.verify_student.signals.idv_update_signal.send')
@patch('lms.djangoapps.verify_student.signals.signals.idv_update_signal.send')
def test_resubmit_in_date_range(self, send_idv_update_mock):
call_command('retry_failed_photo_verifications',
status="submitted",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def _create_attempts(self, num_attempts):
for _ in range(num_attempts):
self.create_and_submit_attempt_for_user()

@patch('lms.djangoapps.verify_student.signals.idv_update_signal.send')
@patch('lms.djangoapps.verify_student.signals.signals.idv_update_signal.send')
def test_command(self, send_idv_update_mock):
call_command('trigger_softwaresecurephotoverifications_post_save_signal', start_date_time='2021-10-31 06:00:00')

Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@

from django.core.exceptions import ObjectDoesNotExist
from django.db.models.signals import post_save
from django.dispatch import Signal
from django.dispatch.dispatcher import receiver
from xmodule.modulestore.django import SignalHandler, modulestore

from common.djangoapps.student.models_api import get_name, get_pending_name_change
from lms.djangoapps.verify_student.apps import VerifyStudentConfig # pylint: disable=unused-import
from lms.djangoapps.verify_student.signals.signals import idv_update_signal
from openedx.core.djangoapps.user_api.accounts.signals import USER_RETIRE_LMS_CRITICAL, USER_RETIRE_LMS_MISC

from .models import SoftwareSecurePhotoVerification, VerificationDeadline, VerificationAttempt
from lms.djangoapps.verify_student.models import (
SoftwareSecurePhotoVerification,
VerificationDeadline,
VerificationAttempt
)

log = logging.getLogger(__name__)


# Signal for emitting IDV submission and review updates
# providing_args = ["attempt_id", "user_id", "status", "full_name", "profile_name"]
idv_update_signal = Signal()


@receiver(SignalHandler.course_published)
def _listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument
"""
Expand Down
109 changes: 109 additions & 0 deletions lms/djangoapps/verify_student/signals/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""
Signal definitions and functions to send those signals for the verify_student application.
"""

from django.dispatch import Signal

from openedx_events.learning.data import UserData, UserPersonalData, VerificationAttemptData
from openedx_events.learning.signals import (
IDV_ATTEMPT_CREATED,
IDV_ATTEMPT_PENDING,
IDV_ATTEMPT_APPROVED,
IDV_ATTEMPT_DENIED,
)

# Signal for emitting IDV submission and review updates
# providing_args = ["attempt_id", "user_id", "status", "full_name", "profile_name"]
idv_update_signal = Signal()


def _create_user_data(user):
"""
Helper function to create a UserData object.
"""
user_data = UserData(
id=user.id,
is_active=user.is_active,
pii=UserPersonalData(
username=user.username,
email=user.email,
name=user.get_full_name()
)
)

return user_data


def emit_idv_attempt_created_event(attempt_id, user, status, name, expiration_date):
"""
Emit the IDV_ATTEMPT_CREATED Open edX event.
"""
user_data = _create_user_data(user)

# .. event_implemented_name: IDV_ATTEMPT_CREATED
IDV_ATTEMPT_CREATED.send_event(
idv_attempt=VerificationAttemptData(
attempt_id=attempt_id,
user=user_data,
status=status,
name=name,
expiration_date=expiration_date,
)
)
return user_data


def emit_idv_attempt_pending_event(attempt_id, user, status, name, expiration_date):
"""
Emit the IDV_ATTEMPT_PENDING Open edX event.
"""
user_data = _create_user_data(user)

# .. event_implemented_name: IDV_ATTEMPT_PENDING
IDV_ATTEMPT_PENDING.send_event(
idv_attempt=VerificationAttemptData(
attempt_id=attempt_id,
user=user_data,
status=status,
name=name,
expiration_date=expiration_date,
)
)
return user_data


def emit_idv_attempt_approved_event(attempt_id, user, status, name, expiration_date):
"""
Emit the IDV_ATTEMPT_APPROVED Open edX event.
"""
user_data = _create_user_data(user)

# .. event_implemented_name: IDV_ATTEMPT_APPROVED
IDV_ATTEMPT_APPROVED.send_event(
idv_attempt=VerificationAttemptData(
attempt_id=attempt_id,
user=user_data,
status=status,
name=name,
expiration_date=expiration_date,
)
)
return user_data


def emit_idv_attempt_denied_event(attempt_id, user, status, name, expiration_date):
"""
Emit the IDV_ATTEMPT_DENIED Open edX event.
"""
user_data = _create_user_data(user)

# .. event_implemented_name: IDV_ATTEMPT_DENIED
IDV_ATTEMPT_DENIED.send_event(
idv_attempt=VerificationAttemptData(
attempt_id=attempt_id,
user=user_data,
status=status,
name=name,
expiration_date=expiration_date,
)
)
49 changes: 47 additions & 2 deletions lms/djangoapps/verify_student/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ def setUp(self):
)
self.attempt.save()

def test_create_verification_attempt(self):
@patch('lms.djangoapps.verify_student.api.emit_idv_attempt_created_event')
def test_create_verification_attempt(self, mock_created_event):
expected_id = 2
self.assertEqual(
create_verification_attempt(
Expand All @@ -86,6 +87,13 @@ def test_create_verification_attempt(self):
self.assertEqual(verification_attempt.name, 'Tester McTest')
self.assertEqual(verification_attempt.status, VerificationAttemptStatus.CREATED)
self.assertEqual(verification_attempt.expiration_datetime, datetime(2024, 12, 31, tzinfo=timezone.utc))
mock_created_event.assert_called_with(
attempt_id=verification_attempt.id,
user=self.user,
status=VerificationAttemptStatus.CREATED,
name='Tester McTest',
expiration_date=datetime(2024, 12, 31, tzinfo=timezone.utc),
)

def test_create_verification_attempt_no_expiration_datetime(self):
expected_id = 2
Expand Down Expand Up @@ -129,7 +137,18 @@ def setUp(self):
('Tester McTest3', VerificationAttemptStatus.DENIED, datetime(2026, 12, 31, tzinfo=timezone.utc)),
)
@ddt.unpack
def test_update_verification_attempt(self, name, status, expiration_datetime):
@patch('lms.djangoapps.verify_student.api.emit_idv_attempt_pending_event')
@patch('lms.djangoapps.verify_student.api.emit_idv_attempt_approved_event')
@patch('lms.djangoapps.verify_student.api.emit_idv_attempt_denied_event')
def test_update_verification_attempt(
self,
name,
status,
expiration_datetime,
mock_denied_event,
mock_approved_event,
mock_pending_event,
):
update_verification_attempt(
attempt_id=self.attempt.id,
name=name,
Expand All @@ -145,6 +164,31 @@ def test_update_verification_attempt(self, name, status, expiration_datetime):
self.assertEqual(verification_attempt.status, status)
self.assertEqual(verification_attempt.expiration_datetime, expiration_datetime)

if status == VerificationAttemptStatus.PENDING:
mock_pending_event.assert_called_with(
attempt_id=verification_attempt.id,
user=self.user,
status=status,
name=name,
expiration_date=expiration_datetime,
)
elif status == VerificationAttemptStatus.APPROVED:
mock_approved_event.assert_called_with(
attempt_id=verification_attempt.id,
user=self.user,
status=status,
name=name,
expiration_date=expiration_datetime,
)
elif status == VerificationAttemptStatus.DENIED:
mock_denied_event.assert_called_with(
attempt_id=verification_attempt.id,
user=self.user,
status=status,
name=name,
expiration_date=expiration_datetime,
)

def test_update_verification_attempt_none_values(self):
update_verification_attempt(
attempt_id=self.attempt.id,
Expand All @@ -166,6 +210,7 @@ def test_update_verification_attempt_not_found(self):
VerificationAttempt.DoesNotExist,
update_verification_attempt,
attempt_id=999999,
name=None,
status=VerificationAttemptStatus.APPROVED,
)

Expand Down
Loading
Loading