Skip to content

Commit

Permalink
feat: create an ID Verification for verified mode enrollments
Browse files Browse the repository at this point in the history
Add a receiver of `COURSE_ENROLLMENT_CHANGED` Open edX Event,
that will create a new Manual ID Verification if the enrollment mode of the enrollment that was changed is `verified`.
Added openedx-events as dependency.
Update dependencies.
Fix lint format code because of the test update dependencies.

fccn/nau-technical#200
  • Loading branch information
igobranco committed Oct 2, 2024
1 parent 6031aa0 commit 8441557
Show file tree
Hide file tree
Showing 17 changed files with 351 additions and 12 deletions.
3 changes: 1 addition & 2 deletions nau_openedx_extensions/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ def ready(self):
"""
Method to perform actions after apps registry is ended
"""
from nau_openedx_extensions import signals # pylint: disable=import-outside-toplevel,unused-import # noqa
from nau_openedx_extensions.permissions import \
load_permissions # pylint: disable=import-outside-toplevel,unused-import # noqa

# load_permissions()
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""
Real implementation of user id verifications service.
"""
from django.contrib.auth import get_user_model
from lms.djangoapps.verify_student.models import ManualVerification # pylint: disable=import-error


def get_user_id_verifications(user_id, *args, **kwargs):
"""
Read the user's `ManualVerification` from the edx-platform.
Args:
user: The user id to read the Id Verifications.
Returns:
An enumeration of those Id Verifications
"""
user = get_user_model().objects.get(id=user_id)
return ManualVerification.objects.filter(user=user).order_by('-created_at')


def create_user_id_verification(user_id, *args, **kwargs):
"""
Create a new `ManualVerification` on the edx-platform.
Args:
user: The user id that this Id verification should be created.
Returns:
The object created
"""
user = get_user_model().objects.get(id=user_id)
ManualVerification(user=user, name=user.profile.name, *args, **kwargs).save()
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
Real implementation of user id verifications service.
"""


def get_user_id_verifications(user_id, *args, **kwargs): # pylint: disable=unused-argument
"""
Read the user's `ManualVerification` from the edx-platform.
Args:
user_id: The user id to read the Id Verifications.
Returns:
An enumeration of those Id Verifications
"""
return []

def create_user_id_verification(user_id, *args, **kwargs): # pylint: disable=unused-argument
"""
Create a new `ManualVerification` on the edx-platform.
Args:
user_id: The user id that this Id verification should be created.
Returns:
The object created
"""
return None
29 changes: 29 additions & 0 deletions nau_openedx_extensions/edxapp_wrapper/verify_student.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
Student backend abstraction
"""
from __future__ import absolute_import, unicode_literals

from importlib import import_module

from django.conf import settings


def get_user_id_verifications(user_id, *args, **kwargs):
"""
Read the user's `ManualVerification` from the edx-platform.
"""

backend_module = settings.NAU_VERIFY_STUDENT_MODULE
backend = import_module(backend_module)

return backend.get_user_id_verifications(user_id, *args, **kwargs)


def create_user_id_verification(user_id, *args, **kwargs):
"""
Create an user Id Verification `ManualVerification` instance on the edx-platform.
"""
backend_module = settings.NAU_VERIFY_STUDENT_MODULE
backend = import_module(backend_module)

return backend.create_user_id_verification(user_id, *args, **kwargs)
Binary file modified nau_openedx_extensions/locale/en/LC_MESSAGES/django.mo
Binary file not shown.
6 changes: 3 additions & 3 deletions nau_openedx_extensions/locale/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: [email protected]\n"
"POT-Creation-Date: 2024-09-26 11:25+0100\n"
"POT-Creation-Date: 2024-10-02 13:56+0100\n"
"PO-Revision-Date: 2021-02-15 15:56+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en\n"
Expand Down Expand Up @@ -133,11 +133,11 @@ msgid ""
"out in order to obtain a certificate."
msgstr ""

#: nau_openedx_extensions/settings/common.py:89
#: nau_openedx_extensions/settings/common.py:35
msgid "Certificate"
msgstr ""

#: nau_openedx_extensions/settings/common.py:90
#: nau_openedx_extensions/settings/common.py:36
msgid "Certificate of Achievement"
msgstr ""

Expand Down
Binary file modified nau_openedx_extensions/locale/pt_PT/LC_MESSAGES/django.mo
Binary file not shown.
6 changes: 3 additions & 3 deletions nau_openedx_extensions/locale/pt_PT/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: [email protected]\n"
"POT-Creation-Date: 2024-09-26 11:25+0100\n"
"POT-Creation-Date: 2024-10-02 13:56+0100\n"
"PO-Revision-Date: 2021-02-15 15:56+0000\n"
"Last-Translator: Ivo Branco <[email protected]>\n"
"Language: pt_PT\n"
Expand Down Expand Up @@ -142,11 +142,11 @@ msgstr ""
"Este curso encontra-se arquivado e já não permite a realização de "
"atividades para obtenção de certificado."

#: nau_openedx_extensions/settings/common.py:89
#: nau_openedx_extensions/settings/common.py:35
msgid "Certificate"
msgstr "Certificado"

#: nau_openedx_extensions/settings/common.py:90
#: nau_openedx_extensions/settings/common.py:36
msgid "Certificate of Achievement"
msgstr "Certificado de Conclusão"

Expand Down
11 changes: 7 additions & 4 deletions nau_openedx_extensions/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ def plugin_settings(settings):
See: https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/plugins/README.rst
"""

