feat: use verified name for certs if enabled

If the verified name feature is enabled and the user has their
preference set to use verified name for certificates, create and
display certificates with their verified name rather than their
profile name.
This commit is contained in:
Bianca Severino
2021-08-03 10:13:02 -04:00
parent 8aedebcdb2
commit d2fa7f7239
7 changed files with 156 additions and 13 deletions

View File

@@ -10,10 +10,9 @@ These methods should be called from tasks.
import logging
from uuid import uuid4
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 lms.djangoapps.certificates.utils import emit_certificate_event
from lms.djangoapps.certificates.utils import emit_certificate_event, get_preferred_certificate_name
log = logging.getLogger(__name__)
@@ -66,9 +65,7 @@ def _generate_certificate(user, course_key, status, enrollment_mode, course_grad
# Retrieve the existing certificate for the learner if it exists
existing_certificate = GeneratedCertificate.certificate_for_student(user, course_key)
profile_name = student_api.get_name(user.id)
if not profile_name:
profile_name = ''
preferred_name = get_preferred_certificate_name(user)
# Retain the `verify_uuid` from an existing certificate if possible, this will make it possible for the learner to
# keep the existing URL to their certificate
@@ -84,7 +81,7 @@ def _generate_certificate(user, course_key, status, enrollment_mode, course_grad
'user': user,
'course_id': course_key,
'mode': enrollment_mode,
'name': profile_name,
'name': preferred_name,
'status': status,
'grade': course_grade,
'download_url': '',

View File

@@ -18,6 +18,8 @@ from django.db.models import Count
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from edx_name_affirmation.api import get_verified_name, should_use_verified_name_for_certs
from edx_name_affirmation.toggles import is_verified_name_enabled
from model_utils import Choices
from model_utils.models import TimeStampedModel
from opaque_keys.edx.django.models import CourseKeyField
@@ -370,9 +372,7 @@ class GeneratedCertificate(models.Model):
if not mode:
mode = self.mode
profile_name = student_api.get_name(self.user.id)
if not profile_name:
profile_name = ''
preferred_name = self._get_preferred_certificate_name(self.user)
self.error_reason = ''
self.download_uuid = ''
@@ -380,7 +380,7 @@ class GeneratedCertificate(models.Model):
self.grade = grade
self.status = status
self.mode = mode
self.name = profile_name
self.name = preferred_name
self.save()
COURSE_CERT_REVOKED.send_robust(
@@ -404,6 +404,23 @@ class GeneratedCertificate(models.Model):
}
emit_certificate_event('revoked', self.user, str(self.course_id), event_data=event_data)
def _get_preferred_certificate_name(self, user):
"""
Copy of `get_preferred_certificate_name` from utils.py - importing it here would introduce
a circular dependency.
"""
name_to_use = student_api.get_name(user.id)
if is_verified_name_enabled() and should_use_verified_name_for_certs(user):
verified_name_obj = get_verified_name(user, is_verified=True)
if verified_name_obj:
name_to_use = verified_name_obj.verified_name
if not name_to_use:
name_to_use = ''
return name_to_use
def is_valid(self):
"""
Return True if certificate is valid else return False.

View File

@@ -1,9 +1,14 @@
"""
Tests for certificate generation
"""
import ddt
import logging
from unittest import mock
from edx_name_affirmation.api import create_verified_name, create_verified_name_config
from edx_name_affirmation.toggles import VERIFIED_NAME_FLAG
from edx_toggles.toggles.testutils import override_waffle_flag
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.models import UserProfile
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
@@ -20,6 +25,7 @@ log = logging.getLogger(__name__)
PROFILE_NAME_METHOD = 'common.djangoapps.student.models_api.get_name'
@ddt.ddt
class CertificateTests(EventTestMixin, ModuleStoreTestCase):
"""
Tests for certificate generation
@@ -187,3 +193,34 @@ class CertificateTests(EventTestMixin, ModuleStoreTestCase):
assert cert.mode == self.enrollment_mode
assert cert.grade == self.grade
assert cert.name == ''
@override_waffle_flag(VERIFIED_NAME_FLAG, active=True)
@ddt.data((True, True), (True, False), (False, False))
@ddt.unpack
def test_generation_verified_name(self, should_use_verified_name_for_certs, is_verified):
"""
Test that if verified name functionality is enabled and the user has their preference set to use
verified name for certificates, their verified name will appear on the certificate rather than
their profile name.
"""
verified_name = 'Jonathan Doe'
create_verified_name(self.u, verified_name, self.name, is_verified=is_verified)
create_verified_name_config(self.u, use_verified_name_for_certs=should_use_verified_name_for_certs)
GeneratedCertificateFactory(
user=self.u,
course_id=self.key,
mode=CourseMode.AUDIT,
status=CertificateStatuses.unverified
)
generate_course_certificate(
self.u, self.key, CertificateStatuses.downloadable, self.enrollment_mode, self.grade, self.gen_mode,
)
cert = GeneratedCertificate.objects.get(user=self.u, course_id=self.key)
if should_use_verified_name_for_certs and is_verified:
assert cert.name == verified_name
else:
assert cert.name == self.name

View File

@@ -12,6 +12,9 @@ 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.toggles import VERIFIED_NAME_FLAG
from edx_toggles.toggles.testutils import override_waffle_flag
from opaque_keys.edx.locator import CourseKey, CourseLocator
from path import Path as path
@@ -360,6 +363,7 @@ class CertificateInvalidationTest(SharedModuleStoreTestCase):
assert mock_revoke_task.call_args[0] == (self.user.username, str(self.course_id))
@ddt.ddt
class GeneratedCertificateTest(SharedModuleStoreTestCase):
"""
Test GeneratedCertificates
@@ -540,6 +544,35 @@ class GeneratedCertificateTest(SharedModuleStoreTestCase):
self._assert_event_data(mock_emit_certificate_event, expected_event_data)
@override_waffle_flag(VERIFIED_NAME_FLAG, active=True)
@ddt.data((True, True), (True, False), (False, False))
@ddt.unpack
def test_invalidate_with_verified_name(self, should_use_verified_name_for_certs, is_verified):
"""
Test the invalidate method with verified name turned on for the user's certificates
"""
verified_name = 'Jonathan Doe'
profile = UserProfile.objects.get(user=self.user)
create_verified_name(self.user, verified_name, profile.name, is_verified=is_verified)
create_verified_name_config(self.user, use_verified_name_for_certs=should_use_verified_name_for_certs)
cert = GeneratedCertificateFactory.create(
status=CertificateStatuses.downloadable,
user=self.user,
course_id=self.course_key,
mode=CourseMode.AUDIT,
name='Fuzzy Hippo'
)
mode = CourseMode.VERIFIED
source = 'invalidated_test'
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 is_verified:
assert cert.name == verified_name
else:
assert cert.name == profile.name
@patch('lms.djangoapps.certificates.utils.emit_certificate_event')
def test_unverified(self, mock_emit_certificate_event):
"""

View File

@@ -12,8 +12,10 @@ 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.toggles import VERIFIED_NAME_FLAG
from edx_toggles.toggles import LegacyWaffleSwitch
from edx_toggles.toggles.testutils import override_waffle_switch
from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch
from organizations import api as organizations_api
from common.djangoapps.course_modes.models import CourseMode
@@ -1524,6 +1526,35 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase)
)
)
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
@override_waffle_flag(VERIFIED_NAME_FLAG, active=True)
@ddt.data((True, True), (True, False), (False, False))
@ddt.unpack
def test_certificate_view_verified_name(self, should_use_verified_name_for_certs, is_verified):
"""
Test that if verified name functionality is enabled and the user has their preference set to use
verified name for certificates, 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, is_verified=is_verified)
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(
user_id=self.user.id,
course_id=str(self.course.id),
uuid=self.cert.verify_uuid
)
response = self.client.get(test_url, HTTP_HOST='test.localhost')
if should_use_verified_name_for_certs and is_verified:
self.assertContains(response, verified_name)
self.assertNotContains(response, self.user.profile.name)
else:
self.assertContains(response, self.user.profile.name)
self.assertNotContains(response, verified_name)
class CertificateEventTests(CommonCertificatesTestCase, EventTrackingTestCase):
"""

