diff --git a/lms/djangoapps/certificates/management/commands/regenerate_user.py b/lms/djangoapps/certificates/management/commands/regenerate_user.py new file mode 100644 index 0000000000..55f0cc1850 --- /dev/null +++ b/lms/djangoapps/certificates/management/commands/regenerate_user.py @@ -0,0 +1,58 @@ +"""Django management command to force certificate regeneration for one user""" + +from optparse import make_option + +from django.contrib.auth.models import User +from django.core.management.base import BaseCommand, CommandError + +from certificates.queue import XQueueCertInterface +from xmodule.course_module import CourseDescriptor +from xmodule.modulestore.django import modulestore + + +class Command(BaseCommand): + help = """Put a request on the queue to recreate the certificate for a particular user in a particular course.""" + + option_list = BaseCommand.option_list + ( + make_option('-n', '--noop', + action='store_true', + dest='noop', + default=False, + help="Don't grade or add certificate requests to the queue"), + make_option('-c', '--course', + metavar='COURSE_ID', + dest='course', + default=False, + help='The course id (e.g., mit/6-002x/circuits-and-electronics) for which the student named in' + ' should be graded'), + make_option('-u', '--user', + metavar='USERNAME', + dest='username', + default=False, + help='The username or email address for whom grading and certification should be requested'), + ) + + def handle(self, *args, **options): + + user = options['username'] + course_id = options['course'] + if not (course_id and user): + raise CommandError('both course id and student username are required') + + student = None + print "Fetching enrollment for student {0} in {1}".format(user, course_id) + if '@' in user: + student = User.objects.get(email=user, courseenrollment__course_id=course_id) + else: + student = User.objects.get(username=user, courseenrollment__course_id=course_id) + + print "Fetching course data for {0}".format(course_id) + course = modulestore().get_instance(course_id, CourseDescriptor.id_to_location(course_id), depth=2) + + if not options['noop']: + # Add the certificate request to the queue + xq = XQueueCertInterface() + ret = xq.regen_cert(student, course_id, course=course) + print '{0} - {1}'.format(student, ret) + else: + print "noop option given, skipping work queueing..." diff --git a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py index c9f944158a..14895385d6 100644 --- a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py +++ b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py @@ -14,12 +14,13 @@ from pytz import UTC class Command(BaseCommand): help = """ - Find all students that need certificates - for courses that have finished and - put their cert requests on the queue + Find all students that need certificates for courses that have finished and + put their cert requests on the queue. - Use the --noop option to test without actually - putting certificates on the queue to be generated. + If --user is given, only grade and certify the requested username. + + Use the --noop option to test without actually putting certificates on the + queue to be generated. """ option_list = BaseCommand.option_list + ( @@ -80,6 +81,7 @@ class Command(BaseCommand): enrolled_students = User.objects.filter( courseenrollment__course_id=course_id).prefetch_related( "groups").order_by('username') + xq = XQueueCertInterface() total = enrolled_students.count() count = 0 diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index dc438b805a..8cd1a292c4 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -52,15 +52,15 @@ Eligibility: class CertificateStatuses(object): - unavailable = 'unavailable' - generating = 'generating' - regenerating = 'regenerating' - deleting = 'deleting' - deleted = 'deleted' + deleted = 'deleted' + deleting = 'deleting' downloadable = 'downloadable' - notpassing = 'notpassing' - restricted = 'restricted' - error = 'error' + error = 'error' + generating = 'generating' + notpassing = 'notpassing' + regenerating = 'regenerating' + restricted = 'restricted' + unavailable = 'unavailable' class CertificateWhitelist(models.Model): diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index bc7bbc0e86..0e1c4714ca 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -75,28 +75,34 @@ class XQueueCertInterface(object): self.whitelist = CertificateWhitelist.objects.all() self.restricted = UserProfile.objects.filter(allow_certificate=False) - def regen_cert(self, student, course_id): - """ + def regen_cert(self, student, course_id, course=None): + """(Re-)Make certificate for a particular student in a particular course + Arguments: - student - User.object + student - User.object course_id - courseenrollment.course_id (string) - Removes certificate for a student, will change - the certificate status to 'regenerating'. + WARNING: this command will leave the old certificate, if one exists, + laying around in AWS taking up space. If this is a problem, + take pains to clean up storage before running this command. - Certificate must be in the 'error' or 'downloadable' state - - If the student has a passing grade a certificate - request will be put on the queue - - If the student is not passing his state will change - to status.notpassing - - otherwise it will return the current state + Change the certificate status to unavailable (if it exists) and request + grading. Passing grades will put a certificate request on the queue. + Return the status object. """ + # TODO: when del_cert is implemented and plumbed through certificates + # repo also, do a deletion followed by a creation r/t a simple + # recreation. XXX: this leaves orphan cert files laying around in + # AWS. See note in the docstring too. + try: + certificate = GeneratedCertificate.objects.get(user=student, course_id=course_id) + certificate.status = status.unavailable + certificate.save() + except GeneratedCertificate.DoesNotExist: + pass - raise NotImplementedError + return self.add_cert(student, course_id, course) def del_cert(self, student, course_id): @@ -134,7 +140,6 @@ class XQueueCertInterface(object): If a student has allow_certificate set to False in the userprofile table the status will change to 'restricted' - If a student does not have a passing grade the status will change to status.notpassing @@ -143,11 +148,12 @@ class XQueueCertInterface(object): """ VALID_STATUSES = [status.generating, - status.unavailable, status.deleted, status.error, - status.notpassing] + status.unavailable, + status.deleted, + status.error, + status.notpassing] - cert_status = certificate_status_for_student( - student, course_id)['status'] + cert_status = certificate_status_for_student(student, course_id)['status'] if cert_status in VALID_STATUSES: # grade the student