Skip to content

Commit

Permalink
feat: add idv events to api (#35468)
Browse files Browse the repository at this point in the history
* feat: add idv events to api

- moved what was in signals.py to a handlers.py (which is what their file should have been called)

* chore: quality

* fix: rename test file + imports

* fix: change handler reverse url in other tests

* fix: refactor signals and handlers pattern

- following OEP-49 pattern for signals directory
- user removed as param for update function
- event now emitted after save

* fix: unpin edx-name-affirmation

* chore: add init to signals dir

* fix: compile requirements

* chore: quality

* chore: fix some imports

* chore: quality

* test: added signal emissions to test_api

* chore: lint
  • Loading branch information
ilee2u authored Sep 17, 2024
1 parent 5927be7 commit 575e240
Show file tree
Hide file tree
Showing 14 changed files with 227 additions and 44 deletions.
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

0 comments on commit 575e240

Please sign in to comment.