get_grades.py 5.08 KB
Newer Older
1 2 3 4
"""
Management command to generate a list of grades for
all students that are enrolled in a course.
"""
5
from courseware import grades, courses
6
from certificates.models import GeneratedCertificate
7 8 9
from django.test.client import RequestFactory
from django.core.management.base import BaseCommand, CommandError
import os
10
from opaque_keys import InvalidKeyError
11 12
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locations import SlashSeparatedCourseKey
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
from django.contrib.auth.models import User
from optparse import make_option
import datetime
from django.core.handlers.base import BaseHandler
import csv


class RequestMock(RequestFactory):
    def request(self, **request):
        "Construct a generic request object."
        request = RequestFactory.request(self, **request)
        handler = BaseHandler()
        handler.load_middleware()
        for middleware_method in handler._request_middleware:
            if middleware_method(request):
                raise Exception("Couldn't create request mock object - "
                                "request middleware returned a response")
        return request


class Command(BaseCommand):

    help = """
    Generate a list of grades for all students
    that are enrolled in a course.

39 40 41 42 43 44 45
    CSV will include the following:
      - username
      - email
      - grade in the certificate table if it exists
      - computed grade
      - grade breakdown

46 47 48 49 50 51 52 53 54 55 56 57 58
    Outputs grades to a csv file.

    Example:
      sudo -u www-data SERVICE_VARIANT=lms /opt/edx/bin/django-admin.py get_grades \
        -c MITx/Chi6.00intro/A_Taste_of_Python_Programming -o /tmp/20130813-6.00x.csv \
        --settings=lms.envs.aws --pythonpath=/opt/wwc/edx-platform
    """

    option_list = BaseCommand.option_list + (
        make_option('-c', '--course',
                    metavar='COURSE_ID',
                    dest='course',
                    default=False,
John Jarvis committed
59
                    help='Course ID for grade distribution'),
60 61 62 63 64 65 66 67 68 69 70 71
        make_option('-o', '--output',
                    metavar='FILE',
                    dest='output',
                    default=False,
                    help='Filename for grade output'))

    def handle(self, *args, **options):
        if os.path.exists(options['output']):
            raise CommandError("File {0} already exists".format(
                options['output']))

        STATUS_INTERVAL = 100
72 73 74 75 76 77 78 79 80 81 82

        # parse out the course into a coursekey
        if options['course']:
            try:
                course_key = CourseKey.from_string(options['course'])
            # if it's not a new-style course key, parse it from an old-style
            # course key
            except InvalidKeyError:
                course_key = SlashSeparatedCourseKey.from_deprecated_string(options['course'])

        print "Fetching enrolled students for {0}".format(course_key)
83
        enrolled_students = User.objects.filter(
84 85
            courseenrollment__course_id=course_key
        )
86 87 88 89 90
        factory = RequestMock()
        request = factory.get('/')

        total = enrolled_students.count()
        print "Total enrolled: {0}".format(total)
91
        course = courses.get_course_by_id(course_key)
92 93 94 95
        total = enrolled_students.count()
        start = datetime.datetime.now()
        rows = []
        header = None
96
        print "Fetching certificate data"
97 98 99 100 101 102
        cert_grades = {
            cert.user.username: cert.grade
            for cert in list(
                GeneratedCertificate.objects.filter(course_id=course_key).prefetch_related('user')
            )
        }
103
        print "Grading students"
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
        for count, student in enumerate(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()
            request.user = student
            grade = grades.grade(student, request, course)
            if not header:
                header = [section['label'] for section in grade[u'section_breakdown']]
121
                rows.append(["email", "username", "certificate-grade", "grade"] + header)
122 123
            percents = {section['label']: section['percent'] for section in grade[u'section_breakdown']}
            row_percents = [percents[label] for label in header]
124 125 126 127
            if student.username in cert_grades:
                rows.append([student.email, student.username, cert_grades[student.username], grade['percent']] + row_percents)
            else:
                rows.append([student.email, student.username, "N/A", grade['percent']] + row_percents)
128 129 130
        with open(options['output'], 'wb') as f:
            writer = csv.writer(f)
            writer.writerows(rows)