# Overwrite the default certificate name
settings.CERT_NAME_SHORT = _("Certificate")
settings.CERT_NAME_LONG = _("Certificate of Achievement")

settings.NAU_CUSTOM_SAML_IDENTITY_PROVIDERS = [
{
"provider_key": "nau_custom_saml_provider",
Expand Down Expand Up @@ -84,7 +88,6 @@ def plugin_settings(settings):
settings.NAU_COHORT_MODULE = (
"nau_openedx_extensions.edxapp_wrapper.backends.cohort_v1"
)

# Overwrite the default certificate name
settings.CERT_NAME_SHORT = _("Certificate")
settings.CERT_NAME_LONG = _("Certificate of Achievement")
settings.NAU_VERIFY_STUDENT_MODULE = (
"nau_openedx_extensions.edxapp_wrapper.backends.verify_student_v1"
)
3 changes: 3 additions & 0 deletions nau_openedx_extensions/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,6 @@ class SettingsClass:
NAU_COHORT_MODULE = (
"nau_openedx_extensions.edxapp_wrapper.backends.cohort_v1_tests"
)
NAU_VERIFY_STUDENT_MODULE = (
"nau_openedx_extensions.edxapp_wrapper.backends.verify_student_v1_tests"
)
7 changes: 7 additions & 0 deletions nau_openedx_extensions/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
File that contains the definition of all signals and its receivers.
"""

from nau_openedx_extensions.verify_student.id_verification import ( # pylint: disable=unused-import
event_receiver_no_id_verify_for_enrollment_modes,
)
183 changes: 183 additions & 0 deletions nau_openedx_extensions/tests/test_verify_student.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
"""
Tests for Id Verification of studentes.
"""

from datetime import datetime
from unittest.mock import Mock, patch

from django.test import TestCase, override_settings
from openedx_events.learning.data import CourseEnrollmentData, UserData
from openedx_events.learning.signals import COURSE_ENROLLMENT_CHANGED

from nau_openedx_extensions.verify_student.id_verification import event_receiver_no_id_verify_for_enrollment_modes


class VerifyStudentTest(TestCase):
"""
Tests for Id Verification patch.
"""

@patch(
"nau_openedx_extensions.verify_student.id_verification.get_user_id_verifications"
)
@patch(
"nau_openedx_extensions.verify_student.id_verification.create_user_id_verification"
)
def test_verify_student_create_new_verification(
self, create_user_id_verification_mock, get_user_id_verifications_mock
):
"""
Test an enrollment mode that requires a ID Verification.
"""
user_id = 10
get_user_id_verifications_mock.return_value = []
COURSE_ENROLLMENT_CHANGED.connect(event_receiver_no_id_verify_for_enrollment_modes)
COURSE_ENROLLMENT_CHANGED.send_event(
enrollment=CourseEnrollmentData(
user=UserData(id=user_id, is_active=True, pii=None),
mode="verified",
course=None,
is_active=None,
creation_date=None,
)
)
create_user_id_verification_mock.assert_called_once()
self.assertEqual(create_user_id_verification_mock.call_args.args[0], user_id)
create_user_id_verification_mock_kwargs = (
create_user_id_verification_mock.call_args.kwargs
)
self.assertEqual(
create_user_id_verification_mock_kwargs,
{
**create_user_id_verification_mock_kwargs,
**{
"status": "approved",
},
},
)
self.assertEqual(
create_user_id_verification_mock_kwargs,
{
**create_user_id_verification_mock_kwargs,
**{
"reason": "Skip id verification from nau_openedx_extensions",
},
},
)
self.assertEqual(
create_user_id_verification_mock.call_args.kwargs.get(
"expiration_date"
).year,
datetime.today().year + 100,
)

@patch(
"nau_openedx_extensions.verify_student.id_verification.get_user_id_verifications"
)
@patch(
"nau_openedx_extensions.verify_student.id_verification.create_user_id_verification"
)
def test_verify_student_enrollment_mode_not_need_id_verification_patch(
self, create_user_id_verification_mock, get_user_id_verifications_mock
):
"""
Test a case enrollment mode that doesn't requires a ID Verification.
"""
user_id = 10
get_user_id_verifications_mock.return_value = []
COURSE_ENROLLMENT_CHANGED.connect(event_receiver_no_id_verify_for_enrollment_modes)
COURSE_ENROLLMENT_CHANGED.send_event(
enrollment=CourseEnrollmentData(
user=UserData(id=user_id, is_active=True, pii=None),
mode="honor",
course=None,
is_active=None,
creation_date=None,
)
)
create_user_id_verification_mock.assert_not_called()

@patch(
"nau_openedx_extensions.verify_student.id_verification.get_user_id_verifications"
)
@patch(
"nau_openedx_extensions.verify_student.id_verification.create_user_id_verification"
)
@override_settings(NAU_NO_ID_VERIFY_FOR_ENROLLMENT_MODES="verified, somemode")
def test_verify_student_change_enrollment_modes_require_id_verification(
self, create_user_id_verification_mock, get_user_id_verifications_mock
):
"""
Test that changing the setting `NAU_NO_ID_VERIFY_FOR_ENROLLMENT_MODES` to include a custom enrollment mode,
and test with that new custom enrollment mode, it still creates an id verification.
"""
user_id = 10
get_user_id_verifications_mock.return_value = []
COURSE_ENROLLMENT_CHANGED.connect(event_receiver_no_id_verify_for_enrollment_modes)
COURSE_ENROLLMENT_CHANGED.send_event(
enrollment=CourseEnrollmentData(
user=UserData(id=user_id, is_active=True, pii=None),
mode="somemode",
course=None,
is_active=None,
creation_date=None,
)
)
create_user_id_verification_mock.assert_called_once()
self.assertEqual(create_user_id_verification_mock.call_args.args[0], user_id)
create_user_id_verification_mock_kwargs = (
create_user_id_verification_mock.call_args.kwargs
)
self.assertEqual(
create_user_id_verification_mock_kwargs,
{
**create_user_id_verification_mock_kwargs,
**{
"status": "approved",
},
},
)
self.assertEqual(
create_user_id_verification_mock_kwargs,
{
**create_user_id_verification_mock_kwargs,
**{
"reason": "Skip id verification from nau_openedx_extensions",
},
},
)
self.assertEqual(
create_user_id_verification_mock.call_args.kwargs.get(
"expiration_date"
).year,
datetime.today().year + 100,
)

@patch(
"nau_openedx_extensions.verify_student.id_verification.get_user_id_verifications"
)
@patch(
"nau_openedx_extensions.verify_student.id_verification.create_user_id_verification"
)
def test_verify_student_with_existing_id_verification(
self, create_user_id_verification_mock, get_user_id_verifications_mock
):
"""
Test that if the user already has an id verification, it won't try to create a new one.
"""
active_at_datetime_mock = Mock()
active_at_datetime_mock.return_value = True

user_id = 10
get_user_id_verifications_mock.return_value = active_at_datetime_mock
COURSE_ENROLLMENT_CHANGED.connect(event_receiver_no_id_verify_for_enrollment_modes)
COURSE_ENROLLMENT_CHANGED.send_event(
enrollment=CourseEnrollmentData(
user=UserData(id=user_id, is_active=True, pii=None),
mode="verify",
course=None,
is_active=None,
creation_date=None,
)
)
create_user_id_verification_mock.assert_not_called()
Loading

0 comments on commit 8441557

Please sign in to comment.