View File

@@ -4,12 +4,16 @@ Certificates utilities
from datetime import datetime
import logging
from edx_name_affirmation.api import get_verified_name, should_use_verified_name_for_certs
from edx_name_affirmation.toggles import is_verified_name_enabled
from django.conf import settings
from django.urls import reverse
from eventtracking import tracker
from opaque_keys.edx.keys import CourseKey
from pytz import utc
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
@@ -228,3 +232,22 @@ def certificate_status_for_student(student, course_id):
except GeneratedCertificate.DoesNotExist:
generated_certificate = None
return certificate_status(generated_certificate)
def get_preferred_certificate_name(user):
"""
If the verified name feature is enabled and the user has their preference set to use their
verified name for certificates, return their verified name. Else, return the user's profile
name, or an empty string if it doesn't exist.
"""
name_to_use = student_api.get_name(user.id)
if is_verified_name_enabled() and should_use_verified_name_for_certs(user):
verified_name_obj = get_verified_name(user, is_verified=True)
if verified_name_obj:
name_to_use = verified_name_obj.verified_name
if not name_to_use:
name_to_use = ''
return name_to_use

View File

@@ -43,7 +43,11 @@ from lms.djangoapps.certificates.models import (
GeneratedCertificate
)
from lms.djangoapps.certificates.permissions import PREVIEW_CERTIFICATES
from lms.djangoapps.certificates.utils import emit_certificate_event, get_certificate_url
from lms.djangoapps.certificates.utils import (
emit_certificate_event,
get_certificate_url,
get_preferred_certificate_name
)
from openedx.core.djangoapps.catalog.api import get_course_run_details
from openedx.core.djangoapps.certificates.api import display_date_for_certificate
from openedx.core.djangoapps.content.course_overviews.api import get_course_overview_or_none
@@ -305,7 +309,8 @@ def _update_context_with_user_info(context, user, user_certificate):
"""
Updates context dictionary with user related info.
"""
user_fullname = user.profile.name
user_fullname = get_preferred_certificate_name(user)
context['username'] = user.username
context['course_mode'] = user_certificate.mode
context['accomplishment_user_id'] = user.id