From c40e772d04a47240abea31408b60491cb764622e Mon Sep 17 00:00:00 2001 From: Justin Hynes Date: Mon, 28 Jun 2021 20:09:28 -0400 Subject: [PATCH] feat: Add temporary mgmt cmd to fix certificate data [MICROBA-1311] * Add temporary mgmt cmd to certificates app to fix certificate records with incorrect data stemming from CR-3792. --- .../management/commands/fix_cert_records.py | 54 ++++++++++++ .../commands/tests/test_fix_cert_records.py | 87 +++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 lms/djangoapps/certificates/management/commands/fix_cert_records.py create mode 100644 lms/djangoapps/certificates/management/commands/tests/test_fix_cert_records.py diff --git a/lms/djangoapps/certificates/management/commands/fix_cert_records.py b/lms/djangoapps/certificates/management/commands/fix_cert_records.py new file mode 100644 index 0000000000..4cbcee3d23 --- /dev/null +++ b/lms/djangoapps/certificates/management/commands/fix_cert_records.py @@ -0,0 +1,54 @@ +""" +(Temporary) Management command used to fix some incorrectly generated certificate records created as a side effect of +CR-3792. +""" +import datetime +import logging + +from django.contrib.auth import get_user_model +from django.core.management.base import BaseCommand + +from lms.djangoapps.certificates.generation_handler import _generate_certificate_task +from lms.djangoapps.certificates.models import GeneratedCertificate + +log = logging.getLogger(__name__) +User = get_user_model() + + +class Command(BaseCommand): + """ + Temporary Management command to fix the incorrect mode on a number of records in the + `CERTIFICATES_GENERATEDCERTIFICATE table. + """ + def add_arguments(self, parser): + parser.add_argument( + '-l', '--limit', + metavar='LIMIT', + dest='limit', + help='number of records to process at once' + ) + + def handle(self, *args, **options): + if options.get('limit'): + limit = int(options['limit']) + else: + limit = 1000 + + # We started creating the incorrect certificate records around May 10th, 2021. + certs = GeneratedCertificate.objects.filter( + mode='honor', + created_date__gte=datetime.date(2021, 5, 10) + ).order_by( + 'created_date' + )[:limit] + + for cert in certs: + user = User.objects.get(id=cert.user_id) + course_id = cert.course_id + + _generate_certificate_task( + user, + course_id, + status=cert.status, + generation_mode='batch' + ) diff --git a/lms/djangoapps/certificates/management/commands/tests/test_fix_cert_records.py b/lms/djangoapps/certificates/management/commands/tests/test_fix_cert_records.py new file mode 100644 index 0000000000..bd775ed41c --- /dev/null +++ b/lms/djangoapps/certificates/management/commands/tests/test_fix_cert_records.py @@ -0,0 +1,87 @@ +""" +Test for temporary `fix_cert_records` mgmt command. +""" +from django.core.management import call_command + +from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory +from lms.djangoapps.certificates.data import CertificateStatuses +from lms.djangoapps.certificates.models import GeneratedCertificate +from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory + + +class FixCertRecordsTest(ModuleStoreTestCase): + """ + Test cases for `fix_cert_records` mgmt command + """ + def setUp(self): + super().setUp() + + self.course = CourseFactory.create() + self.users = [] + + def _create_test_data(self, num_users): + """ + Utility function to create test data for the tests. + """ + self.users = [ + UserFactory.create( + first_name="robot", + last_name=f"person{i}", + email=f"robot.person{i}@test.edx.org", + username=f"robot.person{i}" + ) + for i in range(num_users) + ] + + for user in self.users: + CourseEnrollmentFactory.create( + is_active=True, + mode=GeneratedCertificate.MODES.verified, + course_id=self.course.id, + user=user + ) + GeneratedCertificateFactory.create( + user=user, + course_id=self.course.id, + status=CertificateStatuses.unverified, + mode=GeneratedCertificate.MODES.honor + ) + + def test_happy_path(self): + self._create_test_data(100) + + call_command("fix_cert_records") + + for user in self.users: + cert = GeneratedCertificate.objects.get( + user_id=user.id, + course_id=self.course.id + ) + assert cert.mode == GeneratedCertificate.MODES.verified + assert cert.name == f"{user.first_name} {user.last_name}" + + def test_limit(self): + self._create_test_data(100) + + call_command("fix_cert_records", "--limit", "50") + + fixed_certs_count = GeneratedCertificate.objects.filter( + mode=GeneratedCertificate.MODES.verified + ).count() + + remaining_honor_certs_count = GeneratedCertificate.objects.filter( + mode=GeneratedCertificate.MODES.honor + ).count() + + assert fixed_certs_count == 50 + assert remaining_honor_certs_count == 50 + + call_command("fix_cert_records", "--limit", "50") + + fixed_certs_count = GeneratedCertificate.objects.filter( + mode=GeneratedCertificate.MODES.verified + ).count() + + assert fixed_certs_count == 100