Skip to content
Snippets Groups Projects
Commit 2c96a2ac authored by Justin Hynes's avatar Justin Hynes
Browse files

feat: stop removing `verify_uuid` value when revoking course certificates

[MB-1077]
- Stop removing the verify_uuid when revoking course certificates. In v2 of course certificates this will allow us to retain the same URL to the certificate. If a learner's certificate is revoked (from being invalidated or no longer passing the course) the UUID will remain intact and the URL will not change.
- Look for an existing cert for the user in a course-run during generation. If one exists, use the UUID from the existing certificate when updating the record.
- Stop generating a `key` for v2 web-certificates. This is not needed for web-certs (this is used in PDF cert generation).
parent d1856c27
No related branches found
No related tags found
No related merge requests found
......@@ -59,14 +59,18 @@ def _generate_certificate(user, course_key):
"""
Generate a certificate for this user, in this course run.
"""
# Retrieve the existing certificate for the learner if it exists
existing_certificate = GeneratedCertificate.certificate_for_student(user, course_key)
profile = UserProfile.objects.get(user=user)
profile_name = profile.name
course = modulestore().get_course(course_key, depth=0)
course_grade = CourseGradeFactory().read(user, course)
enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(user, course_key)
key = make_hashkey(random.random())
uuid = uuid4().hex
# 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
uuid = getattr(existing_certificate, 'verify_uuid', uuid4().hex)
cert, created = GeneratedCertificate.objects.update_or_create(
user=user,
......@@ -79,7 +83,7 @@ def _generate_certificate(user, course_key):
'status': CertificateStatuses.downloadable,
'grade': course_grade.percent,
'download_url': '',
'key': key,
'key': '',
'verify_uuid': uuid,
'error_reason': ''
}
......
......@@ -341,12 +341,11 @@ class GeneratedCertificate(models.Model):
def invalidate(self):
"""
Invalidate Generated Certificate by marking it 'unavailable'.
Invalidate Generated Certificate by marking it 'unavailable'.
"""
log.info(f'Marking certificate as unavailable for {self.user.id} : {self.course_id}')
self.error_reason = ''
self.verify_uuid = ''
self.download_uuid = ''
self.download_url = ''
self.grade = ''
......@@ -368,7 +367,6 @@ class GeneratedCertificate(models.Model):
log.info(f'Marking certificate as notpassing for {self.user.id} : {self.course_id}')
self.error_reason = ''
self.verify_uuid = ''
self.download_uuid = ''
self.download_url = ''
self.grade = grade
......
......@@ -3,25 +3,16 @@ Tests for certificate generation
"""
import logging
from edx_toggles.toggles import LegacyWaffleSwitch
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from common.djangoapps.util.testing import EventTestMixin
from lms.djangoapps.certificates.generation import generate_course_certificate
from lms.djangoapps.certificates.models import CertificateStatuses, GeneratedCertificate
from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory
from openedx.core.djangoapps.certificates.config import waffle
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
log = logging.getLogger(__name__)
ID_VERIFIED_METHOD = 'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
AUTO_GENERATION_NAMESPACE = waffle.WAFFLE_NAMESPACE
AUTO_GENERATION_NAME = waffle.AUTO_CERTIFICATE_GENERATION
AUTO_GENERATION_SWITCH_NAME = f'{AUTO_GENERATION_NAMESPACE}.{AUTO_GENERATION_NAME}'
AUTO_GENERATION_SWITCH = LegacyWaffleSwitch(AUTO_GENERATION_NAMESPACE, AUTO_GENERATION_NAME)
class CertificateTests(EventTestMixin, ModuleStoreTestCase):
"""
......@@ -31,70 +22,85 @@ class CertificateTests(EventTestMixin, ModuleStoreTestCase):
def setUp(self): # pylint: disable=arguments-differ
super().setUp('lms.djangoapps.certificates.utils.tracker')
def test_generation(self):
"""
Test certificate generation
"""
# Create user, a course run, and an enrollment
u = UserFactory()
cr = CourseFactory()
key = cr.id # pylint: disable=no-member
self.u = UserFactory()
self.cr = CourseFactory()
self.key = self.cr.id # pylint: disable=no-member
CourseEnrollmentFactory(
user=u,
course_id=key,
user=self.u,
course_id=self.key,
is_active=True,
mode='verified',
)
gen_mode = 'batch'
self.gen_mode = 'batch'
certs = GeneratedCertificate.objects.filter(user=u, course_id=key)
def test_generation(self):
"""
Test certificate generation
"""
certs = GeneratedCertificate.objects.filter(user=self.u, course_id=self.key)
assert len(certs) == 0
generated_cert = generate_course_certificate(u, key, gen_mode)
generated_cert = generate_course_certificate(self.u, self.key, self.gen_mode)
assert generated_cert.status, CertificateStatuses.downloadable
certs = GeneratedCertificate.objects.filter(user=u, course_id=key)
certs = GeneratedCertificate.objects.filter(user=self.u, course_id=self.key)
assert len(certs) == 1
self.assert_event_emitted(
'edx.certificate.created',
user_id=u.id,
course_id=str(key),
user_id=self.u.id,
course_id=str(self.key),
certificate_id=generated_cert.verify_uuid,
enrollment_mode=generated_cert.mode,
certificate_url='',
generation_mode=gen_mode
generation_mode=self.gen_mode
)
def test_generation_existing(self):
"""
Test certificate generation when a certificate already exists
"""
# Create user, a course run, and an enrollment
u = UserFactory()
cr = CourseFactory()
key = cr.id # pylint: disable=no-member
CourseEnrollmentFactory(
user=u,
course_id=key,
is_active=True,
mode='verified',
)
error_reason = 'Some PDF error'
GeneratedCertificateFactory(
user=u,
course_id=key,
user=self.u,
course_id=self.key,
mode='verified',
status=CertificateStatuses.error,
error_reason=error_reason
)
gen_mode = 'batch'
cert = GeneratedCertificate.objects.get(user=u, course_id=key)
cert = GeneratedCertificate.objects.get(user=self.u, course_id=self.key)
assert cert.error_reason == error_reason
generated_cert = generate_course_certificate(u, key, gen_mode)
generated_cert = generate_course_certificate(self.u, self.key, self.gen_mode)
assert generated_cert.status, CertificateStatuses.downloadable
cert = GeneratedCertificate.objects.get(user=u, course_id=key)
cert = GeneratedCertificate.objects.get(user=self.u, course_id=self.key)
assert cert.error_reason == ''
def test_generation_uuid_persists_through_revocation(self):
"""
Test that the `verify_uuid` value of a certificate does not change when it is revoked and re-awarded.
"""
# Create user, a course run, and an enrollment
generated_cert = generate_course_certificate(self.u, self.key, self.gen_mode)
assert generated_cert.status, CertificateStatuses.downloadable
verify_uuid = generated_cert.verify_uuid
generated_cert.invalidate()
assert generated_cert.status, CertificateStatuses.unavailable
assert generated_cert.verify_uuid, verify_uuid
generated_cert = generate_course_certificate(self.u, self.key, self.gen_mode)
assert generated_cert.status, CertificateStatuses.downloadable
assert generated_cert.verify_uuid, verify_uuid
generated_cert.mark_notpassing(50.00)
assert generated_cert.status, CertificateStatuses.notpassing
assert generated_cert.verify_uuid, verify_uuid
generated_cert = generate_course_certificate(self.u, self.key, self.gen_mode)
assert generated_cert.status, CertificateStatuses.downloadable
assert generated_cert.verify_uuid, verify_uuid
......@@ -58,7 +58,7 @@ class CertificateServiceTests(ModuleStoreTestCase):
self.assertDictEqual(
self.generated_certificate_to_dict(invalid_generated_certificate),
{
'verify_uuid': '',
'verify_uuid': invalid_generated_certificate.verify_uuid,
'download_uuid': '',
'download_url': '',
'grade': '',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment