diff --git a/lms/djangoapps/certificates/management/commands/find_unicode_certs.py b/lms/djangoapps/certificates/management/commands/find_unicode_certs.py deleted file mode 100644 index 49497622c6651ecc7544ba61954d45748969f988..0000000000000000000000000000000000000000 --- a/lms/djangoapps/certificates/management/commands/find_unicode_certs.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- - -from django.core.management.base import BaseCommand -from certificates.models import certificate_status_for_student -from certificates.queue import XQueueCertInterface -from django.contrib.auth.models import User -from student.models import UserProfile - - -class Command(BaseCommand): - - help = """ - Looks for names that have unicode characters - and queues them up for a certificate request - """ - - def handle(self, *args, **options): - - # TODO this is only temporary for CS169 certs - - course_id = 'BerkeleyX/CS169.1x/2012_Fall' - - enrolled_students = User.objects.filter( - courseenrollment__course_id=course_id).prefetch_related( - "groups").order_by('username') - xq = XQueueCertInterface() - print "Looking for unusual names.." - for student in enrolled_students: - if certificate_status_for_student( - student, course_id)['status'] == 'unavailable': - continue - name = UserProfile.objects.get(user=student).name - for c in name: - if ord(c) >= 0x200: - ret = xq.add_cert(student, course_id) - if ret == 'generating': - print 'generating for {0}'.format(student) - break diff --git a/lms/djangoapps/certificates/management/commands/gen_cert_report.py b/lms/djangoapps/certificates/management/commands/gen_cert_report.py new file mode 100644 index 0000000000000000000000000000000000000000..21e1775665c342f8891324e00e0e40a8721c956b --- /dev/null +++ b/lms/djangoapps/certificates/management/commands/gen_cert_report.py @@ -0,0 +1,97 @@ +from django.core.management.base import BaseCommand +from certificates.models import GeneratedCertificate +from django.contrib.auth.models import User +from optparse import make_option +from django.conf import settings +from xmodule.course_module import CourseDescriptor +from xmodule.modulestore.django import modulestore +from django.db.models import Count + + +class Command(BaseCommand): + + help = """ + + Generate a certificate status report for all courses that have ended. + This command does not do anything other than report the current + certificate status. + + unavailable - A student is not eligible for a certificate. + generating - A request has been made to generate a certificate, + but it has not been generated yet. + regenerating - A request has been made to regenerate a certificate, + but it has not been generated yet. + deleting - A request has been made to delete a certificate. + + deleted - The certificate has been deleted. + downloadable - The certificate is available for download. + notpassing - The student was graded but is not passing + + """ + + option_list = BaseCommand.option_list + ( + make_option('-c', '--course', + metavar='COURSE_ID', + dest='course', + default=None, + help='Only generate for COURSE_ID'), + ) + + def _ended_courses(self): + for course_id in [course # all courses in COURSE_LISTINGS + for sub in settings.COURSE_LISTINGS + for course in settings.COURSE_LISTINGS[sub]]: + course_loc = CourseDescriptor.id_to_location(course_id) + course = modulestore().get_instance(course_id, course_loc) + if course.has_ended(): + yield course_id + + def handle(self, *args, **options): + + # Find all courses that have ended + + if options['course']: + ended_courses = [options['course']] + else: + ended_courses = self._ended_courses() + + cert_data = {} + + for course_id in ended_courses: + + # find students who are enrolled + print "Looking up certificate states for {0}".format(course_id) + enrolled_students = User.objects.filter( + courseenrollment__course_id=course_id).prefetch_related( + "groups").order_by('username') + unavailable_count = enrolled_students.count() - \ + GeneratedCertificate.objects.filter( + course_id__exact=course_id).count() + cert_data[course_id] = {'enrolled': enrolled_students.count()} + cert_data[course_id].update({'unavailable': unavailable_count}) + + tallies = GeneratedCertificate.objects.values( + 'status').annotate(dcount=Count('status')) + cert_data[course_id].update( + {status['status']: status['dcount'] + for status in tallies}) + + # all states we have seen far all courses + status_headings = set( + [status for course in cert_data + for status in cert_data[course]]) + + # print the heading for the report + print "{:>20}".format("course ID"), + print ' '.join(["{:>12}".format(heading) + for heading in status_headings]) + + # print the report + for course_id in cert_data: + print "{0:>20}".format(course_id[0:18]), + for heading in status_headings: + if heading in cert_data[course_id]: + print "{:>12}".format(cert_data[course_id][heading]), + else: + print " " * 12, + print diff --git a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py index 82e86c2097c16ccb3a1d2d5b57549540c2723526..080918c0ccd594bd64db6f1b7d77d95aaf138ac6 100644 --- a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py +++ b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py @@ -2,29 +2,92 @@ from django.core.management.base import BaseCommand from certificates.models import certificate_status_for_student from certificates.queue import XQueueCertInterface from django.contrib.auth.models import User +from optparse import make_option +from django.conf import settings +from xmodule.course_module import CourseDescriptor +from xmodule.modulestore.django import modulestore +from certificates.models import CertificateStatuses +import datetime class Command(BaseCommand): help = """ - Find all students that have need certificates - and put certificate requests on the queue + Find all students that need certificates + for courses that have finished and + put their cert requests on the queue - This is only for BerkeleyX/CS169.1x/2012_Fall + Use the --noop option to test without actually + putting certificates on the queue to be generated. """ + option_list = BaseCommand.option_list + ( + make_option('-n', '--noop', + action='store_true', + dest='noop', + default=False, + help="Don't add certificate requests to the queue"), + make_option('-c', '--course', + metavar='COURSE_ID', + dest='course', + default=False, + help='Grade and generate certificates for a specific course'), + + ) + def handle(self, *args, **options): - # TODO This is only temporary for CS169 certs - - course_id = 'BerkeleyX/CS169.1x/2012_Fall' - enrolled_students = User.objects.filter( - courseenrollment__course_id=course_id).prefetch_related( - "groups").order_by('username') - xq = XQueueCertInterface() - for student in enrolled_students: - if certificate_status_for_student( - student, course_id)['status'] == 'unavailable': - ret = xq.add_cert(student, course_id) - if ret == 'generating': - print 'generating for {0}'.format(student) + # Will only generate a certificate if the current + # status is in this state + + VALID_STATUSES = [ + CertificateStatuses.unavailable + ] + + # Print update after this many students + + STATUS_INTERVAL = 500 + + if options['course']: + ended_courses = [options['course']] + else: + # Find all courses that have ended + ended_courses = [] + for course_id in [course # all courses in COURSE_LISTINGS + for sub in settings.COURSE_LISTINGS + for course in settings.COURSE_LISTINGS[sub]]: + course_loc = CourseDescriptor.id_to_location(course_id) + course = modulestore().get_instance(course_id, course_loc) + if course.has_ended(): + ended_courses.append(course_id) + + for course_id in ended_courses: + print "Fetching enrolled students for {0}".format(course_id) + enrolled_students = User.objects.filter( + courseenrollment__course_id=course_id).prefetch_related( + "groups").order_by('username') + xq = XQueueCertInterface() + total = enrolled_students.count() + count = 0 + start = datetime.datetime.now() + for student in enrolled_students: + count += 1 + if count % STATUS_INTERVAL == 0: + # Print a status update with an approximation of + # how much time is left based on how long the last + # interval took + diff = datetime.datetime.now() - start + timeleft = diff * (total - count) / STATUS_INTERVAL + hours, remainder = divmod(timeleft.seconds, 3600) + minutes, seconds = divmod(remainder, 60) + print "{0}/{1} completed ~{2:02}:{3:02}m remaining".format( + count, total, hours, minutes) + start = datetime.datetime.now() + + if certificate_status_for_student( + student, course_id)['status'] in VALID_STATUSES: + if not options['noop']: + # Add the certificate request to the queue + ret = xq.add_cert(student, course_id) + if ret == 'generating': + print '{0} - {1}'.format(student, ret)