Merge pull request #29952 from openedx/bseverino/name-affirmation-plugin
[MST-1360] Only enable verified name feature if Name Affirmation is installed
This commit is contained in:
@@ -9,7 +9,6 @@ from django.contrib.auth import get_user_model
|
||||
from django.db import IntegrityError
|
||||
from django.db.models.signals import post_save, pre_save
|
||||
from django.dispatch import receiver
|
||||
from edx_name_affirmation.signals import VERIFIED_NAME_APPROVED
|
||||
|
||||
from lms.djangoapps.courseware.toggles import courseware_mfe_progress_milestones_are_active
|
||||
from common.djangoapps.student.helpers import EMAIL_EXISTS_MSG_FMT, USERNAME_EXISTS_MSG_FMT, AccountValidationError
|
||||
@@ -21,6 +20,7 @@ from common.djangoapps.student.models import (
|
||||
is_username_retired
|
||||
)
|
||||
from common.djangoapps.student.models_api import confirm_name_change
|
||||
from openedx.features.name_affirmation_api.utils import is_name_affirmation_installed
|
||||
|
||||
|
||||
@receiver(pre_save, sender=get_user_model())
|
||||
@@ -81,7 +81,6 @@ def create_course_enrollment_celebration(sender, instance, created, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
@receiver(VERIFIED_NAME_APPROVED)
|
||||
def listen_for_verified_name_approved(sender, user_id, profile_name, **kwargs):
|
||||
"""
|
||||
If the user has a pending name change that corresponds to an approved verified name, confirm it.
|
||||
@@ -92,3 +91,9 @@ def listen_for_verified_name_approved(sender, user_id, profile_name, **kwargs):
|
||||
confirm_name_change(user, pending_name_change)
|
||||
except PendingNameChange.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
if is_name_affirmation_installed():
|
||||
# pylint: disable=import-error
|
||||
from edx_name_affirmation.signals import VERIFIED_NAME_APPROVED
|
||||
VERIFIED_NAME_APPROVED.connect(listen_for_verified_name_approved)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
""" Tests for student signal receivers. """
|
||||
|
||||
from edx_name_affirmation.signals import VERIFIED_NAME_APPROVED
|
||||
from unittest import skipUnless
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag
|
||||
from lms.djangoapps.courseware.toggles import COURSEWARE_MICROFRONTEND_PROGRESS_MILESTONES
|
||||
from common.djangoapps.student.models import (
|
||||
@@ -13,8 +13,14 @@ from common.djangoapps.student.tests.factories import (
|
||||
UserFactory,
|
||||
UserProfileFactory
|
||||
)
|
||||
from openedx.features.name_affirmation_api.utils import is_name_affirmation_installed
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
name_affirmation_installed = is_name_affirmation_installed()
|
||||
if name_affirmation_installed:
|
||||
# pylint: disable=import-error
|
||||
from edx_name_affirmation.signals import VERIFIED_NAME_APPROVED
|
||||
|
||||
|
||||
class ReceiversTest(SharedModuleStoreTestCase):
|
||||
"""
|
||||
@@ -47,6 +53,7 @@ class ReceiversTest(SharedModuleStoreTestCase):
|
||||
CourseEnrollmentFactory()
|
||||
assert CourseEnrollmentCelebration.objects.count() == 0
|
||||
|
||||
@skipUnless(name_affirmation_installed, "Requires Name Affirmation")
|
||||
def test_listen_for_verified_name_approved(self):
|
||||
"""
|
||||
Test that profile name is updated when a pending name change is approved
|
||||
|
||||
@@ -18,7 +18,6 @@ from django.db.models import Count
|
||||
from django.dispatch import receiver
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from edx_name_affirmation.api import get_verified_name, should_use_verified_name_for_certs
|
||||
from model_utils import Choices
|
||||
from model_utils.models import TimeStampedModel
|
||||
from opaque_keys.edx.django.models import CourseKeyField
|
||||
@@ -33,6 +32,7 @@ from lms.djangoapps.certificates.data import CertificateStatuses
|
||||
from lms.djangoapps.instructor_task.models import InstructorTask
|
||||
from openedx.core.djangoapps.signals.signals import COURSE_CERT_AWARDED, COURSE_CERT_CHANGED, COURSE_CERT_REVOKED
|
||||
from openedx.core.djangoapps.xmodule_django.models import NoneToEmptyManager
|
||||
from openedx.features.name_affirmation_api.utils import get_name_affirmation_service
|
||||
|
||||
from openedx_events.learning.data import CourseData, UserData, UserPersonalData, CertificateData # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from openedx_events.learning.signals import CERTIFICATE_CHANGED, CERTIFICATE_CREATED, CERTIFICATE_REVOKED # lint-amnesty, pylint: disable=wrong-import-order
|
||||
@@ -435,9 +435,10 @@ class GeneratedCertificate(models.Model):
|
||||
a circular dependency.
|
||||
"""
|
||||
name_to_use = student_api.get_name(user.id)
|
||||
name_affirmation_service = get_name_affirmation_service()
|
||||
|
||||
if should_use_verified_name_for_certs(user):
|
||||
verified_name_obj = get_verified_name(user, is_verified=True)
|
||||
if name_affirmation_service and name_affirmation_service.should_use_verified_name_for_certs(user):
|
||||
verified_name_obj = name_affirmation_service.get_verified_name(user, is_verified=True)
|
||||
if verified_name_obj:
|
||||
name_to_use = verified_name_obj.verified_name
|
||||
|
||||
|
||||
@@ -3,10 +3,7 @@ Tests for certificate generation
|
||||
"""
|
||||
import ddt
|
||||
import logging # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from unittest import mock # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
from edx_name_affirmation.api import create_verified_name, create_verified_name_config
|
||||
from edx_name_affirmation.statuses import VerifiedNameStatus
|
||||
from unittest import mock, skipUnless # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.student.models import UserProfile
|
||||
@@ -16,12 +13,14 @@ from lms.djangoapps.certificates.data import CertificateStatuses
|
||||
from lms.djangoapps.certificates.generation import generate_course_certificate
|
||||
from lms.djangoapps.certificates.models import GeneratedCertificate
|
||||
from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory
|
||||
from openedx.features.name_affirmation_api.utils import get_name_affirmation_service
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
PROFILE_NAME_METHOD = 'common.djangoapps.student.models_api.get_name'
|
||||
name_affirmation_service = get_name_affirmation_service()
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@@ -193,9 +192,10 @@ class CertificateTests(EventTestMixin, ModuleStoreTestCase):
|
||||
assert cert.grade == self.grade
|
||||
assert cert.name == ''
|
||||
|
||||
@ddt.data((True, VerifiedNameStatus.APPROVED),
|
||||
(True, VerifiedNameStatus.DENIED),
|
||||
(False, VerifiedNameStatus.PENDING))
|
||||
@skipUnless(name_affirmation_service is not None, 'Requires Name Affirmation')
|
||||
@ddt.data((True, 'approved'),
|
||||
(True, 'denied'),
|
||||
(False, 'pending'))
|
||||
@ddt.unpack
|
||||
def test_generation_verified_name(self, should_use_verified_name_for_certs, status):
|
||||
"""
|
||||
@@ -204,8 +204,11 @@ class CertificateTests(EventTestMixin, ModuleStoreTestCase):
|
||||
their profile name.
|
||||
"""
|
||||
verified_name = 'Jonathan Doe'
|
||||
create_verified_name(self.u, verified_name, self.name, status=status)
|
||||
create_verified_name_config(self.u, use_verified_name_for_certs=should_use_verified_name_for_certs)
|
||||
name_affirmation_service.create_verified_name(self.u, verified_name, self.name, status=status)
|
||||
name_affirmation_service.create_verified_name_config(
|
||||
self.u,
|
||||
use_verified_name_for_certs=should_use_verified_name_for_certs
|
||||
)
|
||||
|
||||
GeneratedCertificateFactory(
|
||||
user=self.u,
|
||||
@@ -220,7 +223,7 @@ class CertificateTests(EventTestMixin, ModuleStoreTestCase):
|
||||
|
||||
cert = GeneratedCertificate.objects.get(user=self.u, course_id=self.key)
|
||||
|
||||
if should_use_verified_name_for_certs and status == VerifiedNameStatus.APPROVED:
|
||||
if should_use_verified_name_for_certs and status == 'approved':
|
||||
assert cert.name == verified_name
|
||||
else:
|
||||
assert cert.name == self.name
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
from unittest import mock
|
||||
from unittest import mock, skipUnless
|
||||
|
||||
import ddt
|
||||
import pytest
|
||||
@@ -12,8 +12,6 @@ from django.core.exceptions import ValidationError
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from edx_name_affirmation.api import create_verified_name, create_verified_name_config
|
||||
from edx_name_affirmation.statuses import VerifiedNameStatus
|
||||
from opaque_keys.edx.locator import CourseKey, CourseLocator
|
||||
from openedx_events.tests.utils import OpenEdxEventsTestMixin
|
||||
from path import Path as path
|
||||
@@ -39,6 +37,7 @@ from lms.djangoapps.certificates.tests.factories import (
|
||||
)
|
||||
from lms.djangoapps.instructor_task.tests.factories import InstructorTaskFactory
|
||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
||||
from openedx.features.name_affirmation_api.utils import get_name_affirmation_service
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
@@ -53,6 +52,8 @@ TEST_DATA_DIR = 'common/test/data/'
|
||||
PLATFORM_ROOT = TEST_DIR.parent.parent.parent.parent
|
||||
TEST_DATA_ROOT = PLATFORM_ROOT / TEST_DATA_DIR
|
||||
|
||||
name_affirmation_service = get_name_affirmation_service()
|
||||
|
||||
|
||||
class ExampleCertificateTest(TestCase, OpenEdxEventsTestMixin):
|
||||
"""Tests for the ExampleCertificate model. """
|
||||
@@ -624,9 +625,10 @@ class GeneratedCertificateTest(SharedModuleStoreTestCase, OpenEdxEventsTestMixin
|
||||
|
||||
self._assert_event_data(mock_emit_certificate_event, expected_event_data)
|
||||
|
||||
@ddt.data((True, VerifiedNameStatus.APPROVED),
|
||||
(True, VerifiedNameStatus.DENIED),
|
||||
(False, VerifiedNameStatus.PENDING))
|
||||
@skipUnless(name_affirmation_service is not None, 'Requires Name Affirmation')
|
||||
@ddt.data((True, 'approved'),
|
||||
(True, 'denied'),
|
||||
(False, 'pending'))
|
||||
@ddt.unpack
|
||||
def test_invalidate_with_verified_name(self, should_use_verified_name_for_certs, status):
|
||||
"""
|
||||
@@ -634,8 +636,11 @@ class GeneratedCertificateTest(SharedModuleStoreTestCase, OpenEdxEventsTestMixin
|
||||
"""
|
||||
verified_name = 'Jonathan Doe'
|
||||
profile = UserProfile.objects.get(user=self.user)
|
||||
create_verified_name(self.user, verified_name, profile.name, status=status)
|
||||
create_verified_name_config(self.user, use_verified_name_for_certs=should_use_verified_name_for_certs)
|
||||
name_affirmation_service.create_verified_name(self.user, verified_name, profile.name, status=status)
|
||||
name_affirmation_service.create_verified_name_config(
|
||||
self.user,
|
||||
use_verified_name_for_certs=should_use_verified_name_for_certs
|
||||
)
|
||||
|
||||
cert = GeneratedCertificateFactory.create(
|
||||
status=CertificateStatuses.downloadable,
|
||||
@@ -649,7 +654,7 @@ class GeneratedCertificateTest(SharedModuleStoreTestCase, OpenEdxEventsTestMixin
|
||||
cert.invalidate(mode=mode, source=source)
|
||||
|
||||
cert = GeneratedCertificate.objects.get(user=self.user, course_id=self.course_key)
|
||||
if should_use_verified_name_for_certs and status == VerifiedNameStatus.APPROVED:
|
||||
if should_use_verified_name_for_certs and status == 'approved':
|
||||
assert cert.name == verified_name
|
||||
else:
|
||||
assert cert.name == profile.name
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
import datetime
|
||||
from unittest import skipUnless
|
||||
from unittest.mock import patch
|
||||
from urllib.parse import urlencode
|
||||
from uuid import uuid4
|
||||
@@ -11,8 +12,6 @@ from django.conf import settings
|
||||
from django.test.client import Client, RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from edx_name_affirmation.api import create_verified_name, create_verified_name_config
|
||||
from edx_name_affirmation.statuses import VerifiedNameStatus
|
||||
from edx_toggles.toggles.testutils import override_waffle_switch
|
||||
from organizations import api as organizations_api
|
||||
|
||||
@@ -51,6 +50,7 @@ from openedx.core.djangoapps.site_configuration.tests.test_util import (
|
||||
from openedx.core.djangolib.js_utils import js_escaped_string
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from openedx.core.lib.tests.assertions.events import assert_event_matches
|
||||
from openedx.features.name_affirmation_api.utils import get_name_affirmation_service
|
||||
from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
@@ -66,6 +66,8 @@ FEATURES_WITH_CERTS_DISABLED['CERTIFICATES_HTML_VIEW'] = False
|
||||
FEATURES_WITH_CUSTOM_CERTS_ENABLED = FEATURES_WITH_CERTS_ENABLED.copy()
|
||||
FEATURES_WITH_CUSTOM_CERTS_ENABLED['CUSTOM_CERTIFICATE_TEMPLATES_ENABLED'] = True
|
||||
|
||||
name_affirmation_service = get_name_affirmation_service()
|
||||
|
||||
|
||||
class CommonCertificatesTestCase(ModuleStoreTestCase):
|
||||
"""
|
||||
@@ -1613,10 +1615,11 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase)
|
||||
)
|
||||
)
|
||||
|
||||
@skipUnless(name_affirmation_service is not None, 'Requires Name Affirmation')
|
||||
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
|
||||
@ddt.data((True, VerifiedNameStatus.APPROVED),
|
||||
(True, VerifiedNameStatus.DENIED),
|
||||
(False, VerifiedNameStatus.PENDING))
|
||||
@ddt.data((True, 'approved'),
|
||||
(True, 'denied'),
|
||||
(False, 'pending'))
|
||||
@ddt.unpack
|
||||
def test_certificate_view_verified_name(self, should_use_verified_name_for_certs, status):
|
||||
"""
|
||||
@@ -1624,8 +1627,16 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase)
|
||||
their verified name will appear on the certificate rather than their profile name.
|
||||
"""
|
||||
verified_name = 'Jonathan Doe'
|
||||
create_verified_name(self.user, verified_name, self.user.profile.name, status=status)
|
||||
create_verified_name_config(self.user, use_verified_name_for_certs=should_use_verified_name_for_certs)
|
||||
name_affirmation_service.create_verified_name(
|
||||
self.user,
|
||||
verified_name,
|
||||
self.user.profile.name,
|
||||
status=status
|
||||
)
|
||||
name_affirmation_service.create_verified_name_config(
|
||||
self.user,
|
||||
use_verified_name_for_certs=should_use_verified_name_for_certs
|
||||
)
|
||||
|
||||
self._add_course_certificates(count=1, signatory_count=1)
|
||||
test_url = get_certificate_url(
|
||||
@@ -1635,7 +1646,7 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase)
|
||||
)
|
||||
|
||||
response = self.client.get(test_url, HTTP_HOST='test.localhost')
|
||||
if should_use_verified_name_for_certs and status == VerifiedNameStatus.APPROVED:
|
||||
if should_use_verified_name_for_certs and status == 'approved':
|
||||
self.assertContains(response, verified_name)
|
||||
self.assertNotContains(response, self.user.profile.name)
|
||||
else:
|
||||
|
||||
@@ -4,8 +4,6 @@ Certificates utilities
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
from edx_name_affirmation.api import get_verified_name, should_use_verified_name_for_certs
|
||||
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from eventtracking import tracker
|
||||
@@ -16,6 +14,7 @@ from common.djangoapps.student import models_api as student_api
|
||||
from lms.djangoapps.certificates.data import CertificateStatuses
|
||||
from lms.djangoapps.certificates.models import GeneratedCertificate
|
||||
from openedx.core.djangoapps.content.course_overviews.api import get_course_overview_or_none
|
||||
from openedx.features.name_affirmation_api.utils import get_name_affirmation_service
|
||||
from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -240,9 +239,10 @@ def get_preferred_certificate_name(user):
|
||||
name, or an empty string if it doesn't exist.
|
||||
"""
|
||||
name_to_use = student_api.get_name(user.id)
|
||||
name_affirmation_service = get_name_affirmation_service()
|
||||
|
||||
if should_use_verified_name_for_certs(user):
|
||||
verified_name_obj = get_verified_name(user, is_verified=True)
|
||||
if name_affirmation_service and name_affirmation_service.should_use_verified_name_for_certs(user):
|
||||
verified_name_obj = name_affirmation_service.get_verified_name(user, is_verified=True)
|
||||
if verified_name_obj:
|
||||
name_to_use = verified_name_obj.verified_name
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.validators import ValidationError, validate_email
|
||||
from django.utils.translation import override as override_language
|
||||
from django.utils.translation import gettext as _
|
||||
from edx_name_affirmation.name_change_validator import NameChangeValidator
|
||||
from pytz import UTC
|
||||
from common.djangoapps.student import views as student_views
|
||||
from common.djangoapps.student.models import (
|
||||
@@ -38,8 +37,14 @@ from openedx.core.djangoapps.user_authn.utils import check_pwned_password
|
||||
from openedx.core.djangoapps.user_authn.views.registration_form import validate_name, validate_username
|
||||
from openedx.core.lib.api.view_utils import add_serializer_errors
|
||||
from openedx.features.enterprise_support.utils import get_enterprise_readonly_account_fields
|
||||
from openedx.features.name_affirmation_api.utils import is_name_affirmation_installed
|
||||
from .serializers import AccountLegacyProfileSerializer, AccountUserSerializer, UserReadOnlySerializer, _visible_fields
|
||||
|
||||
name_affirmation_installed = is_name_affirmation_installed()
|
||||
if name_affirmation_installed:
|
||||
# pylint: disable=import-error
|
||||
from edx_name_affirmation.name_change_validator import NameChangeValidator
|
||||
|
||||
# Public access point for this function.
|
||||
visible_fields = _visible_fields
|
||||
|
||||
@@ -274,6 +279,9 @@ def _does_name_change_require_verification(user_profile, old_name, new_name):
|
||||
"""
|
||||
If name change requires ID verification, do not update it through this API.
|
||||
"""
|
||||
if not name_affirmation_installed:
|
||||
return False
|
||||
|
||||
profile_meta = user_profile.get_meta()
|
||||
old_names_list = profile_meta['old_names'] if 'old_names' in profile_meta else []
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ from django.conf import settings
|
||||
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.urls import reverse
|
||||
from edx_name_affirmation.api import get_verified_name
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
@@ -29,6 +28,7 @@ from openedx.core.djangoapps.user_api.accounts.utils import is_secondary_email_f
|
||||
from openedx.core.djangoapps.user_api.models import RetirementState, UserPreference, UserRetirementStatus
|
||||
from openedx.core.djangoapps.user_api.serializers import ReadOnlyFieldsSerializerMixin
|
||||
from openedx.core.djangoapps.user_authn.views.registration_form import contains_html, contains_url
|
||||
from openedx.features.name_affirmation_api.utils import get_name_affirmation_service
|
||||
|
||||
from . import (
|
||||
ACCOUNT_VISIBILITY_PREF_KEY,
|
||||
@@ -170,11 +170,10 @@ class UserReadOnlySerializer(serializers.Serializer): # lint-amnesty, pylint: d
|
||||
"extended_profile_fields": None,
|
||||
"phone_number": None,
|
||||
"pending_name_change": None,
|
||||
"verified_name": None,
|
||||
}
|
||||
|
||||
if user_profile:
|
||||
verified_name_obj = get_verified_name(user, is_verified=True)
|
||||
verified_name = verified_name_obj.verified_name if verified_name_obj else None
|
||||
data.update(
|
||||
{
|
||||
"bio": AccountLegacyProfileSerializer.convert_empty_to_None(user_profile.bio),
|
||||
@@ -187,7 +186,6 @@ class UserReadOnlySerializer(serializers.Serializer): # lint-amnesty, pylint: d
|
||||
user_profile.language_proficiencies.all().order_by('code'), many=True
|
||||
).data,
|
||||
"name": user_profile.name,
|
||||
"verified_name": verified_name,
|
||||
"gender": AccountLegacyProfileSerializer.convert_empty_to_None(user_profile.gender),
|
||||
"goals": user_profile.goals,
|
||||
"year_of_birth": user_profile.year_of_birth,
|
||||
@@ -211,6 +209,12 @@ class UserReadOnlySerializer(serializers.Serializer): # lint-amnesty, pylint: d
|
||||
except PendingNameChange.DoesNotExist:
|
||||
pass
|
||||
|
||||
name_affirmation_service = get_name_affirmation_service()
|
||||
if name_affirmation_service:
|
||||
verified_name_obj = name_affirmation_service.get_verified_name(user, is_verified=True)
|
||||
if verified_name_obj:
|
||||
data.update({"verified_name": verified_name_obj.verified_name})
|
||||
|
||||
if is_secondary_email_feature_enabled():
|
||||
data.update(
|
||||
{
|
||||
|
||||
@@ -14,8 +14,6 @@ from django.conf import settings
|
||||
from django.test.testcases import TransactionTestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from edx_name_affirmation.api import create_verified_name
|
||||
from edx_name_affirmation.statuses import VerifiedNameStatus
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient, APITestCase
|
||||
|
||||
@@ -26,6 +24,7 @@ from openedx.core.djangoapps.user_api.accounts import ACCOUNT_VISIBILITY_PREF_KE
|
||||
from openedx.core.djangoapps.user_api.models import UserPreference
|
||||
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
|
||||
from openedx.features.name_affirmation_api.utils import get_name_affirmation_service
|
||||
|
||||
from .. import ALL_USERS_VISIBILITY, CUSTOM_VISIBILITY, PRIVATE_VISIBILITY
|
||||
|
||||
@@ -55,6 +54,7 @@ class UserAPITestCase(APITestCase):
|
||||
self.staff_user = UserFactory(is_staff=True, password=TEST_PASSWORD)
|
||||
self.staff_client = APIClient()
|
||||
self.user = UserFactory.create(password=TEST_PASSWORD) # will be assigned to self.client by default
|
||||
self.name_affirmation_service = get_name_affirmation_service()
|
||||
|
||||
def login_client(self, api_client, user):
|
||||
"""Helper method for getting the client and user and logging in. Returns client. """
|
||||
@@ -142,9 +142,16 @@ class UserAPITestCase(APITestCase):
|
||||
def create_mock_verified_name(self, user):
|
||||
"""
|
||||
Helper method to create an approved VerifiedName entry in name affirmation.
|
||||
Will not do anything if Name Affirmation is not installed.
|
||||
"""
|
||||
legacy_profile = UserProfile.objects.get(id=user.id)
|
||||
create_verified_name(user, self.VERIFIED_NAME, legacy_profile.name, status=VerifiedNameStatus.APPROVED)
|
||||
if self.name_affirmation_service:
|
||||
legacy_profile = UserProfile.objects.get(id=user.id)
|
||||
self.name_affirmation_service.create_verified_name(
|
||||
user,
|
||||
self.VERIFIED_NAME,
|
||||
legacy_profile.name,
|
||||
status='approved'
|
||||
)
|
||||
|
||||
def create_user_registration(self, user):
|
||||
"""
|
||||
@@ -152,6 +159,14 @@ class UserAPITestCase(APITestCase):
|
||||
"""
|
||||
RegistrationFactory(user=user)
|
||||
|
||||
def _get_num_queries(self, num_queries):
|
||||
"""
|
||||
If Name Affirmation is installed, it will add an extra query
|
||||
"""
|
||||
if self.name_affirmation_service:
|
||||
return num_queries + 1
|
||||
return num_queries
|
||||
|
||||
def _verify_profile_image_data(self, data, has_profile_image):
|
||||
"""
|
||||
Verify the profile image data in a GET response for self.user
|
||||
@@ -240,7 +255,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
TOTAL_QUERY_COUNT = 27
|
||||
TOTAL_QUERY_COUNT = 26
|
||||
FULL_RESPONSE_FIELD_COUNT = 30
|
||||
|
||||
def setUp(self):
|
||||
@@ -324,7 +339,6 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
|
||||
# additional admin fields (13)
|
||||
assert self.user.email == data['email']
|
||||
assert self.user.id == data['id']
|
||||
assert self.VERIFIED_NAME == data['verified_name']
|
||||
assert data['extended_profile'] is not None
|
||||
assert 'MA' == data['state']
|
||||
assert 'f' == data['gender']
|
||||
@@ -335,6 +349,10 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
|
||||
assert data['secondary_email'] is None
|
||||
assert data['secondary_email_enabled'] is None
|
||||
assert year_of_birth == data['year_of_birth']
|
||||
if self.name_affirmation_service:
|
||||
assert self.VERIFIED_NAME == data['verified_name']
|
||||
else:
|
||||
assert data['verified_name'] is None
|
||||
|
||||
def test_anonymous_access(self):
|
||||
"""
|
||||
@@ -501,7 +519,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
|
||||
"""
|
||||
self.different_client.login(username=self.different_user.username, password=TEST_PASSWORD)
|
||||
self.create_mock_profile(self.user)
|
||||
with self.assertNumQueries(self.TOTAL_QUERY_COUNT):
|
||||
with self.assertNumQueries(self._get_num_queries(self.TOTAL_QUERY_COUNT)):
|
||||
response = self.send_get(self.different_client)
|
||||
self._verify_full_shareable_account_response(response, account_privacy=ALL_USERS_VISIBILITY)
|
||||
|
||||
@@ -516,7 +534,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
|
||||
"""
|
||||
self.different_client.login(username=self.different_user.username, password=TEST_PASSWORD)
|
||||
self.create_mock_profile(self.user)
|
||||
with self.assertNumQueries(self.TOTAL_QUERY_COUNT):
|
||||
with self.assertNumQueries(self._get_num_queries(self.TOTAL_QUERY_COUNT)):
|
||||
response = self.send_get(self.different_client)
|
||||
self._verify_private_account_response(response)
|
||||
|
||||
@@ -667,12 +685,12 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
|
||||
assert data['accomplishments_shared'] is False
|
||||
|
||||
self.client.login(username=self.user.username, password=TEST_PASSWORD)
|
||||
verify_get_own_information(25)
|
||||
verify_get_own_information(self._get_num_queries(24))
|
||||
|
||||
# Now make sure that the user can get the same information, even if not active
|
||||
self.user.is_active = False
|
||||
self.user.save()
|
||||
verify_get_own_information(17)
|
||||
verify_get_own_information(self._get_num_queries(16))
|
||||
|
||||
def test_get_account_empty_string(self):
|
||||
"""
|
||||
@@ -687,7 +705,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
|
||||
legacy_profile.save()
|
||||
|
||||
self.client.login(username=self.user.username, password=TEST_PASSWORD)
|
||||
with self.assertNumQueries(25):
|
||||
with self.assertNumQueries(self._get_num_queries(24)):
|
||||
response = self.send_get(self.client)
|
||||
for empty_field in ("level_of_education", "gender", "country", "state", "bio",):
|
||||
assert response.data[empty_field] is None
|
||||
|
||||
@@ -7,10 +7,12 @@ from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
from edx_proctoring.runtime import set_runtime_service
|
||||
|
||||
from openedx.features.name_affirmation_api.utils import get_name_affirmation_service
|
||||
|
||||
|
||||
class NameAffirmationApiConfig(AppConfig):
|
||||
"""
|
||||
Application Configuration for Misc Services.
|
||||
Application Configuration for Name Affirmation API.
|
||||
"""
|
||||
name = 'openedx.features.name_affirmation_api'
|
||||
|
||||
@@ -19,5 +21,6 @@ class NameAffirmationApiConfig(AppConfig):
|
||||
Connect services.
|
||||
"""
|
||||
if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'):
|
||||
from edx_name_affirmation.services import NameAffirmationService
|
||||
set_runtime_service('name_affirmation', NameAffirmationService())
|
||||
name_affirmation_service = get_name_affirmation_service()
|
||||
if name_affirmation_service:
|
||||
set_runtime_service('name_affirmation', name_affirmation_service)
|
||||
|
||||
36
openedx/features/name_affirmation_api/tests/test_utils.py
Normal file
36
openedx/features/name_affirmation_api/tests/test_utils.py
Normal file
@@ -0,0 +1,36 @@
|
||||
""" Tests for Name Affirmation API utils """
|
||||
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch
|
||||
|
||||
import ddt
|
||||
from edx_django_utils.plugins import PluginError
|
||||
|
||||
from openedx.features.name_affirmation_api.utils import is_name_affirmation_installed, get_name_affirmation_service
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestNameAffirmationAPIUtils(TestCase):
|
||||
""" Tests for Name Affirmation API utils """
|
||||
|
||||
@patch('openedx.features.name_affirmation_api.utils.PluginManager')
|
||||
def test_name_affirmation_installed(self, mock_manager):
|
||||
mock_manager.get_plugin.return_value = 'mock plugin'
|
||||
self.assertTrue(is_name_affirmation_installed())
|
||||
|
||||
@patch('openedx.features.name_affirmation_api.utils.PluginManager')
|
||||
def test_name_affirmation_not_installed(self, mock_manager):
|
||||
mock_manager.side_effect = PluginError('No such plugin')
|
||||
with self.assertRaises(PluginError):
|
||||
self.assertFalse(is_name_affirmation_installed())
|
||||
|
||||
@patch('edx_name_affirmation.services.NameAffirmationService')
|
||||
@ddt.data(True, False)
|
||||
def test_get_name_affirmation_service(self, name_affirmation_installed, mock_service):
|
||||
with patch('openedx.features.name_affirmation_api.utils.is_name_affirmation_installed',
|
||||
return_value=name_affirmation_installed):
|
||||
name_affirmation_service = get_name_affirmation_service()
|
||||
if name_affirmation_installed:
|
||||
self.assertEqual(name_affirmation_service, mock_service())
|
||||
else:
|
||||
self.assertIsNone(name_affirmation_service)
|
||||
31
openedx/features/name_affirmation_api/utils.py
Normal file
31
openedx/features/name_affirmation_api/utils.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Utility functions for integration with Name Affirmation plugin
|
||||
(https://github.com/edx/edx-name-affirmation)
|
||||
"""
|
||||
|
||||
from edx_django_utils.plugins import PluginError, PluginManager
|
||||
|
||||
|
||||
def is_name_affirmation_installed():
|
||||
"""
|
||||
Returns boolean describing whether Name Affirmation plugin is installed.
|
||||
"""
|
||||
manager = PluginManager()
|
||||
try:
|
||||
plugin = manager.get_plugin('edx_name_affirmation', 'lms.djangoapp')
|
||||
return bool(plugin)
|
||||
except PluginError:
|
||||
return False
|
||||
|
||||
|
||||
def get_name_affirmation_service():
|
||||
"""
|
||||
Returns Name Affirmation service which exposes API .
|
||||
If Name Affirmation is not installed, return None.
|
||||
"""
|
||||
if is_name_affirmation_installed():
|
||||
# pylint: disable=import-error
|
||||
from edx_name_affirmation.services import NameAffirmationService
|
||||
return NameAffirmationService()
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user