ungenerated_certs.py 7.31 KB
Newer Older
1 2 3 4
"""
Management command to find all students that need certificates for
courses that have finished, and put their cert requests on the queue.
"""
5 6 7
import logging
import datetime
from pytz import UTC
8
from django.core.management.base import BaseCommand, CommandError
9
from certificates.models import certificate_status_for_student
10
from certificates.api import generate_user_certificates
11
from django.contrib.auth.models import User
12
from optparse import make_option
13
from opaque_keys import InvalidKeyError
14 15
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locations import SlashSeparatedCourseKey
16 17
from xmodule.modulestore.django import modulestore
from certificates.models import CertificateStatuses
18 19 20


LOGGER = logging.getLogger(__name__)
21 22


23
class Command(BaseCommand):
24 25 26 27
    """
    Management command to find all students that need certificates
    for courses that have finished and put their cert requests on the queue.
    """
28

29
    help = """
30 31
    Find all students that need certificates for courses that have finished and
    put their cert requests on the queue.
32

33 34 35 36
    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.
37
    """
38

39 40
    option_list = BaseCommand.option_list + (
        make_option('-n', '--noop',
John Jarvis committed
41 42 43 44
                    action='store_true',
                    dest='noop',
                    default=False,
                    help="Don't add certificate requests to the queue"),
45 46 47 48 49
        make_option('--insecure',
                    action='store_true',
                    dest='insecure',
                    default=False,
                    help="Don't use https for the callback url to the LMS, useful in http test environments"),
50
        make_option('-c', '--course',
John Jarvis committed
51 52 53 54 55
                    metavar='COURSE_ID',
                    dest='course',
                    default=False,
                    help='Grade and generate certificates '
                    'for a specific course'),
John Jarvis committed
56 57 58 59
        make_option('-f', '--force-gen',
                    metavar='STATUS',
                    dest='force',
                    default=False,
60 61 62 63
                    help='Will generate new certificates for only those users '
                    'whose entry in the certificate table matches STATUS. '
                    'STATUS can be generating, unavailable, deleted, error '
                    'or notpassing.'),
John Jarvis committed
64
    )
65

66 67
    def handle(self, *args, **options):

68 69 70 71 72 73 74 75 76
        LOGGER.info(
            (
                u"Starting to create tasks for ungenerated certificates "
                u"with arguments %s and options %s"
            ),
            unicode(args),
            unicode(options)
        )

77
        # Will only generate a certificate if the current
John Jarvis committed
78 79
        # status is in the unavailable state, can be set
        # to something else with the force flag
80

John Jarvis committed
81
        if options['force']:
82
            valid_statuses = [getattr(CertificateStatuses, options['force'])]
John Jarvis committed
83 84
        else:
            valid_statuses = [CertificateStatuses.unavailable]
85 86 87 88 89 90

        # Print update after this many students

        STATUS_INTERVAL = 500

        if options['course']:
91 92 93 94
            # try to parse out the course from the serialized form
            try:
                course = CourseKey.from_string(options['course'])
            except InvalidKeyError:
95 96 97 98 99 100 101
                LOGGER.warning(
                    (
                        u"Course id %s could not be parsed as a CourseKey; "
                        u"falling back to SlashSeparatedCourseKey.from_deprecated_string()"
                    ),
                    options['course']
                )
102 103
                course = SlashSeparatedCourseKey.from_deprecated_string(options['course'])
            ended_courses = [course]
104
        else:
105 106 107
            raise CommandError("You must specify a course")

        for course_key in ended_courses:
108
            # prefetch all chapters/sequentials by saying depth=2
109
            course = modulestore().get_course(course_key, depth=2)
110

111
            enrolled_students = User.objects.filter(
112 113
                courseenrollment__course_id=course_key
            )
114

115 116
            total = enrolled_students.count()
            count = 0
117
            start = datetime.datetime.now(UTC)
118

119 120 121 122 123 124
            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
125
                    diff = datetime.datetime.now(UTC) - start
126 127
                    timeleft = diff * (total - count) / STATUS_INTERVAL
                    hours, remainder = divmod(timeleft.seconds, 3600)
stv committed
128
                    minutes, _seconds = divmod(remainder, 60)
129
                    print "{0}/{1} completed ~{2:02}:{3:02}m remaining".format(
John Jarvis committed
130
                        count, total, hours, minutes)
131
                    start = datetime.datetime.now(UTC)
132

133 134 135 136 137 138 139 140 141 142 143 144 145
                cert_status = certificate_status_for_student(student, course_key)['status']
                LOGGER.info(
                    (
                        u"Student %s has certificate status '%s' "
                        u"in course '%s'"
                    ),
                    student.id,
                    cert_status,
                    unicode(course_key)
                )

                if cert_status in valid_statuses:

146 147
                    if not options['noop']:
                        # Add the certificate request to the queue
148 149 150 151 152 153
                        ret = generate_user_certificates(
                            student,
                            course_key,
                            course=course,
                            insecure=options['insecure']
                        )
154

155
                        if ret == 'generating':
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
                            LOGGER.info(
                                (
                                    u"Added a certificate generation task to the XQueue "
                                    u"for student %s in course '%s'. "
                                    u"The new certificate status is '%s'."
                                ),
                                student.id,
                                unicode(course_key),
                                ret
                            )

                    else:
                        LOGGER.info(
                            (
                                u"Skipping certificate generation for "
                                u"student %s in course '%s' "
                                u"because the noop flag is set."
                            ),
                            student.id,
                            unicode(course_key)
                        )

                else:
                    LOGGER.info(
                        (
                            u"Skipped student %s because "
                            u"certificate status '%s' is not in %s"
                        ),
                        student.id,
                        cert_status,
                        unicode(valid_statuses)
                    )

            LOGGER.info(
                (
                    u"Completed ungenerated certificates command "
                    u"for course '%s'"
                ),
                unicode(course_key)
            )