Skip to content

Commit 575e240

Browse files
authored
feat: add idv events to api (#35468)
* 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
1 parent 5927be7 commit 575e240

File tree

14 files changed

+227
-44
lines changed

14 files changed

+227
-44
lines changed

lms/djangoapps/verify_student/api.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
from lms.djangoapps.verify_student.emails import send_verification_approved_email
1414
from lms.djangoapps.verify_student.exceptions import VerificationAttemptInvalidStatus
1515
from lms.djangoapps.verify_student.models import VerificationAttempt
16+
from lms.djangoapps.verify_student.signals.signals import (
17+
emit_idv_attempt_approved_event,
18+
emit_idv_attempt_created_event,
19+
emit_idv_attempt_denied_event,
20+
emit_idv_attempt_pending_event,
21+
)
1622
from lms.djangoapps.verify_student.statuses import VerificationAttemptStatus
1723
from lms.djangoapps.verify_student.tasks import send_verification_status_email
1824

@@ -70,14 +76,22 @@ def create_verification_attempt(user: User, name: str, status: str, expiration_d
7076
expiration_datetime=expiration_datetime,
7177
)
7278

79+
emit_idv_attempt_created_event(
80+
attempt_id=verification_attempt.id,
81+
user=user,
82+
status=status,
83+
name=name,
84+
expiration_date=expiration_datetime,
85+
)
86+
7387
return verification_attempt.id
7488

7589

7690
def update_verification_attempt(
7791
attempt_id: int,
7892
name: Optional[str] = None,
7993
status: Optional[str] = None,
80-
expiration_datetime: Optional[datetime] = None
94+
expiration_datetime: Optional[datetime] = None,
8195
):
8296
"""
8397
Update a verification attempt.
@@ -125,3 +139,29 @@ def update_verification_attempt(
125139
attempt.expiration_datetime = expiration_datetime
126140

127141
attempt.save()
142+
143+
user = attempt.user
144+
if status == VerificationAttemptStatus.PENDING:
145+
emit_idv_attempt_pending_event(
146+
attempt_id=attempt_id,
147+
user=user,
148+
status=status,
149+
name=name,
150+
expiration_date=expiration_datetime,
151+
)
152+
elif status == VerificationAttemptStatus.APPROVED:
153+
emit_idv_attempt_approved_event(
154+
attempt_id=attempt_id,
155+
user=user,
156+
status=status,
157+
name=name,
158+
expiration_date=expiration_datetime,
159+
)
160+
elif status == VerificationAttemptStatus.DENIED:
161+
emit_idv_attempt_denied_event(
162+
attempt_id=attempt_id,
163+
user=user,
164+
status=status,
165+
name=name,
166+
expiration_date=expiration_datetime,
167+
)

lms/djangoapps/verify_student/apps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ def ready(self):
1717
"""
1818
Connect signal handlers.
1919
"""
20-
from lms.djangoapps.verify_student import signals # pylint: disable=unused-import
20+
from lms.djangoapps.verify_student.signals import signals # pylint: disable=unused-import
2121
from lms.djangoapps.verify_student import tasks # pylint: disable=unused-import

lms/djangoapps/verify_student/management/commands/tests/test_retry_failed_photo_verifications.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def _create_attempts(self, num_attempts):
121121
for _ in range(num_attempts):
122122
self.create_upload_and_submit_attempt_for_user()
123123

124-
@patch('lms.djangoapps.verify_student.signals.idv_update_signal.send')
124+
@patch('lms.djangoapps.verify_student.signals.signals.idv_update_signal.send')
125125
def test_resubmit_in_date_range(self, send_idv_update_mock):
126126
call_command('retry_failed_photo_verifications',
127127
status="submitted",

lms/djangoapps/verify_student/management/commands/tests/test_trigger_softwaresecurephotoverifications_post_save_signal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def _create_attempts(self, num_attempts):
3838
for _ in range(num_attempts):
3939
self.create_and_submit_attempt_for_user()
4040

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

lms/djangoapps/verify_student/signals/__init__.py

Whitespace-only changes.

lms/djangoapps/verify_student/signals.py renamed to lms/djangoapps/verify_student/signals/handlers.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@
55

66
from django.core.exceptions import ObjectDoesNotExist
77
from django.db.models.signals import post_save
8-
from django.dispatch import Signal
98
from django.dispatch.dispatcher import receiver
109
from xmodule.modulestore.django import SignalHandler, modulestore
1110

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

15-
from .models import SoftwareSecurePhotoVerification, VerificationDeadline, VerificationAttempt
16+
from lms.djangoapps.verify_student.models import (
17+
SoftwareSecurePhotoVerification,
18+
VerificationDeadline,
19+
VerificationAttempt
20+
)
1621

1722
log = logging.getLogger(__name__)
1823

1924

20-
# Signal for emitting IDV submission and review updates
21-
# providing_args = ["attempt_id", "user_id", "status", "full_name", "profile_name"]
22-
idv_update_signal = Signal()
23-
24-
2525
@receiver(SignalHandler.course_published)
2626
def _listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument
2727
"""
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""
2+
Signal definitions and functions to send those signals for the verify_student application.
3+
"""
4+
5+
from django.dispatch import Signal
6+
7+
from openedx_events.learning.data import UserData, UserPersonalData, VerificationAttemptData
8+
from openedx_events.learning.signals import (
9+
IDV_ATTEMPT_CREATED,
10+
IDV_ATTEMPT_PENDING,
11+
IDV_ATTEMPT_APPROVED,
12+
IDV_ATTEMPT_DENIED,
13+
)
14+
15+
# Signal for emitting IDV submission and review updates
16+
# providing_args = ["attempt_id", "user_id", "status", "full_name", "profile_name"]
17+
idv_update_signal = Signal()
18+
19+
20+
def _create_user_data(user):
21+
"""
22+
Helper function to create a UserData object.
23+
"""
24+
user_data = UserData(
25+
id=user.id,
26+
is_active=user.is_active,
27+
pii=UserPersonalData(
28+
username=user.username,
29+
email=user.email,
30+
name=user.get_full_name()
31+
)
32+
)
33+
34+
return user_data
35+
36+
37+
def emit_idv_attempt_created_event(attempt_id, user, status, name, expiration_date):
38+
"""
39+
Emit the IDV_ATTEMPT_CREATED Open edX event.
40+
"""
41+
user_data = _create_user_data(user)
42+
43+
# .. event_implemented_name: IDV_ATTEMPT_CREATED
44+
IDV_ATTEMPT_CREATED.send_event(
45+
idv_attempt=VerificationAttemptData(
46+
attempt_id=attempt_id,
47+
user=user_data,
48+
status=status,
49+
name=name,
50+
expiration_date=expiration_date,
51+
)
52+
)
53+
return user_data
54+
55+
56+
def emit_idv_attempt_pending_event(attempt_id, user, status, name, expiration_date):
57+
"""
58+
Emit the IDV_ATTEMPT_PENDING Open edX event.
59+
"""
60+
user_data = _create_user_data(user)
61+
62+
# .. event_implemented_name: IDV_ATTEMPT_PENDING
63+
IDV_ATTEMPT_PENDING.send_event(
64+
idv_attempt=VerificationAttemptData(
65+
attempt_id=attempt_id,
66+
user=user_data,
67+
status=status,
68+
name=name,
69+
expiration_date=expiration_date,
70+
)
71+
)
72+
return user_data
73+
74+
75+
def emit_idv_attempt_approved_event(attempt_id, user, status, name, expiration_date):
76+
"""
77+
Emit the IDV_ATTEMPT_APPROVED Open edX event.
78+
"""
79+
user_data = _create_user_data(user)
80+
81+
# .. event_implemented_name: IDV_ATTEMPT_APPROVED
82+
IDV_ATTEMPT_APPROVED.send_event(
83+
idv_attempt=VerificationAttemptData(
84+
attempt_id=attempt_id,
85+
user=user_data,
86+
status=status,
87+
name=name,
88+
expiration_date=expiration_date,
89+
)
90+
)
91+
return user_data
92+
93+
94+
def emit_idv_attempt_denied_event(attempt_id, user, status, name, expiration_date):
95+
"""
96+
Emit the IDV_ATTEMPT_DENIED Open edX event.
97+
"""
98+
user_data = _create_user_data(user)
99+
100+
# .. event_implemented_name: IDV_ATTEMPT_DENIED
101+
IDV_ATTEMPT_DENIED.send_event(
102+
idv_attempt=VerificationAttemptData(
103+
attempt_id=attempt_id,
104+
user=user_data,
105+
status=status,
106+
name=name,
107+
expiration_date=expiration_date,
108+
)
109+
)

lms/djangoapps/verify_student/tests/test_api.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ def setUp(self):
6969
)
7070
self.attempt.save()
7171

72-
def test_create_verification_attempt(self):
72+
@patch('lms.djangoapps.verify_student.api.emit_idv_attempt_created_event')
73+
def test_create_verification_attempt(self, mock_created_event):
7374
expected_id = 2
7475
self.assertEqual(
7576
create_verification_attempt(
@@ -86,6 +87,13 @@ def test_create_verification_attempt(self):
8687
self.assertEqual(verification_attempt.name, 'Tester McTest')
8788
self.assertEqual(verification_attempt.status, VerificationAttemptStatus.CREATED)
8889
self.assertEqual(verification_attempt.expiration_datetime, datetime(2024, 12, 31, tzinfo=timezone.utc))
90+
mock_created_event.assert_called_with(
91+
attempt_id=verification_attempt.id,
92+
user=self.user,
93+
status=VerificationAttemptStatus.CREATED,
94+
name='Tester McTest',
95+
expiration_date=datetime(2024, 12, 31, tzinfo=timezone.utc),
96+
)
8997

9098
def test_create_verification_attempt_no_expiration_datetime(self):
9199
expected_id = 2
@@ -129,7 +137,18 @@ def setUp(self):
129137
('Tester McTest3', VerificationAttemptStatus.DENIED, datetime(2026, 12, 31, tzinfo=timezone.utc)),
130138
)
131139
@ddt.unpack
132-
def test_update_verification_attempt(self, name, status, expiration_datetime):
140+
@patch('lms.djangoapps.verify_student.api.emit_idv_attempt_pending_event')
141+
@patch('lms.djangoapps.verify_student.api.emit_idv_attempt_approved_event')
142+
@patch('lms.djangoapps.verify_student.api.emit_idv_attempt_denied_event')
143+
def test_update_verification_attempt(
144+
self,
145+
name,
146+
status,
147+
expiration_datetime,
148+
mock_denied_event,
149+
mock_approved_event,
150+
mock_pending_event,
151+
):
133152
update_verification_attempt(
134153
attempt_id=self.attempt.id,
135154
name=name,
@@ -145,6 +164,31 @@ def test_update_verification_attempt(self, name, status, expiration_datetime):
145164
self.assertEqual(verification_attempt.status, status)
146165
self.assertEqual(verification_attempt.expiration_datetime, expiration_datetime)
147166

167+
if status == VerificationAttemptStatus.PENDING:
168+
mock_pending_event.assert_called_with(
169+
attempt_id=verification_attempt.id,
170+
user=self.user,
171+
status=status,
172+
name=name,
173+
expiration_date=expiration_datetime,
174+
)
175+
elif status == VerificationAttemptStatus.APPROVED:
176+
mock_approved_event.assert_called_with(
177+
attempt_id=verification_attempt.id,
178+
user=self.user,
179+
status=status,
180+
name=name,
181+
expiration_date=expiration_datetime,
182+
)
183+
elif status == VerificationAttemptStatus.DENIED:
184+
mock_denied_event.assert_called_with(
185+
attempt_id=verification_attempt.id,
186+
user=self.user,
187+
status=status,
188+
name=name,
189+
expiration_date=expiration_datetime,
190+
)
191+
148192
def test_update_verification_attempt_none_values(self):
149193
update_verification_attempt(
150194
attempt_id=self.attempt.id,
@@ -166,6 +210,7 @@ def test_update_verification_attempt_not_found(self):
166210
VerificationAttempt.DoesNotExist,
167211
update_verification_attempt,
168212
attempt_id=999999,
213+
name=None,
169214
status=VerificationAttemptStatus.APPROVED,
170215
)
171216

0 commit comments

Comments
 (0)